From 8761ae849b4efb643730f1074f7d31159e09106c Mon Sep 17 00:00:00 2001 From: Logan oos Even <46396513+Logan007@users.noreply.github.com> Date: Tue, 12 Jan 2021 17:34:03 +0545 Subject: [PATCH] added optional rtt based supernode selection: federation (#580) --- doc/Building.md | 10 ++ doc/Federation.md | 4 +- src/sn_selection.c | 291 +++++++++++++++++++++++++-------------------- 3 files changed, 174 insertions(+), 131 deletions(-) diff --git a/doc/Building.md b/doc/Building.md index d6bb851..1047f1e 100644 --- a/doc/Building.md +++ b/doc/Building.md @@ -137,3 +137,13 @@ which then will include ZSTD if found on the system. It will be available via `- `./configure --with-zstd --with-openssl CFLAGS="-O3 -march=native"` 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. \ No newline at end of file diff --git a/doc/Federation.md b/doc/Federation.md index 972a0ae..a8817c8 100644 --- a/doc/Federation.md +++ b/doc/Federation.md @@ -1,4 +1,4 @@ -# Supernode Federation +# Supernode Federation ## Idea To enhance resilience in terms of backup and fail-over, also for load-balancing, multiple supernodes can easily interconnect and form a special community, called **federation**. @@ -33,3 +33,5 @@ Once edges have saved those informations, it is up to them choosing the supernod An edge connects to the supernode with the lowest work-load and it is re-considered from time to time, with each re-registration. We used a stickyness factor to avoid too much jumping between supernodes. Thanks to this last feature, n2n is now able to handle security attacks (e.g., 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](Building.md) is offered. Note, that workload distribution among supernodes is not so fair then. \ No newline at end of file diff --git a/src/sn_selection.c b/src/sn_selection.c index 444caa3..1f1c1d2 100644 --- a/src/sn_selection.c +++ b/src/sn_selection.c @@ -1,130 +1,161 @@ -/** - * (C) 2007-20 - 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 - * - */ - -#include "n2n.h" - - -static SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_common_read (n2n_edge_t *eee); -static int sn_selection_criterion_sort (peer_info_t *a, peer_info_t *b); - -/* ****************************************************************************** */ - -/* Initialize selection_criterion field in peer_info structure*/ -int sn_selection_criterion_init (peer_info_t *peer) { - - if(peer != NULL) { - sn_selection_criterion_default(&(peer->selection_criterion)); - } - - return 0; /* OK */ -} - -/* 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; - - return 0; /* OK */ -} - -/* Take data from PEER_INFO payload and transform them into a selection_criterion. - * This function is highly dependant of the chosen selection criterion. - */ -int sn_selection_criterion_calculate (n2n_edge_t *eee, peer_info_t *peer, SN_SELECTION_CRITERION_DATA_TYPE *data) { - - SN_SELECTION_CRITERION_DATA_TYPE common_data; - int sum = 0; - - common_data = sn_selection_criterion_common_read(eee); - 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); - } - - return 0; /* OK */ -} - -/* Set sn_selection_criterion_common_data field to default value. */ -int sn_selection_criterion_common_data_default (n2n_edge_t *eee) { - - 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); - - return 0; /* OK */ -} - -/* Return the value of sn_selection_criterion_common_data field. */ -static SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_common_read (n2n_edge_t *eee) { - - return eee->sn_selection_criterion_common_data; -} - -/* Function that compare two selection_criterion fields and sorts them in ascending order. */ -static int sn_selection_criterion_sort (peer_info_t *a, peer_info_t *b) { - - // comparison function for sorting supernodes in ascending order of their selection_criterion. - return (a->selection_criterion - b->selection_criterion); -} - -/* Function that sorts peer_list using sn_selection_criterion_sort. */ -int sn_selection_sort (peer_info_t **peer_list) { - - HASH_SORT(*peer_list, sn_selection_criterion_sort); - - return 0; /* OK */ -} - -/* Function that gathers requested data on a supernode. */ -SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_gather_data (n2n_sn_t *sss) { - - SN_SELECTION_CRITERION_DATA_TYPE data = 0, tmp = 0; - struct sn_community *comm, *tmp_comm; - - HASH_ITER(hh, sss->communities, comm, tmp_comm) { - tmp = HASH_COUNT(comm->edges) + 1; /* number of nodes in the community + the community itself. */ - if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { /*double-count encrypted communities (and their nodes): they exert more load on supernode. */ - tmp *= 2; - } - data += tmp; - } - - return htobe32(data); -} - -/* 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) { - - if(NULL == out) { - return NULL; - } - memset(out, 0, SN_SELECTION_CRITERION_BUF_SIZE); - snprintf(out, SN_SELECTION_CRITERION_BUF_SIZE - 1, "ld = %d", (short int)(peer->selection_criterion)); - - return out; -} +/** + * (C) 2007-21 - 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 + * + */ + + +#include "n2n.h" + + +static SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_common_read (n2n_edge_t *eee); +static int sn_selection_criterion_sort (peer_info_t *a, peer_info_t *b); + + +/* Initialize selection_criterion field in peer_info structure*/ +int sn_selection_criterion_init (peer_info_t *peer) { + + if(peer != NULL) { + sn_selection_criterion_default(&(peer->selection_criterion)); + } + + return 0; /* OK */ +} + + +/* 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; + + return 0; /* OK */ +} + + +/* Take data from PEER_INFO payload and transform them into a selection_criterion. + * This function is highly dependant of the chosen selection criterion. + */ +int sn_selection_criterion_calculate (n2n_edge_t *eee, peer_info_t *peer, SN_SELECTION_CRITERION_DATA_TYPE *data) { + + SN_SELECTION_CRITERION_DATA_TYPE common_data; + int sum = 0; + + common_data = sn_selection_criterion_common_read(eee); + +#ifndef SN_SELECTION_RTT + 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); + } +#else + peer->selection_criterion = (SN_SELECTION_CRITERION_DATA_TYPE)(time_stamp() >> 22) - common_data; +#endif + + return 0; /* OK */ +} + + +/* 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; + + 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); +#else + eee->sn_selection_criterion_common_data = (SN_SELECTION_CRITERION_DATA_TYPE)(time_stamp() >> 22); +#endif + + return 0; /* OK */ +} + + +/* Return the value of sn_selection_criterion_common_data field. */ +static SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_common_read (n2n_edge_t *eee) { + + return eee->sn_selection_criterion_common_data; +} + + +/* Function that compare two selection_criterion fields and sorts them in ascending order. */ +static int sn_selection_criterion_sort (peer_info_t *a, peer_info_t *b) { + + // comparison function for sorting supernodes in ascending order of their selection_criterion. + return (a->selection_criterion - b->selection_criterion); +} + + +/* Function that sorts peer_list using sn_selection_criterion_sort. */ +int sn_selection_sort (peer_info_t **peer_list) { + + HASH_SORT(*peer_list, sn_selection_criterion_sort); + + return 0; /* OK */ +} + + +/* Function that gathers requested data on a supernode. + * it remains unaffected by SN_SELECT_RTT macro because it refers to edge behaviour only + */ +SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_gather_data (n2n_sn_t *sss) { + + SN_SELECTION_CRITERION_DATA_TYPE data = 0, tmp = 0; + struct sn_community *comm, *tmp_comm; + + HASH_ITER(hh, sss->communities, comm, tmp_comm) { + // number of nodes in the community + the community itself + tmp = HASH_COUNT(comm->edges) + 1; + if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { + // double-count encrypted communities (and their nodes): they exert more load on supernode + tmp *= 2; + } + data += tmp; + } + + return htobe32(data); +} + + +/* 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) { + + if(NULL == out) { + return NULL; + } + memset(out, 0, SN_SELECTION_CRITERION_BUF_SIZE); + +#ifndef SN_SELECTION_RTT + snprintf(out, SN_SELECTION_CRITERION_BUF_SIZE - 1, + (int16_t)(peer->selection_criterion) != -1 ? "ld = %7d" : + "ld = _______", peer->selection_criterion); +#else + snprintf(out, SN_SELECTION_CRITERION_BUF_SIZE - 1, + (int16_t)(peer->selection_criterion) != -1 ? "rtt %5d ms" : + "rtt _____ ms", peer->selection_criterion); + +#endif + + return out; +}