added n2n-route tool (#982)

* moved dev to version 3.1.1

* laid ground for n2n-route tool

* adapted code style

* overhauled n2n-route's tool program logic and removed route code from edge

* added missing initialization of federation's purgeable field

* lifted un/purgeable confusion

* added warning about removed -n cli option

* realized that Windows does not offer inet_aton()

* removed -n option documentation from edge's man page

* slightly simplified n2n-route program logic

* applied more logic changes to n2n-route tool

* added 'info' read command to edge's management port

* corrected indention

* added Linux route control to n2n-route tool

* temporarily restricted n2n-route tool to Linux only

* We must be over the routing!

* pulled default gateway change detection into main loop to cover mobile use, and added devstr_t type

* corrected use of new UNPURGEABLE - so far gone unnoticed

* addresses possiible address issue

* i broke it

* reverted bad ideas

* added command line options, help text, and prevented vpn gateway being used for supernode/peer traffic routes

* added option to manually provide default gateway, also verbosity options

* getting there

* added option to limit networks to be routed (-n), adapted documentation

* fine-tuned minor things
This commit is contained in:
Logan oos Even 2022-05-24 20:38:51 +02:00 committed by GitHub
parent a274818854
commit 99b6b6b66d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1228 additions and 505 deletions

View File

@ -202,7 +202,8 @@ add_library(n2n STATIC
src/sn_selection.c src/sn_selection.c
src/auth.c src/auth.c
src/curve25519.c src/curve25519.c
src/n2n_port_mapping.c) src/n2n_port_mapping.c
src/json.c)
if(DEFINED WIN32) if(DEFINED WIN32)
@ -261,6 +262,8 @@ add_executable(n2n-benchmark tools/n2n-benchmark.c)
target_link_libraries(n2n-benchmark n2n) target_link_libraries(n2n-benchmark n2n)
add_executable(n2n-keygen tools/n2n-keygen.c) add_executable(n2n-keygen tools/n2n-keygen.c)
target_link_libraries(n2n-keygen n2n) target_link_libraries(n2n-keygen n2n)
add_executable(n2n-route tools/n2n-route.c)
target_link_libraries(n2n-route n2n)
add_executable(tests-auth tools/tests-auth.c) add_executable(tests-auth tools/tests-auth.c)
target_link_libraries(tests-auth n2n) target_link_libraries(tests-auth n2n)

View File

@ -33,7 +33,7 @@ The [TAP Configuration Guide](TapConfiguration.md) contains hints on various set
## Routing the Traffic ## Routing the Traffic
Reaching a remote network or tunneling all the internet traffic via n2n are two common tasks which require a proper routing setup. n2n supports routing needs providing options for packet forwarding (`-r`) including broadcasts (`-E`) as well as temporarily modifying the routing table (`-n`). Details can be found in the [Routing document](Routing.md). Reaching a remote network or tunneling all the internet traffic via n2n are two common tasks which require a proper routing setup. n2n supports routing needs providing options for packet forwarding (`-r`) including broadcasts (`-E`) as well as temporarily modifying the routing table (`tools/n2n-route`). Details can be found in the [Routing document](Routing.md).
## Traffic Restrictions ## Traffic Restrictions

View File

@ -12,14 +12,16 @@ In order to enable routing, the `server` must be configured as follows:
2. Enable packet forwarding with `sudo sysctl -w net.ipv4.ip_forward=1` 2. Enable packet forwarding with `sudo sysctl -w net.ipv4.ip_forward=1`
3. Enable IP masquerading: `sudo iptables -t nat -A POSTROUTING -j MASQUERADE` 3. Enable IP masquerading: `sudo iptables -t nat -A POSTROUTING -j MASQUERADE`
On the client side, the easiest way to configure routing is via the `-n` option. For example: On the client side, the easiest way to configure routing is via the `tools/n2n-route` utility. For example:
- In order to connect to the remote network `192.168.100.0/24`, use `-n 192.168.100.0/24:10.0.0.1` - In order to tunnel all the internet traffic, use `tools/n2n-route 10.0.0.1`
- In order to tunnel all the internet traffic, use `-n 0.0.0.0/0:10.0.0.1` - In order to connect to the remote network `192.168.100.0/24`, use `tools/n2n-route -n 192.168.100.0/24 10.0.0.1`
10.0.0.1 is the IP address of the gateway to use to route the specified network. It should correspond to the IP address of the `server` within n2n. Multiple `-n` options can be specified. 10.0.0.1 is the IP address of the gateway to use to route the specified network. It should correspond to the IP address of the `server` within n2n. Multiple `-n` options can be specified.
As an alternative to the `-n` option, the `ip route` linux command can be manually used. See the [n2n-gateway.sh](scripts/n2n-gateway.sh) script for an example. See also the following description of other use cases and in depth explanation. The utility connects to the local edge's management port to receive information about peers and supernodes. It currently works on Linux only.
As an alternative to the `tools/n2n-route` utility, the `ip route` linux command can be manually used. See the [n2n-gateway.sh](scripts/n2n-gateway.sh) script for an example. See also the following description of other use cases and in depth explanation.
## Special Scenarios ## Special Scenarios

4
edge.8
View File

@ -203,10 +203,6 @@ access to JSON API at the management port.
\fB\-v\fR, \fB\-\-verbose\fR \fB\-v\fR, \fB\-\-verbose\fR
make more verbose, repeat as required make more verbose, repeat as required
.TP .TP
\fB\-n \fR<\fIcidr:gateway\fR>
route an IPv4 network via the gateway, use 0.0.0.0/0 for
the default gateway, can be set multiple times
.TP
\fB\-u \fR<\fIUID\fR>, \fB\-\-euid\fR=<\fIUID\fR> \fB\-u \fR<\fIUID\fR>, \fB\-\-euid\fR=<\fIUID\fR>
numeric user ID to use when privileges are dropped numeric user ID to use when privileges are dropped
.TP .TP

70
include/json.h Normal file
View File

@ -0,0 +1,70 @@
/**
* (C) 2007-22 - ntop.org and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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 see <http://www.gnu.org/licenses/>
*
*/
// taken from (and modified)
// https://github.com/Logan007/C-Simple-JSON-Parser
// which is declared license-free code by the author according to
// https://github.com/forkachild/C-Simple-JSON-Parser/issues/3#issuecomment-1073520808
#ifndef JSON_H
#define JSON_H
#include <string.h>
#include <stdlib.h>
#define json_str_is_whitespace(x) x == '\r' || x == '\n' || x == '\t' || x == ' '
#define json_str_is_numeral(x) (x >= '0' && x <= '9') || x == 'e' || x == 'E' \
|| x == '.' || x == '+' || x == '-'
#define json_str_remove_whitespace_calc_offset(x, y) while(json_str_is_whitespace(*x)) { x++; y++; }
struct _jsonobject;
struct _jsonpair;
union _jsonvalue;
typedef enum {
JSON_STRING = 0,
JSON_DOUBLE,
JSON_OBJECT
} json_value_type;
typedef struct _jsonobject {
struct _jsonpair *pairs;
int count;
} json_object_t;
typedef struct _jsonpair {
char *key;
union _jsonvalue *value;
json_value_type type;
} json_pair_t;
typedef union _jsonvalue {
char *string_value;
double double_value;
struct _jsonobject *json_object;
} json_value_t;
json_object_t *json_parse (char *str);
void json_free (json_object_t *obj);
#endif

View File

@ -162,6 +162,8 @@
#include "n2n_port_mapping.h" #include "n2n_port_mapping.h"
#endif // HAVE_MINIUPNP || HAVE_NATPMP #endif // HAVE_MINIUPNP || HAVE_NATPMP
#include "json.h"
/* ************************************** */ /* ************************************** */
#include "header_encryption.h" #include "header_encryption.h"
@ -210,6 +212,7 @@ void tuntap_close (struct tuntap_dev *tuntap);
void tuntap_get_address (struct tuntap_dev *tuntap); void tuntap_get_address (struct tuntap_dev *tuntap);
/* Utils */ /* Utils */
char* inaddrtoa (ipstr_t out, struct in_addr addr);
char* intoa (uint32_t addr, char* buf, uint16_t buf_len); char* intoa (uint32_t addr, char* buf, uint16_t buf_len);
uint32_t bitlen2mask (uint8_t bitlen); uint32_t bitlen2mask (uint8_t bitlen);
uint8_t mask2bitlen (uint32_t mask); uint8_t mask2bitlen (uint32_t mask);

View File

@ -97,12 +97,8 @@
#define FEDERATION_NAME "*Federation" #define FEDERATION_NAME "*Federation"
enum federation {IS_NO_FEDERATION = 0,IS_FEDERATION = 1}; enum federation {IS_NO_FEDERATION = 0,IS_FEDERATION = 1};
/* (un)purgeable community indicator (supernode) */ /* (un)purgeable indicator for supernodes, communities, routes, ... */
#define COMMUNITY_UNPURGEABLE 0 enum sn_purge {UNPURGEABLE = 0, PURGEABLE = 1};
#define COMMUNITY_PURGEABLE 1
/* (un)purgeable supernode indicator */
enum sn_purge {SN_PURGEABLE = 0, SN_UNPURGEABLE = 1};
/* Header encryption indicators */ /* Header encryption indicators */
#define HEADER_ENCRYPTION_UNKNOWN 0 #define HEADER_ENCRYPTION_UNKNOWN 0

View File

@ -213,6 +213,21 @@ typedef struct filter_rule {
} filter_rule_t; } filter_rule_t;
/** Uncomment this to enable the MTU check, then try to ssh to generate a fragmented packet. */
/** NOTE: see doc/MTU.md for an explanation on the 1400 value */
//#define MTU_ASSERT_VALUE 1400
/** Common type used to hold stringified IP addresses. */
typedef char ipstr_t[INET_ADDRSTRLEN];
/** Common type used to hold stringified MAC addresses. */
#define N2N_MACSTR_SIZE 32
typedef char macstr_t[N2N_MACSTR_SIZE];
typedef char dec_ip_str_t[N2N_NETMASK_STR_SIZE];
typedef char dec_ip_bit_str_t[N2N_NETMASK_STR_SIZE + 4];
typedef char devstr_t[N2N_IFNAMSIZ];
#ifndef WIN32 #ifndef WIN32
typedef struct tuntap_dev { typedef struct tuntap_dev {
int fd; int fd;
@ -221,24 +236,12 @@ typedef struct tuntap_dev {
uint32_t ip_addr; uint32_t ip_addr;
uint32_t device_mask; uint32_t device_mask;
uint16_t mtu; uint16_t mtu;
char dev_name[N2N_IFNAMSIZ]; devstr_t dev_name;
} tuntap_dev; } tuntap_dev;
#define SOCKET int #define SOCKET int
#endif /* #ifndef WIN32 */ #endif /* #ifndef WIN32 */
/** Uncomment this to enable the MTU check, then try to ssh to generate a fragmented packet. */
/** NOTE: see doc/MTU.md for an explanation on the 1400 value */
//#define MTU_ASSERT_VALUE 1400
/** Common type used to hold stringified IP addresses. */
typedef char ipstr_t[32];
/** Common type used to hold stringified MAC addresses. */
#define N2N_MACSTR_SIZE 32
typedef char macstr_t[N2N_MACSTR_SIZE];
typedef char dec_ip_str_t[N2N_NETMASK_STR_SIZE];
typedef char dec_ip_bit_str_t[N2N_NETMASK_STR_SIZE + 4];
typedef struct speck_context_t he_context_t; typedef struct speck_context_t he_context_t;
typedef char n2n_sn_name_t[N2N_EDGE_SN_HOST_SIZE]; typedef char n2n_sn_name_t[N2N_EDGE_SN_HOST_SIZE];
@ -461,12 +464,6 @@ struct peer_info {
typedef struct peer_info peer_info_t; typedef struct peer_info peer_info_t;
typedef struct n2n_route {
in_addr_t net_addr;
uint8_t net_bitlen;
in_addr_t gateway;
} n2n_route_t;
typedef struct n2n_edge n2n_edge_t; typedef struct n2n_edge n2n_edge_t;
/* *************************************************** */ /* *************************************************** */
@ -551,7 +548,7 @@ typedef struct n2n_edge_callbacks {
} n2n_edge_callbacks_t; } n2n_edge_callbacks_t;
typedef struct n2n_tuntap_priv_config { typedef struct n2n_tuntap_priv_config {
char tuntap_dev_name[N2N_IFNAMSIZ]; devstr_t tuntap_dev_name;
char ip_mode[N2N_IF_MODE_SIZE]; char ip_mode[N2N_IF_MODE_SIZE];
dec_ip_str_t ip_addr; dec_ip_str_t ip_addr;
dec_ip_str_t netmask; dec_ip_str_t netmask;
@ -654,7 +651,6 @@ typedef struct n2n_port_map_parameter {
typedef struct n2n_edge_conf { typedef struct n2n_edge_conf {
struct peer_info *supernodes; /**< List of supernodes */ struct peer_info *supernodes; /**< List of supernodes */
n2n_route_t *routes; /**< Networks to route through n2n */
n2n_community_t community_name; /**< The community. 16 full octets. */ n2n_community_t community_name; /**< The community. 16 full octets. */
n2n_desc_t dev_desc; /**< The device description (hint) */ n2n_desc_t dev_desc; /**< The device description (hint) */
n2n_private_public_key_t *public_key; /**< edge's public key (for user/password based authentication) */ n2n_private_public_key_t *public_key; /**< edge's public key (for user/password based authentication) */
@ -668,7 +664,6 @@ typedef struct n2n_edge_conf {
he_context_t *header_iv_ctx_dynamic; /**< Header IV ecnryption cipher context, REMOVE as soon as separate fileds for checksum and replay protection available */ he_context_t *header_iv_ctx_dynamic; /**< Header IV ecnryption cipher context, REMOVE as soon as separate fileds for checksum and replay protection available */
n2n_transform_t transop_id; /**< The transop to use. */ n2n_transform_t transop_id; /**< The transop to use. */
uint8_t compression; /**< Compress outgoing data packets before encryption */ uint8_t compression; /**< Compress outgoing data packets before encryption */
uint16_t num_routes; /**< Number of routes in routes */
uint8_t tuntap_ip_mode; /**< Interface IP address allocated mode, eg. DHCP. */ uint8_t tuntap_ip_mode; /**< Interface IP address allocated mode, eg. DHCP. */
uint8_t allow_routing; /**< Accept packet no to interface address. */ uint8_t allow_routing; /**< Accept packet no to interface address. */
uint8_t drop_multicast; /**< Multicast ethernet addresses. */ uint8_t drop_multicast; /**< Multicast ethernet addresses. */
@ -719,7 +714,6 @@ struct n2n_edge {
#ifdef HAVE_ZSTD #ifdef HAVE_ZSTD
n2n_trans_op_t transop_zstd; /**< The transop for ZSTD compression */ n2n_trans_op_t transop_zstd; /**< The transop for ZSTD compression */
#endif #endif
n2n_route_t *sn_route_to_clean; /**< Supernode route to clean */
n2n_edge_callbacks_t cb; /**< API callbacks */ n2n_edge_callbacks_t cb; /**< API callbacks */
void *user_data; /**< Can hold user data */ void *user_data; /**< Can hold user data */
SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_common_data; SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_common_data;

View File

@ -51,7 +51,6 @@ int fetch_and_eventually_process_data (n2n_edge_t *eee, SOCKET sock,
uint8_t *pktbuf, uint16_t *expected, uint16_t *position, uint8_t *pktbuf, uint16_t *expected, uint16_t *position,
time_t now); time_t now);
int resolve_check (n2n_resolve_parameter_t *param, uint8_t resolution_request, time_t now); int resolve_check (n2n_resolve_parameter_t *param, uint8_t resolution_request, time_t now);
int edge_init_routes (n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_routes);
/* ***************************************************** */ /* ***************************************************** */
@ -212,7 +211,6 @@ static void help (int level) {
"[--management-password <pw>] " "[--management-password <pw>] "
"\n " "\n "
"[-v] " "[-v] "
"[-n <cidr:gateway>] "
#ifndef WIN32 #ifndef WIN32
"\n " "\n "
"[-u <numerical user id>] " "[-u <numerical user id>] "
@ -342,8 +340,6 @@ static void help (int level) {
printf(" --management_... | management port password, defaults to '%s'\n" printf(" --management_... | management port password, defaults to '%s'\n"
" ...password <pw> | \n", N2N_MGMT_PASSWORD); " ...password <pw> | \n", N2N_MGMT_PASSWORD);
printf(" -v | make more verbose, repeat as required\n"); printf(" -v | make more verbose, repeat as required\n");
printf(" -n <cidr:gateway> | route an IPv4 network via the gateway, use 0.0.0.0/0 for\n"
" | the default gateway, can be set multiple times\n");
#ifndef WIN32 #ifndef WIN32
printf(" -u <UID> | numeric user ID to use when privileges are dropped\n"); printf(" -u <UID> | numeric user ID to use when privileges are dropped\n");
printf(" -g <GID> | numeric group ID to use when privileges are dropped\n"); printf(" -g <GID> | numeric group ID to use when privileges are dropped\n");
@ -574,8 +570,8 @@ static int setOption (int optkey, char *optargument, n2n_tuntap_priv_config_t *e
#if defined(N2N_CAN_NAME_IFACE) #if defined(N2N_CAN_NAME_IFACE)
case 'd': /* TUNTAP name */ { case 'd': /* TUNTAP name */ {
strncpy(ec->tuntap_dev_name, optargument, N2N_IFNAMSIZ); strncpy(ec->tuntap_dev_name, optargument, sizeof(devstr_t));
ec->tuntap_dev_name[N2N_IFNAMSIZ - 1] = '\0'; ec->tuntap_dev_name[sizeof(devstr_t) - 1] = '\0';
break; break;
} }
#endif #endif
@ -686,41 +682,11 @@ static int setOption (int optkey, char *optargument, n2n_tuntap_priv_config_t *e
} }
#endif #endif
case 'n': { case 'n': {
char cidr_net[64], gateway[64]; traceEvent(TRACE_WARNING, "route support (-n) has been removed from n2n's core since version 3.1, "
n2n_route_t route; "please try tools/n2n-route instead");
if(sscanf(optargument, "%63[^/]/%hhd:%63s", cidr_net, &route.net_bitlen, gateway) != 3) {
traceEvent(TRACE_WARNING, "bad cidr/gateway format '%d'", optargument);
return 2; return 2;
} }
route.net_addr = inet_addr(cidr_net);
route.gateway = inet_addr(gateway);
if((route.net_bitlen < 0) || (route.net_bitlen > 32)) {
traceEvent(TRACE_WARNING, "bad prefix '%d' in '%s'", route.net_bitlen, optargument);
return 2;
}
if(route.net_addr == INADDR_NONE) {
traceEvent(TRACE_WARNING, "bad network '%s' in '%s'", cidr_net, optargument);
return 2;
}
if(route.gateway == INADDR_NONE) {
traceEvent(TRACE_WARNING, "bad gateway '%s' in '%s'", gateway, optargument);
return 2;
}
traceEvent(TRACE_NORMAL, "adding %s/%d via %s", cidr_net, route.net_bitlen, gateway);
conf->routes = realloc(conf->routes, sizeof(struct n2n_route) * (conf->num_routes + 1));
conf->routes[conf->num_routes] = route;
conf->num_routes++;
break;
}
case 'S': { case 'S': {
int solitude; int solitude;
if(optargument) { if(optargument) {
@ -1259,11 +1225,6 @@ int main (int argc, char* argv[]) {
eee->tuntap_priv_conf.ip_addr, eee->tuntap_priv_conf.ip_addr,
eee->tuntap_priv_conf.netmask, eee->tuntap_priv_conf.netmask,
macaddr_str(mac_buf, eee->device.mac_addr)); macaddr_str(mac_buf, eee->device.mac_addr));
// routes
if(edge_init_routes(eee, eee->conf.routes, eee->conf.num_routes) < 0) {
traceEvent(TRACE_ERROR, "routes setup failed");
exit(1);
}
runlevel = 5; runlevel = 5;
// no more answers required // no more answers required
seek_answer = 0; seek_answer = 0;

View File

@ -160,6 +160,33 @@ static void mgmt_edges (mgmt_req_t *req, strbuf_t *buf) {
} }
} }
static void mgmt_edge_info (mgmt_req_t *req, strbuf_t *buf) {
size_t msg_len;
macstr_t mac_buf;
struct in_addr ip_addr, ip_addr_mask;
ipstr_t ip_address, ip_address_mask;
ip_addr.s_addr = req->eee->device.ip_addr;
inaddrtoa(ip_address, ip_addr);
ip_addr_mask.s_addr = req->eee->device.device_mask;
inaddrtoa(ip_address_mask, ip_addr_mask);
msg_len = snprintf(buf->str, buf->size,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"version\":\"%s\","
"\"macaddr\":\"%s\","
"\"ip4addr\":\"%s\","
"\"ip4netmask\":\"%s\"}\n",
req->tag,
PACKAGE_VERSION,
is_null_mac(req->eee->device.mac_addr) ? "" : macaddr_str(mac_buf, req->eee->device.mac_addr),
ip_address, ip_address_mask);
send_reply(req, buf, msg_len);
}
static void mgmt_timestamps (mgmt_req_t *req, strbuf_t *buf) { static void mgmt_timestamps (mgmt_req_t *req, strbuf_t *buf) {
size_t msg_len; size_t msg_len;
@ -252,6 +279,7 @@ static const mgmt_handler_t mgmt_handlers[] = {
{ .cmd = "communities", .help = "Show current community", .func = mgmt_communities}, { .cmd = "communities", .help = "Show current community", .func = mgmt_communities},
{ .cmd = "edges", .help = "List current edges/peers", .func = mgmt_edges}, { .cmd = "edges", .help = "List current edges/peers", .func = mgmt_edges},
{ .cmd = "supernodes", .help = "List current supernodes", .func = mgmt_supernodes}, { .cmd = "supernodes", .help = "List current supernodes", .func = mgmt_supernodes},
{ .cmd = "info", .help = "Provide basic edge information", .func = mgmt_edge_info},
{ .cmd = "timestamps", .help = "Event timestamps", .func = mgmt_timestamps}, { .cmd = "timestamps", .help = "Event timestamps", .func = mgmt_timestamps},
{ .cmd = "packetstats", .help = "traffic counters", .func = mgmt_packetstats}, { .cmd = "packetstats", .help = "traffic counters", .func = mgmt_packetstats},
{ .cmd = "post.test", .help = "send a test event", .func = mgmt_post_test}, { .cmd = "post.test", .help = "send a test event", .func = mgmt_post_test},
@ -556,7 +584,7 @@ void readFromMgmtSocket (n2n_edge_t *eee) {
msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len),
"%-19s %1s%1s | %-17s | %-21s | %-15s | %9s | %10s\n", "%-19s %1s%1s | %-17s | %-21s | %-15s | %9s | %10s\n",
peer->version, peer->version,
(peer->purgeable == SN_UNPURGEABLE) ? "l" : "", (peer->purgeable == UNPURGEABLE) ? "l" : "",
(peer == eee->curr_sn) ? (eee->sn_wait ? "." : "*" ) : "", (peer == eee->curr_sn) ? (eee->sn_wait ? "." : "*" ) : "",
is_null_mac(peer->mac_addr) ? "" : macaddr_str(mac_buf, peer->mac_addr), is_null_mac(peer->mac_addr) ? "" : macaddr_str(mac_buf, peer->mac_addr),
sock_to_cstr(sockbuf, &(peer->sock)), sock_to_cstr(sockbuf, &(peer->sock)),

View File

@ -45,8 +45,6 @@ static void check_peer_registration_needed (n2n_edge_t *eee,
const n2n_sock_t *peer); const n2n_sock_t *peer);
static int edge_init_sockets (n2n_edge_t *eee); static int edge_init_sockets (n2n_edge_t *eee);
int edge_init_routes (n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_routes);
static void edge_cleanup_routes (n2n_edge_t *eee);
static void check_known_peer_sock_change (n2n_edge_t *eee, static void check_known_peer_sock_change (n2n_edge_t *eee,
uint8_t from_supernode, uint8_t from_supernode,
@ -1812,7 +1810,7 @@ static char *get_ip_from_arp (dec_ip_str_t buf, const n2n_mac_t req_mac) {
FILE *fd; FILE *fd;
dec_ip_str_t ip_str = {'\0'}; dec_ip_str_t ip_str = {'\0'};
char dev_str[N2N_IFNAMSIZ] = {'\0'}; devstr_t dev_str = {'\0'};
macstr_t mac_str = {'\0'}; macstr_t mac_str = {'\0'};
n2n_mac_t mac = {'\0'}; n2n_mac_t mac = {'\0'};
@ -3020,8 +3018,6 @@ void edge_term (n2n_edge_t * eee) {
eee->transop_zstd.deinit(&eee->transop_zstd); eee->transop_zstd.deinit(&eee->transop_zstd);
#endif #endif
edge_cleanup_routes(eee);
destroy_network_traffic_filter(eee->network_traffic_filter); destroy_network_traffic_filter(eee->network_traffic_filter);
closeTraceFile(); closeTraceFile();
@ -3029,6 +3025,7 @@ void edge_term (n2n_edge_t * eee) {
free(eee); free(eee);
} }
/* ************************************** */ /* ************************************** */
@ -3074,411 +3071,9 @@ static int edge_init_sockets (n2n_edge_t *eee) {
return(0); return(0);
} }
/* ************************************** */
#ifdef __linux__
static uint32_t get_gateway_ip () {
FILE *fd;
char *token = NULL;
char *gateway_ip_str = NULL;
char buf[256];
uint32_t gateway = 0;
if(!(fd = fopen("/proc/net/route", "r")))
return(0);
while(fgets(buf, sizeof(buf), fd)) {
if(strtok(buf, "\t") && (token = strtok(NULL, "\t")) && (!strcmp(token, "00000000"))) {
token = strtok(NULL, "\t");
if(token) {
struct in_addr addr;
addr.s_addr = strtoul(token, NULL, 16);
gateway_ip_str = inet_ntoa(addr);
if(gateway_ip_str) {
gateway = addr.s_addr;
break;
}
}
}
}
fclose(fd);
return(gateway);
}
static char* route_cmd_to_str (int cmd, const n2n_route_t *route, char *buf, size_t bufsize) {
const char *cmd_str;
struct in_addr addr;
char netbuf[64], gwbuf[64];
switch(cmd) {
case RTM_NEWROUTE:
cmd_str = "Add";
break;
case RTM_DELROUTE:
cmd_str = "Delete";
break;
default:
cmd_str = "?";
}
addr.s_addr = route->net_addr;
inet_ntop(AF_INET, &addr, netbuf, sizeof(netbuf));
addr.s_addr = route->gateway;
inet_ntop(AF_INET, &addr, gwbuf, sizeof(gwbuf));
snprintf(buf, bufsize, "%s %s/%d via %s", cmd_str, netbuf, route->net_bitlen, gwbuf);
return(buf);
}
/* Adapted from https://olegkutkov.me/2019/08/29/modifying-linux-network-routes-using-netlink/ */
#define NLMSG_TAIL(nmsg) \
((struct rtattr *) (((char *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
/* Add new data to rtattr */
static int rtattr_add (struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) {
int len = RTA_LENGTH(alen);
struct rtattr *rta;
if(NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
traceEvent(TRACE_ERROR, "rtattr_add error: message exceeded bound of %d\n", maxlen);
return -1;
}
rta = NLMSG_TAIL(n);
rta->rta_type = type;
rta->rta_len = len;
if(alen)
memcpy(RTA_DATA(rta), data, alen);
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
return 0;
}
static int routectl (int cmd, int flags, n2n_route_t *route, int if_idx) {
int rv = -1;
int rv2;
char nl_buf[8192]; /* >= 8192 to avoid truncation, see "man 7 netlink" */
char route_buf[256];
struct iovec iov;
struct msghdr msg;
struct sockaddr_nl sa;
uint8_t read_reply = 1;
int nl_sock;
struct {
struct nlmsghdr n;
struct rtmsg r;
char buf[4096];
} nl_request;
if((nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) {
traceEvent(TRACE_ERROR, "netlink socket creation failed [%d]: %s", errno, strerror(errno));
return(-1);
}
/* Subscribe to route change events */
iov.iov_base = nl_buf;
iov.iov_len = sizeof(nl_buf);
memset(&sa, 0, sizeof(sa));
sa.nl_family = PF_NETLINK;
sa.nl_groups = RTMGRP_IPV4_ROUTE | RTMGRP_NOTIFY;
sa.nl_pid = getpid();
memset(&msg, 0, sizeof(msg));
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* Subscribe to route events */
if(bind(nl_sock, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
traceEvent(TRACE_ERROR, "netlink socket bind failed [%d]: %s", errno, strerror(errno));
goto out;
}
/* Initialize request structure */
memset(&nl_request, 0, sizeof(nl_request));
nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nl_request.n.nlmsg_flags = NLM_F_REQUEST | flags;
nl_request.n.nlmsg_type = cmd;
nl_request.r.rtm_family = AF_INET;
nl_request.r.rtm_table = RT_TABLE_MAIN;
nl_request.r.rtm_scope = RT_SCOPE_NOWHERE;
/* Set additional flags if NOT deleting route */
if(cmd != RTM_DELROUTE) {
nl_request.r.rtm_protocol = RTPROT_BOOT;
nl_request.r.rtm_type = RTN_UNICAST;
}
nl_request.r.rtm_family = AF_INET;
nl_request.r.rtm_dst_len = route->net_bitlen;
/* Select scope, for simplicity we supports here only IPv6 and IPv4 */
if(nl_request.r.rtm_family == AF_INET6)
nl_request.r.rtm_scope = RT_SCOPE_UNIVERSE;
else
nl_request.r.rtm_scope = RT_SCOPE_LINK;
/* Set gateway */
if(route->net_bitlen) {
if(rtattr_add(&nl_request.n, sizeof(nl_request), RTA_GATEWAY, &route->gateway, 4) < 0)
goto out;
nl_request.r.rtm_scope = 0;
nl_request.r.rtm_family = AF_INET;
}
/* Don't set destination and interface in case of default gateways */
if(route->net_bitlen) {
/* Set destination network */
if(rtattr_add(&nl_request.n, sizeof(nl_request), /*RTA_NEWDST*/ RTA_DST, &route->net_addr, 4) < 0)
goto out;
/* Set interface */
if(if_idx > 0) {
if(rtattr_add(&nl_request.n, sizeof(nl_request), RTA_OIF, &if_idx, sizeof(int)) < 0)
goto out;
}
}
/* Send message to the netlink */
if((rv2 = send(nl_sock, &nl_request, sizeof(nl_request), 0)) != sizeof(nl_request)) {
traceEvent(TRACE_ERROR, "netlink send failed [%d]: %s", errno, strerror(errno));
goto out;
}
/* Wait for the route notification. Assume that the first reply we get is the correct one. */
traceEvent(TRACE_DEBUG, "waiting for netlink response...");
while(read_reply) {
ssize_t len = recvmsg(nl_sock, &msg, 0);
struct nlmsghdr *nh;
for(nh = (struct nlmsghdr *)nl_buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
/* Stop after the first reply */
read_reply = 0;
if(nh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = NLMSG_DATA(nh);
int errcode = err->error;
if(errcode < 0)
errcode = -errcode;
/* Ignore EEXIST as existing rules are ok */
if(errcode != EEXIST) {
traceEvent(TRACE_ERROR, "[err=%d] route: %s", errcode, route_cmd_to_str(cmd, route, route_buf, sizeof(route_buf)));
goto out;
}
}
if(nh->nlmsg_type == NLMSG_DONE)
break;
if(nh->nlmsg_type == cmd) {
traceEvent(TRACE_DEBUG, "Found netlink reply");
break;
}
}
}
traceEvent(TRACE_DEBUG, route_cmd_to_str(cmd, route, route_buf, sizeof(route_buf)));
rv = 0;
out:
close(nl_sock);
return(rv);
}
#endif
/* ************************************** */ /* ************************************** */
#ifdef __linux__
static int edge_init_routes_linux (n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_routes) {
int i;
for(i = 0; i<num_routes; i++) {
n2n_route_t *route = &routes[i];
if((route->net_addr == 0) && (route->net_bitlen == 0)) {
/* This is a default gateway rule. We need to:
*
* 1. Add a route to the supernode via the host internet gateway
* 2. Add the new default gateway route
*
* Instead of modifying the system default gateway, we use the trick
* of adding a route to the networks 0.0.0.0/1 and 128.0.0.0/1, thus
* covering the whole IPv4 range. Such routes in linux take precedence
* over the default gateway (0.0.0.0/0) since are more specific.
* This leaves the default gateway unchanged so that after n2n is
* stopped the cleanup is easier.
* See https://github.com/zerotier/ZeroTierOne/issues/178#issuecomment-204599227
*/
n2n_sock_t sn;
n2n_route_t custom_route;
uint32_t *a;
if(eee->sn_route_to_clean) {
traceEvent(TRACE_ERROR, "only one default gateway route allowed");
return(-1);
}
if(eee->conf.sn_num != 1) {
traceEvent(TRACE_ERROR, "only one supernode supported with routes");
return(-1);
}
if(supernode2sock(&sn, eee->conf.supernodes->ip_addr) < 0)
return(-1);
if(sn.family != AF_INET) {
traceEvent(TRACE_ERROR, "only IPv4 routes supported");
return(-1);
}
a = (u_int32_t*)sn.addr.v4;
custom_route.net_addr = *a;
custom_route.net_bitlen = 32;
custom_route.gateway = get_gateway_ip();
if(!custom_route.gateway) {
traceEvent(TRACE_ERROR, "could not determine the gateway IP address");
return(-1);
}
/* ip route add supernode via internet_gateway */
if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, -1) < 0)
return(-1);
/* Save the route to delete it when n2n is stopped */
eee->sn_route_to_clean = calloc(1, sizeof(n2n_route_t));
/* Store a copy of the rules into the runtime to delete it during shutdown */
if(eee->sn_route_to_clean)
*eee->sn_route_to_clean = custom_route;
/* ip route add 0.0.0.0/1 via n2n_gateway */
custom_route.net_addr = 0;
custom_route.net_bitlen = 1;
custom_route.gateway = route->gateway;
if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, eee->device.if_idx) < 0)
return(-1);
/* ip route add 128.0.0.0/1 via n2n_gateway */
custom_route.net_addr = 128;
custom_route.net_bitlen = 1;
custom_route.gateway = route->gateway;
if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, &custom_route, eee->device.if_idx) < 0)
return(-1);
} else {
/* ip route add net via n2n_gateway */
if(routectl(RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, route, eee->device.if_idx) < 0)
return(-1);
}
}
return(0);
}
#endif
/* ************************************** */
#ifdef WIN32
static int edge_init_routes_win (n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_routes, uint8_t verb /* 0 = add, 1 = delete */) {
int i;
struct in_addr net_addr, gateway;
char c_net_addr[32];
char c_gateway[32];
char c_interface[32];
char c_verb[32];
char cmd[256];
for(i = 0; i < num_routes; i++) {
n2n_route_t *route = &routes[i];
if((route->net_addr == 0) && (route->net_bitlen == 0)) {
// REVISIT: there might be a chance to get it working on Windows following the hints at
// https://docs.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipinterface_row
//
// " The DisableDefaultRoutes member of the MIB_IPINTERFACE_ROW structure can be used to disable
// using the default route on an interface. This member can be used as a security measure by
// VPN clients to restrict split tunneling when split tunneling is not required by the VPN client.
// A VPN client can call the SetIpInterfaceEntry function to set the DisableDefaultRoutes member
// to TRUE when required. A VPN client can query the current state of the DisableDefaultRoutes
// member by calling the GetIpInterfaceEntry function. "
traceEvent(TRACE_WARNING, "the 0.0.0.0/0 route settings are not supported on Windows");
return(-1);
} else {
/* ip route add net via n2n_gateway */
memcpy(&net_addr, &(route->net_addr), sizeof(net_addr));
memcpy(&gateway, &(route->gateway), sizeof(gateway));
_snprintf(c_net_addr, sizeof(c_net_addr), inet_ntoa(net_addr));
_snprintf(c_gateway, sizeof(c_gateway), inet_ntoa(gateway));
_snprintf(c_interface, sizeof(c_interface), "if %u", eee->device.if_idx);
_snprintf(c_verb, sizeof(c_verb), verb ? "delete" : "add");
_snprintf(cmd, sizeof(cmd), "route %s %s/%d %s %s > nul", c_verb, c_net_addr, route->net_bitlen, c_gateway, c_interface);
traceEvent(TRACE_NORMAL, "ROUTE CMD = '%s'\n", cmd);
system(cmd);
}
}
return (0);
}
#endif // WIN32
/* ************************************** */
/* Add the user-provided routes to the linux routing table. Network routes
* are bound to the n2n TAP device, so they are automatically removed when
* the TAP device is destroyed. */
int edge_init_routes (n2n_edge_t *eee, n2n_route_t *routes, uint16_t num_routes) {
#ifdef __linux__
return edge_init_routes_linux(eee, routes, num_routes);
#endif
#ifdef WIN32
return edge_init_routes_win(eee, routes, num_routes, 0 /* add */);
#endif
return 0;
}
/* ************************************** */
static void edge_cleanup_routes (n2n_edge_t *eee) {
#ifdef __linux__
if(eee->sn_route_to_clean) {
/* ip route del supernode via internet_gateway */
routectl(RTM_DELROUTE, 0, eee->sn_route_to_clean, -1);
free(eee->sn_route_to_clean);
}
#endif
#ifdef WIN32
edge_init_routes_win(eee, eee->conf.routes, eee->conf.num_routes, 1 /* del */);
#endif
}
/* ************************************** */
void edge_init_conf_defaults (n2n_edge_conf_t *conf) { void edge_init_conf_defaults (n2n_edge_conf_t *conf) {
@ -3534,7 +3129,6 @@ void edge_init_conf_defaults (n2n_edge_conf_t *conf) {
void edge_term_conf (n2n_edge_conf_t *conf) { void edge_term_conf (n2n_edge_conf_t *conf) {
if(conf->routes) free(conf->routes);
if(conf->encrypt_key) free(conf->encrypt_key); if(conf->encrypt_key) free(conf->encrypt_key);
if(conf->network_traffic_filter_rules) { if(conf->network_traffic_filter_rules) {
@ -3581,7 +3175,7 @@ int edge_conf_add_supernode (n2n_edge_conf_t *conf, const char *ip_and_port) {
strncpy(sn->ip_addr, ip_and_port, N2N_EDGE_SN_HOST_SIZE - 1); strncpy(sn->ip_addr, ip_and_port, N2N_EDGE_SN_HOST_SIZE - 1);
memcpy(&(sn->sock), sock, sizeof(n2n_sock_t)); memcpy(&(sn->sock), sock, sizeof(n2n_sock_t));
memcpy(sn->mac_addr, null_mac, sizeof(n2n_mac_t)); memcpy(sn->mac_addr, null_mac, sizeof(n2n_mac_t));
sn->purgeable = SN_UNPURGEABLE; sn->purgeable = UNPURGEABLE;
} }
} }

202
src/json.c Normal file
View File

@ -0,0 +1,202 @@
/**
* (C) 2007-22 - ntop.org and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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 see <http://www.gnu.org/licenses/>
*
*/
// taken from (and modified)
// https://github.com/Logan007/C-Simple-JSON-Parser
// which is declared license-free code by the author according to
// https://github.com/forkachild/C-Simple-JSON-Parser/issues/3#issuecomment-1073520808
#include "json.h"
static int json_str_next_occurence (char *str, char ch);
static int json_str_next_non_numeral (char *str);
static json_object_t *_json_parse (char *str, int *offset);
json_object_t *json_parse (char *str) {
int offset = 0;
json_object_t *temp_obj = _json_parse(str, &offset);
return temp_obj;
}
void json_free (json_object_t *obj) {
int i;
if(obj == NULL)
return;
if(obj->pairs == NULL) {
free(obj);
return;
}
for(i = 0; i < obj->count; i++) {
if(obj->pairs[i].key != NULL)
free(obj->pairs[i].key);
if(obj->pairs[i].value != NULL) {
switch(obj->pairs[i].type) {
case JSON_STRING:
free(obj->pairs[i].value->string_value);
break;
case JSON_DOUBLE:
break;
case JSON_OBJECT:
json_free(obj->pairs[i].value->json_object);
}
free(obj->pairs[i].value);
}
}
}
static int json_str_next_occurence (char *str, char ch) {
int pos = 0;
if(str == NULL)
return -1;
while(*str != ch && *str != '\0') {
str++;
pos++;
}
return (*str == '\0') ? -1 : pos;
}
static int json_str_next_non_numeral (char *str) {
int pos = 0;
if(str == NULL)
return -1;
while((json_str_is_numeral(*str)) && (*str != '\0')) {
str++;
pos++;
}
return (*str == '\0') ? -1 : pos;
}
static json_object_t *_json_parse (char *str, int *offset) {
int _offset = 0;
json_object_t *obj = (json_object_t*)malloc(sizeof(json_object_t));
obj->count = 1;
obj->pairs = (json_pair_t*)malloc(sizeof(json_pair_t));
while(*str != '\0') {
json_str_remove_whitespace_calc_offset(str, _offset);
if(*str == '{') {
str++;
_offset++;
} else if(*str == '"') {
int i = json_str_next_occurence(++str, '"');
if(i <= 0) {
json_free(obj);
return NULL;
}
json_pair_t tempPtr = obj->pairs[obj->count - 1];
tempPtr.key = (char*)malloc((i + 1) * sizeof(char));
memcpy(tempPtr.key, str, i * sizeof(char));
tempPtr.key[i] = '\0';
str += i + 1;
_offset += i + 2;
i = json_str_next_occurence(str, ':');
if(i == -1)
return NULL;
str += i + 1;
_offset += i + 1;
json_str_remove_whitespace_calc_offset(str, _offset);
if(*str == '{') {
int _offsetBeforeParsingChildObject = _offset;
int _sizeOfChildObject;
tempPtr.value = (json_value_t*)malloc(sizeof(json_value_t));
tempPtr.type = JSON_OBJECT;
tempPtr.value->json_object = _json_parse(str, &_offset);
if(tempPtr.value->json_object == NULL) {
json_free(obj);
return NULL;
}
// Advance the string pointer by the size of the processed child object
_sizeOfChildObject = _offset - _offsetBeforeParsingChildObject;
str += _sizeOfChildObject;
} else if(*str == '"') {
i = json_str_next_occurence(++str, '"');
if(i == -1) {
json_free(obj);
return NULL;
}
tempPtr.value = (json_value_t*)malloc(sizeof(json_value_t));
tempPtr.type = JSON_STRING;
tempPtr.value->string_value = (char*)malloc((i + 1) * sizeof(char));
memcpy(tempPtr.value->string_value, str, i * sizeof(char));
tempPtr.value->string_value[i] = '\0';
str += i + 1;
_offset += i + 2;
} else if(json_str_is_numeral(*str)) {
i = json_str_next_non_numeral(str);
if(i == -1) {
json_free(obj);
return NULL;
}
char *tempStr = (char*)malloc((i + 1) * sizeof(char));
memcpy(tempStr, str, i * sizeof(char));
tempStr[i] = '\0';
tempPtr.value = (json_value_t*)malloc(sizeof(json_value_t));
tempPtr.type = JSON_DOUBLE;
tempPtr.value->double_value = atof(tempStr);
free(tempStr);
str += i;
_offset += i + 1;
}
obj->pairs[obj->count - 1] = tempPtr;
} else if (*str == ',') {
obj->count++;
obj->pairs = (json_pair_t*)realloc(obj->pairs, obj->count * sizeof(json_pair_t));
str++;
_offset++;
} else if (*str == '}') {
(*offset) += _offset + 1;
return obj;
}
}
return obj;
}

View File

@ -165,8 +165,20 @@ void _traceEvent (int eventTraceLevel, char* file, int line, char * format, ...)
} }
/* *********************************************** */ /* *********************************************** */
/* stringify in_addr type to ipstr_t */
char* inaddrtoa (ipstr_t out, struct in_addr addr) {
if(!inet_ntop(AF_INET, &addr, out, sizeof(ipstr_t)))
out[0] = '\0';
return out;
}
/* addr should be in network order. Things are so much simpler that way. */ /* addr should be in network order. Things are so much simpler that way. */
char* intoa (uint32_t /* host order */ addr, char* buf, uint16_t buf_len) { char* intoa (uint32_t /* host order */ addr, char* buf, uint16_t buf_len) {
@ -623,7 +635,7 @@ size_t purge_peer_list (struct peer_info **peer_list,
size_t retval = 0; size_t retval = 0;
HASH_ITER(hh, *peer_list, scan, tmp) { HASH_ITER(hh, *peer_list, scan, tmp) {
if((scan->purgeable == SN_PURGEABLE) && (scan->last_seen < purge_before)) { if((scan->purgeable == PURGEABLE) && (scan->last_seen < purge_before)) {
if((scan->socket_fd >=0) && (scan->socket_fd != socket_not_to_close)) { if((scan->socket_fd >=0) && (scan->socket_fd != socket_not_to_close)) {
if(tcp_connections) { if(tcp_connections) {
HASH_FIND_INT(*tcp_connections, &scan->socket_fd, conn); HASH_FIND_INT(*tcp_connections, &scan->socket_fd, conn);
@ -653,7 +665,7 @@ size_t clear_peer_list (struct peer_info ** peer_list) {
size_t retval = 0; size_t retval = 0;
HASH_ITER(hh, *peer_list, scan, tmp) { HASH_ITER(hh, *peer_list, scan, tmp) {
if (scan->purgeable == SN_UNPURGEABLE && scan->ip_addr) { if (scan->purgeable == UNPURGEABLE && scan->ip_addr) {
free(scan->ip_addr); free(scan->ip_addr);
} }
HASH_DEL(*peer_list, scan); HASH_DEL(*peer_list, scan);

View File

@ -355,7 +355,7 @@ int process_mgmt (n2n_sn_t *sss,
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"%s '%s'\n", "%s '%s'\n",
(community->is_federation) ? "FEDERATION" : ((community->purgeable == COMMUNITY_UNPURGEABLE) ? "FIXED NAME COMMUNITY" : "COMMUNITY"), (community->is_federation) ? "FEDERATION" : ((community->purgeable == UNPURGEABLE) ? "FIXED NAME COMMUNITY" : "COMMUNITY"),
(community->is_federation) ? "-/-" : community->community); (community->is_federation) ? "-/-" : community->community);
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
ressize = 0; ressize = 0;
@ -366,7 +366,7 @@ int process_mgmt (n2n_sn_t *sss,
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"%4u | %-19s | %-17s | %-21s %-3s | %-15s | %9s\n", "%4u | %-19s | %-17s | %-21s %-3s | %-15s | %9s\n",
++num, ++num,
(peer->dev_addr.net_addr == 0) ? ((peer->purgeable == SN_UNPURGEABLE) ? "-l" : "") : ip_subnet_to_str(ip_bit_str, &peer->dev_addr), (peer->dev_addr.net_addr == 0) ? ((peer->purgeable == UNPURGEABLE) ? "-l" : "") : ip_subnet_to_str(ip_bit_str, &peer->dev_addr),
(is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr), (is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr),
sock_to_cstr(sockbuf, &(peer->sock)), sock_to_cstr(sockbuf, &(peer->sock)),
((peer->socket_fd >= 0) && (peer->socket_fd != sss->sock)) ? "TCP" : "", ((peer->socket_fd >= 0) && (peer->socket_fd != sss->sock)) ? "TCP" : "",

View File

@ -379,7 +379,7 @@ int load_allowed_sn_community (n2n_sn_t *sss) {
if(comm != NULL) { if(comm != NULL) {
comm_init(comm, cmn_str); comm_init(comm, cmn_str);
/* loaded from file, this community is unpurgeable */ /* loaded from file, this community is unpurgeable */
comm->purgeable = COMMUNITY_UNPURGEABLE; comm->purgeable = UNPURGEABLE;
/* we do not know if header encryption is used in this community, /* we do not know if header encryption is used in this community,
* first packet will show. just in case, setup the key. */ * first packet will show. just in case, setup the key. */
comm->header_encryption = HEADER_ENCRYPTION_UNKNOWN; comm->header_encryption = HEADER_ENCRYPTION_UNKNOWN;
@ -766,7 +766,7 @@ int sn_init_defaults (n2n_sn_t *sss) {
sss->federation->community[N2N_COMMUNITY_SIZE - 1] = '\0'; sss->federation->community[N2N_COMMUNITY_SIZE - 1] = '\0';
/* enable the flag for federation */ /* enable the flag for federation */
sss->federation->is_federation = IS_FEDERATION; sss->federation->is_federation = IS_FEDERATION;
sss->federation->purgeable = COMMUNITY_UNPURGEABLE; sss->federation->purgeable = UNPURGEABLE;
/* header encryption enabled by default */ /* header encryption enabled by default */
sss->federation->header_encryption = HEADER_ENCRYPTION_ENABLED; sss->federation->header_encryption = HEADER_ENCRYPTION_ENABLED;
/*setup the encryption key */ /*setup the encryption key */
@ -1443,7 +1443,7 @@ static int purge_expired_communities (n2n_sn_t *sss,
} }
} }
if((comm->edges == NULL) && (comm->purgeable == COMMUNITY_PURGEABLE)) { if((comm->edges == NULL) && (comm->purgeable == PURGEABLE)) {
traceEvent(TRACE_INFO, "purging idle community %s", comm->community); traceEvent(TRACE_INFO, "purging idle community %s", comm->community);
if(NULL != comm->header_encryption_ctx_static) { if(NULL != comm->header_encryption_ctx_static) {
/* this should not happen as 'purgeable' and thus only communities w/o encrypted header here */ /* this should not happen as 'purgeable' and thus only communities w/o encrypted header here */
@ -1909,7 +1909,7 @@ static int process_udp (n2n_sn_t * sss,
comm->header_encryption_ctx_static = NULL; comm->header_encryption_ctx_static = NULL;
comm->header_encryption_ctx_dynamic = NULL; comm->header_encryption_ctx_dynamic = NULL;
/* ... and also are purgeable during periodic purge */ /* ... and also are purgeable during periodic purge */
comm->purgeable = COMMUNITY_PURGEABLE; comm->purgeable = PURGEABLE;
comm->number_enc_packets = 0; comm->number_enc_packets = 0;
HASH_ADD_STR(sss->communities, community, comm); HASH_ADD_STR(sss->communities, community, comm);

View File

@ -223,7 +223,7 @@ static int setOption (int optkey, char *_optarg, n2n_sn_t *sss) {
strncpy(anchor_sn->ip_addr, _optarg, N2N_EDGE_SN_HOST_SIZE - 1); strncpy(anchor_sn->ip_addr, _optarg, N2N_EDGE_SN_HOST_SIZE - 1);
memcpy(&(anchor_sn->sock), socket, sizeof(n2n_sock_t)); memcpy(&(anchor_sn->sock), socket, sizeof(n2n_sock_t));
memcpy(anchor_sn->mac_addr, null_mac, sizeof(n2n_mac_t)); memcpy(anchor_sn->mac_addr, null_mac, sizeof(n2n_mac_t));
anchor_sn->purgeable = SN_UNPURGEABLE; anchor_sn->purgeable = UNPURGEABLE;
anchor_sn->last_valid_time_stamp = initial_time_stamp(); anchor_sn->last_valid_time_stamp = initial_time_stamp();
} }
} }
@ -286,6 +286,7 @@ static int setOption (int optkey, char *_optarg, n2n_sn_t *sss) {
case 'F': { /* federation name */ case 'F': { /* federation name */
snprintf(sss->federation->community, N2N_COMMUNITY_SIZE - 1 ,"*%s", _optarg); snprintf(sss->federation->community, N2N_COMMUNITY_SIZE - 1 ,"*%s", _optarg);
sss->federation->community[N2N_COMMUNITY_SIZE - 1] = '\0'; sss->federation->community[N2N_COMMUNITY_SIZE - 1] = '\0';
sss->federation->purgeable = UNPURGEABLE;
break; break;
} }
#ifdef SN_MANUAL_MAC #ifdef SN_MANUAL_MAC

View File

@ -139,7 +139,7 @@ int tuntap_open (tuntap_dev *device,
} }
// store the device name for later reuse // store the device name for later reuse
strncpy(device->dev_name, ifr.ifr_name, MIN(IFNAMSIZ, N2N_IFNAMSIZ)); strncpy(device->dev_name, ifr.ifr_name, MIN(IFNAMSIZ, sizeof(devstr_t)));
if(device_mac && device_mac[0]) { if(device_mac && device_mac[0]) {
// use the user-provided MAC // use the user-provided MAC

View File

@ -14,7 +14,7 @@ LDFLAGS+=-L..
N2N_LIB=../libn2n.a N2N_LIB=../libn2n.a
TOOLS=n2n-benchmark n2n-keygen TOOLS=n2n-benchmark n2n-keygen n2n-route
TOOLS+=@ADDITIONAL_TOOLS@ TOOLS+=@ADDITIONAL_TOOLS@
TESTS=tests-compress tests-elliptic tests-hashing tests-transform TESTS=tests-compress tests-elliptic tests-hashing tests-transform
@ -26,6 +26,7 @@ all: $(TOOLS) $(TESTS)
n2n-benchmark.o: $(N2N_LIB) $(HEADERS) ../Makefile Makefile n2n-benchmark.o: $(N2N_LIB) $(HEADERS) ../Makefile Makefile
n2n-keygen.o: $(N2N_LIB) $(HEADERS) ../Makefile Makefile n2n-keygen.o: $(N2N_LIB) $(HEADERS) ../Makefile Makefile
n2n-route.o: $(N2N_LIB) $(HEADERS) ../Makefile Makefile
n2n-decode: n2n-decode.c $(N2N_LIB) $(HEADERS) ../Makefile Makefile n2n-decode: n2n-decode.c $(N2N_LIB) $(HEADERS) ../Makefile Makefile
$(CC) $(CFLAGS) $< $(LDFLAGS) $(LDLIBS) -lpcap -o $@ $(CC) $(CFLAGS) $< $(LDFLAGS) $(LDLIBS) -lpcap -o $@

860
tools/n2n-route.c Normal file
View File

@ -0,0 +1,860 @@
/**
* (C) 2007-22 - ntop.org and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* 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 see <http://www.gnu.org/licenses/>
*
*/
#include "n2n.h"
#ifdef __linux__ /* currently, Linux only */
#include <net/route.h>
#define WITH_ADDRESS 1
#define CORRECT_TAG 2
#define SOCKET_TIMEOUT 2
#define GATEWAY_INTERVAL 5
#define INFO_INTERVAL 5
#define REFRESH_INTERVAL 10
#define PURGE_INTERVAL 30
#define REMOVE_ROUTE_AGE 75
#define LOWER_HALF "0.0.0.0"
#define UPPER_HALF "128.0.0.0"
#define MASK_HALF "128.0.0.0" /* <ip address>/1 */
#define HOST_MASK "255.255.255.255" /* <ip address>/32 */
#define ROUTE_ADD 0
#define ROUTE_DEL 1
#define NO_DETECT 0
#define AUTO_DETECT 1
typedef struct n2n_route {
struct in_addr net_addr; /* network address to be routed, also key for hash table*/
struct in_addr net_mask; /* network address mask */
struct in_addr gateway; /* gateway address */
uint8_t purgeable; /* unpurgeable user-supplied or new default route */
time_t last_seen; /* last seen at management port output */
UT_hash_handle hh; /* makes this structure hashable */
} n2n_route_t;
typedef struct n2n_route_conf {
struct in_addr gateway_vpn; /* vpn gateway address */
struct in_addr gateway_org; /* original default gateway used for peer/supernode traffic */
uint8_t gateway_detect; /* have the gateway automatically detected */
char* password; /* pointer to management port password */
uint16_t port; /* management port */
n2n_route_t *routes; /* list of routes */
} n2n_route_conf_t;
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) {
uid_t euid = geteuid();
return euid == 0;
}
// -------------------------------------------------------------------------------------------------------
// PLATFORM-DEPENDANT CODE
void set_term_handler(const void *handler) {
#ifdef __linux__
signal(SIGPIPE, SIG_IGN);
signal(SIGTERM, handler);
signal(SIGINT, handler);
#endif
#ifdef WIN32 /* the beginning of Windows support ...? */
SetConsoleCtrlHandler(handler, TRUE);
#endif
}
#ifdef WIN32 /* the beginning of Windows support ...? */
BOOL WINAPI term_handler (DWORD sig) {
#else
static void term_handler (int sig) {
#endif
static int called = 0;
if(called) {
traceEvent(TRACE_NORMAL, "ok, leaving now");
_exit(0);
} else {
traceEvent(TRACE_NORMAL, "shutting down...");
called = 1;
}
keep_running = 0;
#ifdef WIN32 /* the beginning of Windows support ...? */
return TRUE;
#endif
}
// -------------------------------------------------------------------------------------------------------
// 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) {
int ret = 0;
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
SOCKET sock = -1;
int msgseq = 0;
struct nlmsghdr *nlh, *nlmsg;
struct rtmsg *route_entry;
struct rtattr *route_attribute; /* this contains route attributes (route type) */
ipstr_t gateway_address;
devstr_t interface;
char msgbuf[RTLINK_BUFFER_SIZE], buffer[RTLINK_BUFFER_SIZE];
char *ptr = buffer;
struct timeval tv;
if((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
traceEvent(TRACE_WARNING, "error from socket() while determining gateway");
// return immediately
return EXIT_FAILURE;
}
memset(msgbuf, 0, sizeof(msgbuf));
memset(buffer, 0, sizeof(buffer));
memset(gateway_address, 0, sizeof(gateway_address));
memset(interface, 0, sizeof(interface));
// point the header and the msg structure pointers into the buffer
nlmsg = (struct nlmsghdr*)msgbuf;
// fill in the nlmsg header
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = RTM_GETROUTE; /* get the routes from kernel routing table */
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; /* the message is a request for dump */
nlmsg->nlmsg_seq = msgseq++; /* sequence of the message packet */
nlmsg->nlmsg_pid = getpid(); /* PID of process sending the request */
// 1 sec timeout to avoid stall
tv.tv_sec = 1;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
// send msg
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
traceEvent(TRACE_WARNING, "error from send() while determining gateway");
ret = EXIT_FAILURE;
goto find_default_gateway_end;
}
// receive response
do {
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if(received_bytes < 0) {
traceEvent(TRACE_WARNING, "error from recv() while determining gateway");
ret = EXIT_FAILURE;
goto find_default_gateway_end;
}
nlh = (struct nlmsghdr *) ptr;
// check if the header is valid
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR)) {
traceEvent(TRACE_WARNING, "error in received packet while determining gateway");
ret = EXIT_FAILURE;
goto find_default_gateway_end;
}
// if we received all data break
if(nlh->nlmsg_type == NLMSG_DONE) {
break;
} else {
ptr += received_bytes;
msg_len += received_bytes;
}
// break if its not a multi part message
if((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
break;
} while((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
// parse response
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes)) {
// get the route data
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
// we are just interested in main routing table
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
route_attribute = (struct rtattr*)RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh);
gateway_address[0] = '\0';
interface[0] = '\0';
// loop through all attributes
for( ; RTA_OK(route_attribute, route_attribute_len);
route_attribute = RTA_NEXT(route_attribute, route_attribute_len)) {
switch(route_attribute->rta_type) {
case RTA_OIF:
// for informational purposes only
if_indextoname(*(int*)RTA_DATA(route_attribute), interface);
break;
case RTA_GATEWAY:
inaddrtoa(gateway_address, *(struct in_addr*)RTA_DATA(route_attribute));
break;
default:
break;
}
}
if((*gateway_address) && (*interface)) {
// REVISIT: inet_ntop followed by inet_pton... maybe not too elegant
if(inet_pton(AF_INET, gateway_address, gateway_addr)) {
// do not use the one to be excluded
if(!memcmp(gateway_addr, exclude, sizeof(*gateway_addr)))
continue;
traceEvent(TRACE_DEBUG, "assuming default gateway %s on interface %s",
gateway_address, interface);
break;
}
}
}
find_default_gateway_end:
closesocket(sock);
return ret;
}
// -------------------------------------------------------------------------------------------------------
// PLATFORM-DEPENDANT CODE
/* adds (verb == ROUTE_ADD) or deletes (verb == ROUTE_DEL) a route */
void handle_route (n2n_route_t* in_route, int verb) {
struct sockaddr_in *addr_tmp;
struct rtentry route;
SOCKET sock;
struct sockaddr_in *dst, *mask, *gateway;
ipstr_t dst_ip_str, gateway_ip_str;
in_addr_t mask_addr;
int bitlen = 0;
// prepare rtentry-typed route entry
memset(&route, 0, sizeof(route));
addr_tmp = (struct sockaddr_in*)&route.rt_dst;
addr_tmp->sin_family = AF_INET;
addr_tmp->sin_addr.s_addr = in_route->net_addr.s_addr;
addr_tmp = (struct sockaddr_in*)&route.rt_genmask;
addr_tmp->sin_family = AF_INET;
addr_tmp->sin_addr.s_addr = in_route->net_mask.s_addr;
addr_tmp = (struct sockaddr_in*)&route.rt_gateway;
addr_tmp->sin_family = AF_INET;
addr_tmp->sin_addr.s_addr = in_route->gateway.s_addr;
route.rt_flags = RTF_UP | RTF_GATEWAY;
route.rt_metric = 0;
// open a socket
sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
// prepare route data for eventual text output
dst = (struct sockaddr_in*)&route.rt_dst;
mask = (struct sockaddr_in*)&route.rt_genmask;
mask_addr = ntohl(mask->sin_addr.s_addr);
for(bitlen = 0; (int)mask_addr < 0; mask_addr <<= 1)
bitlen++;
gateway = (struct sockaddr_in*)&route.rt_gateway;
// try to set route through ioctl
if(ioctl(sock, verb == ROUTE_ADD ? SIOCADDRT : SIOCDELRT, &route) < 0) {
traceEvent(TRACE_WARNING, "error '%s' while %s route to %s/%u via %s",
strerror(errno),
!verb ? "adding" : "deleting",
inaddrtoa(dst_ip_str, dst->sin_addr),
bitlen,
inaddrtoa(gateway_ip_str, gateway->sin_addr));
} else {
traceEvent(TRACE_INFO, "%s route to %s/%u via %s",
!verb ? "added" : "deleted",
inaddrtoa(dst_ip_str, dst->sin_addr),
bitlen,
inaddrtoa(gateway_ip_str, gateway->sin_addr));
}
closesocket(sock);
}
// -------------------------------------------------------------------------------------------------------
void fill_route (n2n_route_t* route, struct in_addr net_addr, struct in_addr net_mask, struct in_addr gateway) {
route->net_addr = net_addr;
route->net_mask = net_mask;
route->gateway = gateway;
}
// applies inet_pton on input string and returns address struct-in_addr-typed address
struct in_addr inet_address (char* in) {
struct in_addr out;
if(inet_pton(AF_INET, in, &out) <= 0) {
out.s_addr = INADDR_NONE;
}
return out;
}
int inet_address_valid (struct in_addr in) {
if(in.s_addr == INADDR_NONE)
return 0;
else
return 1;
}
int same_subnet (struct in_addr addr0, struct in_addr addr1, struct in_addr subnet) {
return (addr0.s_addr & subnet.s_addr) == (addr1.s_addr & subnet.s_addr);
}
// -------------------------------------------------------------------------------------------------------
SOCKET connect_to_management_port (n2n_route_conf_t *rrr) {
SOCKET ret;
struct sockaddr_in sock_addr;
ret = socket (PF_INET, SOCK_DGRAM, 0);
if((int)ret < 0)
return -1;
sock_addr.sin_family = AF_INET;
sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
sock_addr.sin_port = htons(rrr->port);
if(0 != connect(ret, (struct sockaddr *)&sock_addr, sizeof(sock_addr)))
return -1;
return ret;
}
// -------------------------------------------------------------------------------------------------------
int get_addr_from_json (struct in_addr *addr, json_object_t *json, char *key, int tag, int flags) {
int i;
char *colon = NULL;
if(NULL == json)
return 0;
for(i = 0; i < json->count; i++) {
if(json->pairs[i].type == JSON_STRING) {
if(!strcmp(json->pairs[i].key, key)) {
// cut off port from IP address
if((colon = strchr(json->pairs[i].value->string_value, ':'))) {
*colon = '\0';
}
*addr = inet_address(json->pairs[i].value->string_value);
flags |= WITH_ADDRESS;
}
if(!strcmp(json->pairs[i].key, "_tag" )) {
if(atoi(json->pairs[i].value->string_value) == tag) {
flags |= CORRECT_TAG;
}
}
} else if(json->pairs[i].type == JSON_OBJECT) {
flags |= get_addr_from_json(addr, json, key, tag, flags);
}
}
return flags;
}
// -------------------------------------------------------------------------------------------------------
// 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 () {
struct timeval tv;
fd_set fds;
tv.tv_sec = 0;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds); //STDIN_FILENO is 0
select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
return FD_ISSET(STDIN_FILENO, &fds);
}
// -------------------------------------------------------------------------------------------------------
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"
"\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"
"\n it can add routes to supernodes and peers via the original default"
"\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 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."
"\n\n Run with sufficient rights to let the tool add and delete routes."
"\n\n",
N2N_EDGE_MGMT_PORT, N2N_MGMT_PASSWORD);
exit(0);
}
static int set_option (n2n_route_conf_t *rrr, int optkey, char *optargument) {
switch(optkey) {
case 't': /* management port */ {
uint16_t port = atoi(optargument);
if(port) {
rrr->port = port;
} else {
traceEvent(TRACE_WARNING, "invalid management port provided with '-t'");
}
break;
}
case 'p': /* management port password string */ {
rrr->password = optargument;
break;
}
case 'g': /* user-provided original default route */ {
rrr->gateway_org = inet_address(optargument);
if(inet_address_valid(rrr->gateway_org)) {
rrr->gateway_detect = NO_DETECT;
} else {
traceEvent(TRACE_WARNING, "invalid original default gateway provided with '-g'");
}
break;
}
case 'n': /* user-provided network to be routed */ {
char cidr_net[64], bitlen;
n2n_route_t *route;
struct in_addr mask;
if(sscanf(optargument, "%63[^/]/%hhd", cidr_net, &bitlen) != 2) {
traceEvent(TRACE_WARNING, "bad cidr network format '%d'", optargument);
return 1;
}
if((bitlen < 0) || (bitlen > 32)) {
traceEvent(TRACE_WARNING, "bad prefix '%d' in '%s'", bitlen, optargument);
return 1;
}
if(!inet_address_valid(inet_address(cidr_net))) {
traceEvent(TRACE_WARNING, "bad network '%s' in '%s'", cidr_net, optargument);
return 1;
}
traceEvent(TRACE_NORMAL, "routing %s/%d", cidr_net, bitlen);
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(""));
HASH_ADD(hh, rrr->routes, net_addr, sizeof(route->net_addr), route);
// will be added to system table later
}
break;
}
case 'V': /* more verbose */ {
setTraceLevel(getTraceLevel() + 1);
break;
}
case 'v': /* less verbose */ {
setTraceLevel(getTraceLevel() - 1);
break;
}
default: /* unknown option */ {
return 1; /* for help */
}
}
return 0;
}
// -------------------------------------------------------------------------------------------------------
int main (int argc, char* argv[]) {
n2n_route_conf_t rrr;
uint8_t c;
SOCKET sock;
size_t msg_len;
char udp_buf[N2N_PKT_BUF_SIZE];
fd_set socket_mask;
struct timeval wait_time;
time_t now = 0;
time_t last_gateway_check = 0;
time_t last_info_req = 0;
time_t last_read_req = 0;
time_t last_purge = 0;
json_object_t *json;
int ret;
int tag_info, tag_route_ip;
struct in_addr addr, edge, edge_netmask, addr_tmp;
ipstr_t ip_str;
n2n_route_t *route, *tmp_route;
// version
print_n2n_version();
// handle signals to properly end the tool
set_term_handler(term_handler);
// init data structure
rrr.gateway_vpn = inet_address("");
rrr.gateway_org = inet_address("");
rrr.gateway_detect = AUTO_DETECT;
rrr.password = N2N_MGMT_PASSWORD;
rrr.port = N2N_EDGE_MGMT_PORT;
rrr.routes = NULL;
setTraceLevel(2); /* NORMAL, should already be default */
n2n_srand(n2n_seed());
// get command line options and eventually overwrite initialized conf
while((c = getopt_long(argc, argv, "t:p:g:n:vV", NULL, NULL)) != '?') {
if(c == 255) break;
help(set_option(&rrr, c, optarg));
}
// get mandatory vpn gateway from command line and ...
if(argv[optind]) {
rrr.gateway_vpn = inet_address(argv[optind]);
}
// ... output help if invalid
help(!inet_address_valid(rrr.gateway_vpn));
traceEvent(TRACE_NORMAL, "using vpn gateway %s", inaddrtoa(ip_str, rrr.gateway_vpn));
// verify conf and react with output to conf-related changes
if(rrr.gateway_detect == NO_DETECT) {
traceEvent(TRACE_NORMAL, "using default gateway %s", inaddrtoa(ip_str, rrr.gateway_org));
}
// if nothing else set, set new default route
if(!rrr.routes) {
route = calloc(1, sizeof(n2n_route_t));
if(route) {
traceEvent(TRACE_NORMAL, "routing 0.0.0.1/1");
fill_route(route, inet_address(LOWER_HALF), inet_address(MASK_HALF), rrr.gateway_vpn);
HASH_ADD(hh, rrr.routes, net_addr, sizeof(route->net_addr), route);
}
route = calloc(1, sizeof(n2n_route_t));
if(route) {
traceEvent(TRACE_NORMAL, "routing 128.0.0.1/1");
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);
}
}
// set gateway for all so far present routes as '-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;
route->purgeable = UNPURGEABLE;
handle_route(route, ROUTE_ADD);
}
// additional checks
// check for sufficient rights for adding/deleting routes
if(!is_privileged()) {
traceEvent(TRACE_WARNING, "did not detect sufficient privileges to exercise route control");
}
// REVISIT: can we check if forwarding is enabled and, if not so, warn the user?
// connect to mamagement port
traceEvent(TRACE_NORMAL, "connecting to edge management port %d", rrr.port);
sock = connect_to_management_port(&rrr);
if(sock == -1) {
traceEvent(TRACE_ERROR, "unable to open socket for management port connection");
goto end_route_tool;
}
// output status
traceEvent(TRACE_NORMAL, "press ENTER to end the program");
reset_main_loop:
wait_time.tv_sec = SOCKET_TIMEOUT;
wait_time.tv_usec = 0;
edge = inet_address("");
edge_netmask = inet_address("");
addr_tmp = inet_address("");
tag_info = 0;
tag_route_ip = 0;
// 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()) {
// current time
now = time(NULL);
// in case of AUTO_DETECT, check for (changed) default gateway from time to time (and initially)
if((rrr.gateway_detect == AUTO_DETECT) && (now > last_gateway_check + GATEWAY_INTERVAL)) {
// determine the original default gateway excluding the VPN gateway from search
find_default_gateway(&addr_tmp, &rrr.gateway_vpn);
if(memcmp(&addr_tmp, &rrr.gateway_org, sizeof(rrr.gateway_org))) {
// store the detected change
rrr.gateway_org = addr_tmp;
// delete all purgeable routes as they are still relying on old original default gateway
HASH_ITER(hh, rrr.routes, route, tmp_route) {
if((route->purgeable == PURGEABLE)) {
handle_route(route, ROUTE_DEL);
HASH_DEL(rrr.routes, route);
free(route);
}
}
// give way for new info and read requests
last_info_req = 0;
last_read_req = 0;
traceEvent(TRACE_NORMAL, "using default gateway %s", inaddrtoa(ip_str, rrr.gateway_org));
}
last_gateway_check = now;
}
// check if we need to send info request again
if(now > last_info_req + INFO_INTERVAL) {
// send info read request
while(!(tag_info = ((uint32_t)n2n_rand()) >> 23));
msg_len = 0;
msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len),
"r %u info\n", tag_info);
ret = send(sock, udp_buf, msg_len, 0);
last_info_req = now;
}
// check if we need to send read request again
if(now > last_read_req + REFRESH_INTERVAL) {
// the following requests shall only be sent if we have a valid local edge ip address,
// i.e. a valid answer to the info request
if(inet_address_valid(edge)) {
// REVISIT: send unsubscribe request to management port if required to re-subscribe
// send subscribe request to management port, generate fresh tag
while(!(tag_route_ip = ((uint32_t)n2n_rand()) >> 23)); /* >> 23: tags too long can crash the mgmt */
msg_len = 0;
msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len),
"s %u:1:%s peer\n", tag_route_ip, rrr.password);
// REVISIT: something smashes the edge when sending subscritpion request or when edge sends event
// so, the subscription request is not sent yet
// ret = send(sock, udp_buf, msg_len, 0);
// send read requests to management port with same tag
msg_len = 0;
msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len),
"r %u edges\n", tag_route_ip);
ret = send(sock, udp_buf, msg_len, 0);
msg_len = 0;
msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len),
"r %u supernodes\n", tag_route_ip);
ret = send(sock, udp_buf, msg_len, 0);
last_read_req = now;
}
}
// purge the routes from time to time
if(now > last_purge + PURGE_INTERVAL) {
last_purge = now;
HASH_ITER(hh, rrr.routes, route, tmp_route) {
if((route->purgeable == PURGEABLE) && (now > route->last_seen + REMOVE_ROUTE_AGE)) {
handle_route(route, ROUTE_DEL);
HASH_DEL(rrr.routes, route);
free(route);
}
}
}
// REVISIT: check all routes from rrr.routes for still being in system table from time to time?
// or even apply some rtlink magic to get notified on route changes?
// wait for any answer to info or read request
FD_ZERO(&socket_mask);
FD_SET(sock, &socket_mask);
ret = select(sock + 1, &socket_mask, NULL, NULL, &wait_time);
// refresh current time after having waited
now = time(NULL);
if(ret > 0) {
if(FD_ISSET(sock, &socket_mask)) {
msg_len = recv(sock, udp_buf, sizeof(udp_buf), 0);
if((msg_len > 0) && (msg_len < sizeof(udp_buf))) {
// make sure it is a string and replace all newlines with spaces
udp_buf[msg_len] = '\0';
for (char *p = udp_buf; (p = strchr(p, '\n')) != NULL; p++) *p = ' ';
traceEvent(TRACE_DEBUG, "received '%s' from management port", udp_buf);
// handle the answer, json needs to be freed later
json = json_parse(udp_buf);
// look for local edge information
if(tag_info) {
// local IP address (for information)
ret = get_addr_from_json(&addr, json, "ip4addr", tag_info, 0);
if(ret == (WITH_ADDRESS | CORRECT_TAG)) {
traceEvent(TRACE_DEBUG, "received information about %s being edge's IP address", inaddrtoa(ip_str, addr));
if(memcmp(&edge, &addr, sizeof(edge))) {
edge = addr;
traceEvent(TRACE_NORMAL, "found %s being edge's IP address", inaddrtoa(ip_str, addr));
}
}
// local netmask
ret = get_addr_from_json(&addr, json, "ip4netmask", tag_info, 0);
if(ret == (WITH_ADDRESS | CORRECT_TAG)) {
traceEvent(TRACE_DEBUG, "received information about %s being edge's IP netmask", inaddrtoa(ip_str, addr));
if(memcmp(&edge_netmask, &addr, sizeof(edge_netmask))) {
edge_netmask = addr;
traceEvent(TRACE_NORMAL, "found %s being edge's IP netmask", inaddrtoa(ip_str, addr));
// check if vpn gateway matches edge information and warn user if not so
if(!same_subnet(edge, rrr.gateway_vpn, edge_netmask)) {
traceEvent(TRACE_WARNING, "vpn gateway and edge do not share the same subnet");
}
}
}
}
// look for edge/supernode ip addresses
if(tag_route_ip) {
ret = get_addr_from_json(&addr, json, "sockaddr", tag_route_ip, 0);
if(ret == (WITH_ADDRESS | CORRECT_TAG)) {
// add to hash list if required
traceEvent(TRACE_DEBUG, "received information about %s to be routed via default gateway", inaddrtoa(ip_str, addr));
HASH_FIND(hh, rrr.routes, &addr, sizeof(route->net_addr), route);
if(!route)
route = calloc(1, sizeof(n2n_route_t));
else
HASH_DEL(rrr.routes, route);
if(route) {
fill_route(route, addr, inet_address(HOST_MASK), rrr.gateway_org);
route->purgeable = PURGEABLE;
if(!(route->last_seen)) {
handle_route(route, ROUTE_ADD);
}
route->last_seen = now;
HASH_ADD(hh, rrr.routes, net_addr, sizeof(route->net_addr), route);
}
}
}
// no need for current json object anymore
json_free(json);
}
} else {
// can this happen? reset the loop
traceEvent(TRACE_ERROR, "loop reset");
goto reset_main_loop;
}
} else if(ret == 0) {
// select() timeout
// action required?
} else {
// select() error
// action required?
}
}
// REVISIT: send unsubscribe request to management port if required
end_route_tool:
// delete all routes
HASH_ITER(hh, rrr.routes, route, tmp_route) {
handle_route(route, ROUTE_DEL);
HASH_DEL(rrr.routes, route);
free(route);
}
// close connection
closesocket(sock);
return 0;
}
#else /* ifdef __linux__ -- currently, Linux only */
int main (int argc, char* argv[]) {
traceEvent(TRACE_WARNING, "currently, only Linux is 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 */

View File

@ -120,7 +120,7 @@ static int lookup_adapter_info_reg(const char *target_adapter, char *regpath, si
long len, rc; long len, rc;
char index[16]; char index[16];
int err, i; int err, i;
char adapter_name[N2N_IFNAMSIZ]; devstr_t adapter_name;
int rv = 0; int rv = 0;
if((rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, ADAPTER_INFO_KEY, 0, KEY_READ, &key))) { if((rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, ADAPTER_INFO_KEY, 0, KEY_READ, &key))) {