mirror of
https://github.com/yggdrasil-network/water.git
synced 2025-05-19 08: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