mirror of
https://github.com/yggdrasil-network/water.git
synced 2025-05-19 16:35:10 +03:00
commit
1a3940420d
11 changed files with 432 additions and 10 deletions
|
@ -3,3 +3,4 @@ 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>
|
63
README.md
63
README.md
|
@ -15,6 +15,7 @@ See https://github.com/songgao/packets for functions for parsing various packets
|
|||
## Supported Platforms
|
||||
|
||||
* Linux
|
||||
* Windows
|
||||
* macOS (point-to-point TUN only)
|
||||
|
||||
## Installation
|
||||
|
@ -143,6 +144,68 @@ You'd see the ICMP packets printed out:
|
|||
2016/10/23 20:22:40 Packet Received: 00 00 00 02 45 00 00 54 4a 2e 00 00 40 01 1c 5c 0a 01 00 0a 0a 01 00 14 08 00 31 51 f0 f9 00 00 58 0d 7e 80 00 03 14 21 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37
|
||||
```
|
||||
|
||||
### 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.NewTAP("O_O")
|
||||
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
|
||||
|
||||
|
|
2
if.go
2
if.go
|
@ -52,6 +52,7 @@ func New(config Config) (ifce *Interface, err error) {
|
|||
// 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.
|
||||
//
|
||||
// Note: this function is deprecated and will be removed from the library.
|
||||
// Please use New() instead.
|
||||
|
@ -61,6 +62,7 @@ func NewTAP(ifName string) (ifce *Interface, err error) {
|
|||
|
||||
// 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.
|
||||
//
|
||||
// Note: this function is deprecated and will be removed from the library.
|
||||
// Please use New() instead.
|
||||
|
|
12
if_windows.go
Normal file
12
if_windows.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
// +build windows
|
||||
|
||||
package water
|
||||
|
||||
import "errors"
|
||||
|
||||
func newDev(config Config) (ifce *Interface, err error) {
|
||||
if config.DeviceType != TAP && config.DeviceType != TUN {
|
||||
return nil, errors.New("unknown device type")
|
||||
}
|
||||
return openDev(config)
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
// +build linux,darwin
|
||||
package water
|
||||
|
||||
import (
|
||||
|
@ -6,6 +7,12 @@ import (
|
|||
"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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !linux
|
||||
// +build !linux,!windows
|
||||
|
||||
package water
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package water
|
|||
|
||||
import (
|
||||
"net"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -24,12 +23,6 @@ func startRead(ch chan<- []byte, ifce *Interface) {
|
|||
}()
|
||||
}
|
||||
|
||||
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 TestBroadcast(t *testing.T) {
|
||||
var (
|
||||
self = net.IPv4(10, 0, 42, 1)
|
||||
|
|
28
ipv4_windows_test.go
Normal file
28
ipv4_windows_test.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// +build windows
|
||||
|
||||
package water
|
||||
|
||||
// PlatformSpecificParams defines parameters in Config that are specific to
|
||||
|
@ -9,10 +11,22 @@ type PlatformSpecificParams struct {
|
|||
// 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",
|
||||
ComponentID: "tap0901",
|
||||
Network: "192.168.1.10/24",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build !linux,!darwin
|
||||
// +build !linux,!darwin,!windows
|
||||
|
||||
package water
|
||||
|
||||
|
|
302
syscalls_windows.go
Normal file
302
syscalls_windows.go
Normal file
|
@ -0,0 +1,302 @@
|
|||
// +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 bytes.Equal(v.HardwareAddr[:6], mac[:6]) {
|
||||
ifce.name = v.Name
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errIfceNameNotFound
|
||||
}
|
||||
|
||||
func newTAP(ifName string) (ifce *Interface, err error) {
|
||||
config := defaultConfig()
|
||||
config.DeviceType = TAP
|
||||
return openDev(config)
|
||||
}
|
||||
|
||||
func newTUN(ifName string) (ifce *Interface, err error) {
|
||||
config := defaultConfig()
|
||||
config.DeviceType = TUN
|
||||
return openDev(config)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue