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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 244 additions and 37 deletions

View File

@ -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)

View File

@ -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 <remote edge address>`
- `tools/n2n-route -n 10.10.10.0/24 <remote edge address>`
- `tools/n2n-route -n 8.8.8.8/32 <remote edge address>`
- `tools/n2n-route -n 8.8.8.8/32:192.168.0.5 <some (other) remote edge address>`
### `n2n-portfwd`

View File

@ -92,8 +92,7 @@
#include <net/if_arp.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/route.h>
#endif /* #ifdef __linux__ */
#ifdef __FreeBSD__
@ -108,7 +107,6 @@
#endif
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
@ -137,6 +135,9 @@
#include "n2n_typedefs.h"
#ifdef WIN32
#include <windows.h> /* for privilege check in tools/n2n-route */
#include <lmaccess.h> /* for privilege check in tools/n2n-route */
#include <lmapibuf.h> /* for privilege check in tools/n2n-route */
#include <winsock2.h> /* for tcp */
#define SHUT_RDWR SD_BOTH /* for tcp */
#include "wintap.h"

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..

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 */