OpenVPN
main.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) 2002-2025 OpenVPN Inc <sales@openvpn.net>
6 * Copyright (C) 2018-2025 Simon Rozman <simon@rozman.si>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License version 2
10 * as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, see <https://www.gnu.org/licenses/>.
19 */
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include "tap.h"
26#include "error.h"
27
28#include <objbase.h>
29#include <setupapi.h>
30#include <stdio.h>
31#include <wchar.h>
32
33#ifdef _MSC_VER
34#pragma comment(lib, "ole32.lib")
35#pragma comment(lib, "setupapi.lib")
36#endif
37
38
39/* clang-format off */
40const WCHAR title_string[] =
41 _L(PACKAGE_NAME) L" " _L(PACKAGE_VERSION)
42;
43
44static const WCHAR usage_message[] =
45 L"%ls\n"
46 L"\n"
47 L"Usage:\n"
48 L"\n"
49 L"tapctl <command> [<command specific options>]\n"
50 L"\n"
51 L"Commands:\n"
52 L"\n"
53 L"create Create a new VPN network adapter\n"
54 L"list List VPN network adapters\n"
55 L"delete Delete specified VPN network adapter\n"
56 L"help Display this text\n"
57 L"\n"
58 L"Hint: Use \"tapctl help <command>\" to display help for particular command.\n"
59;
60
61static const WCHAR usage_message_create[] =
62 L"%ls\n"
63 L"\n"
64 L"Creates a new VPN network adapter\n"
65 L"\n"
66 L"Usage:\n"
67 L"\n"
68 L"tapctl create [<options>]\n"
69 L"\n"
70 L"Options:\n"
71 L"\n"
72 L"--name <name> Set VPN network adapter name. Should the adapter with given \n"
73 L" name already exist, an error is returned. If this option is not \n"
74 L" specified, an OpenVPN-specific default name is chosen. \n"
75 L" Note: This name can also be specified as OpenVPN's --dev-node \n"
76 L" option. \n"
77 L"--hwid <hwid> Adapter hardware ID. Default value is ovpn-dco, which uses \n"
78 L" the OpenVPN Data Channel Offload driver. To work with \n"
79 L" tap-windows6 driver, specify root\\tap0901 or tap0901. \n"
80 L"\n"
81 L"Output:\n"
82 L"\n"
83 L"This command prints newly created VPN network adapter's GUID, name and \n"
84 L"hardware ID to stdout. \n"
85;
86
87static const WCHAR usage_message_list[] =
88 L"%ls\n"
89 L"\n"
90 L"Lists VPN network adapters\n"
91 L"\n"
92 L"Usage:\n"
93 L"\n"
94 L"tapctl list\n"
95 L"\n"
96 L"Options:\n"
97 L"\n"
98 L"--hwid <hwid> Adapter hardware ID. By default, root\\tap0901, tap0901 and \n"
99 L" ovpn-dco adapters are listed. Use this switch to limit the list.\n"
100 L"\n"
101 L"Output:\n"
102 L"\n"
103 L"This command prints VPN network adapter GUID, name and hardware ID to stdout. \n"
104;
105
106static const WCHAR usage_message_delete[] =
107 L"%ls\n"
108 L"\n"
109 L"Deletes the specified VPN network adapter\n"
110 L"\n"
111 L"Usage:\n"
112 L"\n"
113 L"tapctl delete <adapter GUID | adapter name>\n"
114;
115/* clang-format on */
116
117
121static void
122usage(void)
123{
124 fwprintf(stderr, usage_message, title_string);
125}
126
135static struct tap_adapter_node *
136find_adapter_by_name(LPCWSTR name, struct tap_adapter_node *adapter_list)
137{
138 for (struct tap_adapter_node *a = adapter_list; a; a = a->pNext)
139 {
140 if (_wcsicmp(name, a->szName) == 0)
141 {
142 return a;
143 }
144 }
145
146 return NULL;
147}
148
160static BOOL
162{
163 static const WCHAR class_key[] =
164 L"SYSTEM\\CurrentControlSet\\Control\\Network\\{4d36e972-e325-11ce-bfc1-08002be10318}";
165
166 HKEY hClassKey = NULL;
167 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, class_key, 0, KEY_READ, &hClassKey) != ERROR_SUCCESS)
168 {
169 return FALSE;
170 }
171
172 BOOL found = FALSE;
173
174 for (DWORD index = 0;; ++index)
175 {
176 WCHAR adapter_id[64];
177 DWORD adapter_id_len = _countof(adapter_id);
178 LONG result = RegEnumKeyEx(hClassKey, index, adapter_id, &adapter_id_len, NULL, NULL, NULL,
179 NULL);
180 if (result == ERROR_NO_MORE_ITEMS)
181 {
182 break;
183 }
184 else if (result != ERROR_SUCCESS)
185 {
186 continue;
187 }
188
189 WCHAR connection_key[512];
190 swprintf_s(connection_key, _countof(connection_key), L"%ls\\%ls\\Connection", class_key,
191 adapter_id);
192
193 DWORD value_size = 0;
194 LONG query = RegGetValueW(HKEY_LOCAL_MACHINE, connection_key, L"Name",
195 RRF_RT_REG_SZ | RRF_NOEXPAND, NULL, NULL, &value_size);
196 if (query != ERROR_SUCCESS || value_size < sizeof(WCHAR))
197 {
198 continue;
199 }
200
201 LPWSTR value = (LPWSTR)malloc(value_size);
202 if (!value)
203 {
204 continue;
205 }
206
207 query = RegGetValueW(HKEY_LOCAL_MACHINE, connection_key, L"Name",
208 RRF_RT_REG_SZ | RRF_NOEXPAND, NULL, value, &value_size);
209 if (query == ERROR_SUCCESS && _wcsicmp(name, value) == 0)
210 {
211 found = TRUE;
212 free(value);
213 break;
214 }
215
216 free(value);
217
218 if (found)
219 {
220 break;
221 }
222 }
223
224 RegCloseKey(hClassKey);
225 return found;
226}
227
237static BOOL
238tap_name_in_use(LPCWSTR name, struct tap_adapter_node *adapter_list)
239{
240 if (name == NULL)
241 {
242 return FALSE;
243 }
244
245 if (find_adapter_by_name(name, adapter_list))
246 {
247 return TRUE;
248 }
249
250 return registry_name_exists(name);
251}
252
260BOOL
262{
263 if (name == NULL)
264 {
265 return FALSE;
266 }
267
268 size_t length = wcslen(name);
269 if (length == 0 || length > 255)
270 {
271 return FALSE;
272 }
273
274 static const WCHAR invalid_chars[] = L"\\/:*?\"<>|";
275
276 for (const WCHAR *p = name; *p; ++p)
277 {
278 WCHAR ch = *p;
279 if (ch < L' ')
280 {
281 return FALSE;
282 }
283 if (wcschr(invalid_chars, ch))
284 {
285 return FALSE;
286 }
287 }
288
289 return TRUE;
290}
291
305static LPWSTR
306tap_resolve_adapter_name(LPCWSTR requested_name, LPCWSTR hwid,
307 struct tap_adapter_node *adapter_list)
308{
309 if (requested_name && requested_name[0])
310 {
311 if (!tap_is_valid_adapter_name(requested_name))
312 {
313 fwprintf(stderr,
314 L"Adapter name \"%ls\" contains invalid characters. Avoid tabs or the "
315 L"characters \\ / : * ? \" < > | and keep the length within 255 characters.\n",
316 requested_name);
317 return NULL;
318 }
319
320 struct tap_adapter_node *conflict = find_adapter_by_name(requested_name, adapter_list);
321 if (conflict)
322 {
323 LPOLESTR adapter_id = NULL;
324 StringFromIID((REFIID)&conflict->guid, &adapter_id);
325 fwprintf(stderr,
326 L"Adapter \"%ls\" already exists (GUID %"
327 L"ls).\n",
328 conflict->szName, adapter_id);
329 CoTaskMemFree(adapter_id);
330 return NULL;
331 }
332
333 if (registry_name_exists(requested_name))
334 {
335 fwprintf(stderr, L"Adapter name \"%ls\" is already in use.\n", requested_name);
336 return NULL;
337 }
338
339 return wcsdup(requested_name);
340 }
341
342 if (hwid == NULL)
343 {
344 return NULL;
345 }
346
347 LPCWSTR base_name = NULL;
348 if (_wcsicmp(hwid, L"ovpn-dco") == 0)
349 {
350 base_name = L"OpenVPN Data Channel Offload";
351 }
352 else if (_wcsicmp(hwid, L"root\\" _L(TAP_WIN_COMPONENT_ID)) == 0
353 || _wcsicmp(hwid, _L(TAP_WIN_COMPONENT_ID)) == 0)
354 {
355 base_name = L"OpenVPN TAP-Windows6";
356 }
357 else
358 {
359 fwprintf(stderr,
360 L"Cannot auto-generate adapter name for hardware ID \"%ls\".\n", hwid);
361 return NULL;
362 }
363
364 if (!tap_name_in_use(base_name, adapter_list))
365 {
366 return wcsdup(base_name);
367 }
368
369 size_t name_len = wcslen(base_name) + 10;
370 LPWSTR name = (LPWSTR)malloc(name_len * sizeof(WCHAR));
371 if (name == NULL)
372 {
373 return NULL;
374 }
375
376 /* Windows never assigns the "#1" suffix, so skip it to avoid netsh failures. */
377 for (int i = 2; i < 100; ++i)
378 {
379 swprintf_s(name, name_len, L"%ls #%d", base_name, i);
380
381 if (!tap_name_in_use(name, adapter_list))
382 {
383 return name;
384 }
385 }
386
387 free(name);
388 fwprintf(stderr, L"Unable to find available adapter name based on \"%ls\".\n", base_name);
389 return NULL;
390}
391
392static int
393command_create(int argc, LPCWSTR argv[], BOOL *bRebootRequired)
394{
395 LPCWSTR szName = NULL;
396 LPCWSTR defaultHwId = L"ovpn-dco";
397 LPCWSTR szHwId = defaultHwId;
398 LPWSTR adapter_name = NULL;
399 struct tap_adapter_node *pAdapterList = NULL;
400 GUID guidAdapter;
401 LPOLESTR szAdapterId = NULL;
402 DWORD dwResult;
403 int iResult = 1;
404 BOOL adapter_created = FALSE;
405
406 for (int i = 2; i < argc; i++)
407 {
408 if (wcsicmp(argv[i], L"--name") == 0)
409 {
410 if (++i >= argc)
411 {
412 fwprintf(stderr, L"--name option requires a value. Ignored.\n");
413 break;
414 }
415 szName = argv[i];
416 if (szName[0] == L'\0')
417 {
418 fwprintf(stderr, L"--name option cannot be empty. Ignored.\n");
419 szName = NULL;
420 }
421 }
422 else if (wcsicmp(argv[i], L"--hwid") == 0)
423 {
424 if (++i >= argc)
425 {
426 fwprintf(stderr,
427 L"--hwid option requires a value. Using default \"%ls\".\n",
428 defaultHwId);
429 break;
430 }
431 szHwId = argv[i];
432 if (szHwId[0] == L'\0')
433 {
434 fwprintf(stderr,
435 L"--hwid option cannot be empty. Using default \"%ls\".\n",
436 defaultHwId);
437 szHwId = defaultHwId;
438 }
439 }
440 else
441 {
442 fwprintf(stderr,
443 L"Unknown option \"%ls"
444 L"\". Please, use \"tapctl help create\" to list supported options. Ignored.\n",
445 argv[i]);
446 }
447 }
448
449 dwResult = tap_create_adapter(NULL, L"Virtual Ethernet", szHwId, bRebootRequired,
450 &guidAdapter);
451 if (dwResult != ERROR_SUCCESS)
452 {
453 fwprintf(stderr, L"Creating network adapter failed (error 0x%x).\n", dwResult);
454 goto cleanup;
455 }
456 adapter_created = TRUE;
457
458 dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
459 if (dwResult != ERROR_SUCCESS)
460 {
461 fwprintf(stderr, L"Enumerating adapters failed (error 0x%x).\n", dwResult);
462 goto cleanup;
463 }
464
465 adapter_name = tap_resolve_adapter_name(szName, szHwId, pAdapterList);
466 if (adapter_name == NULL)
467 {
468 goto cleanup;
469 }
470
471 dwResult = tap_set_adapter_name(&guidAdapter, adapter_name, FALSE);
472 if (dwResult != ERROR_SUCCESS)
473 {
474 StringFromIID((REFIID)&guidAdapter, &szAdapterId);
475 fwprintf(stderr,
476 L"Renaming network adapter %ls to \"%ls\" failed (error 0x%x).\n", szAdapterId,
477 adapter_name, dwResult);
478 CoTaskMemFree(szAdapterId);
479 goto cleanup;
480 }
481
482 iResult = 0;
483 StringFromIID((REFIID)&guidAdapter, &szAdapterId);
484 const WCHAR *name_to_print = (adapter_name && adapter_name[0]) ? adapter_name : L"(unnamed)";
485 const WCHAR *hwid_to_print = (szHwId && szHwId[0]) ? szHwId : L"(unknown hwid)";
486 fwprintf(stdout, L"%ls\t%ls\t%ls\n", szAdapterId, name_to_print, hwid_to_print);
487 CoTaskMemFree(szAdapterId);
488
489cleanup:
490 if (pAdapterList)
491 {
492 tap_free_adapter_list(pAdapterList);
493 }
494 free(adapter_name);
495
496 if (adapter_created && iResult != 0)
497 {
498 tap_delete_adapter(NULL, &guidAdapter, bRebootRequired);
499 }
500
501 return iResult;
502}
503
504static int
505command_list(int argc, LPCWSTR argv[])
506{
507 WCHAR szzHwId[0x100] =
508 L"root\\" _L(TAP_WIN_COMPONENT_ID) L"\0" _L(TAP_WIN_COMPONENT_ID) L"\0"
509 L"ovpn-dco\0";
510
511 for (int i = 2; i < argc; i++)
512 {
513 if (wcsicmp(argv[i], L"--hwid") == 0)
514 {
515 memset(szzHwId, 0, sizeof(szzHwId));
516 ++i;
517 memcpy_s(szzHwId,
518 sizeof(szzHwId) - 2 * sizeof(WCHAR),
519 argv[i], wcslen(argv[i]) * sizeof(WCHAR));
520 }
521 else
522 {
523 fwprintf(stderr,
524 L"Unknown option \"%ls"
525 L"\". Please, use \"tapctl help list\" to list supported options. Ignored.\n",
526 argv[i]);
527 }
528 }
529
530 struct tap_adapter_node *adapter_list = NULL;
531 DWORD dwResult = tap_list_adapters(NULL, szzHwId, &adapter_list);
532 if (dwResult != ERROR_SUCCESS)
533 {
534 fwprintf(stderr, L"Enumerating TUN/TAP adapters failed (error 0x%x).\n", dwResult);
535 return 1;
536 }
537
538 for (struct tap_adapter_node *adapter = adapter_list; adapter; adapter = adapter->pNext)
539 {
540 LPOLESTR adapter_id = NULL;
541 StringFromIID((REFIID)&adapter->guid, &adapter_id);
542 const WCHAR *name = adapter->szName ? adapter->szName : L"";
543 const WCHAR *hwid = (adapter->szzHardwareIDs && adapter->szzHardwareIDs[0])
544 ? adapter->szzHardwareIDs
545 : L"";
546 fwprintf(stdout, L"%ls\t%ls\t%ls\n", adapter_id, name, hwid);
547 CoTaskMemFree(adapter_id);
548 }
549
550 tap_free_adapter_list(adapter_list);
551
552 return 0;
553}
554
555static int
556command_delete(int argc, LPCWSTR argv[], BOOL *bRebootRequired)
557{
558 if (argc < 3)
559 {
560 fwprintf(stderr,
561 L"Missing adapter GUID or name. Please, use \"tapctl help delete\" for usage info.\n");
562 return 1;
563 }
564
565 GUID guidAdapter;
566 if (FAILED(IIDFromString(argv[2], (LPIID)&guidAdapter)))
567 {
568 struct tap_adapter_node *adapter_list = NULL;
569 DWORD dwResult = tap_list_adapters(NULL, NULL, &adapter_list);
570 if (dwResult != ERROR_SUCCESS)
571 {
572 fwprintf(stderr, L"Enumerating TUN/TAP adapters failed (error 0x%x).\n", dwResult);
573 return 1;
574 }
575
576 BOOL found = FALSE;
577 for (struct tap_adapter_node *adapter = adapter_list; adapter; adapter = adapter->pNext)
578 {
579 if (wcsicmp(argv[2], adapter->szName) == 0)
580 {
581 memcpy(&guidAdapter, &adapter->guid, sizeof(GUID));
582 found = TRUE;
583 break;
584 }
585 }
586
587 tap_free_adapter_list(adapter_list);
588
589 if (!found)
590 {
591 fwprintf(stderr, L"\"%ls\" adapter not found.\n", argv[2]);
592 return 1;
593 }
594 }
595
596 DWORD dwResult = tap_delete_adapter(NULL, &guidAdapter, bRebootRequired);
597 if (dwResult != ERROR_SUCCESS)
598 {
599 fwprintf(stderr,
600 L"Deleting adapter \"%ls"
601 L"\" failed (error 0x%x).\n",
602 argv[2], dwResult);
603 return 1;
604 }
605
606 return 0;
607}
608
612int __cdecl wmain(int argc, LPCWSTR argv[])
613{
614 int iResult;
615 BOOL bRebootRequired = FALSE;
616
617 /* Ask SetupAPI to keep quiet. */
618 SetupSetNonInteractiveMode(TRUE);
619
620 if (argc < 2)
621 {
622 usage();
623 return 1;
624 }
625 else if (wcsicmp(argv[1], L"help") == 0)
626 {
627 /* Output help. */
628 if (argc < 3)
629 {
630 usage();
631 }
632 else if (wcsicmp(argv[2], L"create") == 0)
633 {
634 fwprintf(stderr, usage_message_create, title_string);
635 }
636 else if (wcsicmp(argv[2], L"list") == 0)
637 {
638 fwprintf(stderr, usage_message_list, title_string);
639 }
640 else if (wcsicmp(argv[2], L"delete") == 0)
641 {
642 fwprintf(stderr, usage_message_delete, title_string);
643 }
644 else
645 {
646 fwprintf(stderr,
647 L"Unknown command \"%ls"
648 L"\". Please, use \"tapctl help\" to list supported commands.\n",
649 argv[2]);
650 }
651
652 return 1;
653 }
654 else if (wcsicmp(argv[1], L"create") == 0)
655 {
656 iResult = command_create(argc, argv, &bRebootRequired);
657 goto quit;
658 }
659 else if (wcsicmp(argv[1], L"list") == 0)
660 {
661 iResult = command_list(argc, argv);
662 goto quit;
663 }
664 else if (wcsicmp(argv[1], L"delete") == 0)
665 {
666 iResult = command_delete(argc, argv, &bRebootRequired);
667 goto quit;
668 }
669 else
670 {
671 fwprintf(stderr,
672 L"Unknown command \"%ls"
673 L"\". Please, use \"tapctl help\" to list supported commands.\n",
674 argv[1]);
675 return 1;
676 }
677
678quit:
679 if (bRebootRequired)
680 {
681 fwprintf(stderr, L"A system reboot is required.\n");
682 }
683
684 return iResult;
685}
686
687
688bool
689dont_mute(unsigned int flags)
690{
691 UNREFERENCED_PARAMETER(flags);
692
693 return true;
694}
695
696
697void
698x_msg_va(const unsigned int flags, const char *format, va_list arglist)
699{
700 /* Output message string. Note: Message strings don't contain line terminators. */
701 vfprintf(stderr, format, arglist);
702 fwprintf(stderr, L"\n");
703
704 if ((flags & M_ERRNO) != 0)
705 {
706 /* Output system error message (if possible). */
707 DWORD dwResult = GetLastError();
708 LPWSTR szErrMessage = NULL;
709 if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
710 | FORMAT_MESSAGE_IGNORE_INSERTS,
711 0, dwResult, 0, (LPWSTR)&szErrMessage, 0, NULL)
712 && szErrMessage)
713 {
714 /* Trim trailing whitespace. Set terminator after the last non-whitespace character.
715 * This prevents excessive trailing line breaks. */
716 for (size_t i = 0, i_last = 0;; i++)
717 {
718 if (szErrMessage[i])
719 {
720 if (!iswspace(szErrMessage[i]))
721 {
722 i_last = i + 1;
723 }
724 }
725 else
726 {
727 szErrMessage[i_last] = 0;
728 break;
729 }
730 }
731
732 /* Output error message. */
733 fwprintf(stderr, L"Error 0x%x: %ls\n", dwResult, szErrMessage);
734
735 LocalFree(szErrMessage);
736 }
737 else
738 {
739 fwprintf(stderr, L"Error 0x%x\n", dwResult);
740 }
741 }
742}
int __cdecl wmain(int argc, LPCWSTR argv[])
Program entry point.
Definition main.c:612
static const WCHAR usage_message_delete[]
Definition main.c:106
BOOL tap_is_valid_adapter_name(LPCWSTR name)
Check whether a proposed adapter name satisfies Windows connection-name rules.
Definition main.c:261
static const WCHAR usage_message_list[]
Definition main.c:87
bool dont_mute(unsigned int flags)
Check muting filter.
Definition main.c:689
static int command_list(int argc, LPCWSTR argv[])
Definition main.c:505
static BOOL tap_name_in_use(LPCWSTR name, struct tap_adapter_node *adapter_list)
Determine whether a friendly name is currently in use by an adapter or reserved in the registry.
Definition main.c:238
static LPWSTR tap_resolve_adapter_name(LPCWSTR requested_name, LPCWSTR hwid, struct tap_adapter_node *adapter_list)
Resolve the adapter name we should apply:
Definition main.c:306
static int command_delete(int argc, LPCWSTR argv[], BOOL *bRebootRequired)
Definition main.c:556
void x_msg_va(const unsigned int flags, const char *format, va_list arglist)
Definition main.c:698
static BOOL registry_name_exists(LPCWSTR name)
Check whether the registry still reserves a given network-connection name.
Definition main.c:161
static int command_create(int argc, LPCWSTR argv[], BOOL *bRebootRequired)
Definition main.c:393
const WCHAR title_string[]
Definition main.c:40
static const WCHAR usage_message_create[]
Definition main.c:61
static const WCHAR usage_message[]
Definition main.c:44
static void usage(void)
Print the help message.
Definition main.c:122
static struct tap_adapter_node * find_adapter_by_name(LPCWSTR name, struct tap_adapter_node *adapter_list)
Locate an adapter node by its friendly name within the enumerated list.
Definition main.c:136
#define M_ERRNO
Definition error.h:95
Definition argv.h:35
Network adapter list node.
Definition tap.h:124
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:1036
DWORD tap_set_adapter_name(_In_ LPCGUID pguidAdapter, _In_ LPCWSTR szName, _In_ BOOL bSilent)
Sets adapter name.
Definition tap.c:963
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
DWORD tap_delete_adapter(_In_opt_ HWND hwndParent, _In_ LPCGUID pguidAdapter, _Inout_ LPBOOL pbRebootRequired)
Deletes an adapter.
Definition tap.c:909
void tap_free_adapter_list(_In_ struct tap_adapter_node *pAdapterList)
Frees a list of network adapters.
Definition tap.c:1226
#define _L(q)
Definition basic.h:38
static int cleanup(void **state)