diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..42bb3e8 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,10 @@ +Song Gao +Harshal Sheth +KOJIMA Takanori +Sean Purser-Haskell +daregod +Lucus Lee +Arroyo Networks, LLC +Tony Lu +ajee cai +yinheli \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..65deac8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2016, Song Gao +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of water nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b53f1db --- /dev/null +++ b/README.md @@ -0,0 +1,227 @@ +# 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. + +## Supported Platforms + +* Linux +* Windows (experimental; APIs might change) +* 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 + +### TAP on Linux: + +```go +package main + +import ( + "log" + + "github.com/songgao/packets/ethernet" + "github.com/songgao/water" +) + +func main() { + config := water.Config{ + DeviceType: water.TAP, + } + config.Name = "O_O" + + ifce, err := water.New(config) + if err != nil { + log.Fatal(err) + } + var frame ethernet.Frame + + for { + frame.Resize(1500) + n, err := ifce.Read([]byte(frame)) + if err != nil { + 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 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 addr add 10.1.0.10/24 dev O_O +sudo ip link set dev O_O up +``` + +Wait until the output `main.go` terminal, try sending some ICMP broadcast message: +```bash +ping -c1 -b 10.1.0.255 +``` + +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 +``` + +### TUN on macOS + +```go +package main + +import ( + "log" + + "github.com/songgao/water" +) + +func main() { + ifce, err := water.New(water.Config{ + DeviceType: water.TUN, + }) + 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: + +``` +2017/03/20 21:17:30 Interface Name: utun2 +2017/03/20 21:17:40 Packet Received: 45 00 00 54 e9 1d 00 00 40 01 7d 6c 0a 01 00 0a 0a 01 00 14 08 00 ee 04 21 15 00 00 58 d0 a9 64 00 08 fb a5 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 +``` + +#### Caveats + +1. Only Point-to-Point user TUN devices are supported. TAP devices are *not* supported natively by macOS. +2. Custom interface names are not supported by macOS. Interface names are automatically generated serially, using the `utun<#>` naming convention. + +### TAP on Windows: + +To use it with windows, you will need to install a [tap driver](https://github.com/OpenVPN/tap-windows6), or [OpenVPN client](https://github.com/OpenVPN/openvpn) for windows. + +It's compatible with the Linux code. + +```go +package main + +import ( + "log" + + "github.com/songgao/packets/ethernet" + "github.com/songgao/water" +) + +func main() { + ifce, err := water.New(water.Config{ + DeviceType: water.TAP, + }) + if err != nil { + log.Fatal(err) + } + var frame ethernet.Frame + + for { + frame.Resize(1500) + n, err := ifce.Read([]byte(frame)) + if err != nil { + 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()) + } +} +``` + +Same as Linux version, but you don't need to bring up the device by hand, the only thing you need is to assign an IP address to it. + +```dos +go run main.go +``` + +It will output a lot of lines because of some windows services and dhcp. +You will need admin right to assign IP. + +In a new cmd (admin right): + +```dos +# Replace with your device name, it can be achieved by ifce.Name(). +netsh interface ip set address name="Ehternet 2" source=static addr=10.1.0.10 mask=255.255.255.0 gateway=none +``` + +The `main.go` terminal should be silenced after IP assignment, try sending some ICMP broadcast message: + +```dos +ping 10.1.0.255 +``` + +You'll see output containing the IPv4 ICMP frame same as the Linux version. + +## 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 new file mode 100644 index 0000000..287412b --- /dev/null +++ b/doc.go @@ -0,0 +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 diff --git a/if.go b/if.go new file mode 100644 index 0000000..143f3eb --- /dev/null +++ b/if.go @@ -0,0 +1,80 @@ +package water + +import ( + "errors" + "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 + io.ReadWriteCloser + name string +} + +// DeviceType is the type for specifying device types. +type DeviceType int + +// TUN and TAP device types. +const ( + _ = iota + TUN + TAP +) + +// Config defines parameters required to create a TUN/TAP interface. It's only +// used when the device is initialized. A zero-value Config is a valid +// configuration. +type Config struct { + // DeviceType specifies whether the device is a TUN or TAP interface. A + // zero-value is treated as TUN. + DeviceType DeviceType + + // PlatformSpecificParams defines parameters that differ on different + // platforms. See comments for the type for more details. + PlatformSpecificParams +} + +func defaultConfig() Config { + return Config{ + DeviceType: TUN, + PlatformSpecificParams: defaultPlatformSpecificParams(), + } +} + +var zeroConfig Config + +// New creates a new TUN/TAP interface using config. +func New(config Config) (ifce *Interface, err error) { + if zeroConfig == config { + config = defaultConfig() + } + config.PlatformSpecificParams = defaultPlatformSpecificParams() + switch config.DeviceType { + case TUN: + return newTUN(config) + case TAP: + return newTAP(config) + default: + 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 new file mode 100644 index 0000000..7752dc0 --- /dev/null +++ b/if_linux.go @@ -0,0 +1,32 @@ +// +build linux + +package water + +import ( + "fmt" +) + +// 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. +// 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) { + fmt.Println("Deprecated: NewTAP(..) may be removed in the future. Please use New() instead.") + config := Config{DeviceType: TAP} + config.Name = ifName + return newTAP(config) +} + +// 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 +// 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) { + fmt.Println("Deprecated: NewTUN(..) may be removed in the future. Please use New() instead.") + config := Config{DeviceType: TUN} + config.Name = ifName + return newTUN(config) +} diff --git a/ipv4_linux_test.go b/ipv4_linux_test.go new file mode 100644 index 0000000..54f6884 --- /dev/null +++ b/ipv4_linux_test.go @@ -0,0 +1,23 @@ +// +build linux,darwin +package water + +import ( + "net" + "os/exec" + "testing" +) + +func startBroadcast(t *testing.T, dst net.IP) { + if err := exec.Command("ping", "-b", "-c", "2", dst.String()).Start(); err != nil { + t.Fatal(err) + } +} + +func setupIfce(t *testing.T, ipNet net.IPNet, dev string) { + if err := exec.Command("ip", "link", "set", dev, "up").Run(); err != nil { + t.Fatal(err) + } + if err := exec.Command("ip", "addr", "add", ipNet.String(), "dev", dev).Run(); err != nil { + t.Fatal(err) + } +} diff --git a/ipv4_other_test.go b/ipv4_other_test.go new file mode 100644 index 0000000..7d2177f --- /dev/null +++ b/ipv4_other_test.go @@ -0,0 +1,12 @@ +// +build !linux,!windows + +package water + +import ( + "net" + "testing" +) + +func setupIfce(t *testing.T, ipNet net.IPNet, dev string) { + t.Fatal("unsupported platform") +} diff --git a/ipv4_test.go b/ipv4_test.go new file mode 100644 index 0000000..ec6d89b --- /dev/null +++ b/ipv4_test.go @@ -0,0 +1,76 @@ +package water + +import ( + "net" + "testing" + "time" + + "github.com/songgao/water/waterutil" +) + +const BUFFERSIZE = 1522 + +func startRead(ch chan<- []byte, ifce *Interface) { + go func() { + for { + buffer := make([]byte, BUFFERSIZE) + n, err := ifce.Read(buffer) + if err == nil { + buffer = buffer[:n:n] + ch <- buffer + } + } + }() +} + +func TestBroadcast(t *testing.T) { + var ( + self = net.IPv4(10, 0, 42, 1) + mask = net.IPv4Mask(255, 255, 255, 0) + brd = net.IPv4(10, 0, 42, 255) + ) + + ifce, err := New(Config{DeviceType: TAP}) + if err != nil { + t.Fatalf("creating TAP error: %v\n", err) + } + + setupIfce(t, net.IPNet{IP: self, Mask: mask}, ifce.Name()) + startBroadcast(t, brd) + + dataCh := make(chan []byte, 8) + startRead(dataCh, ifce) + + timeout := time.NewTimer(8 * time.Second).C + +readFrame: + for { + select { + case buffer := <-dataCh: + ethertype := waterutil.MACEthertype(buffer) + if ethertype != waterutil.IPv4 { + continue readFrame + } + if !waterutil.IsBroadcast(waterutil.MACDestination(buffer)) { + continue readFrame + } + packet := waterutil.MACPayload(buffer) + if !waterutil.IsIPv4(packet) { + continue readFrame + } + if !waterutil.IPv4Source(packet).Equal(self) { + continue readFrame + } + if !waterutil.IPv4Destination(packet).Equal(brd) { + continue readFrame + } + if waterutil.IPv4Protocol(packet) != waterutil.ICMP { + continue readFrame + } + t.Logf("received broadcast frame: %#v\n", buffer) + break readFrame + case <-timeout: + t.Fatal("Waiting for broadcast packet timeout") + } + } +} diff --git a/ipv4_windows_test.go b/ipv4_windows_test.go new file mode 100644 index 0000000..6a46e7c --- /dev/null +++ b/ipv4_windows_test.go @@ -0,0 +1,28 @@ +// +build windows +package water + +import ( + "fmt" + "net" + "os/exec" + "strings" + "testing" +) + +func startBroadcast(t *testing.T, dst net.IP) { + if err := exec.Command("ping", "-n", "2", dst.String()).Start(); err != nil { + t.Fatal(err) + } +} + +func setupIfce(t *testing.T, ipNet net.IPNet, dev string) { + sargs := fmt.Sprintf("interface ip set address name=REPLACE_ME source=static addr=REPLACE_ME mask=REPLACE_ME gateway=none") + args := strings.Split(sargs, " ") + args[4] = fmt.Sprintf("name=%s", dev) + args[6] = fmt.Sprintf("addr=%s", ipNet.IP) + args[7] = fmt.Sprintf("mask=%d.%d.%d.%d", ipNet.Mask[0], ipNet.Mask[1], ipNet.Mask[2], ipNet.Mask[3]) + cmd := exec.Command("netsh", args...) + if err := cmd.Run(); err != nil { + t.Fatal(err) + } +} diff --git a/params_darwin.go b/params_darwin.go new file mode 100644 index 0000000..bba950e --- /dev/null +++ b/params_darwin.go @@ -0,0 +1,14 @@ +// +build darwin + +package water + +// PlatformSpecificParams defines parameters in Config that are specific to +// macOS. A zero-value of such type is valid, yielding an interface +// with OS defined name. +// Currently it is not possible to set the interface name in macOS. +type PlatformSpecificParams struct { +} + +func defaultPlatformSpecificParams() PlatformSpecificParams { + return PlatformSpecificParams{} +} diff --git a/params_linux.go b/params_linux.go new file mode 100644 index 0000000..ffbd3a6 --- /dev/null +++ b/params_linux.go @@ -0,0 +1,43 @@ +// +build linux + +package water + +type DevicePermissions struct { + // ID of the user which will be granted ownership of the device. + // If set to a negative value, the owner value will not be changed. + // By default, Linux sets the owner to -1, which allows any user. + Owner uint + + // ID of the group which will be granted access to the device. + // If set to a negative value, the group value will not be changed. + // By default, Linux sets the group to -1, which allows any group. + Group uint +} + +// PlatformSpecificParams defines parameters in Config that are specific to +// Linux. A zero-value of such type is valid, yielding an interface +// with OS defined name. +type PlatformSpecificParams struct { + // Name is the name to be set for the interface to be created. This overrides + // the default name assigned by OS such as tap0 or tun0. A zero-value of this + // field, i.e. an empty string, indicates that the default name should be + // used. + Name string + + // Enable or disable persistence mode for the interface device. + Persist bool + + // Owner and Group permissions for the device. + // A zero-value of this field, i.e. nil, indicates that no changes to owner + // or group will be made. + Permissions *DevicePermissions + + // Support multiqueue tun/tap interface. + // From version 3.8, Linux supports multiqueue tuntap which can uses multiple + // file descriptors (queues) to parallelize packets sending or receiving. + MultiQueue bool +} + +func defaultPlatformSpecificParams() PlatformSpecificParams { + return PlatformSpecificParams{} +} diff --git a/params_others.go b/params_others.go new file mode 100644 index 0000000..c5c4f6e --- /dev/null +++ b/params_others.go @@ -0,0 +1,11 @@ +// +build !linux,!darwin,!windows + +package water + +// PlatformSpeficParams +type PlatformSpecificParams struct { +} + +func defaultPlatformSpecificParams() PlatformSpecificParams { + return PlatformSpecificParams{} +} diff --git a/params_windows.go b/params_windows.go new file mode 100644 index 0000000..89016ce --- /dev/null +++ b/params_windows.go @@ -0,0 +1,32 @@ +// +build windows + +package water + +// PlatformSpecificParams defines parameters in Config that are specific to +// Windows. A zero-value of such type is valid. +type PlatformSpecificParams struct { + // ComponentID associates with the virtual adapter that exists in Windows. + // This is usually configured when driver for the adapter is installed. A + // zero-value of this field, i.e., an empty string, causes the interface to + // use the default ComponentId. The default ComponentId is set to tap0901, + // the one used by OpenVPN. + ComponentID string + // Network is required when creating a TUN interface. The library will call + // net.ParseCIDR() to parse this string into LocalIP, RemoteNetaddr, + // RemoteNetmask. The underlying driver will need those to generate ARP + // response to Windows kernel, to emulate an TUN interface. + // Please note that it cannot perceive the IP changes caused by DHCP, user + // configuration to the adapter and etc,. If IP changed, please reconfigure + // the adapter using syscall, just like openDev(). + // For detail, please refer + // https://github.com/OpenVPN/tap-windows6/blob/master/src/device.c#L431 + // and https://github.com/songgao/water/pull/13#issuecomment-270341777 + Network string +} + +func defaultPlatformSpecificParams() PlatformSpecificParams { + return PlatformSpecificParams{ + ComponentID: "tap0901", + Network: "192.168.1.10/24", + } +} diff --git a/syscalls_darwin.go b/syscalls_darwin.go new file mode 100644 index 0000000..c998557 --- /dev/null +++ b/syscalls_darwin.go @@ -0,0 +1,195 @@ +// +build darwin + +package water + +import ( + "errors" + "fmt" + "io" + "os" + "sync" + "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(config Config) (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: &tunReadCloser{ + f: os.NewFile(uintptr(fd), string(ifName.name[:])), + }, + }, nil +} + +func newTAP(config Config) (ifce *Interface, err error) { + return nil, errors.New("tap interface not implemented on this platform") +} + +// tunReadCloser is a hack to work around the first 4 bytes "packet +// information" because there doesn't seem to be an IFF_NO_PI for darwin. +type tunReadCloser struct { + f io.ReadWriteCloser + + rMu sync.Mutex + rBuf []byte + + wMu sync.Mutex + wBuf []byte +} + +var _ io.ReadWriteCloser = (*tunReadCloser)(nil) + +func (t *tunReadCloser) Read(to []byte) (int, error) { + t.rMu.Lock() + defer t.rMu.Unlock() + + if cap(t.rBuf) < len(to)+4 { + t.rBuf = make([]byte, len(to)+4) + } + t.rBuf = t.rBuf[:len(to)+4] + + n, err := t.f.Read(t.rBuf) + copy(to, t.rBuf[4:]) + return n - 4, err +} + +func (t *tunReadCloser) Write(from []byte) (int, error) { + + if len(from) == 0 { + return 0, syscall.EIO + } + + t.wMu.Lock() + defer t.wMu.Unlock() + + if cap(t.wBuf) < len(from)+4 { + t.wBuf = make([]byte, len(from)+4) + } + t.wBuf = t.wBuf[:len(from)+4] + + // Determine the IP Family for the NULL L2 Header + ipVer := from[0] >> 4 + if ipVer == 4 { + t.wBuf[3] = syscall.AF_INET + } else if ipVer == 6 { + t.wBuf[3] = syscall.AF_INET6 + } else { + return 0, errors.New("Unable to determine IP version from packet.") + } + + copy(t.wBuf[4:], from) + + n, err := t.f.Write(t.wBuf) + return n - 4, err +} + +func (t *tunReadCloser) Close() error { + // lock to make sure no read/write is in process. + t.rMu.Lock() + defer t.rMu.Unlock() + t.wMu.Lock() + defer t.wMu.Unlock() + + return t.f.Close() +} diff --git a/syscalls_linux.go b/syscalls_linux.go new file mode 100644 index 0000000..502c5a2 --- /dev/null +++ b/syscalls_linux.go @@ -0,0 +1,118 @@ +// +build linux + +package water + +import ( + "os" + "strings" + "syscall" + "unsafe" +) + +const ( + cIFF_TUN = 0x0001 + cIFF_TAP = 0x0002 + cIFF_NO_PI = 0x1000 + cIFF_MULTI_QUEUE = 0x0100 +) + +type ifReq struct { + Name [0x10]byte + Flags uint16 + pad [0x28 - 0x10 - 2]byte +} + +func ioctl(fd uintptr, request uintptr, argp uintptr) error { + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(request), argp) + if errno != 0 { + return os.NewSyscallError("ioctl", errno) + } + return nil +} + +func newTAP(config Config) (ifce *Interface, err error) { + file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0) + if err != nil { + return nil, err + } + + var flags uint16 + flags = cIFF_TAP | cIFF_NO_PI + if config.PlatformSpecificParams.MultiQueue { + flags |= cIFF_MULTI_QUEUE + } + name, err := createInterface(file.Fd(), config.Name, flags) + if err != nil { + return nil, err + } + + if err = setDeviceOptions(file.Fd(), config); err != nil { + return nil, err + } + + ifce = &Interface{isTAP: true, ReadWriteCloser: file, name: name} + return +} + +func newTUN(config Config) (ifce *Interface, err error) { + file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0) + if err != nil { + return nil, err + } + + var flags uint16 + flags = cIFF_TUN | cIFF_NO_PI + if config.PlatformSpecificParams.MultiQueue { + flags |= cIFF_MULTI_QUEUE + } + name, err := createInterface(file.Fd(), config.Name, flags) + if err != nil { + return nil, err + } + + if err = setDeviceOptions(file.Fd(), config); err != nil { + return nil, err + } + + ifce = &Interface{isTAP: false, ReadWriteCloser: file, name: name} + return +} + +func createInterface(fd uintptr, ifName string, flags uint16) (createdIFName string, err error) { + var req ifReq + req.Flags = flags + copy(req.Name[:], ifName) + + err = ioctl(fd, syscall.TUNSETIFF, uintptr(unsafe.Pointer(&req))) + if err != nil { + return + } + + createdIFName = strings.Trim(string(req.Name[:]), "\x00") + return +} + +func setDeviceOptions(fd uintptr, config Config) (err error) { + + // Device Permissions + if config.Permissions != nil { + + // Set Owner + if err = ioctl(fd, syscall.TUNSETOWNER, uintptr(config.Permissions.Owner)); err != nil { + return + } + + // Set Group + if err = ioctl(fd, syscall.TUNSETGROUP, uintptr(config.Permissions.Group)); err != nil { + return + } + } + + // Set/Clear Persist Device Flag + value := 0 + if config.Persist { + value = 1 + } + return ioctl(fd, syscall.TUNSETPERSIST, uintptr(value)) + +} diff --git a/syscalls_other.go b/syscalls_other.go new file mode 100644 index 0000000..ed49244 --- /dev/null +++ b/syscalls_other.go @@ -0,0 +1,13 @@ +// +build !linux,!darwin,!windows + +package water + +import "errors" + +func newTAP(config Config) (ifce *Interface, err error) { + return nil, errors.New("tap interface not implemented on this platform") +} + +func newTUN(config Config) (ifce *Interface, err error) { + return nil, errors.New("tap interface not implemented on this platform") +} diff --git a/syscalls_windows.go b/syscalls_windows.go new file mode 100644 index 0000000..053560e --- /dev/null +++ b/syscalls_windows.go @@ -0,0 +1,298 @@ +// +build windows + +// To use it with windows, you need a tap driver installed on windows. +// https://github.com/OpenVPN/tap-windows6 +// or just install OpenVPN +// https://github.com/OpenVPN/openvpn +package water + +import ( + "bytes" + "errors" + "fmt" + "net" + "sync" + "syscall" + "unsafe" + + "golang.org/x/sys/windows/registry" +) + +var ( + errIfceNameNotFound = errors.New("Failed to find the name of interface") + // Device Control Codes + tap_win_ioctl_get_mac = tap_control_code(1, 0) + tap_win_ioctl_get_version = tap_control_code(2, 0) + tap_win_ioctl_get_mtu = tap_control_code(3, 0) + tap_win_ioctl_get_info = tap_control_code(4, 0) + tap_ioctl_config_point_to_point = tap_control_code(5, 0) + tap_ioctl_set_media_status = tap_control_code(6, 0) + tap_win_ioctl_config_dhcp_masq = tap_control_code(7, 0) + tap_win_ioctl_get_log_line = tap_control_code(8, 0) + tap_win_ioctl_config_dhcp_set_opt = tap_control_code(9, 0) + tap_ioctl_config_tun = tap_control_code(10, 0) + // w32 api + file_device_unknown = uint32(0x00000022) + nCreateEvent, + nResetEvent, + nGetOverlappedResult uintptr +) + +func init() { + k32, err := syscall.LoadLibrary("kernel32.dll") + if err != nil { + panic("LoadLibrary " + err.Error()) + } + defer syscall.FreeLibrary(k32) + + nCreateEvent = getProcAddr(k32, "CreateEventW") + nResetEvent = getProcAddr(k32, "ResetEvent") + nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult") +} + +func getProcAddr(lib syscall.Handle, name string) uintptr { + addr, err := syscall.GetProcAddress(lib, name) + if err != nil { + panic(name + " " + err.Error()) + } + return addr +} + +func resetEvent(h syscall.Handle) error { + r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0) + if r == 0 { + return err + } + return nil +} + +func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) { + var n int + r, _, err := syscall.Syscall6(nGetOverlappedResult, 4, + uintptr(h), + uintptr(unsafe.Pointer(overlapped)), + uintptr(unsafe.Pointer(&n)), 1, 0, 0) + if r == 0 { + return n, err + } + + return n, nil +} + +func newOverlapped() (*syscall.Overlapped, error) { + var overlapped syscall.Overlapped + r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0) + if r == 0 { + return nil, err + } + overlapped.HEvent = syscall.Handle(r) + return &overlapped, nil +} + +type wfile struct { + fd syscall.Handle + rl sync.Mutex + wl sync.Mutex + ro *syscall.Overlapped + wo *syscall.Overlapped +} + +func (f *wfile) Close() error { + return syscall.Close(f.fd) +} + +func (f *wfile) Write(b []byte) (int, error) { + f.wl.Lock() + defer f.wl.Unlock() + + if err := resetEvent(f.wo.HEvent); err != nil { + return 0, err + } + var n uint32 + err := syscall.WriteFile(f.fd, b, &n, f.wo) + if err != nil && err != syscall.ERROR_IO_PENDING { + return int(n), err + } + return getOverlappedResult(f.fd, f.wo) +} + +func (f *wfile) Read(b []byte) (int, error) { + f.rl.Lock() + defer f.rl.Unlock() + + if err := resetEvent(f.ro.HEvent); err != nil { + return 0, err + } + var done uint32 + err := syscall.ReadFile(f.fd, b, &done, f.ro) + if err != nil && err != syscall.ERROR_IO_PENDING { + return int(done), err + } + return getOverlappedResult(f.fd, f.ro) +} + +func ctl_code(device_type, function, method, access uint32) uint32 { + return (device_type << 16) | (access << 14) | (function << 2) | method +} + +func tap_control_code(request, method uint32) uint32 { + return ctl_code(file_device_unknown, request, method, 0) +} + +// getdeviceid finds out a TAP device from registry, it *may* requires privileged right to prevent some weird issue. +func getdeviceid(componentID string) (deviceid string, err error) { + // TAP driver key location + regkey := `SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}` + k, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey, registry.READ) + if err != nil { + return "", fmt.Errorf("Failed to open the adapter registry, TAP driver may be not installed, %v", err) + } + defer k.Close() + // read all subkeys, it should not return an err here + keys, err := k.ReadSubKeyNames(-1) + if err != nil { + return "", err + } + // find the one matched ComponentId + for _, v := range keys { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey+"\\"+v, registry.READ) + if err != nil { + continue + } + val, _, err := key.GetStringValue("ComponentId") + if err != nil { + key.Close() + continue + } + if val == componentID { + val, _, err = key.GetStringValue("NetCfgInstanceId") + if err != nil { + key.Close() + continue + } + key.Close() + return val, nil + } + key.Close() + } + return "", fmt.Errorf("Failed to find the tap device in registry with specified ComponentId(%s), TAP driver may be not installed", componentID) +} + +// setStatus is used to bring up or bring down the interface +func setStatus(fd syscall.Handle, status bool) error { + var bytesReturned uint32 + rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) + code := []byte{0x00, 0x00, 0x00, 0x00} + if status { + code[0] = 0x01 + } + return syscall.DeviceIoControl(fd, tap_ioctl_set_media_status, &code[0], uint32(4), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) +} + +// setTUN is used to configure the IP address in the underlying driver when using TUN +func setTUN(fd syscall.Handle, network string) error { + var bytesReturned uint32 + rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) + + localIP, remoteNet, err := net.ParseCIDR(network) + if err != nil { + return fmt.Errorf("Failed to parse network CIDR in config, %v", err) + } + if localIP.To4() == nil { + return fmt.Errorf("Provided network(%s) is not a valid IPv4 address", network) + } + code2 := make([]byte, 0, 12) + code2 = append(code2, localIP.To4()[:4]...) + code2 = append(code2, remoteNet.IP.To4()[:4]...) + code2 = append(code2, remoteNet.Mask[:4]...) + if len(code2) != 12 { + return fmt.Errorf("Provided network(%s) is not valid", network) + } + if err := syscall.DeviceIoControl(fd, tap_ioctl_config_tun, &code2[0], uint32(12), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil); err != nil { + return err + } + return nil +} + +// openDev find and open an interface. +func openDev(config Config) (ifce *Interface, err error) { + // find the device in registry. + deviceid, err := getdeviceid(config.PlatformSpecificParams.ComponentID) + if err != nil { + return nil, err + } + path := "\\\\.\\Global\\" + deviceid + ".tap" + pathp, err := syscall.UTF16PtrFromString(path) + if err != nil { + return nil, err + } + // type Handle uintptr + file, err := syscall.CreateFile(pathp, syscall.GENERIC_READ|syscall.GENERIC_WRITE, uint32(syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE), nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_SYSTEM|syscall.FILE_FLAG_OVERLAPPED, 0) + // if err hanppens, close the interface. + defer func() { + if err != nil { + syscall.Close(file) + } + if err := recover(); err != nil { + syscall.Close(file) + } + }() + if err != nil { + return nil, err + } + var bytesReturned uint32 + + // find the mac address of tap device, use this to find the name of interface + mac := make([]byte, 6) + err = syscall.DeviceIoControl(file, tap_win_ioctl_get_mac, &mac[0], uint32(len(mac)), &mac[0], uint32(len(mac)), &bytesReturned, nil) + if err != nil { + return nil, err + } + + // fd := os.NewFile(uintptr(file), path) + ro, err := newOverlapped() + if err != nil { + return + } + wo, err := newOverlapped() + if err != nil { + return + } + 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 { + return nil, err + } + + //TUN + if config.DeviceType == TUN { + if err := setTUN(file, config.PlatformSpecificParams.Network); err != nil { + return nil, err + } + } + + // find the name of tap interface(u need it to set the ip or other command) + ifces, err := net.Interfaces() + if err != nil { + return + } + + for _, v := range ifces { + if len(v.HardwareAddr) >= len(mac) && bytes.Equal(v.HardwareAddr[:6], mac[:6]) { + ifce.name = v.Name + return + } + } + + return nil, errIfceNameNotFound +} + +func newTAP(config Config) (ifce *Interface, err error) { + return openDev(config) +} + +func newTUN(config Config) (ifce *Interface, err error) { + return openDev(config) +} diff --git a/waterutil/doc.go b/waterutil/doc.go new file mode 100644 index 0000000..1a7cea1 --- /dev/null +++ b/waterutil/doc.go @@ -0,0 +1,51 @@ +/* +Package waterutil provides utility functions for interpreting TUN/TAP MAC farme headers and IP packet headers. It defines some constants such as protocol numbers and ethernet frame types. Use waterutil along with package water to work with TUN/TAP interface data. + +Frames/packets are interpreted in following format (as in TUN/TAP devices): + +TAP - MAC Frame: + No Tagging + +----------------------------------------------------------------------------- + | Octet |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|... + +----------------------------------------------------------------------------- + | Field | MAC Destination | MAC Source |EType| Payload + +----------------------------------------------------------------------------- + + Single-Tagged -- Octets [12,13] == {0x81, 0x00} + +----------------------------------------------------------------------------- + | Octet |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|... + +----------------------------------------------------------------------------- + | Field | MAC Destination | MAC Source | Tag | Payload + +----------------------------------------------------------------------------- + + Double-Tagged -- Octets [12,13] == {0x88, 0xA8} + +----------------------------------------------------------------------------- + | Octet |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|... + +----------------------------------------------------------------------------- + | Field | MAC Destination | MAC Source | Outer Tag | Inner Tag | Payload + +----------------------------------------------------------------------------- + +TUN - IPv4 Packet: + +---------------------------------------------------------------------------------------------------------------+ + | | Octet | 0 | 1 | 2 | 3 | + | Octet | Bit |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31| + +---------------------------------------------------------------------------------------------------------------+ + | 0 | 0 | Version | IHL | DSCP | ECN | Total Length | + +---------------------------------------------------------------------------------------------------------------+ + | 4 | 32 | Identification | Flags | Fragment Offset | + +---------------------------------------------------------------------------------------------------------------+ + | 8 | 64 | Time To Live | Protocol | Header Checksum | + +---------------------------------------------------------------------------------------------------------------+ + | 12 | 96 | Source IP Address | + +---------------------------------------------------------------------------------------------------------------+ + | 16 | 128 | Destination IP Address | + +---------------------------------------------------------------------------------------------------------------+ + | 20 | 160 | Options (if IHL > 5) | + +---------------------------------------------------------------------------------------------------------------+ + | 24 | 192 | | + | 30 | 224 | Payload | + | ... | ... | | + +---------------------------------------------------------------------------------------------------------------+ + +*/ +package waterutil diff --git a/waterutil/ethertypes.go b/waterutil/ethertypes.go new file mode 100644 index 0000000..1d2b1aa --- /dev/null +++ b/waterutil/ethertypes.go @@ -0,0 +1,46 @@ +package waterutil + +type Ethertype [2]byte + +// Ethertype values. From: http://en.wikipedia.org/wiki/Ethertype +var ( + IPv4 = Ethertype{0x08, 0x00} + ARP = Ethertype{0x08, 0x06} + WakeOnLAN = Ethertype{0x08, 0x42} + TRILL = Ethertype{0x22, 0xF3} + DECnetPhase4 = Ethertype{0x60, 0x03} + RARP = Ethertype{0x80, 0x35} + AppleTalk = Ethertype{0x80, 0x9B} + AARP = Ethertype{0x80, 0xF3} + IPX1 = Ethertype{0x81, 0x37} + IPX2 = Ethertype{0x81, 0x38} + QNXQnet = Ethertype{0x82, 0x04} + IPv6 = Ethertype{0x86, 0xDD} + EthernetFlowControl = Ethertype{0x88, 0x08} + IEEE802_3 = Ethertype{0x88, 0x09} + CobraNet = Ethertype{0x88, 0x19} + MPLSUnicast = Ethertype{0x88, 0x47} + MPLSMulticast = Ethertype{0x88, 0x48} + PPPoEDiscovery = Ethertype{0x88, 0x63} + PPPoESession = Ethertype{0x88, 0x64} + JumboFrames = Ethertype{0x88, 0x70} + HomePlug1_0MME = Ethertype{0x88, 0x7B} + IEEE802_1X = Ethertype{0x88, 0x8E} + PROFINET = Ethertype{0x88, 0x92} + HyperSCSI = Ethertype{0x88, 0x9A} + AoE = Ethertype{0x88, 0xA2} + EtherCAT = Ethertype{0x88, 0xA4} + EthernetPowerlink = Ethertype{0x88, 0xAB} + LLDP = Ethertype{0x88, 0xCC} + SERCOS3 = Ethertype{0x88, 0xCD} + HomePlugAVMME = Ethertype{0x88, 0xE1} + MRP = Ethertype{0x88, 0xE3} + IEEE802_1AE = Ethertype{0x88, 0xE5} + IEEE1588 = Ethertype{0x88, 0xF7} + IEEE802_1ag = Ethertype{0x89, 0x02} + FCoE = Ethertype{0x89, 0x06} + FCoEInit = Ethertype{0x89, 0x14} + RoCE = Ethertype{0x89, 0x15} + CTP = Ethertype{0x90, 0x00} + VeritasLLT = Ethertype{0xCA, 0xFE} +) diff --git a/waterutil/ip_protocols.go b/waterutil/ip_protocols.go new file mode 100644 index 0000000..2fa1cbe --- /dev/null +++ b/waterutil/ip_protocols.go @@ -0,0 +1,139 @@ +package waterutil + +type IPProtocol byte + +// IP Protocols. From: http://en.wikipedia.org/wiki/List_of_IP_protocol_numbers +const ( + HOPOPT = 0x00 + ICMP = 0x01 + IGMP = 0x02 + GGP = 0x03 + IPv4Encapsulation = 0x04 + ST = 0x05 + TCP = 0x06 + CBT = 0x07 + EGP = 0x08 + IGP = 0x09 + BBN_RCC_MON = 0x0A + NVP_II = 0x0B + PUP = 0x0C + ARGUS = 0x0D + EMCON = 0x0E + XNET = 0x0F + CHAOS = 0x10 + UDP = 0x11 + MUX = 0x12 + DCN_MEAS = 0x13 + HMP = 0x14 + PRM = 0x15 + XNS_IDP = 0x16 + TRUNK_1 = 0x17 + TRUNK_2 = 0x18 + LEAF_1 = 0x19 + LEAF_2 = 0x1A + RDP = 0x1B + IRTP = 0x1C + ISO_TP4 = 0x1D + NETBLT = 0x1E + MFE_NSP = 0x1F + MERIT_INP = 0x20 + DCCP = 0x21 + ThirdPC = 0x22 + IDPR = 0x23 + XTP = 0x24 + DDP = 0x25 + IDPR_CMTP = 0x26 + TPxx = 0x27 + IL = 0x28 + IPv6Encapsulation = 0x29 + SDRP = 0x2A + IPv6_Route = 0x2B + IPv6_Frag = 0x2C + IDRP = 0x2D + RSVP = 0x2E + GRE = 0x2F + MHRP = 0x30 + BNA = 0x31 + ESP = 0x32 + AH = 0x33 + I_NLSP = 0x34 + SWIPE = 0x35 + NARP = 0x36 + MOBILE = 0x37 + TLSP = 0x38 + SKIP = 0x39 + IPv6_ICMP = 0x3A + IPv6_NoNxt = 0x3B + IPv6_Opts = 0x3C + CFTP = 0x3E + SAT_EXPAK = 0x40 + KRYPTOLAN = 0x41 + RVD = 0x42 + IPPC = 0x43 + SAT_MON = 0x45 + VISA = 0x46 + IPCV = 0x47 + CPNX = 0x48 + CPHB = 0x49 + WSN = 0x4A + PVP = 0x4B + BR_SAT_MON = 0x4C + SUN_ND = 0x4D + WB_MON = 0x4E + WB_EXPAK = 0x4F + ISO_IP = 0x50 + VMTP = 0x51 + SECURE_VMTP = 0x52 + VINES = 0x53 + TTP = 0x54 + IPTM = 0x54 + NSFNET_IGP = 0x55 + DGP = 0x56 + TCF = 0x57 + EIGRP = 0x58 + OSPF = 0x59 + Sprite_RPC = 0x5A + LARP = 0x5B + MTP = 0x5C + AX_25 = 0x5D + IPIP = 0x5E + MICP = 0x5F + SCC_SP = 0x60 + ETHERIP = 0x61 + ENCAP = 0x62 + GMTP = 0x64 + IFMP = 0x65 + PNNI = 0x66 + PIM = 0x67 + ARIS = 0x68 + SCPS = 0x69 + QNX = 0x6A + A_N = 0x6B + IPComp = 0x6C + SNP = 0x6D + Compaq_Peer = 0x6E + IPX_in_IP = 0x6F + VRRP = 0x70 + PGM = 0x71 + L2TP = 0x73 + DDX = 0x74 + IATP = 0x75 + STP = 0x76 + SRP = 0x77 + UTI = 0x78 + SMP = 0x79 + SM = 0x7A + PTP = 0x7B + FIRE = 0x7D + CRTP = 0x7E + CRUDP = 0x7F + SSCOPMCE = 0x80 + IPLT = 0x81 + SPS = 0x82 + PIPE = 0x83 + SCTP = 0x84 + FC = 0x85 + manet = 0x8A + HIP = 0x8B + Shim6 = 0x8C +) diff --git a/waterutil/tap.go b/waterutil/tap.go new file mode 100644 index 0000000..5ad78fd --- /dev/null +++ b/waterutil/tap.go @@ -0,0 +1,48 @@ +package waterutil + +import ( + "net" +) + +type Tagging int + +// Indicating whether/how a MAC frame is tagged. The value is number of bytes taken by tagging. +const ( + NotTagged Tagging = 0 + Tagged Tagging = 4 + DoubleTagged Tagging = 8 +) + +func MACDestination(macFrame []byte) net.HardwareAddr { + return net.HardwareAddr(macFrame[:6]) +} + +func MACSource(macFrame []byte) net.HardwareAddr { + return net.HardwareAddr(macFrame[6:12]) +} + +func MACTagging(macFrame []byte) Tagging { + if macFrame[12] == 0x81 && macFrame[13] == 0x00 { + return Tagged + } else if macFrame[12] == 0x88 && macFrame[13] == 0xa8 { + return DoubleTagged + } + return NotTagged +} + +func MACEthertype(macFrame []byte) Ethertype { + ethertypePos := 12 + MACTagging(macFrame) + return Ethertype{macFrame[ethertypePos], macFrame[ethertypePos+1]} +} + +func MACPayload(macFrame []byte) []byte { + return macFrame[12+MACTagging(macFrame)+2:] +} + +func IsBroadcast(addr net.HardwareAddr) bool { + return addr[0] == 0xff && addr[1] == 0xff && addr[2] == 0xff && addr[3] == 0xff && addr[4] == 0xff && addr[5] == 0xff +} + +func IsIPv4Multicast(addr net.HardwareAddr) bool { + return addr[0] == 0x01 && addr[1] == 0x00 && addr[2] == 0x5e +} diff --git a/waterutil/tun.go b/waterutil/tun.go new file mode 100644 index 0000000..a972e8e --- /dev/null +++ b/waterutil/tun.go @@ -0,0 +1,9 @@ +package waterutil + +func IsIPv4(packet []byte) bool { + return 4 == (packet[0] >> 4) +} + +func IsIPv6(packet []byte) bool { + return 6 == (packet[0] >> 4) +} diff --git a/waterutil/tun_ipv4.go b/waterutil/tun_ipv4.go new file mode 100644 index 0000000..6e249e5 --- /dev/null +++ b/waterutil/tun_ipv4.go @@ -0,0 +1,69 @@ +package waterutil + +import ( + "net" +) + +func IPv4DSCP(packet []byte) byte { + return packet[1] >> 2 +} + +func IPv4ECN(packet []byte) byte { + return packet[1] & 0x03 +} + +func IPv4Identification(packet []byte) [2]byte { + return [2]byte{packet[4], packet[5]} +} + +func IPv4TTL(packet []byte) byte { + return packet[8] +} + +func IPv4Protocol(packet []byte) IPProtocol { + return IPProtocol(packet[9]) +} + +func IPv4Source(packet []byte) net.IP { + return net.IPv4(packet[12], packet[13], packet[14], packet[15]) +} + +func SetIPv4Source(packet []byte, source net.IP) { + copy(packet[12:16], source.To4()) +} + +func IPv4Destination(packet []byte) net.IP { + return net.IPv4(packet[16], packet[17], packet[18], packet[19]) +} + +func SetIPv4Destination(packet []byte, dest net.IP) { + copy(packet[16:20], dest.To4()) +} + +func IPv4Payload(packet []byte) []byte { + ihl := packet[0] & 0x0F + return packet[ihl*4:] +} + +// For TCP/UDP +func IPv4SourcePort(packet []byte) uint16 { + payload := IPv4Payload(packet) + return (uint16(payload[0]) << 8) | uint16(payload[1]) +} + +func IPv4DestinationPort(packet []byte) uint16 { + payload := IPv4Payload(packet) + return (uint16(payload[2]) << 8) | uint16(payload[3]) +} + +func SetIPv4SourcePort(packet []byte, port uint16) { + payload := IPv4Payload(packet) + payload[0] = byte(port >> 8) + payload[1] = byte(port & 0xFF) +} + +func SetIPv4DestinationPort(packet []byte, port uint16) { + payload := IPv4Payload(packet) + payload[2] = byte(port >> 8) + payload[3] = byte(port & 0xFF) +}