فهرست منبع

add darwin support for point-to-point TUN and update doc

Song Gao 9 سال پیش
والد
کامیت
a7802d4a18
5فایلهای تغییر یافته به همراه230 افزوده شده و 51 حذف شده
  1. 94 40
      README.md
  2. 3 1
      doc.go
  3. 9 9
      if.go
  4. 123 0
      syscalls_darwin.go
  5. 1 1
      syscalls_other.go

+ 94 - 40
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.~~
+
+See https://github.com/songgao/packets for functions for parsing various packets.
 
-`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.
+## 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:
 ```
-<nil>, &{true 0xf84003f058 tap0}
+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
+```
+
+### TUN on macOS
+
+```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"
 
-Source:      42:35:da:af:2b:00 [10.0.0.1]
-Destination: ff:ff:ff:ff:ff:ff [10.0.0.255]
-Protocol:    1
+	"github.com/songgao/water"
+)
+
+func main() {
+	ifce, err := water.NewTUN("")
+	if err != nil {
+		log.Fatal(err)
+	}
 
-Source:      42:35:da:af:2b:00 [10.0.0.1]
-Destination: ff:ff:ff:ff:ff:ff [10.0.0.255]
-Protocol:    1
+	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)

+ 3 - 1
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

+ 9 - 9
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
 }

+ 123 - 0
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")
+}

+ 1 - 1
syscalls_other.go

@@ -1,4 +1,4 @@
-// +build !linux
+// +build NOT (linux OR darwin)
 
 package water