mirror of
				https://github.com/yggdrasil-network/water.git
				synced 2025-10-31 17:25:09 +03:00 
			
		
		
		
	add darwin support for point-to-point TUN and update doc
This commit is contained in:
		
							parent
							
								
									f87d2289c2
								
							
						
					
					
						commit
						a7802d4a18
					
				
					 5 changed files with 230 additions and 51 deletions
				
			
		
							
								
								
									
										134
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										134
									
								
								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 | ||||
| ``` | ||||
| <nil>, &{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) | ||||
|  |  | |||
							
								
								
									
										4
									
								
								doc.go
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								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 | ||||
|  |  | |||
							
								
								
									
										18
									
								
								if.go
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								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
									
								
								syscalls_darwin.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								syscalls_darwin.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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,4 +1,4 @@ | |||
| // +build !linux | ||||
| // +build NOT (linux OR darwin) | ||||
| 
 | ||||
| package water | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Song Gao
						Song Gao