package main import ( "encoding/binary" "errors" "fmt" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" "net" "sync" "syscall" "time" "unsafe" ) /* Relies on the OpenVPN TAP-Windows driver (NDIS 6 version) * * https://github.com/OpenVPN/tap-windows */ type NativeTUN struct { fd windows.Handle rl sync.Mutex wl sync.Mutex ro *windows.Overlapped wo *windows.Overlapped events chan TUNEvent name string } const ( METHOD_BUFFERED = 0 ComponentID = "tap0901" // tap0801 ) 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) } var ( errIfceNameNotFound = errors.New("Failed to find the name of interface") TAP_IOCTL_GET_MAC = TAP_CONTROL_CODE(1, METHOD_BUFFERED) TAP_IOCTL_GET_VERSION = TAP_CONTROL_CODE(2, METHOD_BUFFERED) TAP_IOCTL_GET_MTU = TAP_CONTROL_CODE(3, METHOD_BUFFERED) TAP_IOCTL_GET_INFO = TAP_CONTROL_CODE(4, METHOD_BUFFERED) TAP_IOCTL_CONFIG_POINT_TO_POINT = TAP_CONTROL_CODE(5, METHOD_BUFFERED) TAP_IOCTL_SET_MEDIA_STATUS = TAP_CONTROL_CODE(6, METHOD_BUFFERED) TAP_IOCTL_CONFIG_DHCP_MASQ = TAP_CONTROL_CODE(7, METHOD_BUFFERED) TAP_IOCTL_GET_LOG_LINE = TAP_CONTROL_CODE(8, METHOD_BUFFERED) TAP_IOCTL_CONFIG_DHCP_SET_OPT = TAP_CONTROL_CODE(9, METHOD_BUFFERED) TAP_IOCTL_CONFIG_TUN = TAP_CONTROL_CODE(10, METHOD_BUFFERED) file_device_unknown = uint32(0x00000022) nCreateEvent, nResetEvent, nGetOverlappedResult uintptr ) func init() { k32, err := windows.LoadLibrary("kernel32.dll") if err != nil { panic("LoadLibrary " + err.Error()) } defer windows.FreeLibrary(k32) nCreateEvent = getProcAddr(k32, "CreateEventW") nResetEvent = getProcAddr(k32, "ResetEvent") nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult") } /* implementation of the read/write/closer interface */ func getProcAddr(lib windows.Handle, name string) uintptr { addr, err := windows.GetProcAddress(lib, name) if err != nil { panic(name + " " + err.Error()) } return addr } func resetEvent(h windows.Handle) error { r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0) if r == 0 { return err } return nil } func getOverlappedResult(h windows.Handle, overlapped *windows.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() (*windows.Overlapped, error) { var overlapped windows.Overlapped r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0) if r == 0 { return nil, err } overlapped.HEvent = windows.Handle(r) return &overlapped, nil } func (f *NativeTUN) Events() chan TUNEvent { return f.events } func (f *NativeTUN) Close() error { return windows.Close(f.fd) } func (f *NativeTUN) 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 := windows.WriteFile(f.fd, b, &n, f.wo) if err != nil && err != windows.ERROR_IO_PENDING { return int(n), err } return getOverlappedResult(f.fd, f.wo) } func (f *NativeTUN) 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 := windows.ReadFile(f.fd, b, &done, f.ro) if err != nil && err != windows.ERROR_IO_PENDING { return int(done), err } return getOverlappedResult(f.fd, f.ro) } func getdeviceid( targetComponentId string, targetDeviceName string, ) (deviceid string, err error) { getName := func(instanceId string) (string, error) { path := fmt.Sprintf( `SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\%s\Connection`, instanceId, ) key, err := registry.OpenKey( registry.LOCAL_MACHINE, path, registry.READ, ) if err != nil { return "", err } defer key.Close() val, _, err := key.GetStringValue("Name") key.Close() return val, err } getInstanceId := func(keyName string) (string, string, error) { path := fmt.Sprintf( `SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\%s`, keyName, ) key, err := registry.OpenKey( registry.LOCAL_MACHINE, path, registry.READ, ) if err != nil { return "", "", err } defer key.Close() componentId, _, err := key.GetStringValue("ComponentId") if err != nil { return "", "", err } instanceId, _, err := key.GetStringValue("NetCfgInstanceId") return componentId, instanceId, err } // find list of all network devices k, err := registry.OpenKey( registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}`, 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() keys, err := k.ReadSubKeyNames(-1) if err != nil { return "", err } // look for matching component id and name var componentFound bool for _, v := range keys { componentId, instanceId, err := getInstanceId(v) if err != nil || componentId != targetComponentId { continue } componentFound = true deviceName, err := getName(instanceId) if err != nil || deviceName != targetDeviceName { continue } return instanceId, nil } // provide a descriptive error message if componentFound { return "", fmt.Errorf("Unable to find tun/tap device with name = %s", targetDeviceName) } return "", fmt.Errorf( "Unable to find device in registry with ComponentId = %s, is tap-windows installed?", targetComponentId, ) } // setStatus is used to bring up or bring down the interface func setStatus(fd windows.Handle, status bool) error { var code [4]byte if status { binary.LittleEndian.PutUint32(code[:], 1) } var bytesReturned uint32 rdbbuf := make([]byte, windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE) return windows.DeviceIoControl( fd, TAP_IOCTL_SET_MEDIA_STATUS, &code[0], uint32(4), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil, ) } /* When operating in TUN mode we must assign an ip address & subnet to the device. * */ func setTUN(fd windows.Handle, network string) error { var bytesReturned uint32 rdbbuf := make([]byte, windows.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) } var param [12]byte copy(param[0:4], localIP.To4()) copy(param[4:8], remoteNet.IP.To4()) copy(param[8:12], remoteNet.Mask) return windows.DeviceIoControl( fd, TAP_IOCTL_CONFIG_TUN, ¶m[0], uint32(12), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil, ) } func (tun *NativeTUN) MTU() (int, error) { var mtu [4]byte var bytesReturned uint32 err := windows.DeviceIoControl( tun.fd, TAP_IOCTL_GET_MTU, &mtu[0], uint32(len(mtu)), &mtu[0], uint32(len(mtu)), &bytesReturned, nil, ) val := binary.LittleEndian.Uint32(mtu[:]) return int(val), err } func (tun *NativeTUN) Name() string { return tun.name } func CreateTUN(name string) (TUNDevice, error) { // find the device in registry. deviceid, err := getdeviceid(ComponentID, name) if err != nil { return nil, err } path := "\\\\.\\Global\\" + deviceid + ".tap" pathp, err := windows.UTF16PtrFromString(path) if err != nil { return nil, err } // create TUN device handle, err := windows.CreateFile( pathp, windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_SYSTEM|windows.FILE_FLAG_OVERLAPPED, 0, ) if err != nil { return nil, err } ro, err := newOverlapped() if err != nil { windows.Close(handle) return nil, err } wo, err := newOverlapped() if err != nil { windows.Close(handle) return nil, err } tun := &NativeTUN{ fd: handle, name: name, ro: ro, wo: wo, events: make(chan TUNEvent, 5), } // find addresses of interface // TODO: fix this hack, the question is how inter, err := net.InterfaceByName(name) if err != nil { windows.Close(handle) return nil, err } addrs, err := inter.Addrs() if err != nil { windows.Close(handle) return nil, err } var ip net.IP for _, addr := range addrs { ip = func() net.IP { switch v := addr.(type) { case *net.IPNet: return v.IP.To4() case *net.IPAddr: return v.IP.To4() } return nil }() if ip != nil { break } } if ip == nil { windows.Close(handle) return nil, errors.New("No IPv4 address found for interface") } // bring up device. if err := setStatus(handle, true); err != nil { windows.Close(handle) return nil, err } // set tun mode mask := ip.String() + "/0" if err := setTUN(handle, mask); err != nil { windows.Close(handle) return nil, err } // start listener go func(native *NativeTUN, ifname string) { // TODO: Fix this very niave implementation var ( statusUp bool statusMTU int ) for ; ; time.Sleep(time.Second) { intr, err := net.InterfaceByName(name) if err != nil { // TODO: handle return } // Up / Down event up := (intr.Flags & net.FlagUp) != 0 if up != statusUp && up { native.events <- TUNEventUp } if up != statusUp && !up { native.events <- TUNEventDown } statusUp = up // MTU changes if intr.MTU != statusMTU { native.events <- TUNEventMTUUpdate } statusMTU = intr.MTU } }(tun, name) return tun, nil }