OpenVPN
tap.c
Go to the documentation of this file.
1/*
2 * tapctl -- Utility to manipulate TUN/TAP adapters on Windows
3 * https://community.openvpn.net/openvpn/wiki/Tapctl
4 *
5 * Copyright (C) 2018-2026 Simon Rozman <simon@rozman.si>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, see <https://www.gnu.org/licenses/>.
18 */
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#include "tap.h"
25#include "error.h"
26
27#include <windows.h>
28#include <cfgmgr32.h>
29#include <objbase.h>
30#include <setupapi.h>
31#include <stdio.h>
32#include <wchar.h>
33#include <newdev.h>
34
35#ifdef _MSC_VER
36#pragma comment(lib, "advapi32.lib")
37#pragma comment(lib, "ole32.lib")
38#pragma comment(lib, "setupapi.lib")
39#pragma comment(lib, "newdev.lib")
40#endif
41
42
43static const GUID GUID_DEVCLASS_NET = {
44 0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 }
45};
46
47static const WCHAR szAdapterRegKeyPathTemplate[] =
48 L"SYSTEM\\CurrentControlSet\\Control\\Network\\%ls\\%ls\\Connection";
49#define ADAPTER_REGKEY_PATH_MAX \
50 (_countof(L"SYSTEM\\CurrentControlSet\\Control\\Network\\") - 1 + 38 + _countof(L"\\") - 1 \
51 + 38 + _countof(L"\\Connection"))
52
66static void *
67find_function(const WCHAR *libname, const char *funcname, HMODULE *m)
68{
69 WCHAR libpath[MAX_PATH];
70 void *fptr = NULL;
71
72 /* Make sure the dll is loaded from the system32 folder */
73 if (!GetSystemDirectoryW(libpath, _countof(libpath)))
74 {
75 return NULL;
76 }
77
78 /* +1 for the path seperator '\' */
79 const size_t path_length = wcslen(libpath) + 1 + wcslen(libname);
80 if (path_length >= _countof(libpath))
81 {
82 SetLastError(ERROR_INSUFFICIENT_BUFFER);
83 return NULL;
84 }
85 wcscat_s(libpath, _countof(libpath), L"\\");
86 wcscat_s(libpath, _countof(libpath), libname);
87
88 *m = LoadLibraryW(libpath);
89 if (*m == NULL)
90 {
91 return NULL;
92 }
93 fptr = GetProcAddress(*m, funcname);
94 if (!fptr)
95 {
96 FreeLibrary(*m);
97 *m = NULL;
98 return NULL;
99 }
100 return fptr;
101}
102
110static inline size_t
111wcszlen(_In_z_ LPCWSTR szz)
112{
113 LPCWSTR s;
114 for (s = szz; s[0]; s += wcslen(s) + 1)
115 {
116 }
117 return s - szz;
118}
119
120
131static LPCWSTR
132wcszistr(_In_z_ LPCWSTR szzHay, _In_z_ LPCWSTR szNeedle)
133{
134 for (LPCWSTR s = szzHay; s[0]; s += wcslen(s) + 1)
135 {
136 if (wcsicmp(s, szNeedle) == 0)
137 {
138 return s;
139 }
140 }
141
142 return NULL;
143}
144
145
162typedef DWORD (*devop_func_t)(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
163 _Inout_ LPBOOL pbRebootRequired);
164
165
182static DWORD
183check_reboot(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
184 _Inout_ LPBOOL pbRebootRequired)
185{
186 if (pbRebootRequired == NULL)
187 {
188 return ERROR_BAD_ARGUMENTS;
189 }
190
191 SP_DEVINSTALL_PARAMS devinstall_params = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS) };
192 if (!SetupDiGetDeviceInstallParams(hDeviceInfoSet, pDeviceInfoData, &devinstall_params))
193 {
194 DWORD dwResult = GetLastError();
195 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceInstallParams failed", __FUNCTION__);
196 return dwResult;
197 }
198
199 if ((devinstall_params.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0)
200 {
201 *pbRebootRequired = TRUE;
202 }
203
204 return ERROR_SUCCESS;
205}
206
207
224static DWORD
225delete_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
226 _Inout_ LPBOOL pbRebootRequired)
227{
228 SP_REMOVEDEVICE_PARAMS params = {
229 .ClassInstallHeader = {
230 .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
231 .InstallFunction = DIF_REMOVE,
232 },
233 .Scope = DI_REMOVEDEVICE_GLOBAL,
234 .HwProfile = 0,
235 };
236
237 /* Set class installer parameters for DIF_REMOVE. */
238 if (!SetupDiSetClassInstallParams(hDeviceInfoSet, pDeviceInfoData, &params.ClassInstallHeader,
239 sizeof(SP_REMOVEDEVICE_PARAMS)))
240 {
241 DWORD dwResult = GetLastError();
242 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
243 return dwResult;
244 }
245
246 /* Call appropriate class installer. */
247 if (!SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfoSet, pDeviceInfoData))
248 {
249 DWORD dwResult = GetLastError();
250 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__);
251 return dwResult;
252 }
253
254 /* Check if a system reboot is required. */
255 check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);
256 return ERROR_SUCCESS;
257}
258
259
278static DWORD
279change_device_state(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
280 _In_ BOOL bEnable, _Inout_ LPBOOL pbRebootRequired)
281{
282 SP_PROPCHANGE_PARAMS params = {
283 .ClassInstallHeader = {
284 .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
285 .InstallFunction = DIF_PROPERTYCHANGE,
286 },
287 .StateChange = bEnable ? DICS_ENABLE : DICS_DISABLE,
288 .Scope = DICS_FLAG_GLOBAL,
289 .HwProfile = 0,
290 };
291
292 /* Set class installer parameters for DIF_PROPERTYCHANGE. */
293 if (!SetupDiSetClassInstallParams(hDeviceInfoSet, pDeviceInfoData, &params.ClassInstallHeader,
294 sizeof(SP_PROPCHANGE_PARAMS)))
295 {
296 DWORD dwResult = GetLastError();
297 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
298 return dwResult;
299 }
300
301 /* Call appropriate class installer. */
302 if (!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDeviceInfoSet, pDeviceInfoData))
303 {
304 DWORD dwResult = GetLastError();
305 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_PROPERTYCHANGE) failed",
306 __FUNCTION__);
307 return dwResult;
308 }
309
310 /* Check if a system reboot is required. */
311 check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);
312 return ERROR_SUCCESS;
313}
314
315
332static DWORD
333enable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
334 _Inout_ LPBOOL pbRebootRequired)
335{
336 return change_device_state(hDeviceInfoSet, pDeviceInfoData, TRUE, pbRebootRequired);
337}
338
339
356static DWORD
357disable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
358 _Inout_ LPBOOL pbRebootRequired)
359{
360 return change_device_state(hDeviceInfoSet, pDeviceInfoData, FALSE, pbRebootRequired);
361}
362
363
378static DWORD
379get_reg_string(_In_ HKEY hKey, _In_ LPCWSTR szName, _Out_ LPWSTR *pszValue)
380{
381 if (pszValue == NULL)
382 {
383 return ERROR_BAD_ARGUMENTS;
384 }
385
386 DWORD dwValueType = REG_NONE, dwSize = 0;
387 DWORD dwResult = RegQueryValueEx(hKey, szName, NULL, &dwValueType, NULL, &dwSize);
388 if (dwResult != ERROR_SUCCESS)
389 {
390 SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError().
391 But we do have an error code. Set last error manually. */
392 msg(M_NONFATAL | M_ERRNO, "%s: enumerating \"%ls\" registry value failed", __FUNCTION__,
393 szName);
394 return dwResult;
395 }
396
397 switch (dwValueType)
398 {
399 case REG_SZ:
400 case REG_EXPAND_SZ:
401 {
402 /* Read value. */
403 LPWSTR szValue = (LPWSTR)malloc(dwSize);
404 if (szValue == NULL)
405 {
406 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSize);
407 return ERROR_OUTOFMEMORY;
408 }
409
410 dwResult = RegQueryValueEx(hKey, szName, NULL, NULL, (LPBYTE)szValue, &dwSize);
411 if (dwResult != ERROR_SUCCESS)
412 {
413 SetLastError(
414 dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But
415 we do have an error code. Set last error manually. */
416 msg(M_NONFATAL | M_ERRNO, "%s: reading \"%ls\" registry value failed", __FUNCTION__,
417 szName);
418 free(szValue);
419 return dwResult;
420 }
421
422 if (dwValueType == REG_EXPAND_SZ)
423 {
424 /* Expand the environment strings. */
425 DWORD
426 dwSizeExp = dwSize * 2, dwCountExp =
427#ifdef UNICODE
428 dwSizeExp / sizeof(WCHAR);
429#else
430 dwSizeExp / sizeof(WCHAR)
431 - 1; /* Note: ANSI version requires one extra char. */
432#endif
433 LPWSTR szValueExp = (LPWSTR)malloc(dwSizeExp);
434 if (szValueExp == NULL)
435 {
436 free(szValue);
437 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSizeExp);
438 return ERROR_OUTOFMEMORY;
439 }
440
441 DWORD dwCountExpResult = ExpandEnvironmentStrings(szValue, szValueExp, dwCountExp);
442 if (dwCountExpResult == 0)
443 {
444 msg(M_NONFATAL | M_ERRNO, "%s: expanding \"%ls\" registry value failed",
445 __FUNCTION__, szName);
446 free(szValueExp);
447 free(szValue);
448 return dwResult;
449 }
450 else if (dwCountExpResult <= dwCountExp)
451 {
452 /* The buffer was big enough. */
453 free(szValue);
454 *pszValue = szValueExp;
455 return ERROR_SUCCESS;
456 }
457 else
458 {
459 /* Retry with a bigger buffer. */
460 free(szValueExp);
461#ifdef UNICODE
462 dwSizeExp = dwCountExpResult * sizeof(WCHAR);
463#else
464 /* Note: ANSI version requires one extra char. */
465 dwSizeExp = (dwCountExpResult + 1) * sizeof(WCHAR);
466#endif
467 dwCountExp = dwCountExpResult;
468 szValueExp = (LPWSTR)malloc(dwSizeExp);
469 if (szValueExp == NULL)
470 {
471 free(szValue);
472 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSizeExp);
473 return ERROR_OUTOFMEMORY;
474 }
475
476 dwCountExpResult = ExpandEnvironmentStrings(szValue, szValueExp, dwCountExp);
477 free(szValue);
478 *pszValue = szValueExp;
479 return ERROR_SUCCESS;
480 }
481 }
482 else
483 {
484 *pszValue = szValue;
485 return ERROR_SUCCESS;
486 }
487 }
488
489 default:
490 msg(M_NONFATAL, "%s: \"%ls\" registry value is not string (type %u)",
491 __FUNCTION__, szName, dwValueType);
492 return ERROR_UNSUPPORTED_TYPE;
493 }
494}
495
496
515static DWORD
516get_net_adapter_guid(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
517 _In_ int iNumAttempts, _Out_ LPGUID pguidAdapter)
518{
519 DWORD dwResult = ERROR_BAD_ARGUMENTS;
520
521 if (pguidAdapter == NULL || iNumAttempts < 1)
522 {
523 return ERROR_BAD_ARGUMENTS;
524 }
525
526 /* Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class<class><id> registry key. */
527 HKEY hKey = SetupDiOpenDevRegKey(hDeviceInfoSet, pDeviceInfoData, DICS_FLAG_GLOBAL, 0,
528 DIREG_DRV, KEY_READ);
529 if (hKey == INVALID_HANDLE_VALUE)
530 {
531 dwResult = GetLastError();
532 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiOpenDevRegKey failed", __FUNCTION__);
533 return dwResult;
534 }
535
536 while (iNumAttempts > 0)
537 {
538 /* Query the NetCfgInstanceId value. Using get_reg_string() right on might clutter the
539 * output with error messages while the registry is still being populated. */
540 LPWSTR szCfgGuidString = NULL;
541 dwResult = RegQueryValueEx(hKey, L"NetCfgInstanceId", NULL, NULL, NULL, NULL);
542 if (dwResult != ERROR_SUCCESS)
543 {
544 if (dwResult == ERROR_FILE_NOT_FOUND && --iNumAttempts > 0)
545 {
546 /* Wait and retry. */
547 Sleep(1000);
548 continue;
549 }
550
551 SetLastError(
552 dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we
553 do have an error code. Set last error manually. */
554 msg(M_NONFATAL | M_ERRNO, "%s: querying \"NetCfgInstanceId\" registry value failed",
555 __FUNCTION__);
556 break;
557 }
558
559 /* Read the NetCfgInstanceId value now. */
560 dwResult = get_reg_string(hKey, L"NetCfgInstanceId", &szCfgGuidString);
561 if (dwResult != ERROR_SUCCESS)
562 {
563 break;
564 }
565
566 dwResult = SUCCEEDED(CLSIDFromString(szCfgGuidString, (LPCLSID)pguidAdapter))
567 ? ERROR_SUCCESS
568 : ERROR_INVALID_DATA;
569 free(szCfgGuidString);
570 break;
571 }
572
573 RegCloseKey(hKey);
574 return dwResult;
575}
576
577
599static DWORD
600get_device_reg_property(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
601 _In_ DWORD dwProperty, _Out_opt_ LPDWORD pdwPropertyRegDataType,
602 _Out_ LPVOID *ppData)
603{
604 DWORD dwResult = ERROR_BAD_ARGUMENTS;
605
606 if (ppData == NULL)
607 {
608 return ERROR_BAD_ARGUMENTS;
609 }
610
611 /* Try with stack buffer first. */
612 BYTE bBufStack[128];
613 DWORD dwRequiredSize = 0;
614 if (SetupDiGetDeviceRegistryProperty(hDeviceInfoSet, pDeviceInfoData, dwProperty,
615 pdwPropertyRegDataType, bBufStack, sizeof(bBufStack),
616 &dwRequiredSize))
617 {
618 /* Copy from stack. */
619 *ppData = malloc(dwRequiredSize);
620 if (*ppData == NULL)
621 {
622 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwRequiredSize);
623 return ERROR_OUTOFMEMORY;
624 }
625
626 memcpy(*ppData, bBufStack, dwRequiredSize);
627 return ERROR_SUCCESS;
628 }
629 else
630 {
631 dwResult = GetLastError();
632 if (dwResult == ERROR_INSUFFICIENT_BUFFER)
633 {
634 /* Allocate on heap and retry. */
635 *ppData = malloc(dwRequiredSize);
636 if (*ppData == NULL)
637 {
638 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwRequiredSize);
639 return ERROR_OUTOFMEMORY;
640 }
641
642 if (SetupDiGetDeviceRegistryProperty(hDeviceInfoSet, pDeviceInfoData, dwProperty,
643 pdwPropertyRegDataType, *ppData, dwRequiredSize,
644 &dwRequiredSize))
645 {
646 return ERROR_SUCCESS;
647 }
648 else
649 {
650 dwResult = GetLastError();
651 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceRegistryProperty(%u) failed",
652 __FUNCTION__, dwProperty);
653 return dwResult;
654 }
655 }
656 else
657 {
658 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceRegistryProperty(%u) failed",
659 __FUNCTION__, dwProperty);
660 return dwResult;
661 }
662 }
663}
664
665
666DWORD
667tap_create_adapter(_In_opt_ HWND hwndParent, _In_opt_ LPCWSTR szDeviceDescription,
668 _In_ LPCWSTR szHwId, _Inout_ LPBOOL pbRebootRequired, _Out_ LPGUID pguidAdapter)
669{
670 DWORD dwResult;
671 HMODULE libnewdev = NULL;
672
673 if (szHwId == NULL || pbRebootRequired == NULL || pguidAdapter == NULL)
674 {
675 return ERROR_BAD_ARGUMENTS;
676 }
677
678 /* Create an empty device info set for network adapter device class. */
679 HDEVINFO hDevInfoList = SetupDiCreateDeviceInfoList(&GUID_DEVCLASS_NET, hwndParent);
680 if (hDevInfoList == INVALID_HANDLE_VALUE)
681 {
682 dwResult = GetLastError();
683 msg(M_NONFATAL, "%s: SetupDiCreateDeviceInfoList failed", __FUNCTION__);
684 return dwResult;
685 }
686
687 /* Get the device class name from GUID. */
688 WCHAR szClassName[MAX_CLASS_NAME_LEN];
689 if (!SetupDiClassNameFromGuid(&GUID_DEVCLASS_NET, szClassName, _countof(szClassName), NULL))
690 {
691 dwResult = GetLastError();
692 msg(M_NONFATAL, "%s: SetupDiClassNameFromGuid failed", __FUNCTION__);
693 goto cleanup_hDevInfoList;
694 }
695
696 /* Create a new device info element and add it to the device info set. */
697 SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
698 if (!SetupDiCreateDeviceInfo(hDevInfoList, szClassName, &GUID_DEVCLASS_NET, szDeviceDescription,
699 hwndParent, DICD_GENERATE_ID, &devinfo_data))
700 {
701 dwResult = GetLastError();
702 msg(M_NONFATAL, "%s: SetupDiCreateDeviceInfo failed", __FUNCTION__);
703 goto cleanup_hDevInfoList;
704 }
705
706 /* Set a device information element as the selected member of a device information set. */
707 if (!SetupDiSetSelectedDevice(hDevInfoList, &devinfo_data))
708 {
709 dwResult = GetLastError();
710 msg(M_NONFATAL, "%s: SetupDiSetSelectedDevice failed", __FUNCTION__);
711 goto cleanup_hDevInfoList;
712 }
713
714 /* Set Plug&Play device hardware ID property. */
715 if (!SetupDiSetDeviceRegistryProperty(hDevInfoList, &devinfo_data, SPDRP_HARDWAREID,
716 (const BYTE *)szHwId,
717 (DWORD)((wcslen(szHwId) + 1) * sizeof(WCHAR))))
718 {
719 dwResult = GetLastError();
720 msg(M_NONFATAL, "%s: SetupDiSetDeviceRegistryProperty failed", __FUNCTION__);
721 goto cleanup_hDevInfoList;
722 }
723
724 /* Register the device instance with the PnP Manager */
725 if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, hDevInfoList, &devinfo_data))
726 {
727 dwResult = GetLastError();
728 msg(M_NONFATAL, "%s: SetupDiCallClassInstaller(DIF_REGISTERDEVICE) failed", __FUNCTION__);
729 goto cleanup_hDevInfoList;
730 }
731
732 /* Install the device using DiInstallDevice()
733 * We instruct the system to use the best driver in the driver store
734 * by setting the drvinfo argument of DiInstallDevice as NULL. This
735 * assumes a driver is already installed in the driver store.
736 */
737#ifdef HAVE_DIINSTALLDEVICE
738 if (!DiInstallDevice(hwndParent, hDevInfoList, &devinfo_data, NULL, 0, pbRebootRequired))
739#else
740 /* mingw does not resolve DiInstallDevice, so load it at run time. */
741 typedef BOOL(WINAPI * DiInstallDeviceFn)(HWND, HDEVINFO, SP_DEVINFO_DATA *, SP_DRVINFO_DATA *,
742 DWORD, BOOL *);
743 DiInstallDeviceFn installfn = find_function(L"newdev.dll", "DiInstallDevice", &libnewdev);
744
745 if (!installfn)
746 {
747 dwResult = GetLastError();
748 msg(M_NONFATAL | M_ERRNO, "%s: Failed to locate DiInstallDevice()", __FUNCTION__);
749 goto cleanup_hDevInfoList;
750 }
751
752 if (!installfn(hwndParent, hDevInfoList, &devinfo_data, NULL, 0, pbRebootRequired))
753#endif
754 {
755 dwResult = GetLastError();
756 msg(M_NONFATAL | M_ERRNO, "%s: DiInstallDevice failed", __FUNCTION__);
757 goto cleanup_remove_device;
758 }
759
760 /* Get network adapter ID from registry. Retry for max 30sec. */
761 dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 30, pguidAdapter);
762
763cleanup_remove_device:
764 if (dwResult != ERROR_SUCCESS)
765 {
766 /* The adapter was installed. But, the adapter ID was unobtainable. Clean-up. */
767 SP_REMOVEDEVICE_PARAMS removedevice_params = {
768 .ClassInstallHeader = {
769 .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
770 .InstallFunction = DIF_REMOVE,
771 },
772 .Scope = DI_REMOVEDEVICE_GLOBAL,
773 .HwProfile = 0,
774 };
775
776 /* Set class installer parameters for DIF_REMOVE. */
777 if (SetupDiSetClassInstallParams(hDevInfoList, &devinfo_data,
778 &removedevice_params.ClassInstallHeader,
779 sizeof(SP_REMOVEDEVICE_PARAMS)))
780 {
781 /* Call appropriate class installer. */
782 if (SetupDiCallClassInstaller(DIF_REMOVE, hDevInfoList, &devinfo_data))
783 {
784 /* Check if a system reboot is required. */
785 check_reboot(hDevInfoList, &devinfo_data, pbRebootRequired);
786 }
787 else
788 {
789 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed",
790 __FUNCTION__);
791 }
792 }
793 else
794 {
795 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
796 }
797 }
798
799cleanup_hDevInfoList:
800 if (libnewdev)
801 {
802 FreeLibrary(libnewdev);
803 }
804 SetupDiDestroyDeviceInfoList(hDevInfoList);
805 return dwResult;
806}
807
808
829static DWORD
830execute_on_first_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter,
831 _In_ devop_func_t funcOperation, _Inout_ LPBOOL pbRebootRequired)
832{
833 DWORD dwResult;
834
835 if (pguidAdapter == NULL)
836 {
837 return ERROR_BAD_ARGUMENTS;
838 }
839
840 /* Create a list of network devices. */
841 HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(&GUID_DEVCLASS_NET, NULL, hwndParent,
842 DIGCF_PRESENT, NULL, NULL, NULL);
843 if (hDevInfoList == INVALID_HANDLE_VALUE)
844 {
845 dwResult = GetLastError();
846 msg(M_NONFATAL, "%s: SetupDiGetClassDevsEx failed", __FUNCTION__);
847 return dwResult;
848 }
849
850 /* Retrieve information associated with a device information set. */
851 SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(
852 SP_DEVINFO_LIST_DETAIL_DATA) };
853 if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))
854 {
855 dwResult = GetLastError();
856 msg(M_NONFATAL, "%s: SetupDiGetDeviceInfoListDetail failed", __FUNCTION__);
857 goto cleanup_hDevInfoList;
858 }
859
860 /* Iterate. */
861 for (DWORD dwIndex = 0;; dwIndex++)
862 {
863 /* Get the device from the list. */
864 SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
865 if (!SetupDiEnumDeviceInfo(hDevInfoList, dwIndex, &devinfo_data))
866 {
867 if (GetLastError() == ERROR_NO_MORE_ITEMS)
868 {
869 LPOLESTR szAdapterId = NULL;
870 StringFromIID((REFIID)pguidAdapter, &szAdapterId);
871 msg(M_NONFATAL, "%s: Adapter %ls not found", __FUNCTION__, szAdapterId);
872 CoTaskMemFree(szAdapterId);
873 dwResult = ERROR_FILE_NOT_FOUND;
874 goto cleanup_hDevInfoList;
875 }
876 else
877 {
878 /* Something is wrong with this device. Skip it. */
879 msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDeviceInfo(%u) failed", __FUNCTION__,
880 dwIndex);
881 continue;
882 }
883 }
884
885 /* Get adapter GUID. */
886 GUID guidAdapter;
887 dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 1, &guidAdapter);
888 if (dwResult != ERROR_SUCCESS)
889 {
890 /* Something is wrong with this device. Skip it. */
891 continue;
892 }
893
894 /* Compare GUIDs. */
895 if (memcmp(pguidAdapter, &guidAdapter, sizeof(GUID)) == 0)
896 {
897 dwResult = funcOperation(hDevInfoList, &devinfo_data, pbRebootRequired);
898 break;
899 }
900 }
901
902cleanup_hDevInfoList:
903 SetupDiDestroyDeviceInfoList(hDevInfoList);
904 return dwResult;
905}
906
907
908DWORD
909tap_delete_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter,
910 _Inout_ LPBOOL pbRebootRequired)
911{
912 return execute_on_first_adapter(hwndParent, pguidAdapter, delete_device, pbRebootRequired);
913}
914
915
916DWORD
917tap_enable_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter, _In_ BOOL bEnable,
918 _Inout_ LPBOOL pbRebootRequired)
919{
920 return execute_on_first_adapter(hwndParent, pguidAdapter,
921 bEnable ? enable_device : disable_device, pbRebootRequired);
922}
923
924static DWORD
925ExecNetsh(PWSTR cmdline)
926{
927 DWORD exit_code;
928 STARTUPINFOW si;
929 PROCESS_INFORMATION pi;
930 DWORD proc_flags = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
931
932 ZeroMemory(&si, sizeof(si));
933 ZeroMemory(&pi, sizeof(pi));
934 si.cb = sizeof(si);
935
936 WCHAR appName[MAX_PATH];
937 WCHAR netsh_exe[] = L"\\netsh.exe";
938 UINT sysdir_len = GetSystemDirectoryW(appName, _countof(appName));
939 if (sysdir_len == 0)
940 {
941 wcscpy_s(appName, _countof(appName), L"C:\\Windows\\system32");
942 }
943 else if (sysdir_len + _countof(netsh_exe) > _countof(appName))
944 {
945 return ERROR_INSUFFICIENT_BUFFER;
946 }
947 wcscat_s(appName, _countof(appName), netsh_exe);
948
949 if (CreateProcessW(appName, cmdline, NULL, NULL, FALSE, proc_flags, NULL, NULL, &si, &pi))
950 {
951 WaitForSingleObject(pi.hProcess, INFINITE);
952 if (!GetExitCodeProcess(pi.hProcess, &exit_code))
953 {
954 exit_code = GetLastError();
955 }
956 CloseHandle(pi.hProcess);
957 CloseHandle(pi.hThread);
958 }
959 else
960 {
961 exit_code = GetLastError();
962 }
963
964 return exit_code;
965}
966
967DWORD
968tap_set_adapter_name(_In_ LPCGUID pguidAdapter, _In_ LPCWSTR szName, _In_ BOOL bSilent)
969{
970 DWORD dwResult;
971 int msg_flag = bSilent ? M_WARN : M_NONFATAL;
972 msg_flag |= M_ERRNO;
973
974 if (pguidAdapter == NULL || szName == NULL)
975 {
976 return ERROR_BAD_ARGUMENTS;
977 }
978
979 /* Get the device class GUID as string. */
980 LPOLESTR szDevClassNetId = NULL;
981 StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);
982
983 /* Get the adapter GUID as string. */
984 LPOLESTR szAdapterId = NULL;
985 StringFromIID((REFIID)pguidAdapter, &szAdapterId);
986
987 /* Render registry key path. */
988 WCHAR szRegKey[ADAPTER_REGKEY_PATH_MAX];
989 swprintf_s(szRegKey, _countof(szRegKey), szAdapterRegKeyPathTemplate, szDevClassNetId,
990 szAdapterId);
991
992 /* Open network adapter registry key. */
993 HKEY hKey = NULL;
994 dwResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegKey, 0, KEY_QUERY_VALUE, &hKey);
995 if (dwResult != ERROR_SUCCESS)
996 {
997 SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError(). But
998 we do have an error code. Set last error manually. */
999 msg(msg_flag, "%s: RegOpenKeyEx(HKLM, \"%ls\") failed", __FUNCTION__, szRegKey);
1000 goto cleanup_szAdapterId;
1001 }
1002
1003 LPWSTR szOldName = NULL;
1004 dwResult = get_reg_string(hKey, L"Name", &szOldName);
1005 if (dwResult != ERROR_SUCCESS)
1006 {
1007 SetLastError(dwResult);
1008 msg(msg_flag, "%s: Error reading adapter name", __FUNCTION__);
1009 goto cleanup_hKey;
1010 }
1011
1012 /* rename adapter via netsh call */
1013 const WCHAR *szFmt = L"netsh interface set interface name=\"%"
1014 L"ls\" newname=\"%ls\"";
1015 size_t ncmdline = wcslen(szFmt) + wcslen(szOldName) + wcslen(szName) + 1;
1016 WCHAR *szCmdLine = malloc(ncmdline * sizeof(WCHAR));
1017 swprintf_s(szCmdLine, ncmdline, szFmt, szOldName, szName);
1018
1019 free(szOldName);
1020
1021 dwResult = ExecNetsh(szCmdLine);
1022 free(szCmdLine);
1023
1024 if (dwResult != ERROR_SUCCESS)
1025 {
1026 SetLastError(dwResult);
1027 msg(msg_flag, "%s: Error renaming adapter", __FUNCTION__);
1028 goto cleanup_hKey;
1029 }
1030
1031cleanup_hKey:
1032 RegCloseKey(hKey);
1033cleanup_szAdapterId:
1034 CoTaskMemFree(szAdapterId);
1035 CoTaskMemFree(szDevClassNetId);
1036 return dwResult;
1037}
1038
1039
1040DWORD
1041tap_list_adapters(_In_opt_ HWND hwndParent, _In_opt_ LPCWSTR szzHwIDs,
1042 _Out_ struct tap_adapter_node **ppAdapter)
1043{
1044 DWORD dwResult;
1045
1046 if (ppAdapter == NULL)
1047 {
1048 return ERROR_BAD_ARGUMENTS;
1049 }
1050
1051 /* Create a list of network devices. */
1052 HDEVINFO hDevInfoList = SetupDiGetClassDevsEx(&GUID_DEVCLASS_NET, NULL, hwndParent,
1053 DIGCF_PRESENT, NULL, NULL, NULL);
1054 if (hDevInfoList == INVALID_HANDLE_VALUE)
1055 {
1056 dwResult = GetLastError();
1057 msg(M_NONFATAL, "%s: SetupDiGetClassDevsEx failed", __FUNCTION__);
1058 return dwResult;
1059 }
1060
1061 /* Retrieve information associated with a device information set. */
1062 SP_DEVINFO_LIST_DETAIL_DATA devinfo_list_detail_data = { .cbSize = sizeof(
1063 SP_DEVINFO_LIST_DETAIL_DATA) };
1064 if (!SetupDiGetDeviceInfoListDetail(hDevInfoList, &devinfo_list_detail_data))
1065 {
1066 dwResult = GetLastError();
1067 msg(M_NONFATAL, "%s: SetupDiGetDeviceInfoListDetail failed", __FUNCTION__);
1068 goto cleanup_hDevInfoList;
1069 }
1070
1071 /* Get the device class GUID as string. */
1072 LPOLESTR szDevClassNetId = NULL;
1073 StringFromIID((REFIID)&GUID_DEVCLASS_NET, &szDevClassNetId);
1074
1075 /* Iterate. */
1076 *ppAdapter = NULL;
1077 struct tap_adapter_node *pAdapterTail = NULL;
1078 for (DWORD dwIndex = 0;; dwIndex++)
1079 {
1080 /* Get the device from the list. */
1081 SP_DEVINFO_DATA devinfo_data = { .cbSize = sizeof(SP_DEVINFO_DATA) };
1082 if (!SetupDiEnumDeviceInfo(hDevInfoList, dwIndex, &devinfo_data))
1083 {
1084 if (GetLastError() == ERROR_NO_MORE_ITEMS)
1085 {
1086 break;
1087 }
1088 else
1089 {
1090 /* Something is wrong with this device. Skip it. */
1091 msg(M_WARN | M_ERRNO, "%s: SetupDiEnumDeviceInfo(%u) failed", __FUNCTION__,
1092 dwIndex);
1093 continue;
1094 }
1095 }
1096
1097 /* Get device hardware ID(s). */
1098 DWORD dwDataType = REG_NONE;
1099 LPWSTR szzDeviceHardwareIDs = NULL;
1100 dwResult = get_device_reg_property(hDevInfoList, &devinfo_data, SPDRP_HARDWAREID,
1101 &dwDataType, (LPVOID)&szzDeviceHardwareIDs);
1102 if (dwResult != ERROR_SUCCESS)
1103 {
1104 /* Something is wrong with this device. Skip it. */
1105 continue;
1106 }
1107
1108 /* Check that hardware ID is REG_SZ/REG_MULTI_SZ, and optionally if it matches ours. */
1109 if (dwDataType == REG_SZ)
1110 {
1111 if (szzHwIDs && !wcszistr(szzHwIDs, szzDeviceHardwareIDs))
1112 {
1113 /* This is not our device. Skip it. */
1114 goto cleanup_szzDeviceHardwareIDs;
1115 }
1116 }
1117 else if (dwDataType == REG_MULTI_SZ)
1118 {
1119 if (szzHwIDs)
1120 {
1121 for (LPWSTR s = szzDeviceHardwareIDs;; s += wcslen(s) + 1)
1122 {
1123 if (s[0] == 0)
1124 {
1125 /* This is not our device. Skip it. */
1126 goto cleanup_szzDeviceHardwareIDs;
1127 }
1128 else if (wcszistr(szzHwIDs, s))
1129 {
1130 /* This is our device. */
1131 break;
1132 }
1133 }
1134 }
1135 }
1136 else
1137 {
1138 /* Unexpected hardware ID format. Skip device. */
1139 goto cleanup_szzDeviceHardwareIDs;
1140 }
1141
1142 /* Get adapter GUID. */
1143 GUID guidAdapter;
1144 dwResult = get_net_adapter_guid(hDevInfoList, &devinfo_data, 1, &guidAdapter);
1145 if (dwResult != ERROR_SUCCESS)
1146 {
1147 /* Something is wrong with this device. Skip it. */
1148 goto cleanup_szzDeviceHardwareIDs;
1149 }
1150
1151 /* Get the adapter GUID as string. */
1152 LPOLESTR szAdapterId = NULL;
1153 StringFromIID((REFIID)&guidAdapter, &szAdapterId);
1154
1155 /* Render registry key path. */
1156 WCHAR szRegKey[ADAPTER_REGKEY_PATH_MAX];
1157 swprintf_s(szRegKey, _countof(szRegKey), szAdapterRegKeyPathTemplate, szDevClassNetId,
1158 szAdapterId);
1159
1160 /* Open network adapter registry key. */
1161 HKEY hKey = NULL;
1162 dwResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegKey, 0, KEY_READ, &hKey);
1163 if (dwResult != ERROR_SUCCESS)
1164 {
1165 SetLastError(dwResult); /* MSDN does not mention RegOpenKeyEx() to set GetLastError().
1166 But we do have an error code. Set last error manually. */
1167 msg(M_WARN | M_ERRNO, "%s: RegOpenKeyEx(HKLM, \"%ls\") failed", __FUNCTION__, szRegKey);
1168 goto cleanup_szAdapterId;
1169 }
1170
1171 /* Read adapter name. */
1172 LPWSTR szName = NULL;
1173 dwResult = get_reg_string(hKey, L"Name", &szName);
1174 if (dwResult != ERROR_SUCCESS)
1175 {
1176 SetLastError(dwResult);
1177 msg(M_WARN | M_ERRNO, "%s: Cannot determine %ls adapter name", __FUNCTION__,
1178 szAdapterId);
1179 goto cleanup_hKey;
1180 }
1181
1182 /* Append to the list. */
1183 size_t hwid_size = (wcszlen(szzDeviceHardwareIDs) + 1) * sizeof(WCHAR);
1184 size_t name_size = (wcslen(szName) + 1) * sizeof(WCHAR);
1185 struct tap_adapter_node *node = (struct tap_adapter_node *)malloc(
1186 sizeof(struct tap_adapter_node) + hwid_size + name_size);
1187 if (node == NULL)
1188 {
1189 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__,
1190 sizeof(struct tap_adapter_node) + hwid_size + name_size);
1191 dwResult = ERROR_OUTOFMEMORY;
1192 goto cleanup_szName;
1193 }
1194
1195 memcpy(&node->guid, &guidAdapter, sizeof(GUID));
1196 node->szzHardwareIDs = (LPWSTR)(node + 1);
1197 memcpy(node->szzHardwareIDs, szzDeviceHardwareIDs, hwid_size);
1198 node->szName = (LPWSTR)((LPBYTE)node->szzHardwareIDs + hwid_size);
1199 memcpy(node->szName, szName, name_size);
1200 node->pNext = NULL;
1201 if (pAdapterTail)
1202 {
1203 pAdapterTail->pNext = node;
1204 pAdapterTail = node;
1205 }
1206 else
1207 {
1208 *ppAdapter = pAdapterTail = node;
1209 }
1210
1211cleanup_szName:
1212 free(szName);
1213cleanup_hKey:
1214 RegCloseKey(hKey);
1215cleanup_szAdapterId:
1216 CoTaskMemFree(szAdapterId);
1217cleanup_szzDeviceHardwareIDs:
1218 free(szzDeviceHardwareIDs);
1219 }
1220
1221 dwResult = ERROR_SUCCESS;
1222
1223 CoTaskMemFree(szDevClassNetId);
1224cleanup_hDevInfoList:
1225 SetupDiDestroyDeviceInfoList(hDevInfoList);
1226 return dwResult;
1227}
1228
1229
1230void
1232{
1233 /* Iterate over all nodes of the list. */
1234 while (pAdapterList)
1235 {
1236 struct tap_adapter_node *node = pAdapterList;
1237 pAdapterList = pAdapterList->pNext;
1238
1239 /* Free the adapter node. */
1240 free(node);
1241 }
1242}
#define M_FATAL
Definition error.h:90
#define M_NONFATAL
Definition error.h:91
#define msg(flags,...)
Definition error.h:152
#define M_WARN
Definition error.h:92
#define M_ERRNO
Definition error.h:95
Network adapter list node.
Definition tap.h:124
LPWSTR szzHardwareIDs
Device hardware ID(s)
Definition tap.h:126
struct tap_adapter_node * pNext
Pointer to next adapter.
Definition tap.h:129
LPWSTR szName
Adapter name.
Definition tap.h:127
GUID guid
Adapter GUID.
Definition tap.h:125
DWORD tap_list_adapters(_In_opt_ HWND hwndParent, _In_opt_ LPCWSTR szzHwIDs, _Out_ struct tap_adapter_node **ppAdapter)
Creates a list of existing network adapters.
Definition tap.c:1041
static DWORD enable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _Inout_ LPBOOL pbRebootRequired)
Enables the device.
Definition tap.c:333
static LPCWSTR wcszistr(_In_z_ LPCWSTR szzHay, _In_z_ LPCWSTR szNeedle)
Checks if string is contained in the string of strings.
Definition tap.c:132
static DWORD execute_on_first_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter, _In_ devop_func_t funcOperation, _Inout_ LPBOOL pbRebootRequired)
Performs a given task on an adapter.
Definition tap.c:830
DWORD(* devop_func_t)(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _Inout_ LPBOOL pbRebootRequired)
Function that performs a specific task on a device.
Definition tap.c:162
static DWORD get_net_adapter_guid(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _In_ int iNumAttempts, _Out_ LPGUID pguidAdapter)
Returns network adapter ID.
Definition tap.c:516
static DWORD disable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _Inout_ LPBOOL pbRebootRequired)
Disables the device.
Definition tap.c:357
DWORD tap_set_adapter_name(_In_ LPCGUID pguidAdapter, _In_ LPCWSTR szName, _In_ BOOL bSilent)
Sets adapter name.
Definition tap.c:968
static const WCHAR szAdapterRegKeyPathTemplate[]
Definition tap.c:47
static DWORD ExecNetsh(PWSTR cmdline)
Definition tap.c:925
#define ADAPTER_REGKEY_PATH_MAX
Definition tap.c:49
static DWORD get_device_reg_property(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _In_ DWORD dwProperty, _Out_opt_ LPDWORD pdwPropertyRegDataType, _Out_ LPVOID *ppData)
Returns a specified Plug and Play device property.
Definition tap.c:600
static size_t wcszlen(_In_z_ LPCWSTR szz)
Returns length of string of strings.
Definition tap.c:111
DWORD tap_create_adapter(_In_opt_ HWND hwndParent, _In_opt_ LPCWSTR szDeviceDescription, _In_ LPCWSTR szHwId, _Inout_ LPBOOL pbRebootRequired, _Out_ LPGUID pguidAdapter)
Creates a TUN/TAP adapter.
Definition tap.c:667
static void * find_function(const WCHAR *libname, const char *funcname, HMODULE *m)
Dynamically load a library and find a function in it.
Definition tap.c:67
DWORD tap_delete_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter, _Inout_ LPBOOL pbRebootRequired)
Deletes an adapter.
Definition tap.c:909
static DWORD delete_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _Inout_ LPBOOL pbRebootRequired)
Deletes the device.
Definition tap.c:225
static DWORD check_reboot(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _Inout_ LPBOOL pbRebootRequired)
Checks device install parameters if a system reboot is required.
Definition tap.c:183
void tap_free_adapter_list(_In_ struct tap_adapter_node *pAdapterList)
Frees a list of network adapters.
Definition tap.c:1231
static const GUID GUID_DEVCLASS_NET
Definition tap.c:43
static DWORD get_reg_string(_In_ HKEY hKey, _In_ LPCWSTR szName, _Out_ LPWSTR *pszValue)
Reads string value from registry key.
Definition tap.c:379
DWORD tap_enable_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter, _In_ BOOL bEnable, _Inout_ LPBOOL pbRebootRequired)
Enables or disables an adapter.
Definition tap.c:917
static DWORD change_device_state(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _In_ BOOL bEnable, _Inout_ LPBOOL pbRebootRequired)
Changes the device state.
Definition tap.c:279
#define _Out_opt_
Definition basic.h:59
#define _Inout_
Definition basic.h:50
#define _In_z_
Definition basic.h:47
#define _Out_
Definition basic.h:56
#define _In_
Definition basic.h:41
#define _In_opt_
Definition basic.h:44