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 TUN/TAP adapter\n"
54 L"list List TUN/TAP adapters\n"
55 L"delete Delete specified 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 TUN/TAP 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 TUN/TAP adapter name. Should the adapter with given name \n"
73 L" already exist, an error is returned. If this option is not \n"
74 L" specified, a default adapter name is chosen by Windows. \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 root\\tap0901, which \n"
78 L" describes tap-windows6 driver. To work with ovpn-dco driver, \n"
79 L" driver, specify 'ovpn-dco'. \n"
80 L"\n"
81 L"Output:\n"
82 L"\n"
83 L"This command prints newly created TUN/TAP adapter's GUID to stdout. \n"
84;
85
86static const WCHAR usage_message_list[] =
87 L"%ls\n"
88 L"\n"
89 L"Lists TUN/TAP adapters\n"
90 L"\n"
91 L"Usage:\n"
92 L"\n"
93 L"tapctl list\n"
94 L"\n"
95 L"Options:\n"
96 L"\n"
97 L"--hwid <hwid> Adapter hardware ID. By default, root\\tap0901, tap0901 and \n"
98 L" ovpn-dco adapters are listed. Use this switch to limit the list.\n"
99 L"\n"
100 L"Output:\n"
101 L"\n"
102 L"This command prints all TUN/TAP adapters to stdout. \n"
103;
104
105static const WCHAR usage_message_delete[] =
106 L"%ls\n"
107 L"\n"
108 L"Deletes the specified network adapter\n"
109 L"\n"
110 L"Usage:\n"
111 L"\n"
112 L"tapctl delete <adapter GUID | adapter name>\n"
113;
114/* clang-format on */
115
116
120static void
121usage(void)
122{
123 fwprintf(stderr, usage_message, title_string);
124}
125
129static BOOL
130is_adapter_name_available(LPCWSTR name, struct tap_adapter_node *adapter_list, BOOL log)
131{
132 for (struct tap_adapter_node *a = adapter_list; a; a = a->pNext)
133 {
134 if (wcsicmp(name, a->szName) == 0)
135 {
136 if (log)
137 {
138 LPOLESTR adapter_id = NULL;
139 StringFromIID((REFIID)&a->guid, &adapter_id);
140 fwprintf(stderr,
141 L"Adapter \"%ls\" already exists (GUID %"
142 L"ls).\n",
143 a->szName, adapter_id);
144 CoTaskMemFree(adapter_id);
145 }
146
147 return FALSE;
148 }
149 }
150
151 return TRUE;
152}
153
158static LPWSTR
159get_unique_adapter_name(LPCWSTR hwid, struct tap_adapter_node *adapter_list)
160{
161 if (hwid == NULL)
162 {
163 return NULL;
164 }
165
166 LPCWSTR base_name;
167 if (wcsicmp(hwid, L"ovpn-dco") == 0)
168 {
169 base_name = L"OpenVPN Data Channel Offload";
170 }
171 else if (wcsicmp(hwid, L"root\\" _L(TAP_WIN_COMPONENT_ID)) == 0)
172 {
173 base_name = L"OpenVPN TAP-Windows6";
174 }
175 else
176 {
177 return NULL;
178 }
179
180 if (is_adapter_name_available(base_name, adapter_list, FALSE))
181 {
182 return wcsdup(base_name);
183 }
184
185 size_t name_len = wcslen(base_name) + 10;
186 LPWSTR name = malloc(name_len * sizeof(WCHAR));
187 if (name == NULL)
188 {
189 return NULL;
190 }
191 for (int i = 1; i < 100; ++i)
192 {
193 swprintf_s(name, name_len, L"%ls #%d", base_name, i);
194
195 if (is_adapter_name_available(name, adapter_list, FALSE))
196 {
197 return name;
198 }
199 }
200
201 return NULL;
202}
203
207int __cdecl wmain(int argc, LPCWSTR argv[])
208{
209 int iResult;
210 BOOL bRebootRequired = FALSE;
211
212 /* Ask SetupAPI to keep quiet. */
213 SetupSetNonInteractiveMode(TRUE);
214
215 if (argc < 2)
216 {
217 usage();
218 return 1;
219 }
220 else if (wcsicmp(argv[1], L"help") == 0)
221 {
222 /* Output help. */
223 if (argc < 3)
224 {
225 usage();
226 }
227 else if (wcsicmp(argv[2], L"create") == 0)
228 {
229 fwprintf(stderr, usage_message_create, title_string);
230 }
231 else if (wcsicmp(argv[2], L"list") == 0)
232 {
233 fwprintf(stderr, usage_message_list, title_string);
234 }
235 else if (wcsicmp(argv[2], L"delete") == 0)
236 {
237 fwprintf(stderr, usage_message_delete, title_string);
238 }
239 else
240 {
241 fwprintf(stderr,
242 L"Unknown command \"%ls"
243 L"\". Please, use \"tapctl help\" to list supported commands.\n",
244 argv[2]);
245 }
246
247 return 1;
248 }
249 else if (wcsicmp(argv[1], L"create") == 0)
250 {
251 LPCWSTR szName = NULL;
252 LPCWSTR szHwId = L"root\\" _L(TAP_WIN_COMPONENT_ID);
253
254 /* Parse options. */
255 for (int i = 2; i < argc; i++)
256 {
257 if (wcsicmp(argv[i], L"--name") == 0)
258 {
259 szName = argv[++i];
260 }
261 else if (wcsicmp(argv[i], L"--hwid") == 0)
262 {
263 szHwId = argv[++i];
264 }
265 else
266 {
267 fwprintf(
268 stderr,
269 L"Unknown option \"%ls"
270 L"\". Please, use \"tapctl help create\" to list supported options. Ignored.\n",
271 argv[i]);
272 }
273 }
274
275 /* Create TUN/TAP adapter. */
276 GUID guidAdapter;
277 LPOLESTR szAdapterId = NULL;
278 DWORD dwResult =
279 tap_create_adapter(NULL, L"Virtual Ethernet", szHwId, &bRebootRequired, &guidAdapter);
280 if (dwResult != ERROR_SUCCESS)
281 {
282 fwprintf(stderr, L"Creating TUN/TAP adapter failed (error 0x%x).\n", dwResult);
283 iResult = 1;
284 goto quit;
285 }
286
287 /* Get existing network adapters. */
288 struct tap_adapter_node *pAdapterList = NULL;
289 dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
290 if (dwResult != ERROR_SUCCESS)
291 {
292 fwprintf(stderr, L"Enumerating adapters failed (error 0x%x).\n", dwResult);
293 iResult = 1;
294 goto create_delete_adapter;
295 }
296
297 LPWSTR adapter_name =
298 szName ? wcsdup(szName) : get_unique_adapter_name(szHwId, pAdapterList);
299 if (adapter_name)
300 {
301 /* Check for duplicates when name was specified,
302 * otherwise get_adapter_default_name() takes care of it */
303 if (szName && !is_adapter_name_available(adapter_name, pAdapterList, TRUE))
304 {
305 iResult = 1;
306 goto create_cleanup_pAdapterList;
307 }
308
309 /* Rename the adapter. */
310 dwResult = tap_set_adapter_name(&guidAdapter, adapter_name, FALSE);
311 if (dwResult != ERROR_SUCCESS)
312 {
313 StringFromIID((REFIID)&guidAdapter, &szAdapterId);
314 fwprintf(stderr,
315 L"Renaming TUN/TAP adapter %ls"
316 L" to \"%ls\" failed (error 0x%x).\n",
317 szAdapterId, adapter_name, dwResult);
318 CoTaskMemFree(szAdapterId);
319 iResult = 1;
320 goto quit;
321 }
322 }
323
324 iResult = 0;
325
326create_cleanup_pAdapterList:
327 free(adapter_name);
328
329 tap_free_adapter_list(pAdapterList);
330 if (iResult)
331 {
332 goto create_delete_adapter;
333 }
334
335 /* Output adapter GUID. */
336 StringFromIID((REFIID)&guidAdapter, &szAdapterId);
337 fwprintf(stdout, L"%ls\n", szAdapterId);
338 CoTaskMemFree(szAdapterId);
339
340 iResult = 0;
341 goto quit;
342
343create_delete_adapter:
344 tap_delete_adapter(NULL, &guidAdapter, &bRebootRequired);
345 iResult = 1;
346 goto quit;
347 }
348 else if (wcsicmp(argv[1], L"list") == 0)
349 {
350 WCHAR szzHwId[0x100] =
351 L"root\\" _L(TAP_WIN_COMPONENT_ID) L"\0" _L(TAP_WIN_COMPONENT_ID) L"\0"
352 L"ovpn-dco\0";
353
354 /* Parse options. */
355 for (int i = 2; i < argc; i++)
356 {
357 if (wcsicmp(argv[i], L"--hwid") == 0)
358 {
359 memset(szzHwId, 0, sizeof(szzHwId));
360 ++i;
361 memcpy_s(szzHwId,
362 sizeof(szzHwId) - 2 * sizeof(WCHAR) /*requires double zero termination*/,
363 argv[i], wcslen(argv[i]) * sizeof(WCHAR));
364 }
365 else
366 {
367 fwprintf(
368 stderr,
369 L"Unknown option \"%ls"
370 L"\". Please, use \"tapctl help list\" to list supported options. Ignored.\n",
371 argv[i]);
372 }
373 }
374
375 /* Output list of adapters with given hardware ID. */
376 struct tap_adapter_node *pAdapterList = NULL;
377 DWORD dwResult = tap_list_adapters(NULL, szzHwId, &pAdapterList);
378 if (dwResult != ERROR_SUCCESS)
379 {
380 fwprintf(stderr, L"Enumerating TUN/TAP adapters failed (error 0x%x).\n", dwResult);
381 iResult = 1;
382 goto quit;
383 }
384
385 for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)
386 {
387 LPOLESTR szAdapterId = NULL;
388 StringFromIID((REFIID)&pAdapter->guid, &szAdapterId);
389 fwprintf(stdout,
390 L"%ls\t%"
391 L"ls\n",
392 szAdapterId, pAdapter->szName);
393 CoTaskMemFree(szAdapterId);
394 }
395
396 iResult = 0;
397 tap_free_adapter_list(pAdapterList);
398 }
399 else if (wcsicmp(argv[1], L"delete") == 0)
400 {
401 if (argc < 3)
402 {
403 fwprintf(
404 stderr,
405 L"Missing adapter GUID or name. Please, use \"tapctl help delete\" for usage info.\n");
406 return 1;
407 }
408
409 GUID guidAdapter;
410 if (FAILED(IIDFromString(argv[2], (LPIID)&guidAdapter)))
411 {
412 /* The argument failed to covert to GUID. Treat it as the adapter name. */
413 struct tap_adapter_node *pAdapterList = NULL;
414 DWORD dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
415 if (dwResult != ERROR_SUCCESS)
416 {
417 fwprintf(stderr, L"Enumerating TUN/TAP adapters failed (error 0x%x).\n", dwResult);
418 iResult = 1;
419 goto quit;
420 }
421
422 for (struct tap_adapter_node *pAdapter = pAdapterList;; pAdapter = pAdapter->pNext)
423 {
424 if (pAdapter == NULL)
425 {
426 fwprintf(stderr, L"\"%ls\" adapter not found.\n", argv[2]);
427 iResult = 1;
428 goto delete_cleanup_pAdapterList;
429 }
430 else if (wcsicmp(argv[2], pAdapter->szName) == 0)
431 {
432 memcpy(&guidAdapter, &pAdapter->guid, sizeof(GUID));
433 break;
434 }
435 }
436
437 iResult = 0;
438
439delete_cleanup_pAdapterList:
440 tap_free_adapter_list(pAdapterList);
441 if (iResult)
442 {
443 goto quit;
444 }
445 }
446
447 /* Delete the network adapter. */
448 DWORD dwResult = tap_delete_adapter(NULL, &guidAdapter, &bRebootRequired);
449 if (dwResult != ERROR_SUCCESS)
450 {
451 fwprintf(stderr,
452 L"Deleting adapter \"%ls"
453 L"\" failed (error 0x%x).\n",
454 argv[2], dwResult);
455 iResult = 1;
456 goto quit;
457 }
458
459 iResult = 0;
460 goto quit;
461 }
462 else
463 {
464 fwprintf(stderr,
465 L"Unknown command \"%ls"
466 L"\". Please, use \"tapctl help\" to list supported commands.\n",
467 argv[1]);
468 return 1;
469 }
470
471quit:
472 if (bRebootRequired)
473 {
474 fwprintf(stderr, L"A system reboot is required.\n");
475 }
476
477 return iResult;
478}
479
480
481bool
482dont_mute(unsigned int flags)
483{
484 UNREFERENCED_PARAMETER(flags);
485
486 return true;
487}
488
489
490void
491x_msg_va(const unsigned int flags, const char *format, va_list arglist)
492{
493 /* Output message string. Note: Message strings don't contain line terminators. */
494 vfprintf(stderr, format, arglist);
495 fwprintf(stderr, L"\n");
496
497 if ((flags & M_ERRNO) != 0)
498 {
499 /* Output system error message (if possible). */
500 DWORD dwResult = GetLastError();
501 LPWSTR szErrMessage = NULL;
502 if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
503 | FORMAT_MESSAGE_IGNORE_INSERTS,
504 0, dwResult, 0, (LPWSTR)&szErrMessage, 0, NULL)
505 && szErrMessage)
506 {
507 /* Trim trailing whitespace. Set terminator after the last non-whitespace character.
508 * This prevents excessive trailing line breaks. */
509 for (size_t i = 0, i_last = 0;; i++)
510 {
511 if (szErrMessage[i])
512 {
513 if (!iswspace(szErrMessage[i]))
514 {
515 i_last = i + 1;
516 }
517 }
518 else
519 {
520 szErrMessage[i_last] = 0;
521 break;
522 }
523 }
524
525 /* Output error message. */
526 fwprintf(stderr, L"Error 0x%x: %ls\n", dwResult, szErrMessage);
527
528 LocalFree(szErrMessage);
529 }
530 else
531 {
532 fwprintf(stderr, L"Error 0x%x\n", dwResult);
533 }
534 }
535}
int __cdecl wmain(int argc, LPCWSTR argv[])
Program entry point.
Definition main.c:207
static const WCHAR usage_message_delete[]
Definition main.c:105
static BOOL is_adapter_name_available(LPCWSTR name, struct tap_adapter_node *adapter_list, BOOL log)
Checks if adapter with given name doesn't already exist.
Definition main.c:130
static const WCHAR usage_message_list[]
Definition main.c:86
bool dont_mute(unsigned int flags)
Check muting filter.
Definition main.c:482
void x_msg_va(const unsigned int flags, const char *format, va_list arglist)
Definition main.c:491
static LPWSTR get_unique_adapter_name(LPCWSTR hwid, struct tap_adapter_node *adapter_list)
Returns unique adapter name based on hwid or NULL if name cannot be generated.
Definition main.c:159
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:121
#define M_ERRNO
Definition error.h:93
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
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