From c61b62ab1bf51b7020238bc9ab50be5fd90e2828 Mon Sep 17 00:00:00 2001 From: Logan oos Even <46396513+Logan007@users.noreply.github.com> Date: Sun, 24 Oct 2021 12:53:25 +0545 Subject: [PATCH] choose supernode selection strategy at run-time '--select-rtt' (#864) --- doc/Building.md | 12 +---- doc/Federation.md | 2 +- edge.8 | 4 ++ include/n2n_define.h | 2 +- include/n2n_typedefs.h | 8 ++- include/sn_selection.h | 2 +- src/edge.c | 16 +++++- src/edge_management.c | 2 +- src/edge_utils.c | 6 ++- src/sn_selection.c | 108 ++++++++++++++++++++++++++++------------- src/sn_utils.c | 2 +- src/wire.c | 4 +- 12 files changed, 113 insertions(+), 55 deletions(-) diff --git a/doc/Building.md b/doc/Building.md index ccd0d39..3dfc1b6 100644 --- a/doc/Building.md +++ b/doc/Building.md @@ -45,7 +45,7 @@ In order to run n2n, you will need the following: - If OpenSSL has been linked dynamically, the corresponding `.dll` file should be available onto the target computer. - + NOTE: Sticking to this tool chain has historically meant that resulting executables are more likely to be able to communicate with Linux or other OS builds, however efforts are being made to address this concern. @@ -173,16 +173,6 @@ which then will include ZSTD if found on the system. It will be available via `- Again, and this needs to be reiterated sufficiently often, please do no forget to `make clean` after (re-)configuration and before building (again) using `make`. -## Federation – Supernode Selection by Round Trip Time - -If used with multiple supernodes, by default, an edge choses the least loaded supernode to connect to. This selection strategy is part of the [federation](Federation.md) feature and aims at a fair workload distribution among the supernodes. To serve special scenarios, an edge can be compiled to always connect to the supernode with the lowest round trip time, i.e. the "closest" with the lowest ping. However, this could result in not so fair workload distribution among supernodes. This option can be configured by defining the macro `SN_SELECTION_RTT` and affects edge's behaviour only: - -`./configure CFLAGS="-DSN_SELECTION_RTT"` - -which of course can be combined with the compiler optimizations mentioned above… - -Note that the activation of this strategy requires a sufficiently accurate local day-of-time clock. It probably will fail on smaller systems using `uclibc` (instead of `glibc`) whose day-of-time clock is said to not provide sub-second accuracy. - ## SPECK – ARM NEON Hardware Acceleration By default, SPECK does not take advantage of ARM NEON hardware acceleration even if compiled with `-march=native`. The reason is that the NEON implementation proved to be slower than the 64-bit scalar code on Raspberry Pi 3B+, see [here](https://github.com/ntop/n2n/issues/563). diff --git a/doc/Federation.md b/doc/Federation.md index 70fda0b..01fdbf3 100644 --- a/doc/Federation.md +++ b/doc/Federation.md @@ -36,4 +36,4 @@ An edge connects to the supernode with the lowest work-load and it is re-conside Thanks to this feature, n2n is now able to handle security attacks such as DoS against supernodes and it can redistribute the entire load of the network in a fair manner between all the supernodes. -To serve scenarios in which an edge is supposed to select the supernode by round trip time, i.e. choosing the "closest" one, a [compile-time option](https://github.com/ntop/n2n/blob/dev/doc/Building.md#federation--supernode-selection-by-round-trip-time) is available. Note, that workload distribution among supernodes is not so fair then. +To serve scenarios in which an edge is supposed to select the supernode by round trip time, i.e. choosing the "closest" one, the `--select-rtt` command line option is available at the edge. Note, that workload distribution among supernodes might not be so fair then. diff --git a/edge.8 b/edge.8 index 0cd5269..5d30fde 100644 --- a/edge.8 +++ b/edge.8 @@ -101,6 +101,10 @@ use header encryption, supernode needs fixed community .TP \fB\-z1\fR ... \fB\-z2\fR compress outgoing data packets, -z1 = lzo1x, disabled by default +.TP +\fB\--select-rtt\fR +select supernode by round trip time if several to choose from (federation), +defaults to load-based selection strategy if not provided. .SH TAP DEVICE AND OVERLAY NETWORK CONFIGURATION .TP \fB\-a \fR[\fImode\fR]<\fIip\fR>[\fI/n\fR] diff --git a/include/n2n_define.h b/include/n2n_define.h index a9bd19d..bf7f55a 100644 --- a/include/n2n_define.h +++ b/include/n2n_define.h @@ -197,7 +197,7 @@ enum skip_add {SN_ADD = 0, SN_ADD_SKIP = 1, SN_ADD_ADDED = 2}; #define N2N_THREAD_PARAMETER_DATATYPE void* #endif -#define SN_SELECTION_CRITERION_DATA_TYPE uint32_t +#define SN_SELECTION_CRITERION_DATA_TYPE uint64_t #define SN_SELECTION_CRITERION_BUF_SIZE 16 #define N2N_TRANSFORM_ID_USER_START 64 diff --git a/include/n2n_typedefs.h b/include/n2n_typedefs.h index 454f835..26bbb27 100644 --- a/include/n2n_typedefs.h +++ b/include/n2n_typedefs.h @@ -286,6 +286,11 @@ typedef enum n2n_pc { typedef char n2n_version_t[N2N_VERSION_STRING_SIZE]; +#define SN_SELECTION_STRATEGY_LOAD 1 +#define SN_SELECTION_STRATEGY_RTT 2 +#define SN_SELECTION_STRATEGY_MAC 3 /* REVISIT: not implemented yet */ + + typedef struct n2n_ip_subnet { uint32_t net_addr; /* Host order IP address. */ uint8_t net_bitlen; /* Subnet prefix. */ @@ -414,7 +419,7 @@ typedef struct n2n_PEER_INFO { n2n_mac_t mac; n2n_sock_t sock; n2n_sock_t preferred_sock; - SN_SELECTION_CRITERION_DATA_TYPE data; + uint32_t load; n2n_version_t version; time_t uptime; } n2n_PEER_INFO_t; @@ -668,6 +673,7 @@ typedef struct n2n_edge_conf { n2n_auth_t auth; filter_rule_t *network_traffic_filter_rules; int metric; /**< Network interface metric (Windows only). */ + uint8_t sn_selection_strategy; /**< encodes currently chosen supernode selection strategy. */ uint8_t number_max_sn_pings; /**< Number of maximum concurrently allowed supernode pings. */ } n2n_edge_conf_t; diff --git a/include/sn_selection.h b/include/sn_selection.h index 82ff869..ffd47f8 100644 --- a/include/sn_selection.h +++ b/include/sn_selection.h @@ -40,7 +40,7 @@ int sn_selection_sort (peer_info_t **peer_list); SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_gather_data (n2n_sn_t *sss); /* management port output function */ -extern char * sn_selection_criterion_str (selection_criterion_str_t out, peer_info_t *peer); +extern char * sn_selection_criterion_str (n2n_edge_t *eee, selection_criterion_str_t out, peer_info_t *peer); #endif /* _SN_SELECTION_ */ diff --git a/src/edge.c b/src/edge.c index 1324628..c1aef2c 100644 --- a/src/edge.c +++ b/src/edge.c @@ -179,6 +179,8 @@ static void help (int level) { "[-z] " "\n " "[-e ] [-S]" + "\n " + "[--select-rtt]" "\n\n tap device and " "[-a [static:|dhcp:][/]] " "\n overlay network " @@ -227,6 +229,7 @@ static void help (int level) { "\n flag options [-H] enable header encryption" "\n [-r] enable packet forwarding through n2n community" "\n [-E] accept multicast MAC addresses" + "\n [--select-rtt] select supernode by round trip time" #ifndef WIN32 "\n [-f] do not fork but run in foreground" #endif @@ -285,6 +288,9 @@ static void help (int level) { "-z2 = zstd, " #endif "disabled by default\n"); + printf("--select-rtt | supernode selection based on round trip time (default:\n" + " | by load)\n"); + printf ("\n"); printf (" TAP DEVICE AND OVERLAY NETWORK CONFIGURATION\n"); printf (" --------------------------------------------\n\n"); @@ -720,6 +726,13 @@ static int setOption (int optkey, char *optargument, n2n_tuntap_priv_config_t *e break; } + case '[': /* round-trip-time-based supernode selection strategy */ { + // overwrites the default load-based strategy + conf->sn_selection_strategy = SN_SELECTION_STRATEGY_RTT; + + break; + } + case 'h': /* quick reference */ { return 2; } @@ -771,8 +784,9 @@ static const struct option long_options[] = { "tap-device", required_argument, NULL, 'd' }, { "euid", required_argument, NULL, 'u' }, { "egid", required_argument, NULL, 'g' }, - { "help" , no_argument, NULL, '@' }, /* special character '@' to identify long help case */ { "verbose", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, '@' }, /* internal special character '@' to identify long help case */ + { "select-rtt", no_argument, NULL, '[' }, /* '[' rtt selection strategy */ { NULL, 0, NULL, 0 } }; diff --git a/src/edge_management.c b/src/edge_management.c index 00de9e1..9f28339 100644 --- a/src/edge_management.c +++ b/src/edge_management.c @@ -134,7 +134,7 @@ static void mgmt_supernodes (n2n_edge_t *eee, char *udp_buf, const struct sockad (peer == eee->curr_sn) ? (eee->sn_wait ? 2 : 1 ) : 0, is_null_mac(peer->mac_addr) ? "" : macaddr_str(mac_buf, peer->mac_addr), sock_to_cstr(sockbuf, &(peer->sock)), - sn_selection_criterion_str(sel_buf, peer), + sn_selection_criterion_str(eee, sel_buf, peer), peer->last_seen, peer->uptime); diff --git a/src/edge_utils.c b/src/edge_utils.c index b5c7981..23d192a 100644 --- a/src/edge_utils.c +++ b/src/edge_utils.c @@ -1982,7 +1982,7 @@ static void readFromMgmtSocket (n2n_edge_t *eee) { (peer == eee->curr_sn) ? (eee->sn_wait ? "." : "*" ) : "", is_null_mac(peer->mac_addr) ? "" : macaddr_str(mac_buf, peer->mac_addr), sock_to_cstr(sockbuf, &(peer->sock)), - sn_selection_criterion_str(sel_buf, peer), + sn_selection_criterion_str(eee, sel_buf, peer), (peer->last_seen) ? time_buf : "", (peer->uptime) ? uptime_buf : ""); @@ -2827,7 +2827,8 @@ void process_udp (n2n_edge_t *eee, const struct sockaddr_in *sender_sock, const scan->uptime = pi.uptime; memcpy(scan->version, pi.version, sizeof(n2n_version_t)); /* The data type depends on the actual selection strategy that has been chosen. */ - sn_selection_criterion_calculate(eee, scan, &pi.data); + SN_SELECTION_CRITERION_DATA_TYPE sn_sel_tmp = pi.load; + sn_selection_criterion_calculate(eee, scan, &sn_sel_tmp); traceEvent(TRACE_INFO, "Rx PONG from supernode %s", macaddr_str(mac_buf1, pi.srcMac)); @@ -3700,6 +3701,7 @@ void edge_init_conf_defaults (n2n_edge_conf_t *conf) { generate_private_key(*(conf->shared_secret), getenv("N2N_PASSWORD")); } + conf->sn_selection_strategy = SN_SELECTION_STRATEGY_LOAD; conf->metric = 0; } diff --git a/src/sn_selection.c b/src/sn_selection.c index f011e89..98b3ec1 100644 --- a/src/sn_selection.c +++ b/src/sn_selection.c @@ -38,7 +38,7 @@ int sn_selection_criterion_init (peer_info_t *peer) { /* Set selection_criterion field to default value according to selected strategy. */ int sn_selection_criterion_default (SN_SELECTION_CRITERION_DATA_TYPE *selection_criterion) { - *selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE) (UINT32_MAX >> 1) - 1; + *selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE)(UINT64_MAX >> 1) - 1; return 0; /* OK */ } @@ -47,7 +47,7 @@ int sn_selection_criterion_default (SN_SELECTION_CRITERION_DATA_TYPE *selection_ /* Set selection_criterion field to 'bad' value (worse than default) according to selected strategy. */ int sn_selection_criterion_bad (SN_SELECTION_CRITERION_DATA_TYPE *selection_criterion) { - *selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE) (UINT32_MAX >> 1); + *selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE)(UINT64_MAX >> 1); return 0; /* OK */ } @@ -55,7 +55,7 @@ int sn_selection_criterion_bad (SN_SELECTION_CRITERION_DATA_TYPE *selection_crit /* Set selection_criterion field to 'good' value (better than default) according to selected strategy. */ int sn_selection_criterion_good (SN_SELECTION_CRITERION_DATA_TYPE *selection_criterion) { - *selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE) (UINT32_MAX >> 1) - 2; + *selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE)(UINT64_MAX >> 1) - 2; return 0; /* OK */ } @@ -71,20 +71,33 @@ int sn_selection_criterion_calculate (n2n_edge_t *eee, peer_info_t *peer, SN_SEL common_data = sn_selection_criterion_common_read(eee); -#ifndef SN_SELECTION_RTT - peer->selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE)(be32toh(*data) + common_data); + switch(eee->conf.sn_selection_strategy) { - /* Mitigation of the real supernode load in order to see less oscillations. - * Edges jump from a supernode to another back and forth due to purging. - * Because this behavior has a cost of switching, the real load is mitigated with a stickyness factor. - * This factor is dynamically calculated basing on network size and prevent that unnecessary switching */ - if(peer == eee->curr_sn) { - sum = HASH_COUNT(eee->known_peers) + HASH_COUNT(eee->pending_peers); - peer->selection_criterion = peer->selection_criterion * sum / (sum + 1); + case SN_SELECTION_STRATEGY_LOAD: { + peer->selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE)(be32toh(*data) + common_data); + + /* Mitigation of the real supernode load in order to see less oscillations. + * Edges jump from a supernode to another back and forth due to purging. + * Because this behavior has a cost of switching, the real load is mitigated with a stickyness factor. + * This factor is dynamically calculated basing on network size and prevent that unnecessary switching */ + if(peer == eee->curr_sn) { + sum = HASH_COUNT(eee->known_peers) + HASH_COUNT(eee->pending_peers); + peer->selection_criterion = peer->selection_criterion * sum / (sum + 1); + } + break; + } + + case SN_SELECTION_STRATEGY_RTT: { + peer->selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE)(time_stamp() >> 22) - common_data; + break; + } + + default: { + // this should never happen + traceEvent(TRACE_ERROR, "selection_criterion unknown selection strategy configuration"); + break; + } } -#else - peer->selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE)(time_stamp() >> 22) - common_data; -#endif return 0; /* OK */ } @@ -93,17 +106,30 @@ int sn_selection_criterion_calculate (n2n_edge_t *eee, peer_info_t *peer, SN_SEL /* Set sn_selection_criterion_common_data field to default value. */ int sn_selection_criterion_common_data_default (n2n_edge_t *eee) { -#ifndef SN_SELECTION_RTT - SN_SELECTION_CRITERION_DATA_TYPE tmp = 0; + switch(eee->conf.sn_selection_strategy) { - tmp = HASH_COUNT(eee->pending_peers); - if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) { - tmp *= 2; + case SN_SELECTION_STRATEGY_LOAD: { + SN_SELECTION_CRITERION_DATA_TYPE tmp = 0; + + tmp = HASH_COUNT(eee->pending_peers); + if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) { + tmp *= 2; + } + eee->sn_selection_criterion_common_data = tmp / HASH_COUNT(eee->conf.supernodes); + break; + } + + case SN_SELECTION_STRATEGY_RTT: { + eee->sn_selection_criterion_common_data = (SN_SELECTION_CRITERION_DATA_TYPE)(time_stamp() >> 22); + break; + } + + default: { + // this should never happen + traceEvent(TRACE_ERROR, "selection_criterion unknown selection strategy configuration"); + break; + } } - eee->sn_selection_criterion_common_data = tmp / HASH_COUNT(eee->conf.supernodes); -#else - eee->sn_selection_criterion_common_data = (SN_SELECTION_CRITERION_DATA_TYPE)(time_stamp() >> 22); -#endif return 0; /* OK */ } @@ -134,7 +160,7 @@ int sn_selection_sort (peer_info_t **peer_list) { /* Function that gathers requested data on a supernode. - * it remains unaffected by SN_SELECT_RTT macro because it refers to edge behaviour only + * it remains unaffected by selection strategy because it refers to edge behaviour only */ SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_gather_data (n2n_sn_t *sss) { @@ -156,7 +182,10 @@ SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_gather_data (n2n_sn_t *s /* Convert selection_criterion field in a string for management port output. */ -extern char * sn_selection_criterion_str (selection_criterion_str_t out, peer_info_t *peer) { +extern char * sn_selection_criterion_str (n2n_edge_t *eee, selection_criterion_str_t out, peer_info_t *peer) { + + int chars = 0; + if(NULL == out) { return NULL; @@ -168,15 +197,28 @@ extern char * sn_selection_criterion_str (selection_criterion_str_t out, peer_in // Alternatively, typecast to (int16_t) and check for greater or equal zero if(peer->selection_criterion < (UINT32_MAX >> 2)) { -#ifndef SN_SELECTION_RTT - int chars = snprintf(out, SN_SELECTION_CRITERION_BUF_SIZE, "load = %8d", peer->selection_criterion); -#else - int chars = snprintf(out, SN_SELECTION_CRITERION_BUF_SIZE, "rtt = %6d ms", peer->selection_criterion); -#endif + switch(eee->conf.sn_selection_strategy) { - /* this test is to make "-Wformat-truncation" less sad */ + case SN_SELECTION_STRATEGY_LOAD: { + chars = snprintf(out, SN_SELECTION_CRITERION_BUF_SIZE, "load = %8ld", peer->selection_criterion); + break; + } + + case SN_SELECTION_STRATEGY_RTT: { + chars = snprintf(out, SN_SELECTION_CRITERION_BUF_SIZE, "rtt = %6ld ms", peer->selection_criterion); + break; + } + + default: { + // this should never happen + traceEvent(TRACE_ERROR, "selection_criterion unknown selection strategy configuration"); + break; + } + } + + // this test is to make "-Wformat-truncation" less sad if(chars > SN_SELECTION_CRITERION_BUF_SIZE) { - traceEvent(TRACE_INFO, "selection_criterion buffer overflow"); + traceEvent(TRACE_ERROR, "selection_criterion buffer overflow"); } } diff --git a/src/sn_utils.c b/src/sn_utils.c index 2bb65d6..ab812d0 100644 --- a/src/sn_utils.c +++ b/src/sn_utils.c @@ -2549,7 +2549,7 @@ static int process_udp (n2n_sn_t * sss, pi.sock.family = AF_INET; pi.sock.port = ntohs(sender_sock->sin_port); memcpy(pi.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE); - pi.data = sn_selection_criterion_gather_data(sss); + pi.load = sn_selection_criterion_gather_data(sss); snprintf(pi.version, sizeof(pi.version), "%s", sss->version); pi.uptime = now - sss->start_time; diff --git a/src/wire.c b/src/wire.c index 7265de8..e7e2c25 100644 --- a/src/wire.c +++ b/src/wire.c @@ -663,7 +663,7 @@ int encode_PEER_INFO (uint8_t *base, if(cmn->flags & N2N_FLAGS_SOCKET) { retval += encode_sock(base, idx, &pkt->preferred_sock); } - retval += encode_buf(base, idx, &pkt->data, sizeof(SN_SELECTION_CRITERION_DATA_TYPE)); + retval += encode_uint32(base, idx, (uint32_t)pkt->load); retval += encode_uint32(base, idx, (uint32_t)pkt->uptime); retval += encode_buf(base, idx, pkt->version, sizeof(n2n_version_t)); @@ -687,7 +687,7 @@ int decode_PEER_INFO (n2n_PEER_INFO_t *pkt, if(cmn->flags & N2N_FLAGS_SOCKET) { retval += decode_sock(&pkt->preferred_sock, base, rem, idx); } - retval += decode_buf((uint8_t*)&pkt->data, sizeof(SN_SELECTION_CRITERION_DATA_TYPE), base, rem, idx); + retval += decode_uint32(&pkt->load, base, rem, idx); retval += decode_uint32((uint32_t*)&pkt->uptime, base, rem, idx); retval += decode_buf((uint8_t*)pkt->version, sizeof(n2n_version_t), base, rem, idx);