diff --git a/.travis.yml b/.travis.yml index 45eabe8..95a78f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,5 @@ language: go -go: - - "1.12.1" - - "1.10.8" + go_import_path: github.com/songgao/water install: go get -u golang.org/x/lint/golint script: make ci @@ -10,4 +8,11 @@ matrix: include: - os: linux dist: xenial + go: 1.12.1 + - os: linux + dist: xenial + go: 1.10.8 + - os: osx + go: 1.12.1 - os: osx + go: 1.10.8 diff --git a/README.md b/README.md index 00969a5..0c797b5 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,9 @@ -# water +[![Build Status](https://travis-ci.org/songgao/water.svg?branch=master)](https://travis-ci.org/songgao/water) +[![GoDoc](https://godoc.org/github.com/songgao/water?status.svg)](https://godoc.org/github.com/songgao/water) -`water` is a native Go library for [TUN/TAP](http://en.wikipedia.org/wiki/TUN/TAP) interfaces. - -`water` is designed to be simple and efficient. It - -* wraps almost only syscalls and uses only Go standard types; -* exposes standard interfaces; plays well with standard packages like `io`, `bufio`, etc.. -* does not handle memory management (allocating/destructing slice). It's up to user to decide whether/how to reuse buffers. - -~~`water/waterutil` has some useful functions to interpret MAC frame headers and IP packet headers. It also contains some constants such as protocol numbers and ethernet frame types.~~ - -See https://github.com/songgao/packets for functions for parsing various packets. +`water` is a pure Go package for working with +[TUN/TAP](http://en.wikipedia.org/wiki/TUN/TAP) interfaces. It works well with +Go's standard library and has no external dependency. ## Supported Platforms @@ -19,15 +12,18 @@ See https://github.com/songgao/packets for functions for parsing various packets * macOS (point-to-point TUN only) ## Installation + ``` go get -u github.com/songgao/water -go get -u github.com/songgao/water/waterutil ``` ## Documentation -[http://godoc.org/github.com/songgao/water](http://godoc.org/github.com/songgao/water) -## Example +* [Linux](http://godoc.org/github.com/songgao/water?GOOS=linux) +* [macOS](http://godoc.org/github.com/songgao/water?GOOS=darwin) +* [Windows](http://godoc.org/github.com/songgao/water?GOOS=windows) + +## Getting Started ### TAP on Linux: @@ -234,9 +230,3 @@ If you are going to use multiple TAP devices on the Windows, there is a way to s }, }) ``` - -## TODO -* tuntaposx for TAP on Darwin - -## Alternatives -`tuntap`: [https://code.google.com/p/tuntap/](https://code.google.com/p/tuntap/) diff --git a/doc.go b/doc.go index 287412b..8992a24 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,11 @@ -// Package water is a simple TUN/TAP interface library that efficiently works -// with standard packages like io, bufio, etc.. Use waterutil with it to work -// with TUN/TAP packets/frames. +// Package water encapsulates system calls for working with TUN/TAP interfaces, +// in pure Go. +// +// Platform specific docs: +// +// Linux: https://godoc.org/github.com/songgao/water?GOOS=lnux +// +// macOS: https://godoc.org/github.com/songgao/water?GOOS=darwin +// +// Windows: https://godoc.org/github.com/songgao/water?GOOS=windows package water diff --git a/if.go b/if.go index 4023a1a..b7def99 100644 --- a/if.go +++ b/if.go @@ -5,23 +5,36 @@ import ( "io" ) -// Interface is a TUN/TAP interface. -// -// MultiQueue(Linux kernel > 3.8): With MultiQueue enabled, user should hold multiple -// interfaces to send/receive packet in parallel. -// Kernel document about MultiQueue: https://www.kernel.org/doc/Documentation/networking/tuntap.txt -type Interface struct { - isTAP bool +// Interface represents a TUN/TAP interface. +type Interface interface { + // Use Read() and Write() methods to read from and write into this TUN/TAP + // interface. In TAP interface, each call corresponds to an Ethernet frame. + // In TUN mode, each call corresponds to an IP packet. io.ReadWriteCloser - name string + + // IsTUN returns true if ifce is a TUN interface. + IsTUN() bool + // IsTAP returns true if ifce is a TAP interface. + IsTAP() bool + // DeviceType returns the interface's device type. + Type() DeviceType + // Name returns the name of the interface. + Name() string + // Sys returns the underlying system interface for the interface. This is + // useful if caller needs to perform system calls directly on the tun/tap + // device. + // + // On Unix systems, this returns a file descriptor of uintptr type. On + // Windows, this returns a syscall.Handle. + Sys() interface{} } // DeviceType is the type for specifying device types. type DeviceType int -// TUN and TAP device types. +// Constants for TUN and TAP interfaces. const ( - _ = iota + _ DeviceType = iota TUN TAP ) @@ -49,7 +62,7 @@ func defaultConfig() Config { var zeroConfig Config // New creates a new TUN/TAP interface using config. -func New(config Config) (ifce *Interface, err error) { +func New(config Config) (ifce Interface, err error) { if zeroConfig == config { config = defaultConfig() } @@ -63,18 +76,3 @@ func New(config Config) (ifce *Interface, err error) { return nil, errors.New("unknown device type") } } - -// IsTUN returns true if ifce is a TUN interface. -func (ifce *Interface) IsTUN() bool { - return !ifce.isTAP -} - -// IsTAP returns true if ifce is a TAP interface. -func (ifce *Interface) IsTAP() bool { - return ifce.isTAP -} - -// Name returns the interface name of ifce, e.g. tun0, tap1, tun0, etc.. -func (ifce *Interface) Name() string { - return ifce.name -} diff --git a/if_linux.go b/if_linux.go index 227e8a8..e6463ea 100644 --- a/if_linux.go +++ b/if_linux.go @@ -10,7 +10,7 @@ import ( // ifName cannot be specified on windows, you will need ifce.Name() to use some cmds. // // Deprecated: This function may be removed in the future. Please use New() instead. -func NewTAP(ifName string) (ifce *Interface, err error) { +func NewTAP(ifName string) (ifce Interface, err error) { fmt.Println("Deprecated: NewTAP(..) may be removed in the future. Please use New() instead.") config := Config{DeviceType: TAP} config.Name = ifName @@ -22,7 +22,7 @@ func NewTAP(ifName string) (ifce *Interface, err error) { // ifName cannot be specified on windows, you will need ifce.Name() to use some cmds. // // Deprecated: This function will be removed in the future. Please use New() instead. -func NewTUN(ifName string) (ifce *Interface, err error) { +func NewTUN(ifName string) (ifce Interface, err error) { fmt.Println("Deprecated: NewTUN(..) may be removed in the future. Please use New() instead.") config := Config{DeviceType: TUN} config.Name = ifName diff --git a/if_nix.go b/if_nix.go new file mode 100644 index 0000000..f6bdbde --- /dev/null +++ b/if_nix.go @@ -0,0 +1,32 @@ +// +build linux darwin + +package water + +import "io" + +type ifce struct { + deviceType DeviceType + fd uintptr + name string + io.ReadWriteCloser +} + +func (i *ifce) IsTUN() bool { + return i.deviceType == TUN +} + +func (i *ifce) IsTAP() bool { + return i.deviceType == TAP +} + +func (i *ifce) Type() DeviceType { + return i.deviceType +} + +func (i *ifce) Name() string { + return i.name +} + +func (i *ifce) Sys() interface{} { + return i.fd +} diff --git a/ipv4_darwin_test.go b/ipv4_darwin_test.go index 2a5d646..685b6d5 100644 --- a/ipv4_darwin_test.go +++ b/ipv4_darwin_test.go @@ -22,7 +22,7 @@ func setupIfce(t *testing.T, self net.IP, remote net.IP, dev string) { } } -func teardownIfce(t *testing.T, ifce *Interface) { +func teardownIfce(t *testing.T, ifce Interface) { if err := ifce.Close(); err != nil { t.Fatal(err) } diff --git a/ipv4_linux_test.go b/ipv4_linux_test.go index 6544809..dd804fc 100644 --- a/ipv4_linux_test.go +++ b/ipv4_linux_test.go @@ -25,7 +25,7 @@ func setupIfce(t *testing.T, ipNet net.IPNet, dev string) { } } -func teardownIfce(t *testing.T, ifce *Interface) { +func teardownIfce(t *testing.T, ifce Interface) { if err := ifce.Close(); err != nil { t.Fatal(err) } diff --git a/ipv4_test.go b/ipv4_test.go index 7a8bf1e..3ecfd6b 100644 --- a/ipv4_test.go +++ b/ipv4_test.go @@ -10,7 +10,7 @@ import ( const BUFFERSIZE = 1522 -func startRead(t *testing.T, ifce *Interface) (dataChan <-chan []byte, errChan <-chan error) { +func startRead(t *testing.T, ifce Interface) (dataChan <-chan []byte, errChan <-chan error) { dataCh := make(chan []byte) errCh := make(chan error) go func() { diff --git a/ipv4_windows_test.go b/ipv4_windows_test.go index e45f174..da0e3c5 100644 --- a/ipv4_windows_test.go +++ b/ipv4_windows_test.go @@ -26,7 +26,7 @@ func setupIfce(t *testing.T, ipNet net.IPNet, dev string) { } } -func teardownIfce(t *testing.T, ifce *Interface) { +func teardownIfce(t *testing.T, ifce Interface) { if err := ifce.Close(); err != nil { t.Fatal(err) } diff --git a/params_linux.go b/params_linux.go index 34e9898..e1b24b0 100644 --- a/params_linux.go +++ b/params_linux.go @@ -37,6 +37,11 @@ type PlatformSpecificParams struct { // interface. From version 3.8, Linux supports multiqueue tuntap which can // uses multiple file descriptors (queues) to parallelize packets sending // or receiving. + // + // MultiQueue (Linux kernel > 3.8): With MultiQueue enabled, user should + // hold multiple interfaces to send/receive packet in parallel. Kernel + // document about MultiQueue: + // https://www.kernel.org/doc/Documentation/networking/tuntap.txt MultiQueue bool } diff --git a/syscalls_darwin.go b/syscalls_darwin.go index b344034..6343627 100644 --- a/syscalls_darwin.go +++ b/syscalls_darwin.go @@ -59,11 +59,12 @@ type sockaddrCtl struct { var sockaddrCtlSize uintptr = 32 -func openDev(config Config) (ifce *Interface, err error) { +func openDev(config Config) (Interface, error) { if config.DeviceType != TUN { return nil, errors.New("only tun is implemented on this platform") } var fd int + var err error // Supposed to be socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL), but ... // // In sys/socket.h: @@ -118,9 +119,10 @@ func openDev(config Config) (ifce *Interface, err error) { return nil, fmt.Errorf("setting non-blocking error") } - return &Interface{ - isTAP: false, - name: string(ifName.name[:ifNameSize-1 /* -1 is for \0 */]), + return &ifce{ + deviceType: config.DeviceType, + fd: uintptr(fd), + name: string(ifName.name[:ifNameSize-1 /* -1 is for \0 */]), ReadWriteCloser: &tunReadCloser{ f: os.NewFile(uintptr(fd), string(ifName.name[:])), }, diff --git a/syscalls_linux_go1.11.go b/syscalls_linux_go1.11.go index 90c47a1..0443616 100644 --- a/syscalls_linux_go1.11.go +++ b/syscalls_linux_go1.11.go @@ -7,8 +7,9 @@ import ( "syscall" ) -func openDev(config Config) (ifce *Interface, err error) { +func openDev(config Config) (Interface, error) { var fdInt int + var err error if fdInt, err = syscall.Open( "/dev/net/tun", os.O_RDWR|syscall.O_NONBLOCK, 0); err != nil { return nil, err @@ -19,9 +20,10 @@ func openDev(config Config) (ifce *Interface, err error) { return nil, err } - return &Interface{ - isTAP: config.DeviceType == TAP, - ReadWriteCloser: os.NewFile(uintptr(fdInt), "tun"), + return &ifce{ + deviceType: config.DeviceType, + fd: uintptr(fdInt), name: name, + ReadWriteCloser: os.NewFile(uintptr(fdInt), "tun"), }, nil } diff --git a/syscalls_linux_legacy.go b/syscalls_linux_legacy.go index 6499b37..12d9bf9 100644 --- a/syscalls_linux_legacy.go +++ b/syscalls_linux_legacy.go @@ -6,8 +6,9 @@ import ( "os" ) -func openDev(config Config) (ifce *Interface, err error) { +func openDev(config Config) (Interface, error) { var file *os.File + var err error if file, err = os.OpenFile( "/dev/net/tun", os.O_RDWR, 0); err != nil { return nil, err @@ -18,9 +19,10 @@ func openDev(config Config) (ifce *Interface, err error) { return nil, err } - return &Interface{ - isTAP: config.DeviceType == TAP, - ReadWriteCloser: file, + return &ifce{ + deviceType: config.DeviceType, + fd: file.Fd(), name: name, + ReadWriteCloser: file, }, nil } diff --git a/syscalls_windows.go b/syscalls_windows.go index d495262..6189fe7 100644 --- a/syscalls_windows.go +++ b/syscalls_windows.go @@ -94,11 +94,29 @@ func newOverlapped() (*syscall.Overlapped, error) { } type wfile struct { - fd syscall.Handle - rl sync.Mutex - wl sync.Mutex - ro *syscall.Overlapped - wo *syscall.Overlapped + fd syscall.Handle + rl sync.Mutex + wl sync.Mutex + ro *syscall.Overlapped + wo *syscall.Overlapped + name string + deviceType DeviceType +} + +func (f *wfile) Name() string { + return f.name +} + +func (f *wfile) IsTAP() bool { + return f.deviceType == TAP +} + +func (f *wfile) IsTUN() bool { + return f.deviceType == TUN +} + +func (f *wfile) Type() DeviceType { + return f.deviceType } func (f *wfile) Close() error { @@ -135,6 +153,10 @@ func (f *wfile) Read(b []byte) (int, error) { return getOverlappedResult(f.fd, f.ro) } +func (f *wfile) Sys() interface{} { + return f.fd +} + func ctl_code(device_type, function, method, access uint32) uint32 { return (device_type << 16) | (access << 14) | (function << 2) | method } @@ -233,7 +255,7 @@ func setTUN(fd syscall.Handle, network string) error { } // openDev find and open an interface. -func openDev(config Config) (ifce *Interface, err error) { +func openDev(config Config) (Interface, error) { // find the device in registry. deviceid, err := getdeviceid(config.PlatformSpecificParams.ComponentID, config.PlatformSpecificParams.InterfaceName) if err != nil { @@ -270,14 +292,18 @@ func openDev(config Config) (ifce *Interface, err error) { // fd := os.NewFile(uintptr(file), path) ro, err := newOverlapped() if err != nil { - return + return nil, err } wo, err := newOverlapped() if err != nil { - return + return nil, err + } + fd := &wfile{ + fd: file, + ro: ro, + wo: wo, + deviceType: config.DeviceType, } - fd := &wfile{fd: file, ro: ro, wo: wo} - ifce = &Interface{isTAP: (config.DeviceType == TAP), ReadWriteCloser: fd} // bring up device. if err := setStatus(file, true); err != nil { @@ -294,13 +320,13 @@ func openDev(config Config) (ifce *Interface, err error) { // find the name of tap interface(u need it to set the ip or other command) ifces, err := net.Interfaces() if err != nil { - return + return nil, err } for _, v := range ifces { if bytes.Equal(v.HardwareAddr[:6], mac[:6]) { - ifce.name = v.Name - return + fd.name = v.Name + return fd, nil } }