diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2019-02-07 04:18:27 +0100 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2019-02-07 04:39:59 +0100 |
commit | ea59177f1c6ab9031d5d30771410c9514fa551d9 (patch) | |
tree | cfe5c11f9340d468dfd5f80b76db78584a9ef20a /tun | |
parent | 306d08e692bc9b2249078c0b4ab7eb3e42078230 (diff) |
wintun: Introduce new package for obscuring Windows bits
Diffstat (limited to 'tun')
-rw-r--r-- | tun/tun_windows.go | 493 | ||||
-rw-r--r-- | tun/wintun/guid/guid_windows.go | 44 | ||||
-rw-r--r-- | tun/wintun/guid/mksyscall.go | 8 | ||||
-rw-r--r-- | tun/wintun/guid/zguid_windows.go (renamed from tun/ztun_windows.go) | 2 | ||||
-rw-r--r-- | tun/wintun/setupapi/mksyscall.go | 8 | ||||
-rw-r--r-- | tun/wintun/setupapi/setupapi_windows.go | 476 | ||||
-rw-r--r-- | tun/wintun/setupapi/setupapi_windows_test.go | 452 | ||||
-rw-r--r-- | tun/wintun/setupapi/types_windows.go | 571 | ||||
-rw-r--r-- | tun/wintun/setupapi/zsetupapi_windows.go | 370 | ||||
-rw-r--r-- | tun/wintun/setupapi/zsetupapi_windows_test.go | 20 | ||||
-rw-r--r-- | tun/wintun/wintun_windows.go | 461 |
11 files changed, 2424 insertions, 481 deletions
diff --git a/tun/tun_windows.go b/tun/tun_windows.go index d7cd762..6449974 100644 --- a/tun/tun_windows.go +++ b/tun/tun_windows.go @@ -5,20 +5,14 @@ package tun -//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output ztun_windows.go tun_windows.go - import ( "errors" "fmt" "os" - "strings" - "syscall" - "time" "unsafe" - "git.zx2c4.com/wireguard-go/setupapi" + "git.zx2c4.com/wireguard-go/tun/wintun" "golang.org/x/sys/windows" - "golang.org/x/sys/windows/registry" ) const ( @@ -34,10 +28,6 @@ const ( TUN_SIGNAL_MAX = 2 ) -var deviceClassNetGUID = windows.GUID{0x4d36e972, 0xe325, 0x11ce, [8]byte{0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18}} - -const TUN_HWID = "Wintun" - type tunPacket struct { size uint32 data [TUN_MAX_PACKET_SIZE]byte @@ -50,7 +40,7 @@ type tunRWQueue struct { } type nativeTun struct { - ifid *windows.GUID + wt *wintun.Wintun tunName string signalName *uint16 tunFile *os.File @@ -64,30 +54,28 @@ type nativeTun struct { func CreateTUN(ifname string) (TUNDevice, error) { // Does an interface with this name already exist? - ifid, err := getInterface(ifname, 0) - if ifid == nil || err != nil { + wt, err := wintun.GetInterface(ifname, 0) + if wt == nil || err != nil { // Interface does not exist or an error occured. Create one. - ifid, _, err = createInterface("WireGuard Tunnel Adapter", 0) + wt, _, err = wintun.CreateInterface("WireGuard Tunnel Adapter", 0) if err != nil { return nil, err } // Set interface name. (Ignore errors.) - setInterfaceName(ifid, ifname) + wt.SetInterfaceName(ifname) } - ifidStr := guidToString(ifid) - - signalNameUTF16, err := windows.UTF16PtrFromString(fmt.Sprintf("Global\\WINTUN_EVENT_%s", ifidStr)) + signalNameUTF16, err := windows.UTF16PtrFromString(wt.SignalEventName()) if err != nil { - deleteInterface(ifid, 0) + wt.DeleteInterface(0) return nil, err } // Create instance. tun := &nativeTun{ - ifid: ifid, - tunName: fmt.Sprintf("\\\\.\\Global\\WINTUN_DEVICE_%s", ifidStr), + wt: wt, + tunName: wt.DataFileName(), signalName: signalNameUTF16, events: make(chan TUNEvent, 10), errors: make(chan error, 1), @@ -96,7 +84,7 @@ func CreateTUN(ifname string) (TUNDevice, error) { // Create close event. tun.signals[TUN_SIGNAL_CLOSE], err = windows.CreateEvent(nil, 1 /*TRUE*/, 0 /*FALSE*/, nil) if err != nil { - deleteInterface(ifid, 0) + wt.DeleteInterface(0) return nil, err } @@ -160,7 +148,7 @@ func (tun *nativeTun) closeTUN() (err error) { } func (tun *nativeTun) Name() (string, error) { - return getInterfaceName(tun.ifid) + return tun.wt.GetInterfaceName() } func (tun *nativeTun) File() *os.File { @@ -184,7 +172,7 @@ func (tun *nativeTun) Close() error { close(tun.events) } - _, _, e = deleteInterface(tun.ifid, 0) + _, _, e = tun.wt.DeleteInterface(0) if err == nil { err = e } @@ -317,458 +305,3 @@ func (tun *nativeTun) Write(buff []byte, offset int) (int, error) { // Flush write buffer. return len(buff) - offset, tun.flush() } - -// -// getInterface finds interface ID by name. -// -// hwndParent is a handle to the top-level window to use for any user -// interface that is related to non-device-specific actions (such as a select- -// device dialog box that uses the global class driver list). This handle is -// optional and can be 0. If a specific top-level window is not required, set -// hwndParent to 0. -// -// Function returns interface ID when the interface was found, or nil -// otherwise. -// -func getInterface(ifname string, hwndParent uintptr) (*windows.GUID, error) { - // Create a list of network devices. - devInfoList, err := setupapi.SetupDiGetClassDevsEx(&deviceClassNetGUID, "", hwndParent, setupapi.DIGCF_PRESENT, setupapi.DevInfo(0), "") - if err != nil { - return nil, err - } - defer devInfoList.Close() - - // Retrieve information associated with a device information set. - // TODO: Is this really necessary? - _, err = devInfoList.GetDeviceInfoListDetail() - if err != nil { - return nil, err - } - - // TODO: If we're certain we want case-insensitive name comparison, please document the rationale. - ifname = strings.ToLower(ifname) - - // Iterate. - for index := 0; ; index++ { - // Get the device from the list. - deviceData, err := devInfoList.EnumDeviceInfo(index) - if err != nil { - if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { - break - } - // Something is wrong with this device. Skip it. - continue - } - - // Get interface ID. - ifid, err := getInterfaceId(devInfoList, deviceData, 1) - if err != nil { - // Something is wrong with this device. Skip it. - continue - } - - // Get interface name. - ifname2, err := getInterfaceName(ifid) - if err != nil { - // Something is wrong with this device. Skip it. - continue - } - - if ifname == strings.ToLower(ifname2) { - // Interface name found. - return ifid, nil - } - } - - return nil, nil -} - -// -// createInterface creates a TUN interface. -// -// description is a string that supplies the text description of the device. -// Description is optional and can be "". -// -// hwndParent is a handle to the top-level window to use for any user -// interface that is related to non-device-specific actions (such as a select- -// device dialog box that uses the global class driver list). This handle is -// optional and can be 0. If a specific top-level window is not required, set -// hwndParent to 0. -// -// Function returns the network interface ID and a flag if reboot is required. -// -func createInterface(description string, hwndParent uintptr) (*windows.GUID, bool, error) { - // Create an empty device info set for network adapter device class. - devInfoList, err := setupapi.SetupDiCreateDeviceInfoListEx(&deviceClassNetGUID, hwndParent, "") - if err != nil { - return nil, false, err - } - - // Get the device class name from GUID. - className, err := setupapi.SetupDiClassNameFromGuidEx(&deviceClassNetGUID, "") - if err != nil { - return nil, false, err - } - - // Create a new device info element and add it to the device info set. - deviceData, err := devInfoList.CreateDeviceInfo(className, &deviceClassNetGUID, description, hwndParent, setupapi.DICD_GENERATE_ID) - if err != nil { - return nil, false, err - } - - // Set a device information element as the selected member of a device information set. - err = devInfoList.SetSelectedDevice(deviceData) - if err != nil { - return nil, false, err - } - - // Set Plug&Play device hardware ID property. - hwid, err := syscall.UTF16FromString(TUN_HWID) - if err != nil { - return nil, false, err - } - err = devInfoList.SetDeviceRegistryProperty(deviceData, setupapi.SPDRP_HARDWAREID, setupapi.UTF16ToBuf(append(hwid, 0))) - if err != nil { - return nil, false, err - } - - // Search for the driver. - const driverType = setupapi.SPDIT_CLASSDRIVER - err = devInfoList.BuildDriverInfoList(deviceData, driverType) - if err != nil { - return nil, false, err - } - defer devInfoList.DestroyDriverInfoList(deviceData, driverType) - - driverDate := windows.Filetime{} - driverVersion := uint64(0) - for index := 0; ; index++ { - // Get a driver from the list. - driverData, err := devInfoList.EnumDriverInfo(deviceData, driverType, index) - if err != nil { - if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { - break - } - // Something is wrong with this driver. Skip it. - continue - } - - // Check the driver version first, since the check is trivial and will save us iterating over hardware IDs for any driver versioned prior our best match. - if driverData.IsNewer(driverDate, driverVersion) { - // Get driver info details. - driverDetailData, err := devInfoList.GetDriverInfoDetail(deviceData, driverData) - if err != nil { - // Something is wrong with this driver. Skip it. - continue - } - - if driverDetailData.IsCompatible(TUN_HWID) { - // Matching hardware ID found. Select the driver. - err := devInfoList.SetSelectedDriver(deviceData, driverData) - if err != nil { - // Something is wrong with this driver. Skip it. - continue - } - - driverDate = driverData.DriverDate - driverVersion = driverData.DriverVersion - } - } - } - - if driverVersion == 0 { - return nil, false, fmt.Errorf("No driver for device \"%v\" installed", TUN_HWID) - } - - // Call appropriate class installer. - err = devInfoList.CallClassInstaller(setupapi.DIF_REGISTERDEVICE, deviceData) - if err != nil { - return nil, false, err - } - - // Register device co-installers if any. - devInfoList.CallClassInstaller(setupapi.DIF_REGISTER_COINSTALLERS, deviceData) - - // Install interfaces if any. - devInfoList.CallClassInstaller(setupapi.DIF_INSTALLINTERFACES, deviceData) - - var ifid *windows.GUID - var rebootRequired bool - - // Install the device. - err = devInfoList.CallClassInstaller(setupapi.DIF_INSTALLDEVICE, deviceData) - if err == nil { - // Check if a system reboot is required. (Ignore errors) - if ret, _ := checkReboot(devInfoList, deviceData); ret { - rebootRequired = true - } - - // Get network interface ID from registry. Retry for max 30sec. - ifid, err = getInterfaceId(devInfoList, deviceData, 30) - } - - if err == nil { - return ifid, rebootRequired, nil - } - - // The interface failed to install, or the interface ID was unobtainable. Clean-up. - removeDeviceParams := setupapi.SP_REMOVEDEVICE_PARAMS{ - ClassInstallHeader: setupapi.SP_CLASSINSTALL_HEADER{ - InstallFunction: setupapi.DIF_REMOVE, - }, - Scope: setupapi.DI_REMOVEDEVICE_GLOBAL, - } - removeDeviceParams.ClassInstallHeader.Size = uint32(unsafe.Sizeof(removeDeviceParams.ClassInstallHeader)) - - // Set class installer parameters for DIF_REMOVE. - if devInfoList.SetClassInstallParams(deviceData, &removeDeviceParams.ClassInstallHeader, uint32(unsafe.Sizeof(removeDeviceParams))) == nil { - // Call appropriate class installer. - if devInfoList.CallClassInstaller(setupapi.DIF_REMOVE, deviceData) == nil { - // Check if a system reboot is required. (Ignore errors) - if ret, _ := checkReboot(devInfoList, deviceData); ret { - rebootRequired = true - } - } - } - - return nil, false, err -} - -// -// deleteInterface deletes a TUN interface. -// -// hwndParent is a handle to the top-level window to use for any user -// interface that is related to non-device-specific actions (such as a select- -// device dialog box that uses the global class driver list). This handle is -// optional and can be 0. If a specific top-level window is not required, set -// hwndParent to 0. -// -// Function returns true if the interface was found and deleted and a flag if -// reboot is required. -// -func deleteInterface(ifid *windows.GUID, hwndParent uintptr) (bool, bool, error) { - // Create a list of network devices. - devInfoList, err := setupapi.SetupDiGetClassDevsEx(&deviceClassNetGUID, "", hwndParent, setupapi.DIGCF_PRESENT, setupapi.DevInfo(0), "") - if err != nil { - return false, false, err - } - defer devInfoList.Close() - - // Retrieve information associated with a device information set. - // TODO: Is this really necessary? - _, err = devInfoList.GetDeviceInfoListDetail() - if err != nil { - return false, false, err - } - - // Iterate. - for index := 0; ; index++ { - // Get the device from the list. - deviceData, err := devInfoList.EnumDeviceInfo(index) - if err != nil { - if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { - break - } - // Something is wrong with this device. Skip it. - continue - } - - // Get interface ID. - ifid2, err := getInterfaceId(devInfoList, deviceData, 1) - if err != nil { - // Something is wrong with this device. Skip it. - continue - } - - if ifid == ifid2 { - // Remove the device. - removeDeviceParams := setupapi.SP_REMOVEDEVICE_PARAMS{ - ClassInstallHeader: setupapi.SP_CLASSINSTALL_HEADER{ - InstallFunction: setupapi.DIF_REMOVE, - }, - Scope: setupapi.DI_REMOVEDEVICE_GLOBAL, - } - removeDeviceParams.ClassInstallHeader.Size = uint32(unsafe.Sizeof(removeDeviceParams.ClassInstallHeader)) - - // Set class installer parameters for DIF_REMOVE. - err = devInfoList.SetClassInstallParams(deviceData, &removeDeviceParams.ClassInstallHeader, uint32(unsafe.Sizeof(removeDeviceParams))) - if err != nil { - return false, false, err - } - - // Call appropriate class installer. - err = devInfoList.CallClassInstaller(setupapi.DIF_REMOVE, deviceData) - if err != nil { - return false, false, err - } - - // Check if a system reboot is required. (Ignore errors) - if ret, _ := checkReboot(devInfoList, deviceData); ret { - return true, true, nil - } - - return true, false, nil - } - } - - return false, false, nil -} - -/// -/// checkReboot checks device install parameters if a system reboot is required. -/// -func checkReboot(deviceInfoSet setupapi.DevInfo, deviceInfoData *setupapi.SP_DEVINFO_DATA) (bool, error) { - devInstallParams, err := deviceInfoSet.GetDeviceInstallParams(deviceInfoData) - if err != nil { - return false, err - } - - if (devInstallParams.Flags & (setupapi.DI_NEEDREBOOT | setupapi.DI_NEEDRESTART)) != 0 { - return true, nil - } - - return false, nil -} - -// getInterfaceId returns network interface ID. -// -// After the device is created, it might take some time before the registry -// key is populated. numAttempts parameter specifies the number of attempts -// to read NetCfgInstanceId value from registry. A 1sec sleep is inserted -// between retry attempts. -// -// Function returns the network interface ID. -// -func getInterfaceId(deviceInfoSet setupapi.DevInfo, deviceInfoData *setupapi.SP_DEVINFO_DATA, numAttempts int) (*windows.GUID, error) { - if numAttempts < 1 { - return nil, fmt.Errorf("Invalid numAttempts (expected: >=1, provided: %v)", numAttempts) - } - - // Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\<class>\<id> registry key. - key, err := deviceInfoSet.OpenDevRegKey(deviceInfoData, setupapi.DICS_FLAG_GLOBAL, 0, setupapi.DIREG_DRV, registry.READ) - if err != nil { - return nil, errors.New("Device-specific registry key open failed: " + err.Error()) - } - defer key.Close() - - for { - // Query the NetCfgInstanceId value. Using get_reg_string() right on might clutter the output with error messages while the registry is still being populated. - _, _, err = key.GetValue("NetCfgInstanceId", nil) - if err != nil { - if errWin, ok := err.(syscall.Errno); ok && errWin == windows.ERROR_FILE_NOT_FOUND { - numAttempts-- - if numAttempts > 0 { - // Wait and retry. - // TODO: Wait for a cancellable event instead. - time.Sleep(1000 * time.Millisecond) - continue - } - } - - return nil, errors.New("RegQueryValueEx(\"NetCfgInstanceId\") failed: " + err.Error()) - } - - // Read the NetCfgInstanceId value now. - value, err := getRegStringValue(key, "NetCfgInstanceId") - if err != nil { - return nil, errors.New("RegQueryStringValue(\"NetCfgInstanceId\") failed: " + err.Error()) - } - - // Convert to windows.GUID. - ifid, err := stringToGUID(value) - if err != nil { - return nil, fmt.Errorf("NetCfgInstanceId registry value is not a GUID (expected: \"{...}\", provided: \"%v\")", value) - } - - return ifid, err - } -} - -// -// getInterfaceName returns network interface name. -// -func getInterfaceName(ifid *windows.GUID) (string, error) { - // Open network interface registry key. - key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Control\\Network\\%v\\%v\\Connection", guidToString(&deviceClassNetGUID), guidToString(ifid)), registry.QUERY_VALUE) - if err != nil { - return "", errors.New("Network-specific registry key open failed: " + err.Error()) - } - defer key.Close() - - // Get the interface name. - return getRegStringValue(key, "Name") -} - -// -// setInterfaceName sets network interface name. -// -func setInterfaceName(ifid *windows.GUID, ifname string) error { - // Open network interface registry key. - key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Control\\Network\\%v\\%v\\Connection", guidToString(&deviceClassNetGUID), guidToString(ifid)), registry.SET_VALUE) - if err != nil { - return errors.New("Network-specific registry key open failed: " + err.Error()) - } - defer key.Close() - - // Set the interface name. - return key.SetStringValue("Name", ifname) -} - -//sys clsidFromString(lpsz *uint16, pclsid *windows.GUID) (hr int32) = ole32.CLSIDFromString - -// -// stringToGUID parses "{XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" string to GUID. -// -func stringToGUID(str string) (*windows.GUID, error) { - strUTF16, err := syscall.UTF16PtrFromString(str) - if err != nil { - return nil, err - } - - guid := &windows.GUID{} - - hr := clsidFromString(strUTF16, guid) - if hr < 0 { - return nil, syscall.Errno(hr) - } - - return guid, nil -} - -// -// guidToString function converts GUID to string -// "{XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}". -// -// The resulting string is uppercase. -// -func guidToString(guid *windows.GUID) string { - return fmt.Sprintf("{%06X-%04X-%04X-%04X-%012X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[:2], guid.Data4[2:]) -} - -// -// getRegStringValue function reads a string value from registry. -// -// If the value type is REG_EXPAND_SZ the environment variables are expanded. -// Should expanding fail, original string value and nil error are returned. -// -func getRegStringValue(key registry.Key, name string) (string, error) { - // Read string value. - value, valueType, err := key.GetStringValue(name) - if err != nil { - return "", err - } - - if valueType != registry.EXPAND_SZ { - // Value does not require expansion. - return value, nil - } - - valueExp, err := registry.ExpandString(value) - if err != nil { - // Expanding failed: return original sting value. - return value, nil - } - - // Return expanded value. - return valueExp, nil -} diff --git a/tun/wintun/guid/guid_windows.go b/tun/wintun/guid/guid_windows.go new file mode 100644 index 0000000..0078d2c --- /dev/null +++ b/tun/wintun/guid/guid_windows.go @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package guid + +import ( + "fmt" + "syscall" + + "golang.org/x/sys/windows" +) + +//sys clsidFromString(lpsz *uint16, pclsid *windows.GUID) (hr int32) = ole32.CLSIDFromString + +// +// FromString parses "{XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}" string to GUID. +// +func FromString(str string) (*windows.GUID, error) { + strUTF16, err := syscall.UTF16PtrFromString(str) + if err != nil { + return nil, err + } + + guid := &windows.GUID{} + + hr := clsidFromString(strUTF16, guid) + if hr < 0 { + return nil, syscall.Errno(hr) + } + + return guid, nil +} + +// +// ToString function converts GUID to string +// "{XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}". +// +// The resulting string is uppercase. +// +func ToString(guid *windows.GUID) string { + return fmt.Sprintf("{%06X-%04X-%04X-%04X-%012X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[:2], guid.Data4[2:]) +} diff --git a/tun/wintun/guid/mksyscall.go b/tun/wintun/guid/mksyscall.go new file mode 100644 index 0000000..5c956cf --- /dev/null +++ b/tun/wintun/guid/mksyscall.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package guid + +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zguid_windows.go guid_windows.go diff --git a/tun/ztun_windows.go b/tun/wintun/guid/zguid_windows.go index 9aa6e11..5467849 100644 --- a/tun/ztun_windows.go +++ b/tun/wintun/guid/zguid_windows.go @@ -1,6 +1,6 @@ // Code generated by 'go generate'; DO NOT EDIT. -package tun +package guid import ( "syscall" diff --git a/tun/wintun/setupapi/mksyscall.go b/tun/wintun/setupapi/mksyscall.go new file mode 100644 index 0000000..3447dee --- /dev/null +++ b/tun/wintun/setupapi/mksyscall.go @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package setupapi + +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsetupapi_windows.go setupapi_windows.go diff --git a/tun/wintun/setupapi/setupapi_windows.go b/tun/wintun/setupapi/setupapi_windows.go new file mode 100644 index 0000000..72d4bd8 --- /dev/null +++ b/tun/wintun/setupapi/setupapi_windows.go @@ -0,0 +1,476 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package setupapi + +import ( + "encoding/binary" + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +//sys setupDiCreateDeviceInfoListEx(classGUID *windows.GUID, hwndParent uintptr, machineName *uint16, reserved uintptr) (handle DevInfo, err error) [failretval==DevInfo(windows.InvalidHandle)] = setupapi.SetupDiCreateDeviceInfoListExW + +// SetupDiCreateDeviceInfoListEx function creates an empty device information set on a remote or a local computer and optionally associates the set with a device setup class. +func SetupDiCreateDeviceInfoListEx(classGUID *windows.GUID, hwndParent uintptr, machineName string) (deviceInfoSet DevInfo, err error) { + var machineNameUTF16 *uint16 + if machineName != "" { + machineNameUTF16, err = syscall.UTF16PtrFromString(machineName) + if err != nil { + return + } + } + return setupDiCreateDeviceInfoListEx(classGUID, hwndParent, machineNameUTF16, 0) +} + +//sys setupDiGetDeviceInfoListDetail(deviceInfoSet DevInfo, deviceInfoSetDetailData *_SP_DEVINFO_LIST_DETAIL_DATA) (err error) = setupapi.SetupDiGetDeviceInfoListDetailW + +// SetupDiGetDeviceInfoListDetail function retrieves information associated with a device information set including the class GUID, remote computer handle, and remote computer name. +func SetupDiGetDeviceInfoListDetail(deviceInfoSet DevInfo) (deviceInfoSetDetailData *DevInfoListDetailData, err error) { + var _data _SP_DEVINFO_LIST_DETAIL_DATA + _data.Size = uint32(unsafe.Sizeof(_data)) + + err = setupDiGetDeviceInfoListDetail(deviceInfoSet, &_data) + if err != nil { + return + } + + return _data.toGo(), nil +} + +// GetDeviceInfoListDetail method retrieves information associated with a device information set including the class GUID, remote computer handle, and remote computer name. +func (deviceInfoSet DevInfo) GetDeviceInfoListDetail() (*DevInfoListDetailData, error) { + return SetupDiGetDeviceInfoListDetail(deviceInfoSet) +} + +//sys setupDiCreateDeviceInfo(deviceInfoSet DevInfo, DeviceName *uint16, classGUID *windows.GUID, DeviceDescription *uint16, hwndParent uintptr, CreationFlags DICD, deviceInfoData *SP_DEVINFO_DATA) (err error) = setupapi.SetupDiCreateDeviceInfoW + +// SetupDiCreateDeviceInfo function creates a new device information element and adds it as a new member to the specified device information set. +func SetupDiCreateDeviceInfo(deviceInfoSet DevInfo, deviceName string, classGUID *windows.GUID, deviceDescription string, hwndParent uintptr, creationFlags DICD) (deviceInfoData *SP_DEVINFO_DATA, err error) { + deviceNameUTF16, err := syscall.UTF16PtrFromString(deviceName) + if err != nil { + return + } + + var deviceDescriptionUTF16 *uint16 + if deviceDescription != "" { + deviceDescriptionUTF16, err = syscall.UTF16PtrFromString(deviceDescription) + if err != nil { + return + } + } + + data := SP_DEVINFO_DATA{} + data.Size = uint32(unsafe.Sizeof(data)) + + return &data, setupDiCreateDeviceInfo(deviceInfoSet, deviceNameUTF16, classGUID, deviceDescriptionUTF16, hwndParent, creationFlags, &data) +} + +// CreateDeviceInfo method creates a new device information element and adds it as a new member to the specified device information set. +func (deviceInfoSet DevInfo) CreateDeviceInfo(deviceName string, classGUID *windows.GUID, deviceDescription string, hwndParent uintptr, creationFlags DICD) (*SP_DEVINFO_DATA, error) { + return SetupDiCreateDeviceInfo(deviceInfoSet, deviceName, classGUID, deviceDescription, hwndParent, creationFlags) +} + +//sys setupDiEnumDeviceInfo(deviceInfoSet DevInfo, memberIndex uint32, deviceInfoData *SP_DEVINFO_DATA) (err error) = setupapi.SetupDiEnumDeviceInfo + +// SetupDiEnumDeviceInfo function returns a SP_DEVINFO_DATA structure that specifies a device information element in a device information set. +func SetupDiEnumDeviceInfo(deviceInfoSet DevInfo, memberIndex int) (*SP_DEVINFO_DATA, error) { + data := SP_DEVINFO_DATA{} + data.Size = uint32(unsafe.Sizeof(data)) + + return &data, setupDiEnumDeviceInfo(deviceInfoSet, uint32(memberIndex), &data) +} + +// EnumDeviceInfo method returns a SP_DEVINFO_DATA structure that specifies a device information element in a device information set. +func (deviceInfoSet DevInfo) EnumDeviceInfo(memberIndex int) (*SP_DEVINFO_DATA, error) { + return SetupDiEnumDeviceInfo(deviceInfoSet, memberIndex) +} + +// SetupDiDestroyDeviceInfoList function deletes a device information set and frees all associated memory. +//sys SetupDiDestroyDeviceInfoList(deviceInfoSet DevInfo) (err error) = setupapi.SetupDiDestroyDeviceInfoList + +// Close method deletes a device information set and frees all associated memory. +func (deviceInfoSet DevInfo) Close() error { + return SetupDiDestroyDeviceInfoList(deviceInfoSet) +} + +//sys SetupDiBuildDriverInfoList(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT) (err error) = setupapi.SetupDiBuildDriverInfoList + +// BuildDriverInfoList method builds a list of drivers that is associated with a specific device or with the global class driver list for a device information set. +func (deviceInfoSet DevInfo) BuildDriverInfoList(deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT) error { + return SetupDiBuildDriverInfoList(deviceInfoSet, deviceInfoData, driverType) +} + +//sys SetupDiCancelDriverInfoSearch(deviceInfoSet DevInfo) (err error) = setupapi.SetupDiCancelDriverInfoSearch + +// CancelDriverInfoSearch method cancels a driver list search that is currently in progress in a different thread. +func (deviceInfoSet DevInfo) CancelDriverInfoSearch() error { + return SetupDiCancelDriverInfoSearch(deviceInfoSet) +} + +//sys setupDiEnumDriverInfo(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT, memberIndex uint32, driverInfoData *SP_DRVINFO_DATA) (err error) = setupapi.SetupDiEnumDriverInfoW + +// SetupDiEnumDriverInfo function enumerates the members of a driver list. +func SetupDiEnumDriverInfo(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT, memberIndex int) (*SP_DRVINFO_DATA, error) { + data := &SP_DRVINFO_DATA{} + data.Size = uint32(unsafe.Sizeof(*data)) + + return data, setupDiEnumDriverInfo(deviceInfoSet, deviceInfoData, driverType, uint32(memberIndex), data) +} + +// EnumDriverInfo method enumerates the members of a driver list. +func (deviceInfoSet DevInfo) EnumDriverInfo(deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT, memberIndex int) (*SP_DRVINFO_DATA, error) { + return SetupDiEnumDriverInfo(deviceInfoSet, deviceInfoData, driverType, memberIndex) +} + +//sys setupDiGetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA) (err error) = setupapi.SetupDiGetSelectedDriverW + +// SetupDiGetSelectedDriver function retrieves the selected driver for a device information set or a particular device information element. +func SetupDiGetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA) (*SP_DRVINFO_DATA, error) { + data := &SP_DRVINFO_DATA{} + data.Size = uint32(unsafe.Sizeof(*data)) + + return data, setupDiGetSelectedDriver(deviceInfoSet, deviceInfoData, data) +} + +// GetSelectedDriver method retrieves the selected driver for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) GetSelectedDriver(deviceInfoData *SP_DEVINFO_DATA) (*SP_DRVINFO_DATA, error) { + return SetupDiGetSelectedDriver(deviceInfoSet, deviceInfoData) +} + +//sys SetupDiSetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA) (err error) = setupapi.SetupDiSetSelectedDriverW + +// SetSelectedDriver method sets, or resets, the selected driver for a device information element or the selected class driver for a device information set. +func (deviceInfoSet DevInfo) SetSelectedDriver(deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA) error { + return SetupDiSetSelectedDriver(deviceInfoSet, deviceInfoData, driverInfoData) +} + +//sys setupDiGetDriverInfoDetail(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA, driverInfoDetailData *_SP_DRVINFO_DETAIL_DATA, driverInfoDetailDataSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiGetDriverInfoDetailW + +// SetupDiGetDriverInfoDetail function retrieves driver information detail for a device information set or a particular device information element in the device information set. +func SetupDiGetDriverInfoDetail(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA) (driverInfoDetailData *DrvInfoDetailData, err error) { + const bufCapacity = 0x800 + buf := [bufCapacity]byte{} + var bufLen uint32 + + _data := (*_SP_DRVINFO_DETAIL_DATA)(unsafe.Pointer(&buf[0])) + _data.Size = uint32(unsafe.Sizeof(*_data)) + + err = setupDiGetDriverInfoDetail(deviceInfoSet, deviceInfoData, driverInfoData, _data, bufCapacity, &bufLen) + if err == nil { + // The buffer was was sufficiently big. + return _data.toGo(bufLen), nil + } + + if errWin, ok := err.(syscall.Errno); ok && errWin == windows.ERROR_INSUFFICIENT_BUFFER { + // The buffer was too small. Now that we got the required size, create another one big enough and retry. + buf := make([]byte, bufLen) + _data := (*_SP_DRVINFO_DETAIL_DATA)(unsafe.Pointer(&buf[0])) + _data.Size = uint32(unsafe.Sizeof(*_data)) + + err = setupDiGetDriverInfoDetail(deviceInfoSet, deviceInfoData, driverInfoData, _data, bufLen, &bufLen) + if err == nil { + return _data.toGo(bufLen), nil + } + } + + return +} + +// GetDriverInfoDetail method retrieves driver information detail for a device information set or a particular device information element in the device information set. +func (deviceInfoSet DevInfo) GetDriverInfoDetail(deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA) (*DrvInfoDetailData, error) { + return SetupDiGetDriverInfoDetail(deviceInfoSet, deviceInfoData, driverInfoData) +} + +//sys SetupDiDestroyDriverInfoList(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT) (err error) = setupapi.SetupDiDestroyDriverInfoList + +// DestroyDriverInfoList method deletes a driver list. +func (deviceInfoSet DevInfo) DestroyDriverInfoList(deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT) error { + return SetupDiDestroyDriverInfoList(deviceInfoSet, deviceInfoData, driverType) +} + +//sys setupDiGetClassDevsEx(classGUID *windows.GUID, Enumerator *uint16, hwndParent uintptr, Flags DIGCF, deviceInfoSet DevInfo, machineName *uint16, reserved uintptr) (handle DevInfo, err error) [failretval==DevInfo(windows.InvalidHandle)] = setupapi.SetupDiGetClassDevsExW + +// SetupDiGetClassDevsEx function returns a handle to a device information set that contains requested device information elements for a local or a remote computer. +func SetupDiGetClassDevsEx(classGUID *windows.GUID, enumerator string, hwndParent uintptr, flags DIGCF, deviceInfoSet DevInfo, machineName string) (handle DevInfo, err error) { + var enumeratorUTF16 *uint16 + if enumerator != "" { + enumeratorUTF16, err = syscall.UTF16PtrFromString(enumerator) + if err != nil { + return + } + } + var machineNameUTF16 *uint16 + if machineName != "" { + machineNameUTF16, err = syscall.UTF16PtrFromString(machineName) + if err != nil { + return + } + } + return setupDiGetClassDevsEx(classGUID, enumeratorUTF16, hwndParent, flags, deviceInfoSet, machineNameUTF16, 0) +} + +// SetupDiCallClassInstaller function calls the appropriate class installer, and any registered co-installers, with the specified installation request (DIF code). +//sys SetupDiCallClassInstaller(installFunction DI_FUNCTION, deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA) (err error) = setupapi.SetupDiCallClassInstaller + +// CallClassInstaller member calls the appropriate class installer, and any registered co-installers, with the specified installation request (DIF code). +func (deviceInfoSet DevInfo) CallClassInstaller(installFunction DI_FUNCTION, deviceInfoData *SP_DEVINFO_DATA) error { + return SetupDiCallClassInstaller(installFunction, deviceInfoSet, deviceInfoData) +} + +//sys setupDiOpenDevRegKey(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, Scope DICS_FLAG, HwProfile uint32, KeyType DIREG, samDesired uint32) (key windows.Handle, err error) [failretval==windows.InvalidHandle] = setupapi.SetupDiOpenDevRegKey + +// SetupDiOpenDevRegKey function opens a registry key for device-specific configuration information. +func SetupDiOpenDevRegKey(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, scope DICS_FLAG, hwProfile uint32, keyType DIREG, samDesired uint32) (registry.Key, error) { + handle, err := setupDiOpenDevRegKey(deviceInfoSet, deviceInfoData, scope, hwProfile, keyType, samDesired) + return registry.Key(handle), err +} + +// OpenDevRegKey method opens a registry key for device-specific configuration information. +func (deviceInfoSet DevInfo) OpenDevRegKey(DeviceInfoData *SP_DEVINFO_DATA, Scope DICS_FLAG, HwProfile uint32, KeyType DIREG, samDesired uint32) (registry.Key, error) { + return SetupDiOpenDevRegKey(deviceInfoSet, DeviceInfoData, Scope, HwProfile, KeyType, samDesired) +} + +//sys setupDiGetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, property SPDRP, propertyRegDataType *uint32, propertyBuffer *byte, propertyBufferSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiGetDeviceRegistryPropertyW + +// SetupDiGetDeviceRegistryProperty function retrieves a specified Plug and Play device property. +func SetupDiGetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, property SPDRP) (value interface{}, err error) { + buf := make([]byte, 0x100) + var dataType, bufLen uint32 + err = setupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, &dataType, &buf[0], uint32(cap(buf)), &bufLen) + if err == nil { + // The buffer was sufficiently big. + return getRegistryValue(buf[:bufLen], dataType) + } + + if errWin, ok := err.(syscall.Errno); ok && errWin == windows.ERROR_INSUFFICIENT_BUFFER { + // The buffer was too small. Now that we got the required size, create another one big enough and retry. + buf = make([]byte, bufLen) + err = setupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, &dataType, &buf[0], uint32(cap(buf)), &bufLen) + if err == nil { + return getRegistryValue(buf[:bufLen], dataType) + } + } + + return +} + +func getRegistryValue(buf []byte, dataType uint32) (interface{}, error) { + switch dataType { + case windows.REG_SZ: + return windows.UTF16ToString(BufToUTF16(buf)), nil + case windows.REG_EXPAND_SZ: + return registry.ExpandString(windows.UTF16ToString(BufToUTF16(buf))) + case windows.REG_BINARY: + return buf, nil + case windows.REG_DWORD_LITTLE_ENDIAN: + return binary.LittleEndian.Uint32(buf), nil + case windows.REG_DWORD_BIG_ENDIAN: + return binary.BigEndian.Uint32(buf), nil + case windows.REG_MULTI_SZ: + bufW := BufToUTF16(buf) + a := []string{} + for i := 0; i < len(bufW); { + j := i + wcslen(bufW[i:]) + if i < j { + a = append(a, windows.UTF16ToString(bufW[i:j])) + } + i = j + 1 + } + return a, nil + case windows.REG_QWORD_LITTLE_ENDIAN: + return binary.LittleEndian.Uint64(buf), nil + default: + return nil, fmt.Errorf("Unsupported registry value type: %v", dataType) + } +} + +// BufToUTF16 function reinterprets []byte buffer as []uint16 +func BufToUTF16(buf []byte) []uint16 { + sl := struct { + addr *uint16 + len int + cap int + }{(*uint16)(unsafe.Pointer(&buf[0])), len(buf) / 2, cap(buf) / 2} + return *(*[]uint16)(unsafe.Pointer(&sl)) +} + +// UTF16ToBuf function reinterprets []uint16 as []byte +func UTF16ToBuf(buf []uint16) []byte { + sl := struct { + addr *byte + len int + cap int + }{(*byte)(unsafe.Pointer(&buf[0])), len(buf) * 2, cap(buf) * 2} + return *(*[]byte)(unsafe.Pointer(&sl)) +} + +func wcslen(str []uint16) int { + for i := 0; i < len(str); i++ { + if str[i] == 0 { + return i + } + } + return len(str) +} + +// GetDeviceRegistryProperty method retrieves a specified Plug and Play device property. +func (deviceInfoSet DevInfo) GetDeviceRegistryProperty(deviceInfoData *SP_DEVINFO_DATA, property SPDRP) (interface{}, error) { + return SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property) +} + +//sys setupDiSetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, property SPDRP, propertyBuffer *byte, propertyBufferSize uint32) (err error) = setupapi.SetupDiSetDeviceRegistryPropertyW + +// SetupDiSetDeviceRegistryProperty function sets a Plug and Play device property for a device. +func SetupDiSetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, property SPDRP, propertyBuffers []byte) error { + return setupDiSetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, &propertyBuffers[0], uint32(len(propertyBuffers))) +} + +// SetDeviceRegistryProperty function sets a Plug and Play device property for a device. +func (deviceInfoSet DevInfo) SetDeviceRegistryProperty(deviceInfoData *SP_DEVINFO_DATA, property SPDRP, propertyBuffers []byte) error { + return SetupDiSetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, propertyBuffers) +} + +//sys setupDiGetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, deviceInstallParams *_SP_DEVINSTALL_PARAMS) (err error) = setupapi.SetupDiGetDeviceInstallParamsW + +// SetupDiGetDeviceInstallParams function retrieves device installation parameters for a device information set or a particular device information element. +func SetupDiGetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA) (deviceInstallParams *DevInstallParams, err error) { + var _data _SP_DEVINSTALL_PARAMS + _data.Size = uint32(unsafe.Sizeof(_data)) + + err = setupDiGetDeviceInstallParams(deviceInfoSet, deviceInfoData, &_data) + if err != nil { + return + } + + return _data.toGo(), nil +} + +// GetDeviceInstallParams method retrieves device installation parameters for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) GetDeviceInstallParams(deviceInfoData *SP_DEVINFO_DATA) (*DevInstallParams, error) { + return SetupDiGetDeviceInstallParams(deviceInfoSet, deviceInfoData) +} + +// SetupDiGetClassInstallParams function retrieves class installation parameters for a device information set or a particular device information element. +//sys SetupDiGetClassInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, classInstallParams *SP_CLASSINSTALL_HEADER, classInstallParamsSize uint32, requiredSize *uint32) (err error) = setupapi.SetupDiGetClassInstallParamsW + +// GetClassInstallParams method retrieves class installation parameters for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) GetClassInstallParams(deviceInfoData *SP_DEVINFO_DATA, classInstallParams *SP_CLASSINSTALL_HEADER, classInstallParamsSize uint32, requiredSize *uint32) error { + return SetupDiGetClassInstallParams(deviceInfoSet, deviceInfoData, classInstallParams, classInstallParamsSize, requiredSize) +} + +//sys setupDiSetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, deviceInstallParams *_SP_DEVINSTALL_PARAMS) (err error) = setupapi.SetupDiSetDeviceInstallParamsW + +// SetupDiSetDeviceInstallParams function sets device installation parameters for a device information set or a particular device information element. +func SetupDiSetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, deviceInstallParams *DevInstallParams) (err error) { + _data, err := deviceInstallParams.toWindows() + if err != nil { + return + } + + return setupDiSetDeviceInstallParams(deviceInfoSet, deviceInfoData, _data) +} + +// SetDeviceInstallParams member sets device installation parameters for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) SetDeviceInstallParams(deviceInfoData *SP_DEVINFO_DATA, deviceInstallParams *DevInstallParams) error { + return SetupDiSetDeviceInstallParams(deviceInfoSet, deviceInfoData, deviceInstallParams) +} + +// SetupDiSetClassInstallParams function sets or clears class install parameters for a device information set or a particular device information element. +//sys SetupDiSetClassInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, classInstallParams *SP_CLASSINSTALL_HEADER, classInstallParamsSize uint32) (err error) = setupapi.SetupDiSetClassInstallParamsW + +// SetClassInstallParams method sets or clears class install parameters for a device information set or a particular device information element. +func (deviceInfoSet DevInfo) SetClassInstallParams(deviceInfoData *SP_DEVINFO_DATA, classInstallParams *SP_CLASSINSTALL_HEADER, classInstallParamsSize uint32) error { + return SetupDiSetClassInstallParams(deviceInfoSet, deviceInfoData, classInstallParams, classInstallParamsSize) +} + +//sys setupDiClassNameFromGuidEx(classGUID *windows.GUID, className *uint16, classNameSize uint32, requiredSize *uint32, machineName *uint16, reserved uintptr) (err error) = setupapi.SetupDiClassNameFromGuidExW + +// SetupDiClassNameFromGuidEx function retrieves the class name associated with a class GUID. The class can be installed on a local or remote computer. +func SetupDiClassNameFromGuidEx(classGUID *windows.GUID, machineName string) (className string, err error) { + var classNameUTF16 [MAX_CLASS_NAME_LEN]uint16 + + var machineNameUTF16 *uint16 + if machineName != "" { + machineNameUTF16, err = syscall.UTF16PtrFromString(machineName) + if err != nil { + return + } + } + + err = setupDiClassNameFromGuidEx(classGUID, &classNameUTF16[0], MAX_CLASS_NAME_LEN, nil, machineNameUTF16, 0) + if err != nil { + return + } + + className = windows.UTF16ToString(classNameUTF16[:]) + return +} + +//sys setupDiClassGuidsFromNameEx(className *uint16, classGuidList *windows.GUID, classGuidListSize uint32, requiredSize *uint32, machineName *uint16, reserved uintptr) (err error) = setupapi.SetupDiClassGuidsFromNameExW + +// SetupDiClassGuidsFromNameEx function retrieves the GUIDs associated with the specified class name. This resulting list contains the classes currently installed on a local or remote computer. +func SetupDiClassGuidsFromNameEx(className string, machineName string) (classGuidLists []windows.GUID, err error) { + classNameUTF16, err := syscall.UTF16PtrFromString(className) + if err != nil { + return + } + + const bufCapacity = 4 + var buf [bufCapacity]windows.GUID + var bufLen uint32 + + var machineNameUTF16 *uint16 + if machineName != "" { + machineNameUTF16, err = syscall.UTF16PtrFromString(machineName) + if err != nil { + return + } + } + + err = setupDiClassGuidsFromNameEx(classNameUTF16, &buf[0], bufCapacity, &bufLen, machineNameUTF16, 0) + if err == nil { + // The GUID array was sufficiently big. Return its slice. + return buf[:bufLen], nil + } + + if errWin, ok := err.(syscall.Errno); ok && errWin == windows.ERROR_INSUFFICIENT_BUFFER { + // The GUID array was too small. Now that we got the required size, create another one big enough and retry. + buf := make([]windows.GUID, bufLen) + err = setupDiClassGuidsFromNameEx(classNameUTF16, &buf[0], bufLen, &bufLen, machineNameUTF16, 0) + if err == nil { + return buf[:bufLen], nil + } + } + + return +} + +//sys setupDiGetSelectedDevice(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA) (err error) = setupapi.SetupDiGetSelectedDevice + +// SetupDiGetSelectedDevice function retrieves the selected device information element in a device information set. +func SetupDiGetSelectedDevice(deviceInfoSet DevInfo) (*SP_DEVINFO_DATA, error) { + data := SP_DEVINFO_DATA{} + data.Size = uint32(unsafe.Sizeof(data)) + + return &data, setupDiGetSelectedDevice(deviceInfoSet, &data) +} + +// GetSelectedDevice method retrieves the selected device information element in a device information set. +func (deviceInfoSet DevInfo) GetSelectedDevice() (*SP_DEVINFO_DATA, error) { + return SetupDiGetSelectedDevice(deviceInfoSet) +} + +// SetupDiSetSelectedDevice function sets a device information element as the selected member of a device information set. This function is typically used by an installation wizard. +//sys SetupDiSetSelectedDevice(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA) (err error) = setupapi.SetupDiSetSelectedDevice + +// SetSelectedDevice method sets a device information element as the selected member of a device information set. This function is typically used by an installation wizard. +func (deviceInfoSet DevInfo) SetSelectedDevice(deviceInfoData *SP_DEVINFO_DATA) error { + return SetupDiSetSelectedDevice(deviceInfoSet, deviceInfoData) +} diff --git a/tun/wintun/setupapi/setupapi_windows_test.go b/tun/wintun/setupapi/setupapi_windows_test.go new file mode 100644 index 0000000..e6b00c9 --- /dev/null +++ b/tun/wintun/setupapi/setupapi_windows_test.go @@ -0,0 +1,452 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package setupapi + +import ( + "strings" + "syscall" + "testing" + + "golang.org/x/sys/windows" +) + +var deviceClassNetGUID = windows.GUID{0x4d36e972, 0xe325, 0x11ce, [8]byte{0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18}} +var computerName string + +func init() { + computerName, _ = windows.ComputerName() +} + +func TestSetupDiCreateDeviceInfoListEx(t *testing.T) { + devInfoList, err := SetupDiCreateDeviceInfoListEx(&deviceClassNetGUID, 0, "") + if err == nil { + devInfoList.Close() + } else { + t.Errorf("Error calling SetupDiCreateDeviceInfoListEx: %s", err.Error()) + } + + devInfoList, err = SetupDiCreateDeviceInfoListEx(&deviceClassNetGUID, 0, computerName) + if err == nil { + devInfoList.Close() + } else { + t.Errorf("Error calling SetupDiCreateDeviceInfoListEx: %s", err.Error()) + } + + devInfoList, err = SetupDiCreateDeviceInfoListEx(nil, 0, "") + if err == nil { + devInfoList.Close() + } else { + t.Errorf("Error calling SetupDiCreateDeviceInfoListEx(nil): %s", err.Error()) + } +} + +func TestSetupDiGetDeviceInfoListDetail(t *testing.T) { + devInfoList, err := SetupDiGetClassDevsEx(&deviceClassNetGUID, "", 0, DIGCF_PRESENT, DevInfo(0), "") + if err != nil { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + defer devInfoList.Close() + + data, err := devInfoList.GetDeviceInfoListDetail() + if err != nil { + t.Errorf("Error calling SetupDiGetDeviceInfoListDetail: %s", err.Error()) + } else { + if data.ClassGUID != deviceClassNetGUID { + t.Error("SetupDiGetDeviceInfoListDetail returned different class GUID") + } + + if data.RemoteMachineHandle != windows.Handle(0) { + t.Error("SetupDiGetDeviceInfoListDetail returned non-NULL remote machine handle") + } + + if data.RemoteMachineName != "" { + t.Error("SetupDiGetDeviceInfoListDetail returned non-NULL remote machine name") + } + } + + devInfoList, err = SetupDiGetClassDevsEx(&deviceClassNetGUID, "", 0, DIGCF_PRESENT, DevInfo(0), computerName) + if err != nil { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + defer devInfoList.Close() + + data, err = devInfoList.GetDeviceInfoListDetail() + if err != nil { + t.Errorf("Error calling SetupDiGetDeviceInfoListDetail: %s", err.Error()) + } else { + if data.ClassGUID != deviceClassNetGUID { + t.Error("SetupDiGetDeviceInfoListDetail returned different class GUID") + } + + if data.RemoteMachineHandle == windows.Handle(0) { + t.Error("SetupDiGetDeviceInfoListDetail returned NULL remote machine handle") + } + + if data.RemoteMachineName != computerName { + t.Error("SetupDiGetDeviceInfoListDetail returned different remote machine name") + } + } +} + +func TestSetupDiCreateDeviceInfo(t *testing.T) { + devInfoList, err := SetupDiCreateDeviceInfoListEx(&deviceClassNetGUID, 0, computerName) + if err != nil { + t.Errorf("Error calling SetupDiCreateDeviceInfoListEx: %s", err.Error()) + } + defer devInfoList.Close() + + deviceClassNetName, err := SetupDiClassNameFromGuidEx(&deviceClassNetGUID, computerName) + if err != nil { + t.Errorf("Error calling SetupDiClassNameFromGuidEx: %s", err.Error()) + } + + devInfoData, err := devInfoList.CreateDeviceInfo(deviceClassNetName, &deviceClassNetGUID, "This is a test device", 0, DICD_GENERATE_ID) + if err != nil { + // Access denied is expected, as the SetupDiCreateDeviceInfo() require elevation to succeed. + if errWin, ok := err.(syscall.Errno); !ok || errWin != windows.ERROR_ACCESS_DENIED { + t.Errorf("Error calling SetupDiCreateDeviceInfo: %s", err.Error()) + } + } else if devInfoData.ClassGUID != deviceClassNetGUID { + t.Error("SetupDiCreateDeviceInfo returned different class GUID") + } +} + +func TestSetupDiEnumDeviceInfo(t *testing.T) { + devInfoList, err := SetupDiGetClassDevsEx(&deviceClassNetGUID, "", 0, DIGCF_PRESENT, DevInfo(0), "") + if err != nil { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + defer devInfoList.Close() + + for i := 0; true; i++ { + data, err := devInfoList.EnumDeviceInfo(i) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + continue + } + + if data.ClassGUID != deviceClassNetGUID { + t.Error("SetupDiEnumDeviceInfo returned different class GUID") + } + } +} + +func TestDevInfo_BuildDriverInfoList(t *testing.T) { + devInfoList, err := SetupDiGetClassDevsEx(&deviceClassNetGUID, "", 0, DIGCF_PRESENT, DevInfo(0), "") + if err != nil { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + defer devInfoList.Close() + + for i := 0; true; i++ { + deviceData, err := devInfoList.EnumDeviceInfo(i) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + continue + } + + const driverType SPDIT = SPDIT_COMPATDRIVER + err = devInfoList.BuildDriverInfoList(deviceData, driverType) + if err != nil { + t.Errorf("Error calling SetupDiBuildDriverInfoList: %s", err.Error()) + } + defer devInfoList.DestroyDriverInfoList(deviceData, driverType) + + var selectedDriverData *SP_DRVINFO_DATA + for j := 0; true; j++ { + driverData, err := devInfoList.EnumDriverInfo(deviceData, driverType, j) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + continue + } + + if driverData2, err2 := driverData.toGo().toWindows(); err2 != nil || *driverData2 != *driverData { + t.Error("Error converting between SP_DRVINFO_DATA and DrvInfoData") + } + + if driverData.DriverType == 0 { + continue + } + + if !driverData.IsNewer(windows.Filetime{}, 0) { + t.Error("Driver should have non-zero date and version") + } + if !driverData.IsNewer(windows.Filetime{HighDateTime: driverData.DriverDate.HighDateTime}, 0) { + t.Error("Driver should have non-zero date and version") + } + if driverData.IsNewer(windows.Filetime{HighDateTime: driverData.DriverDate.HighDateTime + 1}, 0) { + t.Error("Driver should report newer version on high-date-time") + } + if !driverData.IsNewer(windows.Filetime{HighDateTime: driverData.DriverDate.HighDateTime, LowDateTime: driverData.DriverDate.LowDateTime}, 0) { + t.Error("Driver should have non-zero version") + } + if driverData.IsNewer(windows.Filetime{HighDateTime: driverData.DriverDate.HighDateTime, LowDateTime: driverData.DriverDate.LowDateTime + 1}, 0) { + t.Error("Driver should report newer version on low-date-time") + } + if driverData.IsNewer(windows.Filetime{HighDateTime: driverData.DriverDate.HighDateTime, LowDateTime: driverData.DriverDate.LowDateTime}, driverData.DriverVersion) { + t.Error("Driver should not be newer than itself") + } + if driverData.IsNewer(windows.Filetime{HighDateTime: driverData.DriverDate.HighDateTime, LowDateTime: driverData.DriverDate.LowDateTime}, driverData.DriverVersion+1) { + t.Error("Driver should report newer version on version") + } + + err = devInfoList.SetSelectedDriver(deviceData, driverData) + if err != nil { + t.Errorf("Error calling SetupDiSetSelectedDriver: %s", err.Error()) + } else { + selectedDriverData = driverData + } + + driverDetailData, err := devInfoList.GetDriverInfoDetail(deviceData, driverData) + if err != nil { + t.Errorf("Error calling SetupDiGetDriverInfoDetail: %s", err.Error()) + } + + if driverDetailData.IsCompatible("foobar-aab6e3a4-144e-4786-88d3-6cec361e1edd") { + t.Error("Invalid HWID compatibitlity reported") + } + if !driverDetailData.IsCompatible(strings.ToUpper(driverDetailData.HardwareID)) { + t.Error("HWID compatibitlity missed") + } + for k := range driverDetailData.CompatIDs { + if !driverDetailData.IsCompatible(strings.ToUpper(driverDetailData.CompatIDs[k])) { + t.Error("HWID compatibitlity missed") + } + } + } + + selectedDriverData2, err := devInfoList.GetSelectedDriver(deviceData) + if err != nil { + t.Errorf("Error calling SetupDiGetSelectedDriver: %s", err.Error()) + } else if *selectedDriverData != *selectedDriverData2 { + t.Error("SetupDiGetSelectedDriver should return driver selected with SetupDiSetSelectedDriver") + } + } +} + +func TestSetupDiGetClassDevsEx(t *testing.T) { + devInfoList, err := SetupDiGetClassDevsEx(&deviceClassNetGUID, "PCI", 0, DIGCF_PRESENT, DevInfo(0), computerName) + if err == nil { + devInfoList.Close() + } else { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + + devInfoList, err = SetupDiGetClassDevsEx(nil, "", 0, DIGCF_PRESENT, DevInfo(0), "") + if err == nil { + devInfoList.Close() + t.Errorf("SetupDiGetClassDevsEx(nil, ...) should fail") + } else { + if errWin, ok := err.(syscall.Errno); !ok || errWin != 87 /*ERROR_INVALID_PARAMETER*/ { + t.Errorf("SetupDiGetClassDevsEx(nil, ...) should fail with ERROR_INVALID_PARAMETER") + } + } +} + +func TestSetupDiOpenDevRegKey(t *testing.T) { + devInfoList, err := SetupDiGetClassDevsEx(&deviceClassNetGUID, "", 0, DIGCF_PRESENT, DevInfo(0), "") + if err != nil { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + defer devInfoList.Close() + + for i := 0; true; i++ { + data, err := devInfoList.EnumDeviceInfo(i) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + continue + } + + key, err := devInfoList.OpenDevRegKey(data, DICS_FLAG_GLOBAL, 0, DIREG_DRV, windows.KEY_READ) + if err != nil { + t.Errorf("Error calling SetupDiOpenDevRegKey: %s", err.Error()) + } + defer key.Close() + } +} + +func TestSetupDiGetDeviceRegistryProperty(t *testing.T) { + devInfoList, err := SetupDiGetClassDevsEx(&deviceClassNetGUID, "", 0, DIGCF_PRESENT, DevInfo(0), "") + if err != nil { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + defer devInfoList.Close() + + for i := 0; true; i++ { + data, err := devInfoList.EnumDeviceInfo(i) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + continue + } + + val, err := devInfoList.GetDeviceRegistryProperty(data, SPDRP_CLASS) + if err != nil { + t.Errorf("Error calling SetupDiGetDeviceRegistryProperty(SPDRP_CLASS): %s", err.Error()) + } else if class, ok := val.(string); !ok || strings.ToLower(class) != "net" { + t.Errorf("SetupDiGetDeviceRegistryProperty(SPDRP_CLASS) should return \"Net\"") + } + + val, err = devInfoList.GetDeviceRegistryProperty(data, SPDRP_CLASSGUID) + if err != nil { + t.Errorf("Error calling SetupDiGetDeviceRegistryProperty(SPDRP_CLASSGUID): %s", err.Error()) + } /* TODO: Parse GUID string: else if classGUID, ok := val.(string); !ok || parseGUID(classGUID) != deviceClassNetGUID { + t.Errorf("SetupDiGetDeviceRegistryProperty(SPDRP_CLASSGUID) should return %x", deviceClassNetGUID) + }*/ + + val, err = devInfoList.GetDeviceRegistryProperty(data, SPDRP_COMPATIBLEIDS) + if err != nil { + // Some devices have no SPDRP_COMPATIBLEIDS. + if errWin, ok := err.(syscall.Errno); !ok || errWin != 13 /*windows.ERROR_INVALID_DATA*/ { + t.Errorf("Error calling SetupDiGetDeviceRegistryProperty(SPDRP_COMPATIBLEIDS): %s", err.Error()) + } + } + + val, err = devInfoList.GetDeviceRegistryProperty(data, SPDRP_CONFIGFLAGS) + if err != nil { + t.Errorf("Error calling SetupDiGetDeviceRegistryProperty(SPDRP_CONFIGFLAGS): %s", err.Error()) + } + + val, err = devInfoList.GetDeviceRegistryProperty(data, SPDRP_DEVICE_POWER_DATA) + if err != nil { + t.Errorf("Error calling SetupDiGetDeviceRegistryProperty(SPDRP_DEVICE_POWER_DATA): %s", err.Error()) + } + } +} + +func TestSetupDiGetDeviceInstallParams(t *testing.T) { + devInfoList, err := SetupDiGetClassDevsEx(&deviceClassNetGUID, "", 0, DIGCF_PRESENT, DevInfo(0), "") + if err != nil { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + defer devInfoList.Close() + + for i := 0; true; i++ { + data, err := devInfoList.EnumDeviceInfo(i) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + continue + } + + _, err = devInfoList.GetDeviceInstallParams(data) + if err != nil { + t.Errorf("Error calling SetupDiGetDeviceInstallParams: %s", err.Error()) + } + } +} + +func TestSetupDiClassNameFromGuidEx(t *testing.T) { + deviceClassNetName, err := SetupDiClassNameFromGuidEx(&deviceClassNetGUID, "") + if err != nil { + t.Errorf("Error calling SetupDiClassNameFromGuidEx: %s", err.Error()) + } else if strings.ToLower(deviceClassNetName) != "net" { + t.Errorf("SetupDiClassNameFromGuidEx(%x) should return \"Net\"", deviceClassNetGUID) + } + + deviceClassNetName, err = SetupDiClassNameFromGuidEx(&deviceClassNetGUID, computerName) + if err != nil { + t.Errorf("Error calling SetupDiClassNameFromGuidEx: %s", err.Error()) + } else if strings.ToLower(deviceClassNetName) != "net" { + t.Errorf("SetupDiClassNameFromGuidEx(%x) should return \"Net\"", deviceClassNetGUID) + } + + _, err = SetupDiClassNameFromGuidEx(nil, "") + if err == nil { + t.Errorf("SetupDiClassNameFromGuidEx(nil) should fail") + } else { + if errWin, ok := err.(syscall.Errno); !ok || errWin != 1784 /*ERROR_INVALID_USER_BUFFER*/ { + t.Errorf("SetupDiClassNameFromGuidEx(nil) should fail with ERROR_INVALID_USER_BUFFER") + } + } +} + +func TestSetupDiClassGuidsFromNameEx(t *testing.T) { + ClassGUIDs, err := SetupDiClassGuidsFromNameEx("Net", "") + if err != nil { + t.Errorf("Error calling SetupDiClassGuidsFromNameEx: %s", err.Error()) + } else { + found := false + for i := range ClassGUIDs { + if ClassGUIDs[i] == deviceClassNetGUID { + found = true + break + } + } + if !found { + t.Errorf("SetupDiClassGuidsFromNameEx(\"Net\") should return %x", deviceClassNetGUID) + } + } + + ClassGUIDs, err = SetupDiClassGuidsFromNameEx("foobar-34274a51-a6e6-45f0-80d6-c62be96dd5fe", computerName) + if err != nil { + t.Errorf("Error calling SetupDiClassGuidsFromNameEx: %s", err.Error()) + } else if len(ClassGUIDs) != 0 { + t.Errorf("SetupDiClassGuidsFromNameEx(\"foobar-34274a51-a6e6-45f0-80d6-c62be96dd5fe\") should return an empty GUID set") + } +} + +func TestSetupDiGetSelectedDevice(t *testing.T) { + devInfoList, err := SetupDiGetClassDevsEx(&deviceClassNetGUID, "", 0, DIGCF_PRESENT, DevInfo(0), "") + if err != nil { + t.Errorf("Error calling SetupDiGetClassDevsEx: %s", err.Error()) + } + defer devInfoList.Close() + + for i := 0; true; i++ { + data, err := devInfoList.EnumDeviceInfo(i) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + continue + } + + err = devInfoList.SetSelectedDevice(data) + if err != nil { + t.Errorf("Error calling SetupDiSetSelectedDevice: %s", err.Error()) + } + + data2, err := devInfoList.GetSelectedDevice() + if err != nil { + t.Errorf("Error calling SetupDiGetSelectedDevice: %s", err.Error()) + } else if *data != *data2 { + t.Error("SetupDiGetSelectedDevice returned different data than was set by SetupDiSetSelectedDevice") + } + } + + err = devInfoList.SetSelectedDevice(nil) + if err == nil { + t.Errorf("SetupDiSetSelectedDevice(nil) should fail") + } else { + if errWin, ok := err.(syscall.Errno); !ok || errWin != 87 /*ERROR_INVALID_PARAMETER*/ { + t.Errorf("SetupDiSetSelectedDevice(nil) should fail with ERROR_INVALID_USER_BUFFER") + } + } +} + +func TestUTF16ToBuf(t *testing.T) { + buf := []uint16{0x0123, 0x4567, 0x89ab, 0xcdef} + buf2 := UTF16ToBuf(buf) + if len(buf)*2 != len(buf2) || + cap(buf)*2 != cap(buf2) || + buf2[0] != 0x23 || buf2[1] != 0x01 || + buf2[2] != 0x67 || buf2[3] != 0x45 || + buf2[4] != 0xab || buf2[5] != 0x89 || + buf2[6] != 0xef || buf2[7] != 0xcd { + t.Errorf("SetupDiSetSelectedDevice(nil) should fail with ERROR_INVALID_USER_BUFFER") + } +} diff --git a/tun/wintun/setupapi/types_windows.go b/tun/wintun/setupapi/types_windows.go new file mode 100644 index 0000000..c4aeff9 --- /dev/null +++ b/tun/wintun/setupapi/types_windows.go @@ -0,0 +1,571 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package setupapi + +import ( + "strings" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + MAX_DEVICE_ID_LEN = 200 + MAX_DEVNODE_ID_LEN = MAX_DEVICE_ID_LEN + MAX_GUID_STRING_LEN = 39 // 38 chars + terminator null + MAX_CLASS_NAME_LEN = 32 + MAX_PROFILE_LEN = 80 + MAX_CONFIG_VALUE = 9999 + MAX_INSTANCE_VALUE = 9999 + CONFIGMG_VERSION = 0x0400 +) + +// +// Define maximum string length constants +// +const ( + LINE_LEN = 256 // Windows 9x-compatible maximum for displayable strings coming from a device INF. + MAX_INF_STRING_LENGTH = 4096 // Actual maximum size of an INF string (including string substitutions). + MAX_INF_SECTION_NAME_LENGTH = 255 // For Windows 9x compatibility, INF section names should be constrained to 32 characters. + MAX_TITLE_LEN = 60 + MAX_INSTRUCTION_LEN = 256 + MAX_LABEL_LEN = 30 + MAX_SERVICE_NAME_LEN = 256 + MAX_SUBTITLE_LEN = 256 +) + +const ( + // SP_MAX_MACHINENAME_LENGTH defines maximum length of a machine name in the format expected by ConfigMgr32 CM_Connect_Machine (i.e., "\\\\MachineName\0"). + SP_MAX_MACHINENAME_LENGTH = windows.MAX_PATH + 3 +) + +// HSPFILEQ is type for setup file queue +type HSPFILEQ uintptr + +// DevInfo holds reference to device information set +type DevInfo windows.Handle + +// SP_DEVINFO_DATA is a device information structure (references a device instance that is a member of a device information set) +type SP_DEVINFO_DATA struct { + Size uint32 + ClassGUID windows.GUID + DevInst uint32 // DEVINST handle + _ uintptr +} + +type _SP_DEVINFO_LIST_DETAIL_DATA struct { + Size uint32 + ClassGUID windows.GUID + RemoteMachineHandle windows.Handle + RemoteMachineName [SP_MAX_MACHINENAME_LENGTH]uint16 +} + +func (_data *_SP_DEVINFO_LIST_DETAIL_DATA) toGo() *DevInfoListDetailData { + return &DevInfoListDetailData{ + ClassGUID: _data.ClassGUID, + RemoteMachineHandle: _data.RemoteMachineHandle, + RemoteMachineName: windows.UTF16ToString(_data.RemoteMachineName[:]), + } +} + +// DevInfoListDetailData is a structure for detailed information on a device information set (used for SetupDiGetDeviceInfoListDetail which supercedes the functionality of SetupDiGetDeviceInfoListClass). +type DevInfoListDetailData struct { + ClassGUID windows.GUID + RemoteMachineHandle windows.Handle + RemoteMachineName string +} + +// DI_FUNCTION is function type for device installer +type DI_FUNCTION uint32 + +const ( + DIF_SELECTDEVICE DI_FUNCTION = 0x00000001 + DIF_INSTALLDEVICE DI_FUNCTION = 0x00000002 + DIF_ASSIGNRESOURCES DI_FUNCTION = 0x00000003 + DIF_PROPERTIES DI_FUNCTION = 0x00000004 + DIF_REMOVE DI_FUNCTION = 0x00000005 + DIF_FIRSTTIMESETUP DI_FUNCTION = 0x00000006 + DIF_FOUNDDEVICE DI_FUNCTION = 0x00000007 + DIF_SELECTCLASSDRIVERS DI_FUNCTION = 0x00000008 + DIF_VALIDATECLASSDRIVERS DI_FUNCTION = 0x00000009 + DIF_INSTALLCLASSDRIVERS DI_FUNCTION = 0x0000000A + DIF_CALCDISKSPACE DI_FUNCTION = 0x0000000B + DIF_DESTROYPRIVATEDATA DI_FUNCTION = 0x0000000C + DIF_VALIDATEDRIVER DI_FUNCTION = 0x0000000D + DIF_DETECT DI_FUNCTION = 0x0000000F + DIF_INSTALLWIZARD DI_FUNCTION = 0x00000010 + DIF_DESTROYWIZARDDATA DI_FUNCTION = 0x00000011 + DIF_PROPERTYCHANGE DI_FUNCTION = 0x00000012 + DIF_ENABLECLASS DI_FUNCTION = 0x00000013 + DIF_DETECTVERIFY DI_FUNCTION = 0x00000014 + DIF_INSTALLDEVICEFILES DI_FUNCTION = 0x00000015 + DIF_UNREMOVE DI_FUNCTION = 0x00000016 + DIF_SELECTBESTCOMPATDRV DI_FUNCTION = 0x00000017 + DIF_ALLOW_INSTALL DI_FUNCTION = 0x00000018 + DIF_REGISTERDEVICE DI_FUNCTION = 0x00000019 + DIF_NEWDEVICEWIZARD_PRESELECT DI_FUNCTION = 0x0000001A + DIF_NEWDEVICEWIZARD_SELECT DI_FUNCTION = 0x0000001B + DIF_NEWDEVICEWIZARD_PREANALYZE DI_FUNCTION = 0x0000001C + DIF_NEWDEVICEWIZARD_POSTANALYZE DI_FUNCTION = 0x0000001D + DIF_NEWDEVICEWIZARD_FINISHINSTALL DI_FUNCTION = 0x0000001E + DIF_INSTALLINTERFACES DI_FUNCTION = 0x00000020 + DIF_DETECTCANCEL DI_FUNCTION = 0x00000021 + DIF_REGISTER_COINSTALLERS DI_FUNCTION = 0x00000022 + DIF_ADDPROPERTYPAGE_ADVANCED DI_FUNCTION = 0x00000023 + DIF_ADDPROPERTYPAGE_BASIC DI_FUNCTION = 0x00000024 + DIF_TROUBLESHOOTER DI_FUNCTION = 0x00000026 + DIF_POWERMESSAGEWAKE DI_FUNCTION = 0x00000027 + DIF_ADDREMOTEPROPERTYPAGE_ADVANCED DI_FUNCTION = 0x00000028 + DIF_UPDATEDRIVER_UI DI_FUNCTION = 0x00000029 + DIF_FINISHINSTALL_ACTION DI_FUNCTION = 0x0000002A +) + +type _SP_DEVINSTALL_PARAMS struct { + Size uint32 + Flags DI_FLAGS + FlagsEx DI_FLAGSEX + hwndParent uintptr + InstallMsgHandler uintptr + InstallMsgHandlerContext uintptr + FileQueue HSPFILEQ + _ uintptr + _ uint32 + DriverPath [windows.MAX_PATH]uint16 +} + +func (_data *_SP_DEVINSTALL_PARAMS) toGo() *DevInstallParams { + return &DevInstallParams{ + Flags: _data.Flags, + FlagsEx: _data.FlagsEx, + hwndParent: _data.hwndParent, + InstallMsgHandler: _data.InstallMsgHandler, + InstallMsgHandlerContext: _data.InstallMsgHandlerContext, + FileQueue: _data.FileQueue, + DriverPath: windows.UTF16ToString(_data.DriverPath[:]), + } +} + +// DevInstallParams is device installation parameters structure (associated with a particular device information element, or globally with a device information set) +type DevInstallParams struct { + Flags DI_FLAGS + FlagsEx DI_FLAGSEX + hwndParent uintptr + InstallMsgHandler uintptr + InstallMsgHandlerContext uintptr + FileQueue HSPFILEQ + DriverPath string +} + +func (DeviceInstallParams *DevInstallParams) toWindows() (_data *_SP_DEVINSTALL_PARAMS, err error) { + _data = &_SP_DEVINSTALL_PARAMS{ + Flags: DeviceInstallParams.Flags, + FlagsEx: DeviceInstallParams.FlagsEx, + hwndParent: DeviceInstallParams.hwndParent, + InstallMsgHandler: DeviceInstallParams.InstallMsgHandler, + InstallMsgHandlerContext: DeviceInstallParams.InstallMsgHandlerContext, + FileQueue: DeviceInstallParams.FileQueue, + } + _data.Size = uint32(unsafe.Sizeof(*_data)) + + driverPathUTF16, err := syscall.UTF16FromString(DeviceInstallParams.DriverPath) + if err != nil { + return + } + copy(_data.DriverPath[:], driverPathUTF16) + + return +} + +// DI_FLAGS is SP_DEVINSTALL_PARAMS.Flags values +type DI_FLAGS uint32 + +const ( + // Flags for choosing a device + DI_SHOWOEM DI_FLAGS = 0x00000001 // support Other... button + DI_SHOWCOMPAT DI_FLAGS = 0x00000002 // show compatibility list + DI_SHOWCLASS DI_FLAGS = 0x00000004 // show class list + DI_SHOWALL DI_FLAGS = 0x00000007 // both class & compat list shown + DI_NOVCP DI_FLAGS = 0x00000008 // don't create a new copy queue--use caller-supplied FileQueue + DI_DIDCOMPAT DI_FLAGS = 0x00000010 // Searched for compatible devices + DI_DIDCLASS DI_FLAGS = 0x00000020 // Searched for class devices + DI_AUTOASSIGNRES DI_FLAGS = 0x00000040 // No UI for resources if possible + + // Flags returned by DiInstallDevice to indicate need to reboot/restart + DI_NEEDRESTART DI_FLAGS = 0x00000080 // Reboot required to take effect + DI_NEEDREBOOT DI_FLAGS = 0x00000100 // "" + + // Flags for device installation + DI_NOBROWSE DI_FLAGS = 0x00000200 // no Browse... in InsertDisk + + // Flags set by DiBuildDriverInfoList + DI_MULTMFGS DI_FLAGS = 0x00000400 // Set if multiple manufacturers in class driver list + + // Flag indicates that device is disabled + DI_DISABLED DI_FLAGS = 0x00000800 // Set if device disabled + + // Flags for Device/Class Properties + DI_GENERALPAGE_ADDED DI_FLAGS = 0x00001000 + DI_RESOURCEPAGE_ADDED DI_FLAGS = 0x00002000 + + // Flag to indicate the setting properties for this Device (or class) caused a change so the Dev Mgr UI probably needs to be updated. + DI_PROPERTIES_CHANGE DI_FLAGS = 0x00004000 + + // Flag to indicate that the sorting from the INF file should be used. + DI_INF_IS_SORTED DI_FLAGS = 0x00008000 + + // Flag to indicate that only the the INF specified by SP_DEVINSTALL_PARAMS.DriverPath should be searched. + DI_ENUMSINGLEINF DI_FLAGS = 0x00010000 + + // Flag that prevents ConfigMgr from removing/re-enumerating devices during device + // registration, installation, and deletion. + DI_DONOTCALLCONFIGMG DI_FLAGS = 0x00020000 + + // The following flag can be used to install a device disabled + DI_INSTALLDISABLED DI_FLAGS = 0x00040000 + + // Flag that causes SetupDiBuildDriverInfoList to build a device's compatible driver + // list from its existing class driver list, instead of the normal INF search. + DI_COMPAT_FROM_CLASS DI_FLAGS = 0x00080000 + + // This flag is set if the Class Install params should be used. + DI_CLASSINSTALLPARAMS DI_FLAGS = 0x00100000 + + // This flag is set if the caller of DiCallClassInstaller does NOT want the internal default action performed if the Class installer returns ERROR_DI_DO_DEFAULT. + DI_NODI_DEFAULTACTION DI_FLAGS = 0x00200000 + + // Flags for device installation + DI_QUIETINSTALL DI_FLAGS = 0x00800000 // don't confuse the user with questions or excess info + DI_NOFILECOPY DI_FLAGS = 0x01000000 // No file Copy necessary + DI_FORCECOPY DI_FLAGS = 0x02000000 // Force files to be copied from install path + DI_DRIVERPAGE_ADDED DI_FLAGS = 0x04000000 // Prop provider added Driver page. + DI_USECI_SELECTSTRINGS DI_FLAGS = 0x08000000 // Use Class Installer Provided strings in the Select Device Dlg + DI_OVERRIDE_INFFLAGS DI_FLAGS = 0x10000000 // Override INF flags + DI_PROPS_NOCHANGEUSAGE DI_FLAGS = 0x20000000 // No Enable/Disable in General Props + + DI_NOSELECTICONS DI_FLAGS = 0x40000000 // No small icons in select device dialogs + + DI_NOWRITE_IDS DI_FLAGS = 0x80000000 // Don't write HW & Compat IDs on install +) + +// DI_FLAGSEX is SP_DEVINSTALL_PARAMS.FlagsEx values +type DI_FLAGSEX uint32 + +const ( + DI_FLAGSEX_CI_FAILED DI_FLAGSEX = 0x00000004 // Failed to Load/Call class installer + DI_FLAGSEX_FINISHINSTALL_ACTION DI_FLAGSEX = 0x00000008 // Class/co-installer wants to get a DIF_FINISH_INSTALL action in client context. + DI_FLAGSEX_DIDINFOLIST DI_FLAGSEX = 0x00000010 // Did the Class Info List + DI_FLAGSEX_DIDCOMPATINFO DI_FLAGSEX = 0x00000020 // Did the Compat Info List + DI_FLAGSEX_FILTERCLASSES DI_FLAGSEX = 0x00000040 + DI_FLAGSEX_SETFAILEDINSTALL DI_FLAGSEX = 0x00000080 + DI_FLAGSEX_DEVICECHANGE DI_FLAGSEX = 0x00000100 + DI_FLAGSEX_ALWAYSWRITEIDS DI_FLAGSEX = 0x00000200 + DI_FLAGSEX_PROPCHANGE_PENDING DI_FLAGSEX = 0x00000400 // One or more device property sheets have had changes made to them, and need to have a DIF_PROPERTYCHANGE occur. + DI_FLAGSEX_ALLOWEXCLUDEDDRVS DI_FLAGSEX = 0x00000800 + DI_FLAGSEX_NOUIONQUERYREMOVE DI_FLAGSEX = 0x00001000 + DI_FLAGSEX_USECLASSFORCOMPAT DI_FLAGSEX = 0x00002000 // Use the device's class when building compat drv list. (Ignored if DI_COMPAT_FROM_CLASS flag is specified.) + DI_FLAGSEX_NO_DRVREG_MODIFY DI_FLAGSEX = 0x00008000 // Don't run AddReg and DelReg for device's software (driver) key. + DI_FLAGSEX_IN_SYSTEM_SETUP DI_FLAGSEX = 0x00010000 // Installation is occurring during initial system setup. + DI_FLAGSEX_INET_DRIVER DI_FLAGSEX = 0x00020000 // Driver came from Windows Update + DI_FLAGSEX_APPENDDRIVERLIST DI_FLAGSEX = 0x00040000 // Cause SetupDiBuildDriverInfoList to append a new driver list to an existing list. + DI_FLAGSEX_PREINSTALLBACKUP DI_FLAGSEX = 0x00080000 // not used + DI_FLAGSEX_BACKUPONREPLACE DI_FLAGSEX = 0x00100000 // not used + DI_FLAGSEX_DRIVERLIST_FROM_URL DI_FLAGSEX = 0x00200000 // build driver list from INF(s) retrieved from URL specified in SP_DEVINSTALL_PARAMS.DriverPath (empty string means Windows Update website) + DI_FLAGSEX_EXCLUDE_OLD_INET_DRIVERS DI_FLAGSEX = 0x00800000 // Don't include old Internet drivers when building a driver list. Ignored on Windows Vista and later. + DI_FLAGSEX_POWERPAGE_ADDED DI_FLAGSEX = 0x01000000 // class installer added their own power page + DI_FLAGSEX_FILTERSIMILARDRIVERS DI_FLAGSEX = 0x02000000 // only include similar drivers in class list + DI_FLAGSEX_INSTALLEDDRIVER DI_FLAGSEX = 0x04000000 // only add the installed driver to the class or compat driver list. Used in calls to SetupDiBuildDriverInfoList + DI_FLAGSEX_NO_CLASSLIST_NODE_MERGE DI_FLAGSEX = 0x08000000 // Don't remove identical driver nodes from the class list + DI_FLAGSEX_ALTPLATFORM_DRVSEARCH DI_FLAGSEX = 0x10000000 // Build driver list based on alternate platform information specified in associated file queue + DI_FLAGSEX_RESTART_DEVICE_ONLY DI_FLAGSEX = 0x20000000 // only restart the device drivers are being installed on as opposed to restarting all devices using those drivers. + DI_FLAGSEX_RECURSIVESEARCH DI_FLAGSEX = 0x40000000 // Tell SetupDiBuildDriverInfoList to do a recursive search + DI_FLAGSEX_SEARCH_PUBLISHED_INFS DI_FLAGSEX = 0x80000000 // Tell SetupDiBuildDriverInfoList to do a "published INF" search +) + +// SP_CLASSINSTALL_HEADER is the first member of any class install parameters structure. It contains the device installation request code that defines the format of the rest of the install parameters structure. +type SP_CLASSINSTALL_HEADER struct { + Size uint32 + InstallFunction DI_FUNCTION +} + +// DICS_FLAG specifies the scope of a device property change +type DICS_FLAG uint32 + +const ( + DICS_FLAG_GLOBAL DICS_FLAG = 0x00000001 // make change in all hardware profiles + DICS_FLAG_CONFIGSPECIFIC DICS_FLAG = 0x00000002 // make change in specified profile only + DICS_FLAG_CONFIGGENERAL DICS_FLAG = 0x00000004 // 1 or more hardware profile-specific changes to follow +) + +// DI_REMOVEDEVICE specifies the scope of the device removal +type DI_REMOVEDEVICE uint32 + +const ( + DI_REMOVEDEVICE_GLOBAL DI_REMOVEDEVICE = 0x00000001 // Make this change in all hardware profiles. Remove information about the device from the registry. + DI_REMOVEDEVICE_CONFIGSPECIFIC DI_REMOVEDEVICE = 0x00000002 // Make this change to only the hardware profile specified by HwProfile. this flag only applies to root-enumerated devices. When Windows removes the device from the last hardware profile in which it was configured, Windows performs a global removal. +) + +// SP_REMOVEDEVICE_PARAMS is a structure corresponding to a DIF_REMOVE install function. +type SP_REMOVEDEVICE_PARAMS struct { + ClassInstallHeader SP_CLASSINSTALL_HEADER + Scope DI_REMOVEDEVICE + HwProfile uint32 +} + +type SP_DRVINFO_DATA struct { + Size uint32 + DriverType uint32 + _ uintptr + Description [LINE_LEN]uint16 + MfgName [LINE_LEN]uint16 + ProviderName [LINE_LEN]uint16 + DriverDate windows.Filetime + DriverVersion uint64 +} + +func (data *SP_DRVINFO_DATA) toGo() *DrvInfoData { + return &DrvInfoData{ + DriverType: data.DriverType, + Description: windows.UTF16ToString(data.Description[:]), + MfgName: windows.UTF16ToString(data.MfgName[:]), + ProviderName: windows.UTF16ToString(data.ProviderName[:]), + DriverDate: data.DriverDate, + DriverVersion: data.DriverVersion, + } +} + +// IsNewer method returns true if SP_DRVINFO_DATA date and version is newer than supplied parameters. +func (data *SP_DRVINFO_DATA) IsNewer(driverDate windows.Filetime, driverVersion uint64) bool { + if data.DriverDate.HighDateTime > driverDate.HighDateTime { + return true + } + if data.DriverDate.HighDateTime < driverDate.HighDateTime { + return false + } + + if data.DriverDate.LowDateTime > driverDate.LowDateTime { + return true + } + if data.DriverDate.LowDateTime < driverDate.LowDateTime { + return false + } + + if data.DriverVersion > driverVersion { + return true + } + if data.DriverVersion < driverVersion { + return false + } + + return false +} + +// DrvInfoData is driver information structure (member of a driver info list that may be associated with a particular device instance, or (globally) with a device information set) +type DrvInfoData struct { + DriverType uint32 + Description string + MfgName string + ProviderName string + DriverDate windows.Filetime + DriverVersion uint64 +} + +func (driverInfoData *DrvInfoData) toWindows() (data *SP_DRVINFO_DATA, err error) { + data = &SP_DRVINFO_DATA{ + DriverType: driverInfoData.DriverType, + DriverDate: driverInfoData.DriverDate, + DriverVersion: driverInfoData.DriverVersion, + } + data.Size = uint32(unsafe.Sizeof(*data)) + + DescriptionUTF16, err := syscall.UTF16FromString(driverInfoData.Description) + if err != nil { + return + } + copy(data.Description[:], DescriptionUTF16) + + MfgNameUTF16, err := syscall.UTF16FromString(driverInfoData.MfgName) + if err != nil { + return + } + copy(data.MfgName[:], MfgNameUTF16) + + ProviderNameUTF16, err := syscall.UTF16FromString(driverInfoData.ProviderName) + if err != nil { + return + } + copy(data.ProviderName[:], ProviderNameUTF16) + + return +} + +type _SP_DRVINFO_DETAIL_DATA struct { + Size uint32 + InfDate windows.Filetime + CompatIDsOffset uint32 + CompatIDsLength uint32 + _ uintptr + SectionName [LINE_LEN]uint16 + InfFileName [windows.MAX_PATH]uint16 + DrvDescription [LINE_LEN]uint16 + HardwareID [1]uint16 +} + +func (_data *_SP_DRVINFO_DETAIL_DATA) toGo(bufLen uint32) (DriverInfoDetailData *DrvInfoDetailData) { + DriverInfoDetailData = &DrvInfoDetailData{ + InfDate: _data.InfDate, + SectionName: windows.UTF16ToString(_data.SectionName[:]), + InfFileName: windows.UTF16ToString(_data.InfFileName[:]), + DrvDescription: windows.UTF16ToString(_data.DrvDescription[:]), + CompatIDs: []string{}, + } + + bufW := _data.getBuf(bufLen) + + if _data.CompatIDsOffset > 1 { + DriverInfoDetailData.HardwareID = windows.UTF16ToString(bufW[:wcslen(bufW)]) + } + + if _data.CompatIDsLength > 0 { + bufW = bufW[_data.CompatIDsOffset : _data.CompatIDsOffset+_data.CompatIDsLength] + for i := 0; i < len(bufW); { + j := i + wcslen(bufW[i:]) + if i < j { + DriverInfoDetailData.CompatIDs = append(DriverInfoDetailData.CompatIDs, windows.UTF16ToString(bufW[i:j])) + } + i = j + 1 + } + } + + return +} + +func (_data *_SP_DRVINFO_DETAIL_DATA) getBuf(bufLen uint32) []uint16 { + len := (bufLen - uint32(unsafe.Offsetof(_data.HardwareID))) / 2 + sl := struct { + addr *uint16 + len int + cap int + }{&_data.HardwareID[0], int(len), int(len)} + return *(*[]uint16)(unsafe.Pointer(&sl)) +} + +// DrvInfoDetailData is driver information details structure (provides detailed information about a particular driver information structure) +type DrvInfoDetailData struct { + InfDate windows.Filetime + SectionName string + InfFileName string + DrvDescription string + HardwareID string + CompatIDs []string +} + +// IsCompatible method tests if given hardware ID matches the driver or is listed on the compatible ID list. +func (driverInfoDetailData *DrvInfoDetailData) IsCompatible(hwid string) bool { + hwidLC := strings.ToLower(hwid) + if strings.ToLower(driverInfoDetailData.HardwareID) == hwidLC { + return true + } + for i := range driverInfoDetailData.CompatIDs { + if strings.ToLower(driverInfoDetailData.CompatIDs[i]) == hwidLC { + return true + } + } + + return false +} + +// DICD flags control SetupDiCreateDeviceInfo +type DICD uint32 + +const ( + DICD_GENERATE_ID DICD = 0x00000001 + DICD_INHERIT_CLASSDRVS DICD = 0x00000002 +) + +// +// SPDIT flags to distinguish between class drivers and +// device drivers. +// (Passed in 'DriverType' parameter of driver information list APIs) +// +type SPDIT uint32 + +const ( + SPDIT_NODRIVER SPDIT = 0x00000000 + SPDIT_CLASSDRIVER SPDIT = 0x00000001 + SPDIT_COMPATDRIVER SPDIT = 0x00000002 +) + +// DIGCF flags control what is included in the device information set built by SetupDiGetClassDevs +type DIGCF uint32 + +const ( + DIGCF_DEFAULT DIGCF = 0x00000001 // only valid with DIGCF_DEVICEINTERFACE + DIGCF_PRESENT DIGCF = 0x00000002 + DIGCF_ALLCLASSES DIGCF = 0x00000004 + DIGCF_PROFILE DIGCF = 0x00000008 + DIGCF_DEVICEINTERFACE DIGCF = 0x00000010 +) + +// DIREG specifies values for SetupDiCreateDevRegKey, SetupDiOpenDevRegKey, and SetupDiDeleteDevRegKey. +type DIREG uint32 + +const ( + DIREG_DEV DIREG = 0x00000001 // Open/Create/Delete device key + DIREG_DRV DIREG = 0x00000002 // Open/Create/Delete driver key + DIREG_BOTH DIREG = 0x00000004 // Delete both driver and Device key +) + +// +// SPDRP specifies device registry property codes +// (Codes marked as read-only (R) may only be used for +// SetupDiGetDeviceRegistryProperty) +// +// These values should cover the same set of registry properties +// as defined by the CM_DRP codes in cfgmgr32.h. +// +// Note that SPDRP codes are zero based while CM_DRP codes are one based! +// +type SPDRP uint32 + +const ( + SPDRP_DEVICEDESC SPDRP = 0x00000000 // DeviceDesc (R/W) + SPDRP_HARDWAREID SPDRP = 0x00000001 // HardwareID (R/W) + SPDRP_COMPATIBLEIDS SPDRP = 0x00000002 // CompatibleIDs (R/W) + SPDRP_SERVICE SPDRP = 0x00000004 // Service (R/W) + SPDRP_CLASS SPDRP = 0x00000007 // Class (R--tied to ClassGUID) + SPDRP_CLASSGUID SPDRP = 0x00000008 // ClassGUID (R/W) + SPDRP_DRIVER SPDRP = 0x00000009 // Driver (R/W) + SPDRP_CONFIGFLAGS SPDRP = 0x0000000A // ConfigFlags (R/W) + SPDRP_MFG SPDRP = 0x0000000B // Mfg (R/W) + SPDRP_FRIENDLYNAME SPDRP = 0x0000000C // FriendlyName (R/W) + SPDRP_LOCATION_INFORMATION SPDRP = 0x0000000D // LocationInformation (R/W) + SPDRP_PHYSICAL_DEVICE_OBJECT_NAME SPDRP = 0x0000000E // PhysicalDeviceObjectName (R) + SPDRP_CAPABILITIES SPDRP = 0x0000000F // Capabilities (R) + SPDRP_UI_NUMBER SPDRP = 0x00000010 // UiNumber (R) + SPDRP_UPPERFILTERS SPDRP = 0x00000011 // UpperFilters (R/W) + SPDRP_LOWERFILTERS SPDRP = 0x00000012 // LowerFilters (R/W) + SPDRP_BUSTYPEGUID SPDRP = 0x00000013 // BusTypeGUID (R) + SPDRP_LEGACYBUSTYPE SPDRP = 0x00000014 // LegacyBusType (R) + SPDRP_BUSNUMBER SPDRP = 0x00000015 // BusNumber (R) + SPDRP_ENUMERATOR_NAME SPDRP = 0x00000016 // Enumerator Name (R) + SPDRP_SECURITY SPDRP = 0x00000017 // Security (R/W, binary form) + SPDRP_SECURITY_SDS SPDRP = 0x00000018 // Security (W, SDS form) + SPDRP_DEVTYPE SPDRP = 0x00000019 // Device Type (R/W) + SPDRP_EXCLUSIVE SPDRP = 0x0000001A // Device is exclusive-access (R/W) + SPDRP_CHARACTERISTICS SPDRP = 0x0000001B // Device Characteristics (R/W) + SPDRP_ADDRESS SPDRP = 0x0000001C // Device Address (R) + SPDRP_UI_NUMBER_DESC_FORMAT SPDRP = 0x0000001D // UiNumberDescFormat (R/W) + SPDRP_DEVICE_POWER_DATA SPDRP = 0x0000001E // Device Power Data (R) + SPDRP_REMOVAL_POLICY SPDRP = 0x0000001F // Removal Policy (R) + SPDRP_REMOVAL_POLICY_HW_DEFAULT SPDRP = 0x00000020 // Hardware Removal Policy (R) + SPDRP_REMOVAL_POLICY_OVERRIDE SPDRP = 0x00000021 // Removal Policy Override (RW) + SPDRP_INSTALL_STATE SPDRP = 0x00000022 // Device Install State (R) + SPDRP_LOCATION_PATHS SPDRP = 0x00000023 // Device Location Paths (R) + SPDRP_BASE_CONTAINERID SPDRP = 0x00000024 // Base ContainerID (R) + + SPDRP_MAXIMUM_PROPERTY SPDRP = 0x00000025 // Upper bound on ordinals +) diff --git a/tun/wintun/setupapi/zsetupapi_windows.go b/tun/wintun/setupapi/zsetupapi_windows.go new file mode 100644 index 0000000..853b6a7 --- /dev/null +++ b/tun/wintun/setupapi/zsetupapi_windows.go @@ -0,0 +1,370 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package setupapi + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modsetupapi = windows.NewLazySystemDLL("setupapi.dll") + + procSetupDiCreateDeviceInfoListExW = modsetupapi.NewProc("SetupDiCreateDeviceInfoListExW") + procSetupDiGetDeviceInfoListDetailW = modsetupapi.NewProc("SetupDiGetDeviceInfoListDetailW") + procSetupDiCreateDeviceInfoW = modsetupapi.NewProc("SetupDiCreateDeviceInfoW") + procSetupDiEnumDeviceInfo = modsetupapi.NewProc("SetupDiEnumDeviceInfo") + procSetupDiDestroyDeviceInfoList = modsetupapi.NewProc("SetupDiDestroyDeviceInfoList") + procSetupDiBuildDriverInfoList = modsetupapi.NewProc("SetupDiBuildDriverInfoList") + procSetupDiCancelDriverInfoSearch = modsetupapi.NewProc("SetupDiCancelDriverInfoSearch") + procSetupDiEnumDriverInfoW = modsetupapi.NewProc("SetupDiEnumDriverInfoW") + procSetupDiGetSelectedDriverW = modsetupapi.NewProc("SetupDiGetSelectedDriverW") + procSetupDiSetSelectedDriverW = modsetupapi.NewProc("SetupDiSetSelectedDriverW") + procSetupDiGetDriverInfoDetailW = modsetupapi.NewProc("SetupDiGetDriverInfoDetailW") + procSetupDiDestroyDriverInfoList = modsetupapi.NewProc("SetupDiDestroyDriverInfoList") + procSetupDiGetClassDevsExW = modsetupapi.NewProc("SetupDiGetClassDevsExW") + procSetupDiCallClassInstaller = modsetupapi.NewProc("SetupDiCallClassInstaller") + procSetupDiOpenDevRegKey = modsetupapi.NewProc("SetupDiOpenDevRegKey") + procSetupDiGetDeviceRegistryPropertyW = modsetupapi.NewProc("SetupDiGetDeviceRegistryPropertyW") + procSetupDiSetDeviceRegistryPropertyW = modsetupapi.NewProc("SetupDiSetDeviceRegistryPropertyW") + procSetupDiGetDeviceInstallParamsW = modsetupapi.NewProc("SetupDiGetDeviceInstallParamsW") + procSetupDiGetClassInstallParamsW = modsetupapi.NewProc("SetupDiGetClassInstallParamsW") + procSetupDiSetDeviceInstallParamsW = modsetupapi.NewProc("SetupDiSetDeviceInstallParamsW") + procSetupDiSetClassInstallParamsW = modsetupapi.NewProc("SetupDiSetClassInstallParamsW") + procSetupDiClassNameFromGuidExW = modsetupapi.NewProc("SetupDiClassNameFromGuidExW") + procSetupDiClassGuidsFromNameExW = modsetupapi.NewProc("SetupDiClassGuidsFromNameExW") + procSetupDiGetSelectedDevice = modsetupapi.NewProc("SetupDiGetSelectedDevice") + procSetupDiSetSelectedDevice = modsetupapi.NewProc("SetupDiSetSelectedDevice") +) + +func setupDiCreateDeviceInfoListEx(classGUID *windows.GUID, hwndParent uintptr, machineName *uint16, reserved uintptr) (handle DevInfo, err error) { + r0, _, e1 := syscall.Syscall6(procSetupDiCreateDeviceInfoListExW.Addr(), 4, uintptr(unsafe.Pointer(classGUID)), uintptr(hwndParent), uintptr(unsafe.Pointer(machineName)), uintptr(reserved), 0, 0) + handle = DevInfo(r0) + if handle == DevInfo(windows.InvalidHandle) { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetDeviceInfoListDetail(deviceInfoSet DevInfo, deviceInfoSetDetailData *_SP_DEVINFO_LIST_DETAIL_DATA) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiGetDeviceInfoListDetailW.Addr(), 2, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoSetDetailData)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiCreateDeviceInfo(deviceInfoSet DevInfo, DeviceName *uint16, classGUID *windows.GUID, DeviceDescription *uint16, hwndParent uintptr, CreationFlags DICD, deviceInfoData *SP_DEVINFO_DATA) (err error) { + r1, _, e1 := syscall.Syscall9(procSetupDiCreateDeviceInfoW.Addr(), 7, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(DeviceName)), uintptr(unsafe.Pointer(classGUID)), uintptr(unsafe.Pointer(DeviceDescription)), uintptr(hwndParent), uintptr(CreationFlags), uintptr(unsafe.Pointer(deviceInfoData)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiEnumDeviceInfo(deviceInfoSet DevInfo, memberIndex uint32, deviceInfoData *SP_DEVINFO_DATA) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiEnumDeviceInfo.Addr(), 3, uintptr(deviceInfoSet), uintptr(memberIndex), uintptr(unsafe.Pointer(deviceInfoData))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiDestroyDeviceInfoList(deviceInfoSet DevInfo) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiDestroyDeviceInfoList.Addr(), 1, uintptr(deviceInfoSet), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiBuildDriverInfoList(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiBuildDriverInfoList.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(driverType)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiCancelDriverInfoSearch(deviceInfoSet DevInfo) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiCancelDriverInfoSearch.Addr(), 1, uintptr(deviceInfoSet), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiEnumDriverInfo(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT, memberIndex uint32, driverInfoData *SP_DRVINFO_DATA) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiEnumDriverInfoW.Addr(), 5, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(driverType), uintptr(memberIndex), uintptr(unsafe.Pointer(driverInfoData)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiGetSelectedDriverW.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(driverInfoData))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiSetSelectedDriver(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiSetSelectedDriverW.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(driverInfoData))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetDriverInfoDetail(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverInfoData *SP_DRVINFO_DATA, driverInfoDetailData *_SP_DRVINFO_DETAIL_DATA, driverInfoDetailDataSize uint32, requiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiGetDriverInfoDetailW.Addr(), 6, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(driverInfoData)), uintptr(unsafe.Pointer(driverInfoDetailData)), uintptr(driverInfoDetailDataSize), uintptr(unsafe.Pointer(requiredSize))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiDestroyDriverInfoList(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, driverType SPDIT) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiDestroyDriverInfoList.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(driverType)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetClassDevsEx(classGUID *windows.GUID, Enumerator *uint16, hwndParent uintptr, Flags DIGCF, deviceInfoSet DevInfo, machineName *uint16, reserved uintptr) (handle DevInfo, err error) { + r0, _, e1 := syscall.Syscall9(procSetupDiGetClassDevsExW.Addr(), 7, uintptr(unsafe.Pointer(classGUID)), uintptr(unsafe.Pointer(Enumerator)), uintptr(hwndParent), uintptr(Flags), uintptr(deviceInfoSet), uintptr(unsafe.Pointer(machineName)), uintptr(reserved), 0, 0) + handle = DevInfo(r0) + if handle == DevInfo(windows.InvalidHandle) { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiCallClassInstaller(installFunction DI_FUNCTION, deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiCallClassInstaller.Addr(), 3, uintptr(installFunction), uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiOpenDevRegKey(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, Scope DICS_FLAG, HwProfile uint32, KeyType DIREG, samDesired uint32) (key windows.Handle, err error) { + r0, _, e1 := syscall.Syscall6(procSetupDiOpenDevRegKey.Addr(), 6, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(Scope), uintptr(HwProfile), uintptr(KeyType), uintptr(samDesired)) + key = windows.Handle(r0) + if key == windows.InvalidHandle { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, property SPDRP, propertyRegDataType *uint32, propertyBuffer *byte, propertyBufferSize uint32, requiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall9(procSetupDiGetDeviceRegistryPropertyW.Addr(), 7, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(property), uintptr(unsafe.Pointer(propertyRegDataType)), uintptr(unsafe.Pointer(propertyBuffer)), uintptr(propertyBufferSize), uintptr(unsafe.Pointer(requiredSize)), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiSetDeviceRegistryProperty(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, property SPDRP, propertyBuffer *byte, propertyBufferSize uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiSetDeviceRegistryPropertyW.Addr(), 5, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(property), uintptr(unsafe.Pointer(propertyBuffer)), uintptr(propertyBufferSize), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, deviceInstallParams *_SP_DEVINSTALL_PARAMS) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiGetDeviceInstallParamsW.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(deviceInstallParams))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiGetClassInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, classInstallParams *SP_CLASSINSTALL_HEADER, classInstallParamsSize uint32, requiredSize *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiGetClassInstallParamsW.Addr(), 5, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(classInstallParams)), uintptr(classInstallParamsSize), uintptr(unsafe.Pointer(requiredSize)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiSetDeviceInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, deviceInstallParams *_SP_DEVINSTALL_PARAMS) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiSetDeviceInstallParamsW.Addr(), 3, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(deviceInstallParams))) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiSetClassInstallParams(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA, classInstallParams *SP_CLASSINSTALL_HEADER, classInstallParamsSize uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiSetClassInstallParamsW.Addr(), 4, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), uintptr(unsafe.Pointer(classInstallParams)), uintptr(classInstallParamsSize), 0, 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiClassNameFromGuidEx(classGUID *windows.GUID, className *uint16, classNameSize uint32, requiredSize *uint32, machineName *uint16, reserved uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiClassNameFromGuidExW.Addr(), 6, uintptr(unsafe.Pointer(classGUID)), uintptr(unsafe.Pointer(className)), uintptr(classNameSize), uintptr(unsafe.Pointer(requiredSize)), uintptr(unsafe.Pointer(machineName)), uintptr(reserved)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiClassGuidsFromNameEx(className *uint16, classGuidList *windows.GUID, classGuidListSize uint32, requiredSize *uint32, machineName *uint16, reserved uintptr) (err error) { + r1, _, e1 := syscall.Syscall6(procSetupDiClassGuidsFromNameExW.Addr(), 6, uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(classGuidList)), uintptr(classGuidListSize), uintptr(unsafe.Pointer(requiredSize)), uintptr(unsafe.Pointer(machineName)), uintptr(reserved)) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func setupDiGetSelectedDevice(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiGetSelectedDevice.Addr(), 2, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} + +func SetupDiSetSelectedDevice(deviceInfoSet DevInfo, deviceInfoData *SP_DEVINFO_DATA) (err error) { + r1, _, e1 := syscall.Syscall(procSetupDiSetSelectedDevice.Addr(), 2, uintptr(deviceInfoSet), uintptr(unsafe.Pointer(deviceInfoData)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} diff --git a/tun/wintun/setupapi/zsetupapi_windows_test.go b/tun/wintun/setupapi/zsetupapi_windows_test.go new file mode 100644 index 0000000..09b9195 --- /dev/null +++ b/tun/wintun/setupapi/zsetupapi_windows_test.go @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package setupapi + +import ( + "syscall" + "testing" + + "golang.org/x/sys/windows" +) + +func TestSetupDiDestroyDeviceInfoList(t *testing.T) { + err := SetupDiDestroyDeviceInfoList(DevInfo(windows.InvalidHandle)) + if errWin, ok := err.(syscall.Errno); !ok || errWin != 6 /*ERROR_INVALID_HANDLE*/ { + t.Errorf("SetupDiDestroyDeviceInfoList(nil, ...) should fail with ERROR_INVALID_HANDLE") + } +} diff --git a/tun/wintun/wintun_windows.go b/tun/wintun/wintun_windows.go new file mode 100644 index 0000000..1413c24 --- /dev/null +++ b/tun/wintun/wintun_windows.go @@ -0,0 +1,461 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package wintun + +import ( + "errors" + "fmt" + "strings" + "syscall" + "time" + "unsafe" + + "git.zx2c4.com/wireguard-go/tun/wintun/guid" + "git.zx2c4.com/wireguard-go/tun/wintun/setupapi" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +type Wintun windows.GUID + +var deviceClassNetGUID = windows.GUID{0x4d36e972, 0xe325, 0x11ce, [8]byte{0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18}} + +const TUN_HWID = "Wintun" + +// +// GetInterface finds interface ID by name. +// +// hwndParent is a handle to the top-level window to use for any user +// interface that is related to non-device-specific actions (such as a select- +// device dialog box that uses the global class driver list). This handle is +// optional and can be 0. If a specific top-level window is not required, set +// hwndParent to 0. +// +// Function returns interface ID when the interface was found, or nil +// otherwise. +// +func GetInterface(ifname string, hwndParent uintptr) (*Wintun, error) { + // Create a list of network devices. + devInfoList, err := setupapi.SetupDiGetClassDevsEx(&deviceClassNetGUID, "", hwndParent, setupapi.DIGCF_PRESENT, setupapi.DevInfo(0), "") + if err != nil { + return nil, err + } + defer devInfoList.Close() + + // Retrieve information associated with a device information set. + // TODO: Is this really necessary? + _, err = devInfoList.GetDeviceInfoListDetail() + if err != nil { + return nil, err + } + + // TODO: If we're certain we want case-insensitive name comparison, please document the rationale. + ifname = strings.ToLower(ifname) + + // Iterate. + for index := 0; ; index++ { + // Get the device from the list. + deviceData, err := devInfoList.EnumDeviceInfo(index) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + // Something is wrong with this device. Skip it. + continue + } + + // Get interface ID. + ifid, err := getInterfaceId(devInfoList, deviceData, 1) + if err != nil { + // Something is wrong with this device. Skip it. + continue + } + + // Get interface name. + ifname2, err := ((*Wintun)(ifid)).GetInterfaceName() + if err != nil { + // Something is wrong with this device. Skip it. + continue + } + + if ifname == strings.ToLower(ifname2) { + // Interface name found. + return (*Wintun)(ifid), nil + } + } + + return nil, nil +} + +// +// CreateInterface creates a TUN interface. +// +// description is a string that supplies the text description of the device. +// description is optional and can be "". +// +// hwndParent is a handle to the top-level window to use for any user +// interface that is related to non-device-specific actions (such as a select- +// device dialog box that uses the global class driver list). This handle is +// optional and can be 0. If a specific top-level window is not required, set +// hwndParent to 0. +// +// Function returns the network interface ID and a flag if reboot is required. +// +func CreateInterface(description string, hwndParent uintptr) (*Wintun, bool, error) { + // Create an empty device info set for network adapter device class. + devInfoList, err := setupapi.SetupDiCreateDeviceInfoListEx(&deviceClassNetGUID, hwndParent, "") + if err != nil { + return nil, false, err + } + + // Get the device class name from GUID. + className, err := setupapi.SetupDiClassNameFromGuidEx(&deviceClassNetGUID, "") + if err != nil { + return nil, false, err + } + + // Create a new device info element and add it to the device info set. + deviceData, err := devInfoList.CreateDeviceInfo(className, &deviceClassNetGUID, description, hwndParent, setupapi.DICD_GENERATE_ID) + if err != nil { + return nil, false, err + } + + // Set a device information element as the selected member of a device information set. + err = devInfoList.SetSelectedDevice(deviceData) + if err != nil { + return nil, false, err + } + + // Set Plug&Play device hardware ID property. + hwid, err := syscall.UTF16FromString(TUN_HWID) + if err != nil { + return nil, false, err + } + err = devInfoList.SetDeviceRegistryProperty(deviceData, setupapi.SPDRP_HARDWAREID, setupapi.UTF16ToBuf(append(hwid, 0))) + if err != nil { + return nil, false, err + } + + // Search for the driver. + const driverType = setupapi.SPDIT_CLASSDRIVER + err = devInfoList.BuildDriverInfoList(deviceData, driverType) + if err != nil { + return nil, false, err + } + defer devInfoList.DestroyDriverInfoList(deviceData, driverType) + + driverDate := windows.Filetime{} + driverVersion := uint64(0) + for index := 0; ; index++ { + // Get a driver from the list. + driverData, err := devInfoList.EnumDriverInfo(deviceData, driverType, index) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + // Something is wrong with this driver. Skip it. + continue + } + + // Check the driver version first, since the check is trivial and will save us iterating over hardware IDs for any driver versioned prior our best match. + if driverData.IsNewer(driverDate, driverVersion) { + // Get driver info details. + driverDetailData, err := devInfoList.GetDriverInfoDetail(deviceData, driverData) + if err != nil { + // Something is wrong with this driver. Skip it. + continue + } + + if driverDetailData.IsCompatible(TUN_HWID) { + // Matching hardware ID found. Select the driver. + err := devInfoList.SetSelectedDriver(deviceData, driverData) + if err != nil { + // Something is wrong with this driver. Skip it. + continue + } + + driverDate = driverData.DriverDate + driverVersion = driverData.DriverVersion + } + } + } + + if driverVersion == 0 { + return nil, false, fmt.Errorf("No driver for device \"%v\" installed", TUN_HWID) + } + + // Call appropriate class installer. + err = devInfoList.CallClassInstaller(setupapi.DIF_REGISTERDEVICE, deviceData) + if err != nil { + return nil, false, err + } + + // Register device co-installers if any. + devInfoList.CallClassInstaller(setupapi.DIF_REGISTER_COINSTALLERS, deviceData) + + // Install interfaces if any. + devInfoList.CallClassInstaller(setupapi.DIF_INSTALLINTERFACES, deviceData) + + var ifid *windows.GUID + var rebootRequired bool + + // Install the device. + err = devInfoList.CallClassInstaller(setupapi.DIF_INSTALLDEVICE, deviceData) + if err == nil { + // Check if a system reboot is required. (Ignore errors) + if ret, _ := checkReboot(devInfoList, deviceData); ret { + rebootRequired = true + } + + // Get network interface ID from registry. Retry for max 30sec. + ifid, err = getInterfaceId(devInfoList, deviceData, 30) + } + + if err == nil { + return (*Wintun)(ifid), rebootRequired, nil + } + + // The interface failed to install, or the interface ID was unobtainable. Clean-up. + removeDeviceParams := setupapi.SP_REMOVEDEVICE_PARAMS{ + ClassInstallHeader: setupapi.SP_CLASSINSTALL_HEADER{ + InstallFunction: setupapi.DIF_REMOVE, + }, + Scope: setupapi.DI_REMOVEDEVICE_GLOBAL, + } + removeDeviceParams.ClassInstallHeader.Size = uint32(unsafe.Sizeof(removeDeviceParams.ClassInstallHeader)) + + // Set class installer parameters for DIF_REMOVE. + if devInfoList.SetClassInstallParams(deviceData, &removeDeviceParams.ClassInstallHeader, uint32(unsafe.Sizeof(removeDeviceParams))) == nil { + // Call appropriate class installer. + if devInfoList.CallClassInstaller(setupapi.DIF_REMOVE, deviceData) == nil { + // Check if a system reboot is required. (Ignore errors) + if ret, _ := checkReboot(devInfoList, deviceData); ret { + rebootRequired = true + } + } + } + + return nil, false, err +} + +// +// DeleteInterface deletes a TUN interface. +// +// hwndParent is a handle to the top-level window to use for any user +// interface that is related to non-device-specific actions (such as a select- +// device dialog box that uses the global class driver list). This handle is +// optional and can be 0. If a specific top-level window is not required, set +// hwndParent to 0. +// +// Function returns true if the interface was found and deleted and a flag if +// reboot is required. +// +func (wintun *Wintun) DeleteInterface(hwndParent uintptr) (bool, bool, error) { + ifid := (*windows.GUID)(wintun) + // Create a list of network devices. + devInfoList, err := setupapi.SetupDiGetClassDevsEx(&deviceClassNetGUID, "", hwndParent, setupapi.DIGCF_PRESENT, setupapi.DevInfo(0), "") + if err != nil { + return false, false, err + } + defer devInfoList.Close() + + // Retrieve information associated with a device information set. + // TODO: Is this really necessary? + _, err = devInfoList.GetDeviceInfoListDetail() + if err != nil { + return false, false, err + } + + // Iterate. + for index := 0; ; index++ { + // Get the device from the list. + deviceData, err := devInfoList.EnumDeviceInfo(index) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == 259 /*ERROR_NO_MORE_ITEMS*/ { + break + } + // Something is wrong with this device. Skip it. + continue + } + + // Get interface ID. + ifid2, err := getInterfaceId(devInfoList, deviceData, 1) + if err != nil { + // Something is wrong with this device. Skip it. + continue + } + + if ifid == ifid2 { + // Remove the device. + removeDeviceParams := setupapi.SP_REMOVEDEVICE_PARAMS{ + ClassInstallHeader: setupapi.SP_CLASSINSTALL_HEADER{ + InstallFunction: setupapi.DIF_REMOVE, + }, + Scope: setupapi.DI_REMOVEDEVICE_GLOBAL, + } + removeDeviceParams.ClassInstallHeader.Size = uint32(unsafe.Sizeof(removeDeviceParams.ClassInstallHeader)) + + // Set class installer parameters for DIF_REMOVE. + err = devInfoList.SetClassInstallParams(deviceData, &removeDeviceParams.ClassInstallHeader, uint32(unsafe.Sizeof(removeDeviceParams))) + if err != nil { + return false, false, err + } + + // Call appropriate class installer. + err = devInfoList.CallClassInstaller(setupapi.DIF_REMOVE, deviceData) + if err != nil { + return false, false, err + } + + // Check if a system reboot is required. (Ignore errors) + if ret, _ := checkReboot(devInfoList, deviceData); ret { + return true, true, nil + } + + return true, false, nil + } + } + + return false, false, nil +} + +/// +/// checkReboot checks device install parameters if a system reboot is required. +/// +func checkReboot(deviceInfoSet setupapi.DevInfo, deviceInfoData *setupapi.SP_DEVINFO_DATA) (bool, error) { + devInstallParams, err := deviceInfoSet.GetDeviceInstallParams(deviceInfoData) + if err != nil { + return false, err + } + + if (devInstallParams.Flags & (setupapi.DI_NEEDREBOOT | setupapi.DI_NEEDRESTART)) != 0 { + return true, nil + } + + return false, nil +} + +// getInterfaceId returns network interface ID. +// +// After the device is created, it might take some time before the registry +// key is populated. numAttempts parameter specifies the number of attempts +// to read NetCfgInstanceId value from registry. A 1sec sleep is inserted +// between retry attempts. +// +// Function returns the network interface ID. +// +func getInterfaceId(deviceInfoSet setupapi.DevInfo, deviceInfoData *setupapi.SP_DEVINFO_DATA, numAttempts int) (*windows.GUID, error) { + if numAttempts < 1 { + return nil, fmt.Errorf("Invalid numAttempts (expected: >=1, provided: %v)", numAttempts) + } + + // Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\<class>\<id> registry key. + key, err := deviceInfoSet.OpenDevRegKey(deviceInfoData, setupapi.DICS_FLAG_GLOBAL, 0, setupapi.DIREG_DRV, registry.READ) + if err != nil { + return nil, errors.New("Device-specific registry key open failed: " + err.Error()) + } + defer key.Close() + + for { + // Query the NetCfgInstanceId value. Using get_reg_string() right on might clutter the output with error messages while the registry is still being populated. + _, _, err = key.GetValue("NetCfgInstanceId", nil) + if err != nil { + if errWin, ok := err.(syscall.Errno); ok && errWin == windows.ERROR_FILE_NOT_FOUND { + numAttempts-- + if numAttempts > 0 { + // Wait and retry. + // TODO: Wait for a cancellable event instead. + time.Sleep(1000 * time.Millisecond) + continue + } + } + + return nil, errors.New("RegQueryValueEx(\"NetCfgInstanceId\") failed: " + err.Error()) + } + + // Read the NetCfgInstanceId value now. + value, err := getRegStringValue(key, "NetCfgInstanceId") + if err != nil { + return nil, errors.New("RegQueryStringValue(\"NetCfgInstanceId\") failed: " + err.Error()) + } + + // Convert to windows.GUID. + ifid, err := guid.FromString(value) + if err != nil { + return nil, fmt.Errorf("NetCfgInstanceId registry value is not a GUID (expected: \"{...}\", provided: \"%v\")", value) + } + + return ifid, err + } +} + +// +// GetInterfaceName returns network interface name. +// +func (wintun *Wintun) GetInterfaceName() (string, error) { + ifid := (*windows.GUID)(wintun) + // Open network interface registry key. + key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Control\\Network\\%v\\%v\\Connection", guid.ToString(&deviceClassNetGUID), guid.ToString(ifid)), registry.QUERY_VALUE) + if err != nil { + return "", errors.New("Network-specific registry key open failed: " + err.Error()) + } + defer key.Close() + + // Get the interface name. + return getRegStringValue(key, "Name") +} + +// +// SetInterfaceName sets network interface name. +// +func (wintun *Wintun) SetInterfaceName(ifname string) error { + ifid := (*windows.GUID)(wintun) + // Open network interface registry key. + key, err := registry.OpenKey(registry.LOCAL_MACHINE, fmt.Sprintf("SYSTEM\\CurrentControlSet\\Control\\Network\\%v\\%v\\Connection", guid.ToString(&deviceClassNetGUID), guid.ToString(ifid)), registry.SET_VALUE) + if err != nil { + return errors.New("Network-specific registry key open failed: " + err.Error()) + } + defer key.Close() + + // Set the interface name. + return key.SetStringValue("Name", ifname) +} + +// +// getRegStringValue function reads a string value from registry. +// +// If the value type is REG_EXPAND_SZ the environment variables are expanded. +// Should expanding fail, original string value and nil error are returned. +// +func getRegStringValue(key registry.Key, name string) (string, error) { + // Read string value. + value, valueType, err := key.GetStringValue(name) + if err != nil { + return "", err + } + + if valueType != registry.EXPAND_SZ { + // Value does not require expansion. + return value, nil + } + + valueExp, err := registry.ExpandString(value) + if err != nil { + // Expanding failed: return original sting value. + return value, nil + } + + // Return expanded value. + return valueExp, nil +} + +func (wintun *Wintun) SignalEventName() string { + return fmt.Sprintf("Global\\WINTUN_EVENT_%s", guid.ToString((*windows.GUID)(wintun))) +} + +func (wintun *Wintun) DataFileName() string { + return fmt.Sprintf("\\\\.\\Global\\WINTUN_DEVICE_%s", guid.ToString((*windows.GUID)(wintun))) +}
\ No newline at end of file |