From a7802d4a18fd0bbb356c1f79de7f3887bfee1544 Mon Sep 17 00:00:00 2001 From: Song Gao Date: Sun, 23 Oct 2016 20:30:27 -0700 Subject: [PATCH] add darwin support for point-to-point TUN and update doc --- README.md | 134 +++++++++++++++++++++++++++++++-------------- doc.go | 4 +- if.go | 18 +++--- syscalls_darwin.go | 123 +++++++++++++++++++++++++++++++++++++++++ syscalls_other.go | 2 +- 5 files changed, 230 insertions(+), 51 deletions(-) create mode 100644 syscalls_darwin.go diff --git a/README.md b/README.md index 8ebd252..28b21d4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,21 @@ # 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 how to deal with buffers; whether to use GC. +* 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. +~~`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. + +## Supported Platforms + +* Linux +* macOS (point-to-point TUN only) ## Installation ``` @@ -18,79 +26,125 @@ go get -u github.com/songgao/water/waterutil ## Documentation [http://godoc.org/github.com/songgao/water](http://godoc.org/github.com/songgao/water) -[http://godoc.org/github.com/songgao/water/waterutil](http://godoc.org/github.com/songgao/water/waterutil) - ## Example +### TAP on Linux: + ```go package main import ( + "log" + + "github.com/songgao/packets/ethernet" "github.com/songgao/water" - "github.com/songgao/water/waterutil" - "fmt" ) -const BUFFERSIZE = 1522 - func main() { - ifce, err := water.NewTAP("") - fmt.Printf("%v, %v\n\n", err, ifce) - buffer := make([]byte, BUFFERSIZE) + ifce, err := water.NewTAP("O_O") + if err != nil { + log.Fatal(err) + } + var frame ethernet.Frame + for { - _, err = ifce.Read(buffer) + frame.Resize(1500) + n, err := ifce.Read([]byte(frame)) if err != nil { - break - } - ethertype := waterutil.MACEthertype(buffer) - if ethertype == waterutil.IPv4 { - packet := waterutil.MACPayload(buffer) - if waterutil.IsIPv4(packet) { - fmt.Printf("Source: %v [%v]\n", waterutil.MACSource(buffer), waterutil.IPv4Source(packet)) - fmt.Printf("Destination: %v [%v]\n", waterutil.MACDestination(buffer), waterutil.IPv4Destination(packet)) - fmt.Printf("Protocol: %v\n\n", waterutil.IPv4Protocol(packet)) - } + log.Fatal(err) } + frame = frame[:n] + log.Printf("Dst: %s\n", frame.Destination()) + log.Printf("Src: %s\n", frame.Source()) + log.Printf("Ethertype: % x\n", frame.Ethertype()) + log.Printf("Payload: % x\n", frame.Payload()) } } ``` -This piece of code creates a `TAP` interface, and prints some header information for every IPv4 packet. After pull up the `main.go`, you'll need to bring up the interface and assign IP address. All of these need root permission. +This piece of code creates a `TAP` interface, and prints some header information for every frame. After pull up the `main.go`, you'll need to bring up the interface and assign an IP address. All of these need root permission. ```bash sudo go run main.go ``` +In a new terminal: + ```bash -sudo ip link set dev tap0 up -sudo ip addr add 10.0.0.1/24 dev tap0 +sudo ip addr add 10.1.0.10/24 dev O_O +sudo ip link set dev O_O up ``` -Now, try sending some ICMP broadcast message: +Wait until the output `main.go` terminal, try sending some ICMP broadcast message: ```bash -ping -b 10.0.0.255 +ping -c1 -b 10.1.0.255 ``` -You'll see the `main.go` print something like: +You'll see output containing the IPv4 ICMP frame: +``` +2016/10/24 03:18:16 Dst: ff:ff:ff:ff:ff:ff +2016/10/24 03:18:16 Src: 72:3c:fc:29:1c:6f +2016/10/24 03:18:16 Ethertype: 08 00 +2016/10/24 03:18:16 Payload: 45 00 00 54 00 00 40 00 40 01 25 9f 0a 01 00 0a 0a 01 00 ff 08 00 01 c1 08 49 00 01 78 7d 0d 58 00 00 00 00 a2 4c 07 00 00 00 00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 ``` -, &{true 0xf84003f058 tap0} -Source: 42:35:da:af:2b:00 [10.0.0.1] -Destination: ff:ff:ff:ff:ff:ff [10.0.0.255] -Protocol: 1 +### TUN on macOS -Source: 42:35:da:af:2b:00 [10.0.0.1] -Destination: ff:ff:ff:ff:ff:ff [10.0.0.255] -Protocol: 1 +```go +package main -Source: 42:35:da:af:2b:00 [10.0.0.1] -Destination: ff:ff:ff:ff:ff:ff [10.0.0.255] -Protocol: 1 +import ( + "log" + + "github.com/songgao/water" +) + +func main() { + ifce, err := water.NewTUN("") + if err != nil { + log.Fatal(err) + } + + log.Printf("Interface Name: %s\n", ifce.Name()) + + packet := make([]byte, 2000) + for { + n, err := ifce.Read(packet) + if err != nil { + log.Fatal(err) + } + log.Printf("Packet Received: % x\n", packet[:n]) + } +} +``` + +Run it! + +```bash +$ sudo go run main.go +``` + +This is a point-to-point only interface. Use `ifconfig` to see its attributes. You need to bring it up and assign IP addresses (apparently replace `utun2` if needed): + +```bash +$ sudo ifconfig utun2 10.1.0.10 10.1.0.20 up +``` + +Now send some ICMP packets to the interface: + +```bash +$ ping 10.1.0.20 +``` + +You'd see the ICMP packets printed out: + +``` +2016/10/23 20:21:53 Interface Name: utun2 +2016/10/23 20:22:40 Packet Received: 00 00 00 02 45 00 00 54 4a 2e 00 00 40 01 1c 5c 0a 01 00 0a 0a 01 00 14 08 00 31 51 f0 f9 00 00 58 0d 7e 80 00 03 14 21 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 ``` ## TODO -* IPv6 Support in `waterutil` -* Darwin(Mac) Support +* tuntaposx for TAP on Darwin ## LICENSE [BSD 3-Clause License](http://opensource.org/licenses/BSD-3-Clause) diff --git a/doc.go b/doc.go index a7e40ba..287412b 100644 --- a/doc.go +++ b/doc.go @@ -1,2 +1,4 @@ -// 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 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 diff --git a/if.go b/if.go index f48d229..21f1d4c 100644 --- a/if.go +++ b/if.go @@ -9,31 +9,31 @@ type Interface struct { name string } -// Create a new TAP interface whose name is ifName. -// If ifName is empty, a default name (tap0, tap1, ... ) will be assigned. -// ifName should not exceed 16 bytes. +// NewTAP creates a new TAP interface whose name is ifName. If ifName is empty, a +// default name (tap0, tap1, ... ) will be assigned. ifName should not exceed +// 16 bytes. TAP interfaces are not supported on darwin. func NewTAP(ifName string) (ifce *Interface, err error) { return newTAP(ifName) } -// Create a new TUN interface whose name is ifName. -// If ifName is empty, a default name (tap0, tap1, ... ) will be assigned. -// ifName should not exceed 16 bytes. +// NewTUN creates a new TUN interface whose name is ifName. If ifName is empty, a +// default name (tap0, tap1, ... ) will be assigned. ifName should not exceed +// 16 bytes. Setting interface name is NOT supported on darwin. func NewTUN(ifName string) (ifce *Interface, err error) { return newTUN(ifName) } -// Returns true if ifce is a TUN interface, otherwise returns false; +// IsTUN returns true if ifce is a TUN interface. func (ifce *Interface) IsTUN() bool { return !ifce.isTAP } -// Returns true if ifce is a TAP interface, otherwise returns false; +// IsTAP returns true if ifce is a TAP interface. func (ifce *Interface) IsTAP() bool { return ifce.isTAP } -// Returns the interface name of ifce, e.g. tun0, tap1, etc.. +// Name returns the interface name of ifce, e.g. tun0, tap1, tun0, etc.. func (ifce *Interface) Name() string { return ifce.name } diff --git a/syscalls_darwin.go b/syscalls_darwin.go new file mode 100644 index 0000000..4f53283 --- /dev/null +++ b/syscalls_darwin.go @@ -0,0 +1,123 @@ +// +build darwin + +package water + +import ( + "errors" + "fmt" + "os" + "syscall" + "unsafe" +) + +const appleUTUNCtl = "com.apple.net.utun_control" + +/* + * From ioctl.h: + * #define IOCPARM_MASK 0x1fff // parameter length, at most 13 bits + * ... + * #define IOC_OUT 0x40000000 // copy out parameters + * #define IOC_IN 0x80000000 // copy in parameters + * #define IOC_INOUT (IOC_IN|IOC_OUT) + * ... + * #define _IOC(inout,group,num,len) \ + * (inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num)) + * ... + * #define _IOWR(g,n,t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) + * + * From kern_control.h: + * #define CTLIOCGINFO _IOWR('N', 3, struct ctl_info) // get id from name + * + */ + +const appleCTLIOCGINFO = (0x40000000 | 0x80000000) | ((100 & 0x1fff) << 16) | uint32(byte('N'))<<8 | 3 + +/* + * #define _IOW(g,n,t) _IOC(IOC_IN, (g), (n), sizeof(t)) + * #define TUNSIFMODE _IOW('t', 94, int) + */ +const appleTUNSIFMODE = (0x80000000) | ((4 & 0x1fff) << 16) | uint32(byte('t'))<<8 | 94 + +/* + * struct sockaddr_ctl { + * u_char sc_len; // depends on size of bundle ID string + * u_char sc_family; // AF_SYSTEM + * u_int16_t ss_sysaddr; // AF_SYS_KERNCONTROL + * u_int32_t sc_id; // Controller unique identifier + * u_int32_t sc_unit; // Developer private unit number + * u_int32_t sc_reserved[5]; + * }; + */ +type sockaddrCtl struct { + scLen uint8 + scFamily uint8 + ssSysaddr uint16 + scID uint32 + scUnit uint32 + scReserved [5]uint32 +} + +var sockaddrCtlSize uintptr = 32 + +func newTUN(string) (ifce *Interface, err error) { + var fd int + // Supposed to be socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL), but ... + // + // In sys/socket.h: + // #define PF_SYSTEM AF_SYSTEM + // + // In sys/sys_domain.h: + // #define SYSPROTO_CONTROL 2 /* kernel control protocol */ + if fd, err = syscall.Socket(syscall.AF_SYSTEM, syscall.SOCK_DGRAM, 2); err != nil { + return nil, fmt.Errorf("error in syscall.Socket: %v", err) + } + + var ctlInfo = &struct { + ctlID uint32 + ctlName [96]byte + }{} + copy(ctlInfo.ctlName[:], []byte(appleUTUNCtl)) + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(appleCTLIOCGINFO), uintptr(unsafe.Pointer(ctlInfo))); errno != 0 { + err = errno + return nil, fmt.Errorf("error in syscall.Syscall(syscall.SYS_IOTL, ...): %v", err) + } + + addrP := unsafe.Pointer(&sockaddrCtl{ + scLen: uint8(sockaddrCtlSize), + scFamily: syscall.AF_SYSTEM, + + /* #define AF_SYS_CONTROL 2 */ + ssSysaddr: 2, + + scID: ctlInfo.ctlID, + scUnit: 0, + }) + if _, _, errno := syscall.RawSyscall(syscall.SYS_CONNECT, uintptr(fd), uintptr(addrP), uintptr(sockaddrCtlSize)); errno != 0 { + err = errno + return nil, fmt.Errorf("error in syscall.RawSyscall(syscall.SYS_CONNECT, ...): %v", err) + } + + var ifName struct { + name [16]byte + } + ifNameSize := uintptr(16) + if _, _, errno := syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(fd), + 2, /* #define SYSPROTO_CONTROL 2 */ + 2, /* #define UTUN_OPT_IFNAME 2 */ + uintptr(unsafe.Pointer(&ifName)), + uintptr(unsafe.Pointer(&ifNameSize)), 0); errno != 0 { + err = errno + return nil, fmt.Errorf("error in syscall.Syscall6(syscall.SYS_GETSOCKOPT, ...): %v", err) + } + + return &Interface{ + isTAP: false, + name: string(ifName.name[:ifNameSize-1 /* -1 is for \0 */]), + ReadWriteCloser: os.NewFile(uintptr(fd), string(ifName.name[:])), + }, nil +} + +func newTAP(ifName string) (ifce *Interface, err error) { + return nil, errors.New("tap interface not implemented on this platform") +} diff --git a/syscalls_other.go b/syscalls_other.go index ace8c3c..26cea1b 100644 --- a/syscalls_other.go +++ b/syscalls_other.go @@ -1,4 +1,4 @@ -// +build !linux +// +build NOT (linux OR darwin) package water