github.com/borderzero/water@v0.0.1/syscalls_windows.go (about)

     1  package water
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"sync"
     9  	"syscall"
    10  	"unsafe"
    11  
    12  	"golang.org/x/sys/windows/registry"
    13  )
    14  
    15  // To use it with windows, you need a tap driver installed on windows.
    16  // https://github.com/OpenVPN/tap-windows6
    17  // or just install OpenVPN
    18  // https://github.com/OpenVPN/openvpn
    19  
    20  const (
    21  	// tapDriverKey is the location of the TAP driver key.
    22  	tapDriverKey = `SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}`
    23  	// netConfigKey is the location of the TAP adapter's network config.
    24  	netConfigKey = `SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}`
    25  )
    26  
    27  var (
    28  	errIfceNameNotFound = errors.New("Failed to find the name of interface")
    29  	// Device Control Codes
    30  	tap_win_ioctl_get_mac             = tap_control_code(1, 0)
    31  	tap_win_ioctl_get_version         = tap_control_code(2, 0)
    32  	tap_win_ioctl_get_mtu             = tap_control_code(3, 0)
    33  	tap_win_ioctl_get_info            = tap_control_code(4, 0)
    34  	tap_ioctl_config_point_to_point   = tap_control_code(5, 0)
    35  	tap_ioctl_set_media_status        = tap_control_code(6, 0)
    36  	tap_win_ioctl_config_dhcp_masq    = tap_control_code(7, 0)
    37  	tap_win_ioctl_get_log_line        = tap_control_code(8, 0)
    38  	tap_win_ioctl_config_dhcp_set_opt = tap_control_code(9, 0)
    39  	tap_ioctl_config_tun              = tap_control_code(10, 0)
    40  	// w32 api
    41  	file_device_unknown = uint32(0x00000022)
    42  	nCreateEvent,
    43  	nResetEvent,
    44  	nGetOverlappedResult uintptr
    45  )
    46  
    47  func init() {
    48  	k32, err := syscall.LoadLibrary("kernel32.dll")
    49  	if err != nil {
    50  		panic("LoadLibrary " + err.Error())
    51  	}
    52  	defer syscall.FreeLibrary(k32)
    53  
    54  	nCreateEvent = getProcAddr(k32, "CreateEventW")
    55  	nResetEvent = getProcAddr(k32, "ResetEvent")
    56  	nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
    57  }
    58  
    59  func getProcAddr(lib syscall.Handle, name string) uintptr {
    60  	addr, err := syscall.GetProcAddress(lib, name)
    61  	if err != nil {
    62  		panic(name + " " + err.Error())
    63  	}
    64  	return addr
    65  }
    66  
    67  func resetEvent(h syscall.Handle) error {
    68  	r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0)
    69  	if r == 0 {
    70  		return err
    71  	}
    72  	return nil
    73  }
    74  
    75  func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
    76  	var n int
    77  	r, _, err := syscall.Syscall6(nGetOverlappedResult, 4,
    78  		uintptr(h),
    79  		uintptr(unsafe.Pointer(overlapped)),
    80  		uintptr(unsafe.Pointer(&n)), 1, 0, 0)
    81  	if r == 0 {
    82  		return n, err
    83  	}
    84  
    85  	return n, nil
    86  }
    87  
    88  func newOverlapped() (*syscall.Overlapped, error) {
    89  	var overlapped syscall.Overlapped
    90  	r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0)
    91  	if r == 0 {
    92  		return nil, err
    93  	}
    94  	overlapped.HEvent = syscall.Handle(r)
    95  	return &overlapped, nil
    96  }
    97  
    98  type wfile struct {
    99  	fd syscall.Handle
   100  	rl sync.Mutex
   101  	wl sync.Mutex
   102  	ro *syscall.Overlapped
   103  	wo *syscall.Overlapped
   104  }
   105  
   106  func (f *wfile) Close() error {
   107  	return syscall.Close(f.fd)
   108  }
   109  
   110  func (f *wfile) Write(b []byte) (int, error) {
   111  	f.wl.Lock()
   112  	defer f.wl.Unlock()
   113  
   114  	if err := resetEvent(f.wo.HEvent); err != nil {
   115  		return 0, err
   116  	}
   117  	var n uint32
   118  	err := syscall.WriteFile(f.fd, b, &n, f.wo)
   119  	if err != nil && err != syscall.ERROR_IO_PENDING {
   120  		return int(n), err
   121  	}
   122  	return getOverlappedResult(f.fd, f.wo)
   123  }
   124  
   125  func (f *wfile) Read(b []byte) (int, error) {
   126  	f.rl.Lock()
   127  	defer f.rl.Unlock()
   128  
   129  	if err := resetEvent(f.ro.HEvent); err != nil {
   130  		return 0, err
   131  	}
   132  	var done uint32
   133  	err := syscall.ReadFile(f.fd, b, &done, f.ro)
   134  	if err != nil && err != syscall.ERROR_IO_PENDING {
   135  		return int(done), err
   136  	}
   137  	return getOverlappedResult(f.fd, f.ro)
   138  }
   139  
   140  func ctl_code(device_type, function, method, access uint32) uint32 {
   141  	return (device_type << 16) | (access << 14) | (function << 2) | method
   142  }
   143  
   144  func tap_control_code(request, method uint32) uint32 {
   145  	return ctl_code(file_device_unknown, request, method, 0)
   146  }
   147  
   148  // getdeviceid finds out a TAP device from registry, it *may* requires privileged right to prevent some weird issue.
   149  func getdeviceid(componentID string, interfaceName string) (deviceid string, err error) {
   150  	k, err := registry.OpenKey(registry.LOCAL_MACHINE, tapDriverKey, registry.READ)
   151  	if err != nil {
   152  		return "", fmt.Errorf("Failed to open the adapter registry, TAP driver may be not installed, %v", err)
   153  	}
   154  	defer k.Close()
   155  	// read all subkeys, it should not return an err here
   156  	keys, err := k.ReadSubKeyNames(-1)
   157  	if err != nil {
   158  		return "", err
   159  	}
   160  	// find the one matched ComponentId
   161  	for _, v := range keys {
   162  		key, err := registry.OpenKey(registry.LOCAL_MACHINE, tapDriverKey+"\\"+v, registry.READ)
   163  		if err != nil {
   164  			continue
   165  		}
   166  		val, _, err := key.GetStringValue("ComponentId")
   167  		if err != nil {
   168  			key.Close()
   169  			continue
   170  		}
   171  		if val == componentID {
   172  			val, _, err = key.GetStringValue("NetCfgInstanceId")
   173  			if err != nil {
   174  				key.Close()
   175  				continue
   176  			}
   177  			if len(interfaceName) > 0 {
   178  				key2 := fmt.Sprintf("%s\\%s\\Connection", netConfigKey, val)
   179  				k2, err := registry.OpenKey(registry.LOCAL_MACHINE, key2, registry.READ)
   180  				if err != nil {
   181  					continue
   182  				}
   183  				defer k2.Close()
   184  				val, _, err := k2.GetStringValue("Name")
   185  				if err != nil || val != interfaceName {
   186  					continue
   187  				}
   188  			}
   189  			key.Close()
   190  			return val, nil
   191  		}
   192  		key.Close()
   193  	}
   194  	if len(interfaceName) > 0 {
   195  		return "", fmt.Errorf("Failed to find the tap device in registry with specified ComponentId '%s' and InterfaceName '%s', TAP driver may be not installed or you may have specified an interface name that doesn't exist", componentID, interfaceName)
   196  	}
   197  
   198  	return "", fmt.Errorf("Failed to find the tap device in registry with specified ComponentId '%s', TAP driver may be not installed", componentID)
   199  }
   200  
   201  // setStatus is used to bring up or bring down the interface
   202  func setStatus(fd syscall.Handle, status bool) error {
   203  	var bytesReturned uint32
   204  	rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
   205  	code := []byte{0x00, 0x00, 0x00, 0x00}
   206  	if status {
   207  		code[0] = 0x01
   208  	}
   209  	return syscall.DeviceIoControl(fd, tap_ioctl_set_media_status, &code[0], uint32(4), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
   210  }
   211  
   212  // setTUN is used to configure the IP address in the underlying driver when using TUN
   213  func setTUN(fd syscall.Handle, network string) error {
   214  	var bytesReturned uint32
   215  	rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
   216  
   217  	localIP, remoteNet, err := net.ParseCIDR(network)
   218  	if err != nil {
   219  		return fmt.Errorf("Failed to parse network CIDR in config, %v", err)
   220  	}
   221  	if localIP.To4() == nil {
   222  		return fmt.Errorf("Provided network(%s) is not a valid IPv4 address", network)
   223  	}
   224  	code2 := make([]byte, 0, 12)
   225  	code2 = append(code2, localIP.To4()[:4]...)
   226  	code2 = append(code2, remoteNet.IP.To4()[:4]...)
   227  	code2 = append(code2, remoteNet.Mask[:4]...)
   228  	if len(code2) != 12 {
   229  		return fmt.Errorf("Provided network(%s) is not valid", network)
   230  	}
   231  	if err := syscall.DeviceIoControl(fd, tap_ioctl_config_tun, &code2[0], uint32(12), &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil); err != nil {
   232  		return err
   233  	}
   234  	return nil
   235  }
   236  
   237  // openDev find and open an interface.
   238  func openDev(config Config) (ifce *Interface, err error) {
   239  	if config.DeviceType == TUN {
   240  		return openTunDev(config)
   241  	}
   242  	// find the device in registry.
   243  	deviceid, err := getdeviceid(config.PlatformSpecificParams.ComponentID, config.PlatformSpecificParams.Name)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	path := "\\\\.\\Global\\" + deviceid + ".tap"
   248  	pathp, err := syscall.UTF16PtrFromString(path)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	// type Handle uintptr
   253  	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)
   254  	// if err hanppens, close the interface.
   255  	defer func() {
   256  		if err != nil {
   257  			syscall.Close(file)
   258  		}
   259  		if err := recover(); err != nil {
   260  			syscall.Close(file)
   261  		}
   262  	}()
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	var bytesReturned uint32
   267  
   268  	// find the mac address of tap device, use this to find the name of interface
   269  	mac := make([]byte, 6)
   270  	err = syscall.DeviceIoControl(file, tap_win_ioctl_get_mac, &mac[0], uint32(len(mac)), &mac[0], uint32(len(mac)), &bytesReturned, nil)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  
   275  	// fd := os.NewFile(uintptr(file), path)
   276  	ro, err := newOverlapped()
   277  	if err != nil {
   278  		return
   279  	}
   280  	wo, err := newOverlapped()
   281  	if err != nil {
   282  		return
   283  	}
   284  	fd := &wfile{fd: file, ro: ro, wo: wo}
   285  	ifce = &Interface{isTAP: (config.DeviceType == TAP), ReadWriteCloser: fd}
   286  
   287  	// bring up device.
   288  	if err := setStatus(file, true); err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	//TUN
   293  	if config.DeviceType == TUN {
   294  		if err := setTUN(file, config.PlatformSpecificParams.Network); err != nil {
   295  			return nil, err
   296  		}
   297  	}
   298  
   299  	// find the name of tap interface(u need it to set the ip or other command)
   300  	ifces, err := net.Interfaces()
   301  	if err != nil {
   302  		return
   303  	}
   304  
   305  	for _, v := range ifces {
   306  		if len(v.HardwareAddr) < 6 {
   307  			continue
   308  		}
   309  		if bytes.Equal(v.HardwareAddr[:6], mac[:6]) {
   310  			ifce.name = v.Name
   311  			return
   312  		}
   313  	}
   314  
   315  	return nil, errIfceNameNotFound
   316  }