/* * tapctl -- Utility to manipulate TUN/TAP adapters on Windows * https://community.openvpn.net/openvpn/wiki/Tapctl * * Copyright (C) 2002-2025 OpenVPN Inc * Copyright (C) 2018-2025 Simon Rozman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include "tap.h" #include "error.h" #include #include #include #include #ifdef _MSC_VER #pragma comment(lib, "ole32.lib") #pragma comment(lib, "setupapi.lib") #endif /* clang-format off */ const WCHAR title_string[] = _L(PACKAGE_NAME) L" " _L(PACKAGE_VERSION) ; static const WCHAR usage_message[] = L"%ls\n" L"\n" L"Usage:\n" L"\n" L"tapctl []\n" L"\n" L"Commands:\n" L"\n" L"create Create a new TUN/TAP adapter\n" L"list List TUN/TAP adapters\n" L"delete Delete specified network adapter\n" L"help Display this text\n" L"\n" L"Hint: Use \"tapctl help \" to display help for particular command.\n" ; static const WCHAR usage_message_create[] = L"%ls\n" L"\n" L"Creates a new TUN/TAP adapter\n" L"\n" L"Usage:\n" L"\n" L"tapctl create []\n" L"\n" L"Options:\n" L"\n" L"--name Set TUN/TAP adapter name. Should the adapter with given name \n" L" already exist, an error is returned. If this option is not \n" L" specified, a default adapter name is chosen by Windows. \n" L" Note: This name can also be specified as OpenVPN's --dev-node \n" L" option. \n" L"--hwid Adapter hardware ID. Default value is root\\tap0901, which \n" L" describes tap-windows6 driver. To work with ovpn-dco driver, \n" L" driver, specify 'ovpn-dco'. \n" L"\n" L"Output:\n" L"\n" L"This command prints newly created TUN/TAP adapter's GUID to stdout. \n" ; static const WCHAR usage_message_list[] = L"%ls\n" L"\n" L"Lists TUN/TAP adapters\n" L"\n" L"Usage:\n" L"\n" L"tapctl list\n" L"\n" L"Options:\n" L"\n" L"--hwid Adapter hardware ID. By default, root\\tap0901, tap0901 and \n" L" ovpn-dco adapters are listed. Use this switch to limit the list.\n" L"\n" L"Output:\n" L"\n" L"This command prints all TUN/TAP adapters to stdout. \n" ; static const WCHAR usage_message_delete[] = L"%ls\n" L"\n" L"Deletes the specified network adapter\n" L"\n" L"Usage:\n" L"\n" L"tapctl delete \n" ; /* clang-format on */ /** * Print the help message. */ static void usage(void) { fwprintf(stderr, usage_message, title_string); } /** * Checks if adapter with given name doesn't already exist */ static BOOL is_adapter_name_available(LPCWSTR name, struct tap_adapter_node *adapter_list, BOOL log) { for (struct tap_adapter_node *a = adapter_list; a; a = a->pNext) { if (wcsicmp(name, a->szName) == 0) { if (log) { LPOLESTR adapter_id = NULL; StringFromIID((REFIID)&a->guid, &adapter_id); fwprintf(stderr, L"Adapter \"%ls\" already exists (GUID %" L"ls).\n", a->szName, adapter_id); CoTaskMemFree(adapter_id); } return FALSE; } } return TRUE; } /** * Returns unique adapter name based on hwid or NULL if name cannot be generated. * Caller is responsible for freeing it. */ static LPWSTR get_unique_adapter_name(LPCWSTR hwid, struct tap_adapter_node *adapter_list) { if (hwid == NULL) { return NULL; } LPCWSTR base_name; if (wcsicmp(hwid, L"ovpn-dco") == 0) { base_name = L"OpenVPN Data Channel Offload"; } else if (wcsicmp(hwid, L"root\\" _L(TAP_WIN_COMPONENT_ID)) == 0) { base_name = L"OpenVPN TAP-Windows6"; } else { return NULL; } if (is_adapter_name_available(base_name, adapter_list, FALSE)) { return wcsdup(base_name); } size_t name_len = wcslen(base_name) + 10; LPWSTR name = malloc(name_len * sizeof(WCHAR)); if (name == NULL) { return NULL; } for (int i = 1; i < 100; ++i) { swprintf_s(name, name_len, L"%ls #%d", base_name, i); if (is_adapter_name_available(name, adapter_list, FALSE)) { return name; } } return NULL; } /** * Program entry point */ int __cdecl wmain(int argc, LPCWSTR argv[]) { int iResult; BOOL bRebootRequired = FALSE; /* Ask SetupAPI to keep quiet. */ SetupSetNonInteractiveMode(TRUE); if (argc < 2) { usage(); return 1; } else if (wcsicmp(argv[1], L"help") == 0) { /* Output help. */ if (argc < 3) { usage(); } else if (wcsicmp(argv[2], L"create") == 0) { fwprintf(stderr, usage_message_create, title_string); } else if (wcsicmp(argv[2], L"list") == 0) { fwprintf(stderr, usage_message_list, title_string); } else if (wcsicmp(argv[2], L"delete") == 0) { fwprintf(stderr, usage_message_delete, title_string); } else { fwprintf(stderr, L"Unknown command \"%ls" L"\". Please, use \"tapctl help\" to list supported commands.\n", argv[2]); } return 1; } else if (wcsicmp(argv[1], L"create") == 0) { LPCWSTR szName = NULL; LPCWSTR szHwId = L"root\\" _L(TAP_WIN_COMPONENT_ID); /* Parse options. */ for (int i = 2; i < argc; i++) { if (wcsicmp(argv[i], L"--name") == 0) { szName = argv[++i]; } else if (wcsicmp(argv[i], L"--hwid") == 0) { szHwId = argv[++i]; } else { fwprintf( stderr, L"Unknown option \"%ls" L"\". Please, use \"tapctl help create\" to list supported options. Ignored.\n", argv[i]); } } /* Create TUN/TAP adapter. */ GUID guidAdapter; LPOLESTR szAdapterId = NULL; DWORD dwResult = tap_create_adapter(NULL, L"Virtual Ethernet", szHwId, &bRebootRequired, &guidAdapter); if (dwResult != ERROR_SUCCESS) { fwprintf(stderr, L"Creating TUN/TAP adapter failed (error 0x%x).\n", dwResult); iResult = 1; goto quit; } /* Get existing network adapters. */ struct tap_adapter_node *pAdapterList = NULL; dwResult = tap_list_adapters(NULL, NULL, &pAdapterList); if (dwResult != ERROR_SUCCESS) { fwprintf(stderr, L"Enumerating adapters failed (error 0x%x).\n", dwResult); iResult = 1; goto create_delete_adapter; } LPWSTR adapter_name = szName ? wcsdup(szName) : get_unique_adapter_name(szHwId, pAdapterList); if (adapter_name) { /* Check for duplicates when name was specified, * otherwise get_adapter_default_name() takes care of it */ if (szName && !is_adapter_name_available(adapter_name, pAdapterList, TRUE)) { iResult = 1; goto create_cleanup_pAdapterList; } /* Rename the adapter. */ dwResult = tap_set_adapter_name(&guidAdapter, adapter_name, FALSE); if (dwResult != ERROR_SUCCESS) { StringFromIID((REFIID)&guidAdapter, &szAdapterId); fwprintf(stderr, L"Renaming TUN/TAP adapter %ls" L" to \"%ls\" failed (error 0x%x).\n", szAdapterId, adapter_name, dwResult); CoTaskMemFree(szAdapterId); iResult = 1; goto quit; } } iResult = 0; create_cleanup_pAdapterList: free(adapter_name); tap_free_adapter_list(pAdapterList); if (iResult) { goto create_delete_adapter; } /* Output adapter GUID. */ StringFromIID((REFIID)&guidAdapter, &szAdapterId); fwprintf(stdout, L"%ls\n", szAdapterId); CoTaskMemFree(szAdapterId); iResult = 0; goto quit; create_delete_adapter: tap_delete_adapter(NULL, &guidAdapter, &bRebootRequired); iResult = 1; goto quit; } else if (wcsicmp(argv[1], L"list") == 0) { WCHAR szzHwId[0x100] = L"root\\" _L(TAP_WIN_COMPONENT_ID) L"\0" _L(TAP_WIN_COMPONENT_ID) L"\0" L"ovpn-dco\0"; /* Parse options. */ for (int i = 2; i < argc; i++) { if (wcsicmp(argv[i], L"--hwid") == 0) { memset(szzHwId, 0, sizeof(szzHwId)); ++i; memcpy_s(szzHwId, sizeof(szzHwId) - 2 * sizeof(WCHAR) /*requires double zero termination*/, argv[i], wcslen(argv[i]) * sizeof(WCHAR)); } else { fwprintf( stderr, L"Unknown option \"%ls" L"\". Please, use \"tapctl help list\" to list supported options. Ignored.\n", argv[i]); } } /* Output list of adapters with given hardware ID. */ struct tap_adapter_node *pAdapterList = NULL; DWORD dwResult = tap_list_adapters(NULL, szzHwId, &pAdapterList); if (dwResult != ERROR_SUCCESS) { fwprintf(stderr, L"Enumerating TUN/TAP adapters failed (error 0x%x).\n", dwResult); iResult = 1; goto quit; } for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext) { LPOLESTR szAdapterId = NULL; StringFromIID((REFIID)&pAdapter->guid, &szAdapterId); fwprintf(stdout, L"%ls\t%" L"ls\n", szAdapterId, pAdapter->szName); CoTaskMemFree(szAdapterId); } iResult = 0; tap_free_adapter_list(pAdapterList); } else if (wcsicmp(argv[1], L"delete") == 0) { if (argc < 3) { fwprintf( stderr, L"Missing adapter GUID or name. Please, use \"tapctl help delete\" for usage info.\n"); return 1; } GUID guidAdapter; if (FAILED(IIDFromString(argv[2], (LPIID)&guidAdapter))) { /* The argument failed to covert to GUID. Treat it as the adapter name. */ struct tap_adapter_node *pAdapterList = NULL; DWORD dwResult = tap_list_adapters(NULL, NULL, &pAdapterList); if (dwResult != ERROR_SUCCESS) { fwprintf(stderr, L"Enumerating TUN/TAP adapters failed (error 0x%x).\n", dwResult); iResult = 1; goto quit; } for (struct tap_adapter_node *pAdapter = pAdapterList;; pAdapter = pAdapter->pNext) { if (pAdapter == NULL) { fwprintf(stderr, L"\"%ls\" adapter not found.\n", argv[2]); iResult = 1; goto delete_cleanup_pAdapterList; } else if (wcsicmp(argv[2], pAdapter->szName) == 0) { memcpy(&guidAdapter, &pAdapter->guid, sizeof(GUID)); break; } } iResult = 0; delete_cleanup_pAdapterList: tap_free_adapter_list(pAdapterList); if (iResult) { goto quit; } } /* Delete the network adapter. */ DWORD dwResult = tap_delete_adapter(NULL, &guidAdapter, &bRebootRequired); if (dwResult != ERROR_SUCCESS) { fwprintf(stderr, L"Deleting adapter \"%ls" L"\" failed (error 0x%x).\n", argv[2], dwResult); iResult = 1; goto quit; } iResult = 0; goto quit; } else { fwprintf(stderr, L"Unknown command \"%ls" L"\". Please, use \"tapctl help\" to list supported commands.\n", argv[1]); return 1; } quit: if (bRebootRequired) { fwprintf(stderr, L"A system reboot is required.\n"); } return iResult; } bool dont_mute(unsigned int flags) { UNREFERENCED_PARAMETER(flags); return true; } void x_msg_va(const unsigned int flags, const char *format, va_list arglist) { /* Output message string. Note: Message strings don't contain line terminators. */ vfprintf(stderr, format, arglist); fwprintf(stderr, L"\n"); if ((flags & M_ERRNO) != 0) { /* Output system error message (if possible). */ DWORD dwResult = GetLastError(); LPWSTR szErrMessage = NULL; if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, 0, dwResult, 0, (LPWSTR)&szErrMessage, 0, NULL) && szErrMessage) { /* Trim trailing whitespace. Set terminator after the last non-whitespace character. * This prevents excessive trailing line breaks. */ for (size_t i = 0, i_last = 0;; i++) { if (szErrMessage[i]) { if (!iswspace(szErrMessage[i])) { i_last = i + 1; } } else { szErrMessage[i_last] = 0; break; } } /* Output error message. */ fwprintf(stderr, L"Error 0x%x: %ls\n", dwResult, szErrMessage); LocalFree(szErrMessage); } else { fwprintf(stderr, L"Error 0x%x\n", dwResult); } } }