From 2ee7dfdb9896e3aca0db312bd617c66c34b20a88 Mon Sep 17 00:00:00 2001 From: Logan oos Even <46396513+Logan007@users.noreply.github.com> Date: Fri, 3 Feb 2023 17:01:57 +0100 Subject: [PATCH] added Windows support to n2n-route tool (#1023) * added Windows support to n2n-route tool * fixed includes * more include fixes * one more include addition * more compile fixes * wish I had a working Windows VM * fixed some more Windows compile errors * considered Windows-specific lib linkage * promised to never code OS specific tools again * removed invisible invalid characters * where to get if_idx from * retrieving interface index for route * bracket... * one more bracket... * added optional gateway parameter to user-defined routes * clarification * clarification * Windows needs special network init * adapted waiting-for-key --- CMakeLists.txt | 2 +- doc/Tools.md | 4 +- include/n2n.h | 7 +- tools/Makefile.in | 1 + tools/n2n-route.c | 267 ++++++++++++++++++++++++++++++++++++++++------ 5 files changed, 244 insertions(+), 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ba5333..f176c59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,7 +206,7 @@ add_library(n2n STATIC if(DEFINED WIN32) add_library(edge_utils_win32 src/edge_utils_win32.c) add_subdirectory(win32) - target_link_libraries(n2n edge_utils_win32 n2n_win32 iphlpapi) + target_link_libraries(n2n edge_utils_win32 n2n_win32 iphlpapi netapi32) endif(DEFINED WIN32) add_executable(edge src/edge.c) diff --git a/doc/Tools.md b/doc/Tools.md index f5aa0f8..17f2b64 100644 --- a/doc/Tools.md +++ b/doc/Tools.md @@ -24,7 +24,7 @@ This C tool sets new routes for all the traffic to be routed via a VPN gateway appropriate routes to supernodes and peers via the original default gateway. The tool can auto-detect the default gateway and also has options to only route -traffic to specified networks through the VPN gateway. +traffic to some specified networks through the VPN gateway. Make sure to run with sufficient rights to let the tool add and delete routes. @@ -34,7 +34,7 @@ including hints how to setup the remote edge (IP routing, masquerading). Example: - `tools/n2n-route ` - `tools/n2n-route -n 10.10.10.0/24 ` -- `tools/n2n-route -n 8.8.8.8/32 ` +- `tools/n2n-route -n 8.8.8.8/32:192.168.0.5 ` ### `n2n-portfwd` diff --git a/include/n2n.h b/include/n2n.h index f5892dd..1ec4a2e 100644 --- a/include/n2n.h +++ b/include/n2n.h @@ -92,8 +92,7 @@ #include #include #include -#include -#include +#include #endif /* #ifdef __linux__ */ #ifdef __FreeBSD__ @@ -108,7 +107,6 @@ #endif #include -#include #include #include #include @@ -137,6 +135,9 @@ #include "n2n_typedefs.h" #ifdef WIN32 +#include /* for privilege check in tools/n2n-route */ +#include /* for privilege check in tools/n2n-route */ +#include /* for privilege check in tools/n2n-route */ #include /* for tcp */ #define SHUT_RDWR SD_BOTH /* for tcp */ #include "wintap.h" diff --git a/tools/Makefile.in b/tools/Makefile.in index 3ab0e95..a5860eb 100644 --- a/tools/Makefile.in +++ b/tools/Makefile.in @@ -8,6 +8,7 @@ HEADERS=$(wildcard include/*.h) CFLAGS+=-I../include ifeq ($(CONFIG_TARGET),mingw) CFLAGS+=-I../win32 +LDLIBS+=-lnetapi32 endif CFLAGS+=$(DEBUG) LDFLAGS+=-L.. diff --git a/tools/n2n-route.c b/tools/n2n-route.c index dcc552e..4740f9a 100644 --- a/tools/n2n-route.c +++ b/tools/n2n-route.c @@ -20,10 +20,7 @@ #include "n2n.h" -#ifdef __linux__ /* currently, Linux only */ - - -#include +#if defined (__linux__) || defined(WIN32) /* currently, Linux and Windows only */ #define WITH_ADDRESS 1 @@ -45,6 +42,11 @@ #define NO_DETECT 0 #define AUTO_DETECT 1 +// REVISIT: may become obsolete +#ifdef WIN32 +#define STDIN_FILENO _fileno(stdin) +#endif + typedef struct n2n_route { struct in_addr net_addr; /* network address to be routed, also key for hash table*/ @@ -74,12 +76,32 @@ static int keep_running = 1; /* for main loop, handled by signals * // PLATFORM-DEPENDANT CODE -// taken from https://stackoverflow.com/questions/4159910/check-if-user-is-root-in-c int is_privileged (void) { +#if defined(__linux__) +// taken from https://stackoverflow.com/questions/4159910/check-if-user-is-root-in-c uid_t euid = geteuid(); return euid == 0; + +#elif defined(WIN32) +// taken from https://stackoverflow.com/a/10553065 + int result; + DWORD rc; + wchar_t user_name[256]; + USER_INFO_1 *info; + DWORD size = sizeof(user_name); + + GetUserNameW(user_name, &size); + rc = NetUserGetInfo(NULL, user_name, 1, (unsigned char**)&info); + if (rc) { + return 0; + } + result = (info->usri1_priv == USER_PRIV_ADMIN); + NetApiBufferFree(info); + + return result; +#endif } @@ -89,21 +111,20 @@ int is_privileged (void) { void set_term_handler(const void *handler) { -#ifdef __linux__ +#if defined(__linux__) signal(SIGPIPE, SIG_IGN); signal(SIGTERM, handler); signal(SIGINT, handler); -#endif -#ifdef WIN32 /* the beginning of Windows support ...? */ +#elif defined(WIN32) SetConsoleCtrlHandler(handler, TRUE); #endif } -#ifdef WIN32 /* the beginning of Windows support ...? */ -BOOL WINAPI term_handler (DWORD sig) { -#else +#if !defined(WIN32) static void term_handler (int sig) { +#else +BOOL WINAPI term_handler (DWORD sig) { #endif static int called = 0; @@ -117,7 +138,7 @@ static void term_handler (int sig) { } keep_running = 0; -#ifdef WIN32 /* the beginning of Windows support ...? */ +#if defined(WIN32) return TRUE; #endif } @@ -127,14 +148,16 @@ static void term_handler (int sig) { // PLATFORM-DEPENDANT CODE -// taken from https://gist.github.com/javiermon/6272065 -// with modifications -// originally licensed under GPLV2, Apache, and/or MIT #define RTLINK_BUFFER_SIZE 8192 int find_default_gateway (struct in_addr *gateway_addr, struct in_addr *exclude) { +#if defined(__linux__) + // taken from https://gist.github.com/javiermon/6272065 + // with modifications + // originally licensed under GPLV2, Apache, and/or MIT + int ret = 0; int received_bytes = 0, msg_len = 0, route_attribute_len = 0; SOCKET sock = -1; @@ -260,6 +283,59 @@ find_default_gateway_end: closesocket(sock); return ret; + +#elif defined(WIN32) + // taken from (and modified) + // https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-createipforwardentry + + PMIB_IPFORWARDTABLE pIpForwardTable = NULL; + DWORD dwSize = 0; + BOOL bOrder = FALSE; + DWORD dwStatus = 0; + unsigned int i; + ipstr_t gateway_address; + + // find out how big our buffer needs to be + dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder); + if(dwStatus == ERROR_INSUFFICIENT_BUFFER) { + // allocate the memory for the table + if(!(pIpForwardTable = (PMIB_IPFORWARDTABLE)malloc(dwSize))) { + traceEvent(TRACE_DEBUG, "malloc failed, out of memory\n"); + return EXIT_FAILURE; + } + // now get the table + dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder); + } + + if (dwStatus != ERROR_SUCCESS) { + traceEvent(TRACE_DEBUG, "getIpForwardTable failed\n"); + if(pIpForwardTable) + free(pIpForwardTable); + return EXIT_FAILURE; + } + + dwStatus = EXIT_FAILURE; + // search for the row in the table we want. The default gateway has a destination of 0.0.0.0 + for(i = 0; i < pIpForwardTable->dwNumEntries; i++) { + if(pIpForwardTable->table[i].dwForwardDest == 0) { + // we have found a default route + // do not use if the gateway is the one to be excluded + if(pIpForwardTable->table[i].dwForwardNextHop == exclude->S_un.S_addr) + continue; + dwStatus = 0; + gateway_addr->S_un.S_addr = pIpForwardTable->table[i].dwForwardNextHop; + traceEvent(TRACE_DEBUG, "assuming default gateway %s", + inaddrtoa(gateway_address, *gateway_addr)); + break; + } + } + + if(pIpForwardTable) { + free(pIpForwardTable); + } + + return dwStatus; +#endif } @@ -267,9 +343,75 @@ find_default_gateway_end: // PLATFORM-DEPENDANT CODE +#if defined(WIN32) +DWORD get_interface_index (struct in_addr addr) { + // taken from (and modified) + // https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-createipforwardentry + + PMIB_IPFORWARDTABLE pIpForwardTable = NULL; + DWORD dwSize = 0; + BOOL bOrder = FALSE; + DWORD dwStatus = 0; + DWORD mask_addr = 0; + DWORD max_idx = 0; + uint8_t bitlen, max_bitlen = 0; + unsigned int i; + ipstr_t gateway_address; + + // find out how big our buffer needs to be + dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder); + if(dwStatus == ERROR_INSUFFICIENT_BUFFER) { + // allocate the memory for the table + if(!(pIpForwardTable = (PMIB_IPFORWARDTABLE)malloc(dwSize))) { + traceEvent(TRACE_DEBUG, "malloc failed, out of memory\n"); + return EXIT_FAILURE; + } + // now get the table + dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder); + } + + if (dwStatus != ERROR_SUCCESS) { + traceEvent(TRACE_DEBUG, "getIpForwardTable failed\n"); + if(pIpForwardTable) + free(pIpForwardTable); + return 0; + } + + // search for the row in the table we want. The default gateway has a destination of 0.0.0.0 + for(i = 0; i < pIpForwardTable->dwNumEntries; i++) { + mask_addr = pIpForwardTable->table[i].dwForwardMask; + // if same subnet ... + if((mask_addr & addr.S_un.S_addr) == (mask_addr & pIpForwardTable->table[i].dwForwardDest)) { + mask_addr = ntohl(mask_addr); + for(bitlen = 0; (int)mask_addr < 0; mask_addr <<= 1) + bitlen++; + if(bitlen > max_bitlen) { + max_bitlen = bitlen; + max_idx = pIpForwardTable->table[i].dwForwardIfIndex; + } + } + } + + traceEvent(TRACE_DEBUG, "found interface index %u for gateway %s", + max_idx, inaddrtoa(gateway_address, addr)); + + if(pIpForwardTable) { + free(pIpForwardTable); + } + + return max_idx; +} +#endif + + +// ------------------------------------------------------------------------------------------------------- +// PLATFORM-DEPENDANT CODE + + /* adds (verb == ROUTE_ADD) or deletes (verb == ROUTE_DEL) a route */ void handle_route (n2n_route_t* in_route, int verb) { +#if defined(__linux__) struct sockaddr_in *addr_tmp; struct rtentry route; SOCKET sock; @@ -320,6 +462,34 @@ void handle_route (n2n_route_t* in_route, int verb) { } closesocket(sock); + +#elif defined(WIN32) + // REVISIT: use 'CreateIpForwardEntry()' and 'DeleteIpForwardEntry()' [iphlpapi.h] + struct in_addr net_addr, gateway; + char c_net_addr[32]; + char c_gateway[32]; + char c_interface[32]; + char c_verb[32]; + uint32_t mask; + uint8_t bitlen; + DWORD if_idx; + char cmd[256]; + + // assemble route command components + _snprintf(c_net_addr, sizeof(c_net_addr), inet_ntoa(in_route->net_addr)); + _snprintf(c_gateway, sizeof(c_gateway), inet_ntoa(in_route->gateway)); + mask = ntohl(in_route->net_mask.S_un.S_addr); + for(bitlen = 0; (int)mask < 0; mask <<= 1) + bitlen++; + if_idx = get_interface_index(in_route->gateway); + _snprintf(c_interface, sizeof(c_interface), "if %u", if_idx); + _snprintf(c_verb, sizeof(c_verb), (verb == ROUTE_ADD) ? "add" : "delete"); + _snprintf(cmd, sizeof(cmd), "route %s %s/%d %s %s > nul", c_verb, c_net_addr, bitlen, c_gateway, c_interface); + traceEvent(TRACE_INFO, "ROUTE CMD = '%s'\n", cmd); + + // issue the route command + system(cmd); +#endif } @@ -363,6 +533,7 @@ int same_subnet (struct in_addr addr0, struct in_addr addr1, struct in_addr subn // ------------------------------------------------------------------------------------------------------- +// PLATFORM-DEPENDANT CODE SOCKET connect_to_management_port (n2n_route_conf_t *rrr) { @@ -370,6 +541,23 @@ SOCKET connect_to_management_port (n2n_route_conf_t *rrr) { SOCKET ret; struct sockaddr_in sock_addr; +#if defined(WIN32) + // Windows requires a call to WSAStartup() before it can work with sockets + WORD wVersionRequested; + WSADATA wsaData; + int err; + + // Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + // tell the user that we could not find a usable Winsock DLL + traceEvent(TRACE_ERROR, "WSAStartup failed with error: %d\n", err); + return -1; + } +#endif + ret = socket (PF_INET, SOCK_DGRAM, 0); if((int)ret < 0) return -1; @@ -420,10 +608,12 @@ int get_addr_from_json (struct in_addr *addr, json_object_t *json, char *key, in // ------------------------------------------------------------------------------------------------------- +// PLATFORM-DEPENDANT CODE +#if !defined(WIN32) // taken from https://web.archive.org/web/20170407122137/http://cc.byexamples.com/2007/04/08/non-blocking-user-input-in-loop-without-ncurses/ -int kbhit () { +int _kbhit () { struct timeval tv; fd_set fds; @@ -436,6 +626,7 @@ int kbhit () { return FD_ISSET(STDIN_FILENO, &fds); } +#endif // ------------------------------------------------------------------------------------------------------- @@ -446,7 +637,8 @@ static void help (int level) { if(level == 0) return; /* no help required */ printf(" n2n-route [-t ] [-p ] [-v] [-V]" - "\n [-g ] [-n /bitlen] " + "\n [-g ] [-n /bitlen[:gateway]]" + "\n " "\n" "\n This tool sets new routes for all the traffic to be routed via the" "\n and polls the management port of a local n2n edge for" @@ -454,7 +646,7 @@ static void help (int level) { "\n gateway. Adapt port (default: %d) and password (default: '%s')" "\n to match your edge's configuration." "\n\n If no provided, the tool will try to auto-detect." - "\n\n To not route all traffic through vpn, inidicate the networks to be" + "\n\n To only route some traffic through vpn, inidicate the networks to be" "\n routed with '-n' option and use as many as required." "\n\n Verbosity can be increased or decreased with -v or -V , repeat as" "\n as needed." @@ -495,14 +687,18 @@ static int set_option (n2n_route_conf_t *rrr, int optkey, char *optargument) { } case 'n': /* user-provided network to be routed */ { - char cidr_net[64], bitlen; + char cidr_net[64], bitlen, gateway[64]; n2n_route_t *route; struct in_addr mask; + int ret; - if(sscanf(optargument, "%63[^/]/%hhd", cidr_net, &bitlen) != 2) { - traceEvent(TRACE_WARNING, "bad cidr network format '%d'", optargument); + gateway[0] = '\0'; // optional parameter + ret = sscanf(optargument, "%63[^/]/%hhd:%63s", cidr_net, &bitlen, gateway); + if((ret < 2) || (ret > 3)) { + traceEvent(TRACE_WARNING, "bad cidr network format '%s'", optargument); return 1; } + if((bitlen < 0) || (bitlen > 32)) { traceEvent(TRACE_WARNING, "bad prefix '%d' in '%s'", bitlen, optargument); return 1; @@ -511,14 +707,19 @@ static int set_option (n2n_route_conf_t *rrr, int optkey, char *optargument) { traceEvent(TRACE_WARNING, "bad network '%s' in '%s'", cidr_net, optargument); return 1; } - - traceEvent(TRACE_NORMAL, "routing %s/%d", cidr_net, bitlen); + if(gateway[0]) { + if(!inet_address_valid(inet_address(gateway))) { + traceEvent(TRACE_WARNING, "bad gateway '%s' in '%s'", gateway, optargument); + return 1; + } + } + traceEvent(TRACE_NORMAL, "routing %s/%d via %s", cidr_net, bitlen, gateway[0] ? gateway : "vpn gateway"); route = calloc(1, sizeof(*route)); if(route) { mask.s_addr = htonl(bitlen2mask(bitlen)); - // gateway is unknown at this point, will be rectified later - fill_route(route, inet_address(cidr_net), mask, inet_address("")); + // gateway might be unknown at this point, will be rectified later + fill_route(route, inet_address(cidr_net), mask, inet_address(gateway)); HASH_ADD(hh, rrr->routes, net_addr, sizeof(route->net_addr), route); // will be added to system table later } @@ -617,11 +818,15 @@ int main (int argc, char* argv[]) { fill_route(route, inet_address(UPPER_HALF), inet_address(MASK_HALF), rrr.gateway_vpn); HASH_ADD(hh, rrr.routes, net_addr, sizeof(route->net_addr), route); } + } else { + traceEvent(TRACE_WARNING, "only user-supplied networks will be routed, not the complete traffic"); } - // set gateway for all so far present routes as '-n'-provided do not have it yet, + // set gateway for all so far present routes if '-n'-provided do not have it yet, // make them UNPURGEABLE and add them to system table HASH_ITER(hh, rrr.routes, route, tmp_route) { - route->gateway = rrr.gateway_vpn; + if(!inet_address_valid(route->gateway)) { + route->gateway = rrr.gateway_vpn; + } route->purgeable = UNPURGEABLE; handle_route(route, ROUTE_ADD); } @@ -658,7 +863,7 @@ reset_main_loop: // read answer packet by packet which are only accepted if a corresponding request was sent before // of which we know about by having set the related tag, tag_info or tag_route_ip resp. // a valid edge ip address indicates that we have seen a valid answer to the info request - while(keep_running && !kbhit()) { + while(keep_running && !_kbhit()) { // current time now = time(NULL); @@ -846,16 +1051,16 @@ end_route_tool: } -#else /* ifdef __linux__ -- currently, Linux only */ +#else /* if defined(__linux__) || defined(WIN32) -- currently, Linux and Windows only */ int main (int argc, char* argv[]) { - traceEvent(TRACE_WARNING, "currently, only Linux is supported"); + traceEvent(TRACE_WARNING, "currently, only Linux and Windows are supported"); traceEvent(TRACE_WARNING, "if you want to port to other OS, please find the source code having clearly marked the platform-dependant portions"); return 0; } -#endif /* ifdef __linux__ -- currently, Linux only */ +#endif /* if defined (__linux__) || defined(WIN32) -- currently, Linux and Windows only */