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
This commit is contained in:
Logan oos Even
2023-02-03 17:01:57 +01:00
committed by GitHub
parent 24c1569c88
commit 2ee7dfdb98
5 changed files with 244 additions and 37 deletions
+1
View File
@@ -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..
+236 -31
View File
@@ -20,10 +20,7 @@
#include "n2n.h"
#ifdef __linux__ /* currently, Linux only */
#include <net/route.h>
#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 <manangement_port>] [-p <management_port_password>] [-v] [-V]"
"\n [-g <default gateway>] [-n <network address>/bitlen] <vpn gateway>"
"\n [-g <default gateway>] [-n <network address>/bitlen[:gateway]]"
"\n <vpn gateway>"
"\n"
"\n This tool sets new routes for all the traffic to be routed via the"
"\n <vpn gateway> 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 <default gateway> 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 */