blob: 73ec40b42b1035ffbe9d474156f053f5b5ec0c45 [file] [log] [blame]
/*
* tapctl -- Utility to manipulate TUN/TAP adapters on Windows
* https://community.openvpn.net/openvpn/wiki/Tapctl
*
* Copyright (C) 2002-2021 OpenVPN Inc <sales@openvpn.net>
* Copyright (C) 2018-2021 Simon Rozman <simon@rozman.si>
*
* 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, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#elif defined(_MSC_VER)
#include <config-msvc.h>
#endif
#ifdef HAVE_CONFIG_VERSION_H
#include <config-version.h>
#endif
#include "tap.h"
#include "error.h"
#include <objbase.h>
#include <setupapi.h>
#include <stdio.h>
#include <tchar.h>
#ifdef _MSC_VER
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "setupapi.lib")
#endif
const TCHAR title_string[] =
TEXT(PACKAGE_NAME) TEXT(" ") TEXT(PACKAGE_VERSION)
TEXT(" built on ") TEXT(__DATE__)
;
static const TCHAR usage_message[] =
TEXT("%s\n")
TEXT("\n")
TEXT("Usage:\n")
TEXT("\n")
TEXT("tapctl <command> [<command specific options>]\n")
TEXT("\n")
TEXT("Commands:\n")
TEXT("\n")
TEXT("create Create a new TUN/TAP adapter\n")
TEXT("list List TUN/TAP adapters\n")
TEXT("delete Delete specified network adapter\n")
TEXT("help Display this text\n")
TEXT("\n")
TEXT("Hint: Use \"tapctl help <command>\" to display help for particular command.\n")
;
static const TCHAR usage_message_create[] =
TEXT("%s\n")
TEXT("\n")
TEXT("Creates a new TUN/TAP adapter\n")
TEXT("\n")
TEXT("Usage:\n")
TEXT("\n")
TEXT("tapctl create [<options>]\n")
TEXT("\n")
TEXT("Options:\n")
TEXT("\n")
TEXT("--name <name> Set TUN/TAP adapter name. Should the adapter with given name \n")
TEXT(" already exist, an error is returned. If this option is not \n")
TEXT(" specified, a default adapter name is chosen by Windows. \n")
TEXT(" Note: This name can also be specified as OpenVPN's --dev-node \n")
TEXT(" option. \n")
TEXT("--hwid <hwid> Adapter hardware ID. Default value is root\\tap0901, which \n")
TEXT(" describes tap-windows6 driver. To work with wintun driver, \n")
TEXT(" specify 'wintun'. \n")
TEXT("\n")
TEXT("Output:\n")
TEXT("\n")
TEXT("This command prints newly created TUN/TAP adapter's GUID to stdout. \n")
;
static const TCHAR usage_message_list[] =
TEXT("%s\n")
TEXT("\n")
TEXT("Lists TUN/TAP adapters\n")
TEXT("\n")
TEXT("Usage:\n")
TEXT("\n")
TEXT("tapctl list\n")
TEXT("\n")
TEXT("Options:\n")
TEXT("\n")
TEXT("--hwid <hwid> Adapter hardware ID. By default, root\\tap0901, tap0901 and \n")
TEXT(" wintun adapters are listed. Use this switch to limit the list. \n")
TEXT("\n")
TEXT("Output:\n")
TEXT("\n")
TEXT("This command prints all TUN/TAP adapters to stdout. \n")
;
static const TCHAR usage_message_delete[] =
TEXT("%s\n")
TEXT("\n")
TEXT("Deletes the specified network adapter\n")
TEXT("\n")
TEXT("Usage:\n")
TEXT("\n")
TEXT("tapctl delete <adapter GUID | adapter name>\n")
;
/**
* Print the help message.
*/
static void
usage(void)
{
_ftprintf(stderr,
usage_message,
title_string);
}
/**
* Program entry point
*/
int __cdecl
_tmain(int argc, LPCTSTR argv[])
{
int iResult;
BOOL bRebootRequired = FALSE;
/* Ask SetupAPI to keep quiet. */
SetupSetNonInteractiveMode(TRUE);
if (argc < 2)
{
usage();
return 1;
}
else if (_tcsicmp(argv[1], TEXT("help")) == 0)
{
/* Output help. */
if (argc < 3)
{
usage();
}
else if (_tcsicmp(argv[2], TEXT("create")) == 0)
{
_ftprintf(stderr, usage_message_create, title_string);
}
else if (_tcsicmp(argv[2], TEXT("list")) == 0)
{
_ftprintf(stderr, usage_message_list, title_string);
}
else if (_tcsicmp(argv[2], TEXT("delete")) == 0)
{
_ftprintf(stderr, usage_message_delete, title_string);
}
else
{
_ftprintf(stderr, TEXT("Unknown command \"%s\". Please, use \"tapctl help\" to list supported commands.\n"), argv[2]);
}
return 1;
}
else if (_tcsicmp(argv[1], TEXT("create")) == 0)
{
LPCTSTR szName = NULL;
LPCTSTR szHwId = TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID);
/* Parse options. */
for (int i = 2; i < argc; i++)
{
if (_tcsicmp(argv[i], TEXT("--name")) == 0)
{
szName = argv[++i];
}
else
if (_tcsicmp(argv[i], TEXT("--hwid")) == 0)
{
szHwId = argv[++i];
}
else
{
_ftprintf(stderr, TEXT("Unknown option \"%s\". 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,
TEXT("Virtual Ethernet"),
szHwId,
&bRebootRequired,
&guidAdapter);
if (dwResult != ERROR_SUCCESS)
{
_ftprintf(stderr, TEXT("Creating TUN/TAP adapter failed (error 0x%x).\n"), dwResult);
iResult = 1; goto quit;
}
if (szName)
{
/* Get existing network adapters. */
struct tap_adapter_node *pAdapterList = NULL;
dwResult = tap_list_adapters(NULL, NULL, &pAdapterList);
if (dwResult != ERROR_SUCCESS)
{
_ftprintf(stderr, TEXT("Enumerating adapters failed (error 0x%x).\n"), dwResult);
iResult = 1; goto create_delete_adapter;
}
/* Check for duplicates. */
for (struct tap_adapter_node *pAdapter = pAdapterList; pAdapter; pAdapter = pAdapter->pNext)
{
if (_tcsicmp(szName, pAdapter->szName) == 0)
{
StringFromIID((REFIID)&pAdapter->guid, &szAdapterId);
_ftprintf(stderr, TEXT("Adapter \"%s\" already exists (GUID %") TEXT(PRIsLPOLESTR) TEXT(").\n"), pAdapter->szName, szAdapterId);
CoTaskMemFree(szAdapterId);
iResult = 1; goto create_cleanup_pAdapterList;
}
}
/* Rename the adapter. */
dwResult = tap_set_adapter_name(&guidAdapter, szName, FALSE);
if (dwResult != ERROR_SUCCESS)
{
StringFromIID((REFIID)&guidAdapter, &szAdapterId);
_ftprintf(stderr, TEXT("Renaming TUN/TAP adapter %") TEXT(PRIsLPOLESTR) TEXT(" to \"%s\" failed (error 0x%x).\n"), szAdapterId, szName, dwResult);
CoTaskMemFree(szAdapterId);
iResult = 1; goto quit;
}
iResult = 0;
create_cleanup_pAdapterList:
tap_free_adapter_list(pAdapterList);
if (iResult)
{
goto create_delete_adapter;
}
}
/* Output adapter GUID. */
StringFromIID((REFIID)&guidAdapter, &szAdapterId);
_ftprintf(stdout, TEXT("%") TEXT(PRIsLPOLESTR) TEXT("\n"), szAdapterId);
CoTaskMemFree(szAdapterId);
iResult = 0; goto quit;
create_delete_adapter:
tap_delete_adapter(
NULL,
&guidAdapter,
&bRebootRequired);
iResult = 1; goto quit;
}
else if (_tcsicmp(argv[1], TEXT("list")) == 0)
{
TCHAR szzHwId[0x100] =
TEXT("root\\") TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0")
TEXT(TAP_WIN_COMPONENT_ID) TEXT("\0")
TEXT("Wintun\0");
/* Parse options. */
for (int i = 2; i < argc; i++)
{
if (_tcsicmp(argv[i], TEXT("--hwid")) == 0)
{
memset(szzHwId, 0, sizeof(szzHwId));
++i;
memcpy_s(szzHwId, sizeof(szzHwId) - 2*sizeof(TCHAR) /*requires double zero termination*/, argv[i], _tcslen(argv[i])*sizeof(TCHAR));
}
else
{
_ftprintf(stderr, TEXT("Unknown option \"%s\". 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)
{
_ftprintf(stderr, TEXT("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);
_ftprintf(stdout, TEXT("%") TEXT(PRIsLPOLESTR) TEXT("\t%") TEXT(PRIsLPTSTR) TEXT("\n"), szAdapterId, pAdapter->szName);
CoTaskMemFree(szAdapterId);
}
iResult = 0;
tap_free_adapter_list(pAdapterList);
}
else if (_tcsicmp(argv[1], TEXT("delete")) == 0)
{
if (argc < 3)
{
_ftprintf(stderr, TEXT("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)
{
_ftprintf(stderr, TEXT("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)
{
_ftprintf(stderr, TEXT("\"%s\" adapter not found.\n"), argv[2]);
iResult = 1; goto delete_cleanup_pAdapterList;
}
else if (_tcsicmp(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)
{
_ftprintf(stderr, TEXT("Deleting adapter \"%s\" failed (error 0x%x).\n"), argv[2], dwResult);
iResult = 1; goto quit;
}
iResult = 0; goto quit;
}
else
{
_ftprintf(stderr, TEXT("Unknown command \"%s\". Please, use \"tapctl help\" to list supported commands.\n"), argv[1]);
return 1;
}
quit:
if (bRebootRequired)
{
_ftprintf(stderr, TEXT("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);
_ftprintf(stderr, TEXT("\n"));
if ((flags & M_ERRNO) != 0)
{
/* Output system error message (if possible). */
DWORD dwResult = GetLastError();
LPTSTR szErrMessage = NULL;
if (FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
0,
dwResult,
0,
(LPTSTR)&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 (!_istspace(szErrMessage[i]))
{
i_last = i + 1;
}
}
else
{
szErrMessage[i_last] = 0;
break;
}
}
/* Output error message. */
_ftprintf(stderr, TEXT("Error 0x%x: %s\n"), dwResult, szErrMessage);
LocalFree(szErrMessage);
}
else
{
_ftprintf(stderr, TEXT("Error 0x%x\n"), dwResult);
}
}
}