This commit is contained in:
netbyte
2022-07-26 22:10:38 +08:00
commit 2a086d07e7
23 changed files with 1026 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
water.test
water.test.exe
+12
View File
@@ -0,0 +1,12 @@
language: go
go:
- "1.18"
go_import_path: github.com/net-byte/water
install: go get -u golang.org/x/lint/golint
script: make ci
matrix:
include:
- os: linux
dist: xenial
- os: osx
+15
View File
@@ -0,0 +1,15 @@
Song Gao <song@gao.io>
Harshal Sheth <hsheth2@gmail.com>
KOJIMA Takanori <tkojima@accense.com>
Sean Purser-Haskell <sean.purserhaskell@gmail.com>
daregod <daregod@yandex.ru>
Lucus Lee <lixin9311@gmail.com>
Arroyo Networks, LLC <open.source@arroyonetworks.com>
Tony Lu <tonyluj@gmail.com>
ajee cai <ajee.cai@gmail.com>
yinheli <hi@yinheli.com>
Paul Querna <pquerna@apache.org>
Cuong Manh Le <cuong.manhle.vn@gmail.com>
Neil Alexander <neilalexander@users.noreply.github.com>
Dmitry Shihovtsev <soffokulus@gmail.com>
Yifan Gu [https://github.com/gyf304]
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) 2016, Song Gao <song@gao.io>
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.
+23
View File
@@ -0,0 +1,23 @@
.phony: default ci test lint vet gofmt
default:
echo 'This make file is for CI.'
exit 1
ci: test lint vet gofmt
test: water.test
sudo ./water.test -test.v
lint:
golint -set_exit_status
vet:
go vet .
gofmt:
gofmt -s -e -l .
water.test: *.go
go test -c
+224
View File
@@ -0,0 +1,224 @@
# 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.
## Supported Platforms
* Linux
* Windows (based on wintun TUN only)
* macOS (point-to-point TUN only)
## Installation
```
go get -u github.com/net-byte/water
```
## Example
### TAP on Linux:
```go
package main
import (
"log"
"github.com/songgao/packets/ethernet"
"github.com/net-byte/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/net-byte/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 [wintun driver](https://www.wintun.net/) for windows.
It's compatible with the Linux code.
```go
package main
import (
"log"
"github.com/net-byte/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])
}
}
```
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.
#### Specifying interface name
If you are going to use multiple TUN devices on the Windows, there is a way to specify an interface name to select the exact device that you need:
```go
ifce, err := water.New(water.Config{
DeviceType: water.TUN,
PlatformSpecificParams: water.PlatformSpecificParams{
InterfaceName: "Ethernet 3",
Network: "172.16.1.10/24",
},
})
```
+4
View File
@@ -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
+13
View File
@@ -0,0 +1,13 @@
module github.com/net-byte/water
go 1.18
require (
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
)
require (
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
)
+8
View File
@@ -0,0 +1,8 @@
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 h1:vDy//hdR+GnROE3OdYbQKt9rdtNdHkDtONvpRwmls/0=
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478/go.mod h1:bVQfyl2sCM/QIIGHpWbFGfHPuDvqnCNkT6MQLTCjO/U=
+80
View File
@@ -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()
}
if config.PlatformSpecificParams == zeroConfig.PlatformSpecificParams {
config.PlatformSpecificParams = defaultPlatformSpecificParams()
}
switch config.DeviceType {
case TUN, TAP:
return openDev(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
}
+30
View File
@@ -0,0 +1,30 @@
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 openDev(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 openDev(config)
}
+32
View File
@@ -0,0 +1,32 @@
package water
// MacOSDriverProvider enumerates possible MacOS TUN/TAP implementations
type MacOSDriverProvider int
const (
// MacOSDriverSystem refers to the default P2P driver
MacOSDriverSystem MacOSDriverProvider = 0
// MacOSDriverTunTapOSX refers to the third-party tuntaposx driver
// see https://sourceforge.net/p/tuntaposx
MacOSDriverTunTapOSX MacOSDriverProvider = 1
)
// 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 {
// Name is the name for the interface to be used.
//
// For TunTapOSXDriver, it should be something like "tap0".
// For SystemDriver, the name should match `utun[0-9]+`, e.g. utun233
Name string
// Driver should be set if an alternative driver is desired
// e.g. TunTapOSXDriver
Driver MacOSDriverProvider
}
func defaultPlatformSpecificParams() PlatformSpecificParams {
return PlatformSpecificParams{}
}
+45
View File
@@ -0,0 +1,45 @@
package water
// DevicePermissions determines the owner and group owner for the newly created
// interface.
type DevicePermissions struct {
// Owner is the 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
// Group is the 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
// Persist specifies whether persistence mode for the interface device
// should be enabled or disabled.
Persist bool
// Permissions, if non-nil, specifies the owner and group owner for the
// interface. A zero-value of this field, i.e. nil, indicates that no
// changes to owner or group will be made.
Permissions *DevicePermissions
// MultiQueue specifies whether the multiqueue flag should be set on the
// 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{}
}
+11
View File
@@ -0,0 +1,11 @@
// +build !linux,!darwin,!windows
package water
// PlatformSpeficParams
type PlatformSpecificParams struct {
}
func defaultPlatformSpecificParams() PlatformSpecificParams {
return PlatformSpecificParams{}
}
+25
View File
@@ -0,0 +1,25 @@
package water
// PlatformSpecificParams defines parameters in Config that are specific to
// Windows. A zero-value of such type is valid.
type PlatformSpecificParams struct {
InterfaceName 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{
InterfaceName: "wintun",
Network: "172.16.1.10/24",
}
}
+270
View File
@@ -0,0 +1,270 @@
package water
import (
"errors"
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
"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 openDev(config Config) (ifce *Interface, err error) {
if config.Driver == MacOSDriverTunTapOSX {
return openDevTunTapOSX(config)
}
if config.Driver == MacOSDriverSystem {
return openDevSystem(config)
}
return nil, errors.New("unrecognized driver")
}
// openDevSystem opens tun device on system
func openDevSystem(config Config) (ifce *Interface, err error) {
if config.DeviceType != TUN {
return nil, errors.New("only tun is implemented for SystemDriver, use TunTapOSXDriver for tap")
}
ifIndex := -1
if config.Name != "" {
const utunPrefix = "utun"
if !strings.HasPrefix(config.Name, utunPrefix) {
return nil, fmt.Errorf("Interface name must be utun[0-9]+")
}
ifIndex, err = strconv.Atoi(config.Name[len(utunPrefix):])
if err != nil || ifIndex < 0 || ifIndex > math.MaxUint32-1 {
return nil, fmt.Errorf("Interface name must be utun[0-9]+")
}
}
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_IOCTL, ...): %v", err)
}
addrP := unsafe.Pointer(&sockaddrCtl{
scLen: uint8(sockaddrCtlSize),
scFamily: syscall.AF_SYSTEM,
/* #define AF_SYS_CONTROL 2 */
ssSysaddr: 2,
scID: ctlInfo.ctlID,
scUnit: uint32(ifIndex) + 1,
})
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)
}
if err = setNonBlock(fd); err != nil {
return nil, fmt.Errorf("setting non-blocking error")
}
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
}
// openDevTunTapOSX opens tun / tap device, assuming tuntaposx is installed
func openDevTunTapOSX(config Config) (ifce *Interface, err error) {
var fd int
var socketFD int
if config.DeviceType == TAP && !strings.HasPrefix(config.Name, "tap") {
return nil, errors.New("device name does not start with tap when creating a tap device")
}
if config.DeviceType == TUN && !strings.HasPrefix(config.Name, "tun") {
return nil, errors.New("device name does not start with tun when creating a tun device")
}
if config.DeviceType != TAP && config.DeviceType != TUN {
return nil, errors.New("unsupported DeviceType")
}
if len(config.Name) >= 15 {
return nil, errors.New("device name is too long")
}
if fd, err = syscall.Open(
"/dev/"+config.Name, os.O_RDWR|syscall.O_NONBLOCK, 0); err != nil {
return nil, err
}
// Note that we are not setting NONBLOCK on the fd itself since it breaks tuntaposx
// see https://sourceforge.net/p/tuntaposx/bugs/6/
// create socket so we can do SIO ioctls, we are not using it afterwards
if socketFD, err = syscall.Socket(syscall.AF_SYSTEM, syscall.SOCK_DGRAM, 2); err != nil {
return nil, fmt.Errorf("error in syscall.Socket: %v", err)
}
var ifReq = &struct {
ifName [16]byte
ifruFlags int16
pad [16]byte
}{}
copy(ifReq.ifName[:], []byte(config.Name))
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(socketFD), uintptr(syscall.SIOCGIFFLAGS), uintptr(unsafe.Pointer(ifReq))); errno != 0 {
err = errno
return nil, fmt.Errorf("error in syscall.Syscall(syscall.SYS_IOCTL, ...): %v", err)
}
ifReq.ifruFlags |= syscall.IFF_RUNNING | syscall.IFF_UP
if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(socketFD), uintptr(syscall.SIOCSIFFLAGS), uintptr(unsafe.Pointer(ifReq))); errno != 0 {
err = errno
return nil, fmt.Errorf("error in syscall.Syscall(syscall.SYS_IOCTL, ...): %v", err)
}
syscall.Close(socketFD)
return &Interface{
isTAP: config.DeviceType == TAP,
ReadWriteCloser: os.NewFile(uintptr(fd), "tun"),
name: config.Name,
}, nil
}
// 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 {
return t.f.Close()
}
+9
View File
@@ -0,0 +1,9 @@
// +build darwin,go1.11
package water
import "syscall"
func setNonBlock(fd int) error {
return syscall.SetNonblock(fd, true)
}
+10
View File
@@ -0,0 +1,10 @@
// +build darwin,!go1.11
package water
func setNonBlock(fd int) error {
// There's a but pre-go1.11 that causes 'resource temporarily unavailable'
// error in non-blocking mode. So just skip it here. Close() won't be able
// to unblock a pending read, but that's better than being broken.
return nil
}
+83
View File
@@ -0,0 +1,83 @@
package water
import (
"os"
"strings"
"syscall"
"unsafe"
)
const (
cIFFTUN = 0x0001
cIFFTAP = 0x0002
cIFFNOPI = 0x1000
cIFFMULTIQUEUE = 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 setupFd(config Config, fd uintptr) (name string, err error) {
var flags uint16 = cIFFNOPI
if config.DeviceType == TUN {
flags |= cIFFTUN
} else {
flags |= cIFFTAP
}
if config.PlatformSpecificParams.MultiQueue {
flags |= cIFFMULTIQUEUE
}
if name, err = createInterface(fd, config.Name, flags); err != nil {
return "", err
}
if err = setDeviceOptions(fd, config); err != nil {
return "", err
}
return name, nil
}
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) {
if config.Permissions != nil {
if err = ioctl(fd, syscall.TUNSETOWNER, uintptr(config.Permissions.Owner)); err != nil {
return
}
if err = ioctl(fd, syscall.TUNSETGROUP, uintptr(config.Permissions.Group)); err != nil {
return
}
}
// set clear the persist flag
value := 0
if config.Persist {
value = 1
}
return ioctl(fd, syscall.TUNSETPERSIST, uintptr(value))
}
+27
View File
@@ -0,0 +1,27 @@
// +build linux,go1.11
package water
import (
"os"
"syscall"
)
func openDev(config Config) (ifce *Interface, err error) {
var fdInt int
if fdInt, err = syscall.Open(
"/dev/net/tun", os.O_RDWR|syscall.O_NONBLOCK, 0); err != nil {
return nil, err
}
name, err := setupFd(config, uintptr(fdInt))
if err != nil {
return nil, err
}
return &Interface{
isTAP: config.DeviceType == TAP,
ReadWriteCloser: os.NewFile(uintptr(fdInt), "tun"),
name: name,
}, nil
}
+26
View File
@@ -0,0 +1,26 @@
// +build linux,!go1.11
package water
import (
"os"
)
func openDev(config Config) (ifce *Interface, err error) {
var file *os.File
if file, err = os.OpenFile(
"/dev/net/tun", os.O_RDWR, 0); err != nil {
return nil, err
}
name, err := setupFd(config, file.Fd())
if err != nil {
return nil, err
}
return &Interface{
isTAP: config.DeviceType == TAP,
ReadWriteCloser: file,
name: name,
}, nil
}
+9
View File
@@ -0,0 +1,9 @@
// +build !linux,!darwin,!windows
package water
import "errors"
func openDev(config Config) (*Interface, error) {
return nil, errors.New("not implemented on this platform")
}
+41
View File
@@ -0,0 +1,41 @@
package water
import (
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/tun"
)
type wintun struct {
dev tun.Device
}
func (w *wintun) Close() error {
return w.dev.Close()
}
func (w *wintun) Write(b []byte) (int, error) {
return w.dev.Write(b, 0)
}
func (w *wintun) Read(b []byte) (int, error) {
return w.dev.Read(b, 0)
}
func openDev(config Config) (ifce *Interface, err error) {
if config.DeviceType == TAP {
return nil, err
}
id := &windows.GUID{
0x0000000,
0xFFFF,
0xFFFF,
[8]byte{0xFF, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e},
}
dev, err := tun.CreateTUNWithRequestedGUID(config.PlatformSpecificParams.InterfaceName, id, 0)
if err != nil {
return nil, err
}
wintun := &wintun{dev: dev}
ifce = &Interface{isTAP: (config.DeviceType == TAP), ReadWriteCloser: wintun}
return ifce, nil
}