mirror of
https://github.com/net-byte/water.git
synced 2024-04-21 12:21:38 +00:00
init
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
water.test
|
||||
water.test.exe
|
||||
+12
@@ -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
|
||||
@@ -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]
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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{}
|
||||
}
|
||||
@@ -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{}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// +build !linux,!darwin,!windows
|
||||
|
||||
package water
|
||||
|
||||
// PlatformSpeficParams
|
||||
type PlatformSpecificParams struct {
|
||||
}
|
||||
|
||||
func defaultPlatformSpecificParams() PlatformSpecificParams {
|
||||
return PlatformSpecificParams{}
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// +build darwin,go1.11
|
||||
|
||||
package water
|
||||
|
||||
import "syscall"
|
||||
|
||||
func setNonBlock(fd int) error {
|
||||
return syscall.SetNonblock(fd, true)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user