From c871518ca0789ff3397ef4dcfc4fbcbc640e1440 Mon Sep 17 00:00:00 2001 From: lucus Date: Sun, 25 Dec 2016 17:35:25 +0900 Subject: [PATCH 01/12] Add windows support. To use it on windows, you need a tap driver, or just install OpenVPN. --- syscalls_other.go | 2 +- syscalls_windows.go | 171 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 syscalls_windows.go diff --git a/syscalls_other.go b/syscalls_other.go index 96d1d63..ab3b9e5 100644 --- a/syscalls_other.go +++ b/syscalls_other.go @@ -1,4 +1,4 @@ -// +build !linux,!darwin +// +build !linux,!darwin,!windows package water diff --git a/syscalls_windows.go b/syscalls_windows.go new file mode 100644 index 0000000..140ab88 --- /dev/null +++ b/syscalls_windows.go @@ -0,0 +1,171 @@ +// +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 ( + "errors" + "net" + "os" + "syscall" + + "golang.org/x/sys/windows/registry" +) + +var ( + IfceNameNotFound = errors.New("Failed to find the name of interface.") + TapDeviceNotFound = errors.New("Failed to find the tap device in registry.") + // 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) +) + +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 requires privileged right. +func getdeviceid() (string, error) { + // TAP driver key location + regkey := `SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}` + k, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey, registry.ALL_ACCESS) + if err != nil { + return "", err + } + defer k.Close() + // read all subkeys + keys, err := k.ReadSubKeyNames(-1) + if err != nil { + return "", err + } + // find the one with ComponentId == "tap0901" + for _, v := range keys { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey+"\\"+v, registry.ALL_ACCESS) + if err != nil { + continue + } + val, _, err := key.GetStringValue("ComponentId") + if err != nil { + goto next + } + if val == "tap0901" { + val, _, err = key.GetStringValue("NetCfgInstanceId") + if err != nil { + goto next + } + key.Close() + return val, nil + } + next: + key.Close() + } + return "", TapDeviceNotFound +} + +// openDev find and open an interface. +func openDev(isTAP bool) (ifce *Interface, err error) { + // ifName won't work + // find the device in registrym you need privileged right. + deviceid, err := getdeviceid() + 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, 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 + rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) + + //TUN + if !isTAP { + code2 := []byte{0x0a, 0x03, 0x00, 0x01, 0x0a, 0x03, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00} + err = syscall.DeviceIoControl(file, tap_ioctl_config_tun, &code2[0], uint32(12), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) + if err != nil { + return + } + } + + // 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) + ifce = &Interface{isTAP: isTAP, ReadWriteCloser: fd} + + // bring up device. + code := []byte{0x01, 0x00, 0x00, 0x00} + err = syscall.DeviceIoControl(file, tap_ioctl_set_media_status, &code[0], uint32(4), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) + if err != nil { + return + } + // find the name of tap interface(u need it to set the ip or other command) + hwaddr_equal := func(a net.HardwareAddr, b []byte) bool { + for i := 0; i < 6; i++ { + if a[i] != b[i] { + return false + } + } + return true + } + + ifces, err := net.Interfaces() + if err != nil { + return + } + + for _, v := range ifces { + if hwaddr_equal(v.HardwareAddr[:6], mac[:6]) { + ifce.name = v.Name + return + } + } + + err = IfceNameNotFound + return +} + +func newTAP(ifName string) (ifce *Interface, err error) { + // ifName won't work + return openDev(true) +} + +func newTUN(ifName string) (ifce *Interface, err error) { + // ifName won't work + return openDev(false) +} From b090a0ff67678046932d3de504f4b7cfb498d19e Mon Sep 17 00:00:00 2001 From: lucus Date: Mon, 26 Dec 2016 04:22:06 +0900 Subject: [PATCH 02/12] Refined code and documentations. --- if.go | 2 ++ syscalls_windows.go | 41 +++++++++++++++++++---------------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/if.go b/if.go index d841023..2113288 100644 --- a/if.go +++ b/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. diff --git a/syscalls_windows.go b/syscalls_windows.go index 140ab88..055a351 100644 --- a/syscalls_windows.go +++ b/syscalls_windows.go @@ -7,6 +7,7 @@ package water import ( + "bytes" "errors" "net" "os" @@ -17,7 +18,8 @@ import ( var ( IfceNameNotFound = errors.New("Failed to find the name of interface.") - TapDeviceNotFound = errors.New("Failed to find the tap device in registry.") + TapDeviceNotFound = errors.New("Failed to find the tap device with specified ComponentId in registry, TAP driver may not installed.") + RegistryOpenErr = errors.New("Failed to open the adapter registry, TAP driver may not installed.") // Device Control Codes tap_win_ioctl_get_mac = tap_control_code(1, 0) tap_win_ioctl_get_version = tap_control_code(2, 0) @@ -31,6 +33,9 @@ var ( tap_ioctl_config_tun = tap_control_code(10, 0) // w32 api file_device_unknown = uint32(0x00000022) + // Driver maker specified ComponentId + // ComponentId is defined here: https://github.com/OpenVPN/tap-windows6/blob/master/version.m4#L5 + componentId = "tap0901" ) func ctl_code(device_type, function, method, access uint32) uint32 { @@ -41,39 +46,40 @@ 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 requires privileged right. +// getdeviceid finds out a TAP device from registry, it *may* requires privileged right to prevent some weird issue. func getdeviceid() (string, error) { // TAP driver key location regkey := `SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}` - k, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey, registry.ALL_ACCESS) + k, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey, registry.READ) if err != nil { - return "", err + return "", RegistryOpenErr } defer k.Close() - // read all subkeys + // read all subkeys, it should not return an err here keys, err := k.ReadSubKeyNames(-1) if err != nil { return "", err } - // find the one with ComponentId == "tap0901" + // find the one matched ComponentId for _, v := range keys { - key, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey+"\\"+v, registry.ALL_ACCESS) + key, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey+"\\"+v, registry.READ) if err != nil { continue } val, _, err := key.GetStringValue("ComponentId") if err != nil { - goto next + key.Close() + continue } - if val == "tap0901" { + if val == componentId { val, _, err = key.GetStringValue("NetCfgInstanceId") if err != nil { - goto next + key.Close() + continue } key.Close() return val, nil } - next: key.Close() } return "", TapDeviceNotFound @@ -82,7 +88,7 @@ func getdeviceid() (string, error) { // openDev find and open an interface. func openDev(isTAP bool) (ifce *Interface, err error) { // ifName won't work - // find the device in registrym you need privileged right. + // find the device in registry. deviceid, err := getdeviceid() if err != nil { return nil, err @@ -135,22 +141,13 @@ func openDev(isTAP bool) (ifce *Interface, err error) { return } // find the name of tap interface(u need it to set the ip or other command) - hwaddr_equal := func(a net.HardwareAddr, b []byte) bool { - for i := 0; i < 6; i++ { - if a[i] != b[i] { - return false - } - } - return true - } - ifces, err := net.Interfaces() if err != nil { return } for _, v := range ifces { - if hwaddr_equal(v.HardwareAddr[:6], mac[:6]) { + if bytes.Equal(v.HardwareAddr[:6], mac[:6]) { ifce.name = v.Name return } From dd56f4a2c65152975640d554bf47391bae2949eb Mon Sep 17 00:00:00 2001 From: lucus Date: Mon, 26 Dec 2016 04:59:00 +0900 Subject: [PATCH 03/12] Add test code for windows. --- ipv4_linux_test.go | 7 +++++++ ipv4_other_test.go | 2 +- ipv4_test.go | 7 ------- ipv4_windows_test.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 ipv4_windows_test.go diff --git a/ipv4_linux_test.go b/ipv4_linux_test.go index b5b680c..54f6884 100644 --- a/ipv4_linux_test.go +++ b/ipv4_linux_test.go @@ -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) diff --git a/ipv4_other_test.go b/ipv4_other_test.go index d70a50f..7d2177f 100644 --- a/ipv4_other_test.go +++ b/ipv4_other_test.go @@ -1,4 +1,4 @@ -// +build !linux +// +build !linux,!windows package water diff --git a/ipv4_test.go b/ipv4_test.go index b0ff3db..3a675b2 100644 --- a/ipv4_test.go +++ b/ipv4_test.go @@ -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) 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) + } +} From e958f2ff693aae7b5428dc2a6add3d4dddb3df32 Mon Sep 17 00:00:00 2001 From: lucus Date: Mon, 26 Dec 2016 05:14:23 +0900 Subject: [PATCH 04/12] Add lucus to contributors. --- CONTRIBUTORS | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index eea43e5..95bdcbd 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,3 +3,4 @@ Harshal Sheth KOJIMA Takanori Sean Purser-Haskell daregod +Lucus Lee \ No newline at end of file From f3fdfd2ce8424515cfb13a311d09d369e5342848 Mon Sep 17 00:00:00 2001 From: lucus Date: Mon, 26 Dec 2016 05:21:19 +0900 Subject: [PATCH 05/12] Fixed some grammar errors. --- syscalls_windows.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syscalls_windows.go b/syscalls_windows.go index 055a351..03ed25d 100644 --- a/syscalls_windows.go +++ b/syscalls_windows.go @@ -18,8 +18,8 @@ import ( var ( IfceNameNotFound = errors.New("Failed to find the name of interface.") - TapDeviceNotFound = errors.New("Failed to find the tap device with specified ComponentId in registry, TAP driver may not installed.") - RegistryOpenErr = errors.New("Failed to open the adapter registry, TAP driver may not installed.") + TapDeviceNotFound = errors.New("Failed to find the tap device in registry with specified ComponentId, TAP driver may be not installed.") + RegistryOpenErr = errors.New("Failed to open the adapter registry, TAP driver may be not installed.") // Device Control Codes tap_win_ioctl_get_mac = tap_control_code(1, 0) tap_win_ioctl_get_version = tap_control_code(2, 0) From c4e1e4e0bc6a0042d744a848861b7ba90421b924 Mon Sep 17 00:00:00 2001 From: lucus Date: Mon, 26 Dec 2016 05:55:12 +0900 Subject: [PATCH 06/12] Updated README. --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/README.md b/README.md index 3a9f2bd..575263f 100644 --- a/README.md +++ b/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,67 @@ 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. You will need admin right to assign IP. + +```dos +go run main.go +``` + +If will output a lot of lines because of some windows services and dhcp. + +In a new cmd (admin right): + +```dos +# Replace with your device 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 From 4faff02c97188e2569697820c28b96baede3cdb0 Mon Sep 17 00:00:00 2001 From: lucus lee Date: Mon, 26 Dec 2016 14:49:16 +0900 Subject: [PATCH 07/12] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 575263f..291720a 100644 --- a/README.md +++ b/README.md @@ -182,18 +182,19 @@ func main() { } ``` -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. You will need admin right to assign IP. +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 ``` -If will output a lot of lines because of some windows services and dhcp. +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 +# 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 ``` From b78551478ae9ef45f62ce3543e931c0f9141e61c Mon Sep 17 00:00:00 2001 From: lucus Date: Wed, 4 Jan 2017 18:47:24 +0900 Subject: [PATCH 08/12] Fixed TUN issue. --- syscalls_windows.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/syscalls_windows.go b/syscalls_windows.go index 03ed25d..9049d9d 100644 --- a/syscalls_windows.go +++ b/syscalls_windows.go @@ -115,15 +115,6 @@ func openDev(isTAP bool) (ifce *Interface, err error) { var bytesReturned uint32 rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) - //TUN - if !isTAP { - code2 := []byte{0x0a, 0x03, 0x00, 0x01, 0x0a, 0x03, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00} - err = syscall.DeviceIoControl(file, tap_ioctl_config_tun, &code2[0], uint32(12), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) - if err != nil { - return - } - } - // 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) @@ -140,6 +131,16 @@ func openDev(isTAP bool) (ifce *Interface, err error) { if err != nil { return } + + //TUN + if !isTAP { + code2 := []byte{0x0a, 0x03, 0x00, 0x01, 0x0a, 0x03, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00} + err = syscall.DeviceIoControl(file, tap_ioctl_config_tun, &code2[0], uint32(12), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) + if err != nil { + return + } + } + // find the name of tap interface(u need it to set the ip or other command) ifces, err := net.Interfaces() if err != nil { From c3b4ba2cd8484c2e5dd7e143c9be8e48af600a36 Mon Sep 17 00:00:00 2001 From: lucus Date: Wed, 4 Jan 2017 18:55:22 +0900 Subject: [PATCH 09/12] Unexported the error. --- syscalls_windows.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/syscalls_windows.go b/syscalls_windows.go index 9049d9d..81ab6ad 100644 --- a/syscalls_windows.go +++ b/syscalls_windows.go @@ -9,6 +9,7 @@ package water import ( "bytes" "errors" + "fmt" "net" "os" "syscall" @@ -17,9 +18,7 @@ import ( ) var ( - IfceNameNotFound = errors.New("Failed to find the name of interface.") - TapDeviceNotFound = errors.New("Failed to find the tap device in registry with specified ComponentId, TAP driver may be not installed.") - RegistryOpenErr = errors.New("Failed to open the adapter registry, TAP driver may be not installed.") + ifceNameNotFound = 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) @@ -52,7 +51,7 @@ func getdeviceid() (string, error) { regkey := `SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}` k, err := registry.OpenKey(registry.LOCAL_MACHINE, regkey, registry.READ) if err != nil { - return "", RegistryOpenErr + 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 @@ -82,7 +81,7 @@ func getdeviceid() (string, error) { } key.Close() } - return "", TapDeviceNotFound + return "", fmt.Errorf("Failed to find the tap device in registry with specified ComponentId(%s), TAP driver may be not installed.", componentId) } // openDev find and open an interface. @@ -154,7 +153,7 @@ func openDev(isTAP bool) (ifce *Interface, err error) { } } - err = IfceNameNotFound + err = ifceNameNotFound return } From 36e6baf409945bab45736c55a639a2457feca960 Mon Sep 17 00:00:00 2001 From: lucus Date: Thu, 5 Jan 2017 00:11:29 +0900 Subject: [PATCH 10/12] Using Overlapped IO to avoid IO lock on Windows. --- syscalls_windows.go | 112 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/syscalls_windows.go b/syscalls_windows.go index 81ab6ad..5ecea9a 100644 --- a/syscalls_windows.go +++ b/syscalls_windows.go @@ -11,8 +11,9 @@ import ( "errors" "fmt" "net" - "os" + "sync" "syscall" + "unsafe" "golang.org/x/sys/windows/registry" ) @@ -35,8 +36,104 @@ var ( // Driver maker specified ComponentId // ComponentId is defined here: https://github.com/OpenVPN/tap-windows6/blob/master/version.m4#L5 componentId = "tap0901" + 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 } @@ -98,7 +195,7 @@ func openDev(isTAP bool) (ifce *Interface, err error) { 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, 0) + 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 { @@ -121,7 +218,16 @@ func openDev(isTAP bool) (ifce *Interface, err error) { return nil, err } - fd := os.NewFile(uintptr(file), path) + // 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: isTAP, ReadWriteCloser: fd} // bring up device. From cde943e7d76d23a02a0ce09ccefb12d9f9644631 Mon Sep 17 00:00:00 2001 From: lucus Date: Mon, 9 Jan 2017 17:50:49 +0900 Subject: [PATCH 11/12] Format the error. --- syscalls_windows.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/syscalls_windows.go b/syscalls_windows.go index 5ecea9a..514f0b8 100644 --- a/syscalls_windows.go +++ b/syscalls_windows.go @@ -19,7 +19,7 @@ import ( ) var ( - ifceNameNotFound = errors.New("Failed to find the name of interface.") + 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) @@ -178,7 +178,7 @@ func getdeviceid() (string, error) { } key.Close() } - return "", fmt.Errorf("Failed to find the tap device in registry with specified ComponentId(%s), TAP driver may be not installed.", componentId) + return "", fmt.Errorf("Failed to find the tap device in registry with specified ComponentId(%s), TAP driver may be not installed", componentId) } // openDev find and open an interface. @@ -259,7 +259,7 @@ func openDev(isTAP bool) (ifce *Interface, err error) { } } - err = ifceNameNotFound + err = errIfceNameNotFound return } From 933e95c05893471877a6d8d7230db1aaf0498ac7 Mon Sep 17 00:00:00 2001 From: lucus Date: Mon, 9 Jan 2017 18:55:36 +0900 Subject: [PATCH 12/12] Support new config method. Refined code. --- if_windows.go | 12 +++++++ params_windows.go | 16 ++++++++- syscalls_windows.go | 80 ++++++++++++++++++++++++++++++--------------- 3 files changed, 81 insertions(+), 27 deletions(-) create mode 100644 if_windows.go diff --git a/if_windows.go b/if_windows.go new file mode 100644 index 0000000..3ef1f8e --- /dev/null +++ b/if_windows.go @@ -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) +} diff --git a/params_windows.go b/params_windows.go index 2287c85..89016ce 100644 --- a/params_windows.go +++ b/params_windows.go @@ -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", } } diff --git a/syscalls_windows.go b/syscalls_windows.go index 514f0b8..5dc4013 100644 --- a/syscalls_windows.go +++ b/syscalls_windows.go @@ -33,9 +33,6 @@ var ( tap_ioctl_config_tun = tap_control_code(10, 0) // w32 api file_device_unknown = uint32(0x00000022) - // Driver maker specified ComponentId - // ComponentId is defined here: https://github.com/OpenVPN/tap-windows6/blob/master/version.m4#L5 - componentId = "tap0901" nCreateEvent, nResetEvent, nGetOverlappedResult uintptr @@ -143,7 +140,7 @@ func tap_control_code(request, method uint32) uint32 { } // getdeviceid finds out a TAP device from registry, it *may* requires privileged right to prevent some weird issue. -func getdeviceid() (string, error) { +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) @@ -167,7 +164,7 @@ func getdeviceid() (string, error) { key.Close() continue } - if val == componentId { + if val == componentID { val, _, err = key.GetStringValue("NetCfgInstanceId") if err != nil { key.Close() @@ -178,14 +175,49 @@ func getdeviceid() (string, error) { } key.Close() } - return "", fmt.Errorf("Failed to find the tap device in registry with specified ComponentId(%s), TAP driver may be not installed", componentId) + 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(isTAP bool) (ifce *Interface, err error) { - // ifName won't work +func openDev(config Config) (ifce *Interface, err error) { // find the device in registry. - deviceid, err := getdeviceid() + deviceid, err := getdeviceid(config.PlatformSpecificParams.ComponentID) if err != nil { return nil, err } @@ -209,7 +241,6 @@ func openDev(isTAP bool) (ifce *Interface, err error) { return nil, err } var bytesReturned uint32 - rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) // find the mac address of tap device, use this to find the name of interface mac := make([]byte, 6) @@ -228,21 +259,17 @@ func openDev(isTAP bool) (ifce *Interface, err error) { return } fd := &wfile{fd: file, ro: ro, wo: wo} - ifce = &Interface{isTAP: isTAP, ReadWriteCloser: fd} + ifce = &Interface{isTAP: (config.DeviceType == TAP), ReadWriteCloser: fd} // bring up device. - code := []byte{0x01, 0x00, 0x00, 0x00} - err = syscall.DeviceIoControl(file, tap_ioctl_set_media_status, &code[0], uint32(4), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) - if err != nil { - return + if err := setStatus(file, true); err != nil { + return nil, err } //TUN - if !isTAP { - code2 := []byte{0x0a, 0x03, 0x00, 0x01, 0x0a, 0x03, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00} - err = syscall.DeviceIoControl(file, tap_ioctl_config_tun, &code2[0], uint32(12), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil) - if err != nil { - return + if config.DeviceType == TUN { + if err := setTUN(file, config.PlatformSpecificParams.Network); err != nil { + return nil, err } } @@ -259,16 +286,17 @@ func openDev(isTAP bool) (ifce *Interface, err error) { } } - err = errIfceNameNotFound - return + return nil, errIfceNameNotFound } func newTAP(ifName string) (ifce *Interface, err error) { - // ifName won't work - return openDev(true) + config := defaultConfig() + config.DeviceType = TAP + return openDev(config) } func newTUN(ifName string) (ifce *Interface, err error) { - // ifName won't work - return openDev(false) + config := defaultConfig() + config.DeviceType = TUN + return openDev(config) }