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
53#ifndef HAVE_DIINSTALLDEVICE
67static void *
68find_function(const WCHAR *libname, const char *funcname, HMODULE *m)
69{
70 WCHAR libpath[MAX_PATH];
71 void *fptr = NULL;
72
73 /* Make sure the dll is loaded from the system32 folder */
74 if (!GetSystemDirectoryW(libpath, _countof(libpath)))
75 {
76 return NULL;
77 }
78
79 /* +1 for the path seperator '\' */
80 const size_t path_length = wcslen(libpath) + 1 + wcslen(libname);
81 if (path_length >= _countof(libpath))
82 {
83 SetLastError(ERROR_INSUFFICIENT_BUFFER);
84 return NULL;
85 }
86 wcscat_s(libpath, _countof(libpath), L"\\");
87 wcscat_s(libpath, _countof(libpath), libname);
88
89 *m = LoadLibraryW(libpath);
90 if (*m == NULL)
91 {
92 return NULL;
93 }
94 fptr = GetProcAddress(*m, funcname);
95 if (!fptr)
96 {
97 FreeLibrary(*m);
98 *m = NULL;
99 return NULL;
100 }
101 return fptr;
102}
103#endif
104
112static inline size_t
113wcszlen(_In_z_ LPCWSTR szz)
114{
115 LPCWSTR s;
116 for (s = szz; s[0]; s += wcslen(s) + 1)
117 {
118 }
119 return s - szz;
120}
121
122
133static LPCWSTR
134wcszistr(_In_z_ LPCWSTR szzHay, _In_z_ LPCWSTR szNeedle)
135{
136 for (LPCWSTR s = szzHay; s[0]; s += wcslen(s) + 1)
137 {
138 if (wcsicmp(s, szNeedle) == 0)
139 {
140 return s;
141 }
142 }
143
144 return NULL;
145}
146
147
164typedef DWORD (*devop_func_t)(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
165 _Inout_ LPBOOL pbRebootRequired);
166
167
184static DWORD
185check_reboot(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
186 _Inout_ LPBOOL pbRebootRequired)
187{
188 if (pbRebootRequired == NULL)
189 {
190 return ERROR_BAD_ARGUMENTS;
191 }
192
193 SP_DEVINSTALL_PARAMS devinstall_params = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS) };
194 if (!SetupDiGetDeviceInstallParams(hDeviceInfoSet, pDeviceInfoData, &devinstall_params))
195 {
196 DWORD dwResult = GetLastError();
197 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiGetDeviceInstallParams failed", __FUNCTION__);
198 return dwResult;
199 }
200
201 if ((devinstall_params.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0)
202 {
203 *pbRebootRequired = TRUE;
204 }
205
206 return ERROR_SUCCESS;
207}
208
209
226static DWORD
227delete_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
228 _Inout_ LPBOOL pbRebootRequired)
229{
230 SP_REMOVEDEVICE_PARAMS params = {
231 .ClassInstallHeader = {
232 .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
233 .InstallFunction = DIF_REMOVE,
234 },
235 .Scope = DI_REMOVEDEVICE_GLOBAL,
236 .HwProfile = 0,
237 };
238
239 /* Set class installer parameters for DIF_REMOVE. */
240 if (!SetupDiSetClassInstallParams(hDeviceInfoSet, pDeviceInfoData, &params.ClassInstallHeader,
241 sizeof(SP_REMOVEDEVICE_PARAMS)))
242 {
243 DWORD dwResult = GetLastError();
244 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
245 return dwResult;
246 }
247
248 /* Call appropriate class installer. */
249 if (!SetupDiCallClassInstaller(DIF_REMOVE, hDeviceInfoSet, pDeviceInfoData))
250 {
251 DWORD dwResult = GetLastError();
252 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_REMOVE) failed", __FUNCTION__);
253 return dwResult;
254 }
255
256 /* Check if a system reboot is required. */
257 check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);
258 return ERROR_SUCCESS;
259}
260
261
280static DWORD
281change_device_state(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
282 _In_ BOOL bEnable, _Inout_ LPBOOL pbRebootRequired)
283{
284 SP_PROPCHANGE_PARAMS params = {
285 .ClassInstallHeader = {
286 .cbSize = sizeof(SP_CLASSINSTALL_HEADER),
287 .InstallFunction = DIF_PROPERTYCHANGE,
288 },
289 .StateChange = bEnable ? DICS_ENABLE : DICS_DISABLE,
290 .Scope = DICS_FLAG_GLOBAL,
291 .HwProfile = 0,
292 };
293
294 /* Set class installer parameters for DIF_PROPERTYCHANGE. */
295 if (!SetupDiSetClassInstallParams(hDeviceInfoSet, pDeviceInfoData, &params.ClassInstallHeader,
296 sizeof(SP_PROPCHANGE_PARAMS)))
297 {
298 DWORD dwResult = GetLastError();
299 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiSetClassInstallParams failed", __FUNCTION__);
300 return dwResult;
301 }
302
303 /* Call appropriate class installer. */
304 if (!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDeviceInfoSet, pDeviceInfoData))
305 {
306 DWORD dwResult = GetLastError();
307 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiCallClassInstaller(DIF_PROPERTYCHANGE) failed",
308 __FUNCTION__);
309 return dwResult;
310 }
311
312 /* Check if a system reboot is required. */
313 check_reboot(hDeviceInfoSet, pDeviceInfoData, pbRebootRequired);
314 return ERROR_SUCCESS;
315}
316
317
334static DWORD
335enable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
336 _Inout_ LPBOOL pbRebootRequired)
337{
338 return change_device_state(hDeviceInfoSet, pDeviceInfoData, TRUE, pbRebootRequired);
339}
340
341
358static DWORD
359disable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
360 _Inout_ LPBOOL pbRebootRequired)
361{
362 return change_device_state(hDeviceInfoSet, pDeviceInfoData, FALSE, pbRebootRequired);
363}
364
365
380static DWORD
381get_reg_string(_In_ HKEY hKey, _In_ LPCWSTR szName, _Out_ LPWSTR *pszValue)
382{
383 if (pszValue == NULL)
384 {
385 return ERROR_BAD_ARGUMENTS;
386 }
387
388 DWORD dwValueType = REG_NONE, dwSize = 0;
389 DWORD dwResult = RegQueryValueEx(hKey, szName, NULL, &dwValueType, NULL, &dwSize);
390 if (dwResult != ERROR_SUCCESS)
391 {
392 SetLastError(dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError().
393 But we do have an error code. Set last error manually. */
394 msg(M_NONFATAL | M_ERRNO, "%s: enumerating \"%ls\" registry value failed", __FUNCTION__,
395 szName);
396 return dwResult;
397 }
398
399 switch (dwValueType)
400 {
401 case REG_SZ:
402 case REG_EXPAND_SZ:
403 {
404 /* Read value. */
405 LPWSTR szValue = (LPWSTR)malloc(dwSize);
406 if (szValue == NULL)
407 {
408 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSize);
409 return ERROR_OUTOFMEMORY;
410 }
411
412 dwResult = RegQueryValueEx(hKey, szName, NULL, NULL, (LPBYTE)szValue, &dwSize);
413 if (dwResult != ERROR_SUCCESS)
414 {
415 SetLastError(
416 dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But
417 we do have an error code. Set last error manually. */
418 msg(M_NONFATAL | M_ERRNO, "%s: reading \"%ls\" registry value failed", __FUNCTION__,
419 szName);
420 free(szValue);
421 return dwResult;
422 }
423
424 if (dwValueType == REG_EXPAND_SZ)
425 {
426 /* Expand the environment strings. */
427 DWORD
428 dwSizeExp = dwSize * 2, dwCountExp =
429#ifdef UNICODE
430 dwSizeExp / sizeof(WCHAR);
431#else
432 dwSizeExp / sizeof(WCHAR)
433 - 1; /* Note: ANSI version requires one extra char. */
434#endif
435 LPWSTR szValueExp = (LPWSTR)malloc(dwSizeExp);
436 if (szValueExp == NULL)
437 {
438 free(szValue);
439 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSizeExp);
440 return ERROR_OUTOFMEMORY;
441 }
442
443 DWORD dwCountExpResult = ExpandEnvironmentStrings(szValue, szValueExp, dwCountExp);
444 if (dwCountExpResult == 0)
445 {
446 msg(M_NONFATAL | M_ERRNO, "%s: expanding \"%ls\" registry value failed",
447 __FUNCTION__, szName);
448 free(szValueExp);
449 free(szValue);
450 return dwResult;
451 }
452 else if (dwCountExpResult <= dwCountExp)
453 {
454 /* The buffer was big enough. */
455 free(szValue);
456 *pszValue = szValueExp;
457 return ERROR_SUCCESS;
458 }
459 else
460 {
461 /* Retry with a bigger buffer. */
462 free(szValueExp);
463#ifdef UNICODE
464 dwSizeExp = dwCountExpResult * sizeof(WCHAR);
465#else
466 /* Note: ANSI version requires one extra char. */
467 dwSizeExp = (dwCountExpResult + 1) * sizeof(WCHAR);
468#endif
469 dwCountExp = dwCountExpResult;
470 szValueExp = (LPWSTR)malloc(dwSizeExp);
471 if (szValueExp == NULL)
472 {
473 free(szValue);
474 msg(M_FATAL, "%s: malloc(%u) failed", __FUNCTION__, dwSizeExp);
475 return ERROR_OUTOFMEMORY;
476 }
477
478 ExpandEnvironmentStrings(szValue, szValueExp, dwCountExp);
479 free(szValue);
480 *pszValue = szValueExp;
481 return ERROR_SUCCESS;
482 }
483 }
484 else
485 {
486 *pszValue = szValue;
487 return ERROR_SUCCESS;
488 }
489 }
490
491 default:
492 msg(M_NONFATAL, "%s: \"%ls\" registry value is not string (type %u)",
493 __FUNCTION__, szName, dwValueType);
494 return ERROR_UNSUPPORTED_TYPE;
495 }
496}
497
498
517static DWORD
518get_net_adapter_guid(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
519 _In_ int iNumAttempts, _Out_ LPGUID pguidAdapter)
520{
521 DWORD dwResult = ERROR_BAD_ARGUMENTS;
522
523 if (pguidAdapter == NULL || iNumAttempts < 1)
524 {
525 return ERROR_BAD_ARGUMENTS;
526 }
527
528 /* Open HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class<class><id> registry key. */
529 HKEY hKey = SetupDiOpenDevRegKey(hDeviceInfoSet, pDeviceInfoData, DICS_FLAG_GLOBAL, 0,
530 DIREG_DRV, KEY_READ);
531 if (hKey == INVALID_HANDLE_VALUE)
532 {
533 dwResult = GetLastError();
534 msg(M_NONFATAL | M_ERRNO, "%s: SetupDiOpenDevRegKey failed", __FUNCTION__);
535 return dwResult;
536 }
537
538 while (iNumAttempts > 0)
539 {
540 /* Query the NetCfgInstanceId value. Using get_reg_string() right on might clutter the
541 * output with error messages while the registry is still being populated. */
542 LPWSTR szCfgGuidString = NULL;
543 dwResult = RegQueryValueEx(hKey, L"NetCfgInstanceId", NULL, NULL, NULL, NULL);
544 if (dwResult != ERROR_SUCCESS)
545 {
546 if (dwResult == ERROR_FILE_NOT_FOUND && --iNumAttempts > 0)
547 {
548 /* Wait and retry. */
549 Sleep(1000);
550 continue;
551 }
552
553 SetLastError(
554 dwResult); /* MSDN does not mention RegQueryValueEx() to set GetLastError(). But we
555 do have an error code. Set last error manually. */
556 msg(M_NONFATAL | M_ERRNO, "%s: querying \"NetCfgInstanceId\" registry value failed",
557 __FUNCTION__);
558 break;
559 }
560
561 /* Read the NetCfgInstanceId value now. */
562 dwResult = get_reg_string(hKey, L"NetCfgInstanceId", &szCfgGuidString);
563 if (dwResult != ERROR_SUCCESS)
564 {
565 break;
566 }
567
568 dwResult = SUCCEEDED(CLSIDFromString(szCfgGuidString, (LPCLSID)pguidAdapter))
569 ? ERROR_SUCCESS
570 : ERROR_INVALID_DATA;
571 free(szCfgGuidString);
572 break;
573 }
574
575 RegCloseKey(hKey);
576 return dwResult;
577}
578
579
601static DWORD
602get_device_reg_property(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData,
603 _In_ DWORD dwProperty, _Out_opt_ LPDWORD pdwPropertyRegDataType,
604 _Out_ LPVOID *ppData)
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 DWORD 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:335
static LPCWSTR wcszistr(_In_z_ LPCWSTR szzHay, _In_z_ LPCWSTR szNeedle)
Checks if string is contained in the string of strings.
Definition tap.c:134
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:164
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:518
static DWORD disable_device(_In_ HDEVINFO hDeviceInfoSet, _In_ PSP_DEVINFO_DATA pDeviceInfoData, _Inout_ LPBOOL pbRebootRequired)
Disables the device.
Definition tap.c:359
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:602
static size_t wcszlen(_In_z_ LPCWSTR szz)
Returns length of string of strings.
Definition tap.c:113
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:68
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:227
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:185
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:381
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:281
#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