|
|
|
/**
|
|
|
|
* (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 <http://www.gnu.org/licenses/>
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "n2n.h"
|
|
|
|
|
|
|
|
#define HASH_FIND_COMMUNITY(head, name, out) HASH_FIND_STR(head, name, out)
|
|
|
|
|
|
|
|
int resolve_create_thread (n2n_resolve_parameter_t **param, struct peer_info *sn_list);
|
|
|
|
int resolve_check (n2n_resolve_parameter_t *param, uint8_t resolution_request, time_t now);
|
|
|
|
int resolve_cancel_thread (n2n_resolve_parameter_t *param);
|
|
|
|
|
|
|
|
|
|
|
|
static ssize_t sendto_peer (n2n_sn_t *sss,
|
|
|
|
const struct peer_info *peer,
|
|
|
|
const uint8_t *pktbuf,
|
|
|
|
size_t pktsize);
|
|
|
|
|
|
|
|
static int sendto_mgmt (n2n_sn_t *sss,
|
|
|
|
const struct sockaddr_in *sender_sock,
|
|
|
|
const uint8_t *mgmt_buf,
|
|
|
|
size_t mgmt_size);
|
|
|
|
|
|
|
|
static uint16_t reg_lifetime (n2n_sn_t *sss);
|
|
|
|
|
|
|
|
static int update_edge (n2n_sn_t *sss,
|
|
|
|
const n2n_REGISTER_SUPER_t* reg,
|
|
|
|
struct sn_community *comm,
|
|
|
|
const n2n_sock_t *sender_sock,
|
|
|
|
const SOCKET socket_fd,
|
|
|
|
n2n_auth_t *answer_auth,
|
|
|
|
int skip_add,
|
|
|
|
time_t now);
|
|
|
|
|
|
|
|
static int re_register_and_purge_supernodes (n2n_sn_t *sss,
|
|
|
|
struct sn_community *comm,
|
|
|
|
time_t *p_last_re_reg_and_purge,
|
|
|
|
time_t now,
|
|
|
|
uint8_t forced);
|
|
|
|
|
|
|
|
static int purge_expired_communities (n2n_sn_t *sss,
|
|
|
|
time_t* p_last_purge,
|
|
|
|
time_t now);
|
|
|
|
|
|
|
|
static int sort_communities (n2n_sn_t *sss,
|
|
|
|
time_t* p_last_sort,
|
|
|
|
time_t now);
|
|
|
|
|
|
|
|
static int process_mgmt (n2n_sn_t *sss,
|
|
|
|
const struct sockaddr_in *sender_sock,
|
|
|
|
const uint8_t *mgmt_buf,
|
|
|
|
size_t mgmt_size,
|
|
|
|
time_t now);
|
|
|
|
|
|
|
|
static int process_udp (n2n_sn_t *sss,
|
|
|
|
const struct sockaddr_in *sender_sock,
|
|
|
|
const SOCKET socket_fd,
|
|
|
|
uint8_t *udp_buf,
|
|
|
|
size_t udp_size,
|
|
|
|
time_t now);
|
|
|
|
|
|
|
|
|
|
|
|
/* ************************************** */
|
|
|
|
|
|
|
|
|
|
|
|
void close_tcp_connection (n2n_sn_t *sss, n2n_tcp_connection_t *conn) {
|
|
|
|
|
|
|
|
struct sn_community *comm, *tmp_comm;
|
|
|
|
struct peer_info *edge, *tmp_edge;
|
|
|
|
|
|
|
|
if(!conn)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// find peer by file descriptor
|
|
|
|
HASH_ITER(hh, sss->communities, comm, tmp_comm) {
|
|
|
|
HASH_ITER(hh, comm->edges, edge, tmp_edge) {
|
|
|
|
if(edge->socket_fd == conn->socket_fd) {
|
|
|
|
// remove peer
|
|
|
|
HASH_DEL(comm->edges, edge);
|
|
|
|
free(edge);
|
|
|
|
goto close_conn; /* break - level 2 */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
close_conn:
|
|
|
|
// close the connection
|
|
|
|
shutdown(conn->socket_fd, SHUT_RDWR);
|
|
|
|
closesocket(conn->socket_fd);
|
|
|
|
// forget about the connection, will be deleted later
|
|
|
|
conn->inactive = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* *************************************************** */
|
|
|
|
|
|
|
|
|
|
|
|
// generate shared secrets for user authentication; can be done only after
|
|
|
|
// federation name is known (-F) and community list completely read (-c)
|
|
|
|
void calculate_shared_secrets (n2n_sn_t *sss) {
|
|
|
|
|
|
|
|
struct sn_community *comm, *tmp_comm;
|
|
|
|
sn_user_t *user, *tmp_user;
|
|
|
|
|
|
|
|
traceEvent(TRACE_INFO, "started shared secrets calculation for edge authentication");
|
|
|
|
|
|
|
|
generate_private_key(sss->private_key, sss->federation->community + 1); /* skip '*' federation leading character */
|
|
|
|
HASH_ITER(hh, sss->communities, comm, tmp_comm) {
|
|
|
|
if(comm->is_federation) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
HASH_ITER(hh, comm->allowed_users, user, tmp_user) {
|
|
|
|
// calculate common shared secret (ECDH)
|
|
|
|
generate_shared_secret(user->shared_secret, sss->private_key, user->public_key);
|
|
|
|
// prepare for use as key
|
|
|
|
user->shared_secret_ctx = (he_context_t*)calloc(1, sizeof(speck_context_t));
|
|
|
|
speck_init((speck_context_t**)&user->shared_secret_ctx, user->shared_secret, 128);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
traceEvent(TRACE_NORMAL, "calculated shared secrets for edge authentication");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// calculate dynamic keys
|
|
|
|
void calculate_dynamic_keys (n2n_sn_t *sss) {
|
|
|
|
|
|
|
|
struct sn_community *comm, *tmp_comm = NULL;
|
|
|
|
|
|
|
|
traceEvent(TRACE_INFO, "calculating dynamic keys");
|
|
|
|
HASH_ITER(hh, sss->communities, comm, tmp_comm) {
|
|
|
|
// skip federation
|
|
|
|
if(comm->is_federation) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate dynamic keys if this is a user/pw auth'ed community
|
|
|
|
if(comm->allowed_users) {
|
|
|
|
calculate_dynamic_key(comm->dynamic_key, /* destination */
|
|
|
|
sss->dynamic_key_time, /* time - same for all */
|
|
|
|
comm->community, /* community name */
|
|
|
|
sss->federation->community); /* federation name */
|
|
|
|
packet_header_change_dynamic_key(comm->dynamic_key,
|
|
|
|
&(comm->header_encryption_ctx_dynamic),
|
|
|
|
&(comm->header_iv_ctx_dynamic));
|
|
|
|
traceEvent(TRACE_DEBUG, "calculated dynamic key for community '%s'", comm->community);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// send RE_REGISTER_SUPER to all edges from user/pw auth'ed communites
|
|
|
|
void send_re_register_super (n2n_sn_t *sss) {
|
|
|
|
|
|
|
|
struct sn_community *comm, *tmp_comm = NULL;
|
|
|
|
struct peer_info *edge, *tmp_edge = NULL;
|
|
|
|
n2n_common_t cmn;
|
|
|
|
uint8_t rereg_buf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
size_t encx = 0;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
|
|
|
|
HASH_ITER(hh, sss->communities, comm, tmp_comm) {
|
|
|
|
if(comm->is_federation) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// send RE_REGISTER_SUPER to edges if this is a user/pw auth community
|
|
|
|
if(comm->allowed_users) {
|
|
|
|
// prepare
|
|
|
|
cmn.ttl = N2N_DEFAULT_TTL;
|
|
|
|
cmn.pc = n2n_re_register_super;
|
|
|
|
cmn.flags = N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
memcpy(cmn.community, comm->community, N2N_COMMUNITY_SIZE);
|
|
|
|
|
|
|
|
HASH_ITER(hh, comm->edges, edge, tmp_edge) {
|
|
|
|
// encode
|
|
|
|
encx = 0;
|
|
|
|
encode_common(rereg_buf, &encx, &cmn);
|
|
|
|
|
|
|
|
// send
|
|
|
|
traceEvent(TRACE_DEBUG, "send RE_REGISTER_SUPER to %s",
|
|
|
|
sock_to_cstr(sockbuf, &(edge->sock)));
|
|
|
|
|
|
|
|
packet_header_encrypt(rereg_buf, encx, encx,
|
|
|
|
comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
|
|
|
|
time_stamp());
|
|
|
|
|
|
|
|
/* sent = */ sendto_peer(sss, edge, rereg_buf, encx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Load the list of allowed communities. Existing/previous ones will be removed,
|
|
|
|
* return 0 on success, -1 if file not found, -2 if no valid entries found
|
|
|
|
*/
|
|
|
|
int load_allowed_sn_community (n2n_sn_t *sss) {
|
|
|
|
|
|
|
|
char buffer[4096], *line, *cmn_str, net_str[20], format[20];
|
|
|
|
|
|
|
|
sn_user_t *user, *tmp_user;
|
|
|
|
n2n_desc_t username;
|
|
|
|
n2n_private_public_key_t public_key;
|
|
|
|
uint8_t ascii_public_key[(N2N_PRIVATE_PUBLIC_KEY_SIZE * 8 + 5) / 6 + 1];
|
|
|
|
|
|
|
|
dec_ip_str_t ip_str = {'\0'};
|
|
|
|
uint8_t bitlen;
|
|
|
|
in_addr_t net;
|
|
|
|
uint32_t mask;
|
|
|
|
FILE *fd = fopen(sss->community_file, "r");
|
|
|
|
|
|
|
|
struct sn_community *comm, *tmp_comm, *last_added_comm = NULL;
|
|
|
|
struct peer_info *edge, *tmp_edge;
|
|
|
|
node_supernode_association_t *assoc, *tmp_assoc;
|
|
|
|
n2n_tcp_connection_t *conn;
|
|
|
|
time_t any_time = 0;
|
|
|
|
|
|
|
|
uint32_t num_communities = 0;
|
|
|
|
|
|
|
|
struct sn_community_regular_expression *re, *tmp_re;
|
|
|
|
uint32_t num_regex = 0;
|
|
|
|
int has_net;
|
|
|
|
|
|
|
|
if(fd == NULL) {
|
|
|
|
traceEvent(TRACE_WARNING, "File %s not found", sss->community_file);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset data structures ------------------------------
|
|
|
|
|
|
|
|
// send RE_REGISTER_SUPER to all edges from user/pw auth communites, this is safe because
|
|
|
|
// follow-up REGISTER_SUPER cannot be handled before this function ends
|
|
|
|
send_re_register_super(sss);
|
|
|
|
|
|
|
|
// remove communities (not: federation)
|
|
|
|
HASH_ITER(hh, sss->communities, comm, tmp_comm) {
|
|
|
|
if(comm->is_federation) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove all edges from community
|
|
|
|
HASH_ITER(hh, comm->edges, edge, tmp_edge) {
|
|
|
|
// remove all edge associations (with other supernodes)
|
|
|
|
HASH_ITER(hh, comm->assoc, assoc, tmp_assoc) {
|
|
|
|
HASH_DEL(comm->assoc, assoc);
|
|
|
|
free(assoc);
|
|
|
|
}
|
|
|
|
|
|
|
|
// close TCP connections, if any (also causes reconnect)
|
|
|
|
// and delete edge from list
|
|
|
|
if((edge->socket_fd != sss->sock) && (edge->socket_fd >= 0)) {
|
|
|
|
HASH_FIND_INT(sss->tcp_connections, &(edge->socket_fd), conn);
|
|
|
|
close_tcp_connection(sss, conn); /* also deletes the edge */
|
|
|
|
} else {
|
|
|
|
HASH_DEL(comm->edges, edge);
|
|
|
|
free(edge);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove allowed users from community
|
|
|
|
HASH_ITER(hh, comm->allowed_users, user, tmp_user) {
|
|
|
|
free(user->shared_secret_ctx);
|
|
|
|
HASH_DEL(comm->allowed_users, user);
|
|
|
|
free(user);
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove community
|
|
|
|
HASH_DEL(sss->communities, comm);
|
|
|
|
if(NULL != comm->header_encryption_ctx_static) {
|
|
|
|
// remove header encryption keys
|
|
|
|
free(comm->header_encryption_ctx_static);
|
|
|
|
free(comm->header_iv_ctx_static);
|
|
|
|
free(comm->header_encryption_ctx_dynamic);
|
|
|
|
free(comm->header_iv_ctx_dynamic);
|
|
|
|
}
|
|
|
|
free(comm);
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove all regular expressions for allowed communities
|
|
|
|
HASH_ITER(hh, sss->rules, re, tmp_re) {
|
|
|
|
HASH_DEL(sss->rules, re);
|
|
|
|
free(re);
|
|
|
|
}
|
|
|
|
|
|
|
|
// prepare reading data -------------------------------
|
|
|
|
|
|
|
|
// new key_time for all communities, requires dynamic keys to be recalculated (see further below),
|
|
|
|
// and edges to re-register (see above) and ...
|
|
|
|
sss->dynamic_key_time = time(NULL);
|
|
|
|
// ... federated supernodes to re-register
|
|
|
|
re_register_and_purge_supernodes(sss, sss->federation, &any_time, any_time, 1 /* forced */);
|
|
|
|
|
|
|
|
// format definition for possible user-key entries
|
|
|
|
sprintf(format, "%c %%%ds %%%ds", N2N_USER_KEY_LINE_STARTER, N2N_DESC_SIZE - 1, sizeof(ascii_public_key)-1);
|
|
|
|
|
|
|
|
while((line = fgets(buffer, sizeof(buffer), fd)) != NULL) {
|
|
|
|
int len = strlen(line);
|
|
|
|
|
|
|
|
if((len < 2) || line[0] == '#') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
len--;
|
|
|
|
while(len > 0) {
|
|
|
|
if((line[len] == '\n') || (line[len] == '\r')) {
|
|
|
|
line[len] = '\0';
|
|
|
|
len--;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// the loop above does not always determine correct 'len'
|
|
|
|
len = strlen(line);
|
|
|
|
|
|
|
|
// user-key line for edge authentication?
|
|
|
|
if(line[0] == N2N_USER_KEY_LINE_STARTER) { /* special first character */
|
|
|
|
if(sscanf(line, format, username, ascii_public_key) == 2) { /* correct format */
|
|
|
|
if(last_added_comm) { /* is there a valid community to add users to */
|
|
|
|
user = (sn_user_t*)calloc(1, sizeof(sn_user_t));
|
|
|
|
if(user) {
|
|
|
|
// username
|
|
|
|
memcpy(user->name, username, sizeof(username));
|
|
|
|
// public key
|
|
|
|
ascii_to_bin(public_key, ascii_public_key);
|
|
|
|
memcpy(user->public_key, public_key, sizeof(public_key));
|
|
|
|
// common shared secret will be calculated later
|
|
|
|
// add to list
|
|
|
|
HASH_ADD(hh, last_added_comm->allowed_users, public_key, sizeof(n2n_private_public_key_t), user);
|
|
|
|
traceEvent(TRACE_INFO, "added user '%s' with public key '%s' to community '%s'",
|
|
|
|
user->name, ascii_public_key, last_added_comm->community);
|
|
|
|
// enable header encryption
|
|
|
|
last_added_comm->header_encryption = HEADER_ENCRYPTION_ENABLED;
|
|
|
|
packet_header_setup_key(last_added_comm->community,
|
|
|
|
&(last_added_comm->header_encryption_ctx_static),
|
|
|
|
&(last_added_comm->header_encryption_ctx_dynamic),
|
|
|
|
&(last_added_comm->header_iv_ctx_static),
|
|
|
|
&(last_added_comm->header_iv_ctx_dynamic));
|
|
|
|
// dynamic key setup follows at a later point in code
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- community name or regular expression
|
|
|
|
|
|
|
|
// cut off any IP sub-network upfront
|
|
|
|
cmn_str = (char*)calloc(len + 1, sizeof(char));
|
|
|
|
has_net = (sscanf(line, "%s %s", cmn_str, net_str) == 2);
|
|
|
|
|
|
|
|
// if it contains typical characters...
|
|
|
|
if(NULL != strpbrk(cmn_str, ".*+?[]\\")) {
|
|
|
|
// ...it is treated as regular expression
|
|
|
|
re = (struct sn_community_regular_expression*)calloc(1, sizeof(struct sn_community_regular_expression));
|
|
|
|
if(re) {
|
|
|
|
re->rule = re_compile(cmn_str);
|
|
|
|
HASH_ADD_PTR(sss->rules, rule, re);
|
|
|
|
num_regex++;
|
|
|
|
traceEvent(TRACE_INFO, "added regular expression for allowed communities '%s'", cmn_str);
|
|
|
|
free(cmn_str);
|
|
|
|
last_added_comm = NULL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
comm = (struct sn_community*)calloc(1,sizeof(struct sn_community));
|
|
|
|
|
|
|
|
if(comm != NULL) {
|
|
|
|
comm_init(comm, cmn_str);
|
|
|
|
/* loaded from file, this community is unpurgeable */
|
|
|
|
comm->purgeable = COMMUNITY_UNPURGEABLE;
|
|
|
|
/* we do not know if header encryption is used in this community,
|
|
|
|
* first packet will show. just in case, setup the key. */
|
|
|
|
comm->header_encryption = HEADER_ENCRYPTION_UNKNOWN;
|
|
|
|
packet_header_setup_key(comm->community,
|
|
|
|
&(comm->header_encryption_ctx_static),
|
|
|
|
&(comm->header_encryption_ctx_dynamic),
|
|
|
|
&(comm->header_iv_ctx_static),
|
|
|
|
&(comm->header_iv_ctx_dynamic));
|
|
|
|
HASH_ADD_STR(sss->communities, community, comm);
|
|
|
|
last_added_comm = comm;
|
|
|
|
|
|
|
|
num_communities++;
|
|
|
|
traceEvent(TRACE_INFO, "added allowed community '%s' [total: %u]",
|
|
|
|
(char*)comm->community, num_communities);
|
|
|
|
|
|
|
|
// check for sub-network address
|
|
|
|
if(has_net) {
|
|
|
|
if(sscanf(net_str, "%15[^/]/%hhu", ip_str, &bitlen) != 2) {
|
|
|
|
traceEvent(TRACE_WARNING, "bad net/bit format '%s' for community '%c', ignoring; see comments inside community.list file",
|
|
|
|
net_str, cmn_str);
|
|
|
|
has_net = 0;
|
|
|
|
}
|
|
|
|
net = inet_addr(ip_str);
|
|
|
|
mask = bitlen2mask(bitlen);
|
|
|
|
if((net == (in_addr_t)(-1)) || (net == INADDR_NONE) || (net == INADDR_ANY)
|
|
|
|
|| ((ntohl(net) & ~mask) != 0)) {
|
|
|
|
traceEvent(TRACE_WARNING, "bad network '%s/%u' in '%s' for community '%s', ignoring",
|
|
|
|
ip_str, bitlen, net_str, cmn_str);
|
|
|
|
has_net = 0;
|
|
|
|
}
|
|
|
|
if((bitlen > 30) || (bitlen == 0)) {
|
|
|
|
traceEvent(TRACE_WARNING, "bad prefix '%hhu' in '%s' for community '%s', ignoring",
|
|
|
|
bitlen, net_str, cmn_str);
|
|
|
|
has_net = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(has_net) {
|
|
|
|
comm->auto_ip_net.net_addr = ntohl(net);
|
|
|
|
comm->auto_ip_net.net_bitlen = bitlen;
|
|
|
|
traceEvent(TRACE_INFO, "assigned sub-network %s/%u to community '%s'",
|
|
|
|
inet_ntoa(*(struct in_addr *) &net),
|
|
|
|
comm->auto_ip_net.net_bitlen,
|
|
|
|
comm->community);
|
|
|
|
} else {
|
|
|
|
assign_one_ip_subnet(sss, comm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(cmn_str);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fd);
|
|
|
|
|
|
|
|
if((num_regex + num_communities) == 0) {
|
|
|
|
traceEvent(TRACE_WARNING, "file %s does not contain any valid community names or regular expressions", sss->community_file);
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
|
|
|
traceEvent(TRACE_NORMAL, "loaded %u fixed-name communities from %s",
|
|
|
|
num_communities, sss->community_file);
|
|
|
|
|
|
|
|
traceEvent(TRACE_NORMAL, "loaded %u regular expressions for community name matching from %s",
|
|
|
|
num_regex, sss->community_file);
|
|
|
|
|
|
|
|
// calculate allowed user's shared secrets (shared with federation)
|
|
|
|
calculate_shared_secrets(sss);
|
|
|
|
|
|
|
|
// calculcate communties' dynamic keys
|
|
|
|
calculate_dynamic_keys(sss);
|
|
|
|
|
|
|
|
// no new communities will be allowed
|
|
|
|
sss->lock_communities = 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* *************************************************** */
|
|
|
|
|
|
|
|
|
|
|
|
/** Send a datagram to a file descriptor socket.
|
|
|
|
*
|
|
|
|
* @return -1 on error otherwise number of bytes sent
|
|
|
|
*/
|
|
|
|
static ssize_t sendto_fd (n2n_sn_t *sss,
|
|
|
|
SOCKET socket_fd,
|
|
|
|
const struct sockaddr *socket,
|
|
|
|
const uint8_t *pktbuf,
|
|
|
|
size_t pktsize) {
|
|
|
|
|
|
|
|
ssize_t sent = 0;
|
|
|
|
n2n_tcp_connection_t *conn;
|
|
|
|
|
|
|
|
sent = sendto(socket_fd, pktbuf, pktsize, 0 /* flags */,
|
|
|
|
socket, sizeof(struct sockaddr_in));
|
|
|
|
|
|
|
|
if((sent <= 0) && (errno)) {
|
|
|
|
char * c = strerror(errno);
|
|
|
|
traceEvent(TRACE_ERROR, "sendto failed (%d) %s", errno, c);
|
|
|
|
#ifdef WIN32
|
|
|
|
traceEvent(TRACE_ERROR, "WSAGetLastError(): %u", WSAGetLastError());
|
|
|
|
#endif
|
|
|
|
// if the erroneous connection is tcp, i.e. not the regular sock...
|
|
|
|
if((socket_fd >= 0) && (socket_fd != sss->sock)) {
|
|
|
|
// ...forget about the corresponding peer and the connection
|
|
|
|
HASH_FIND_INT(sss->tcp_connections, &socket_fd, conn);
|
|
|
|
close_tcp_connection(sss, conn);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_DEBUG, "sendto sent=%d to ", (signed int)sent);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sent;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Send a datagram to a network order socket of type struct sockaddr.
|
|
|
|
*
|
|
|
|
* @return -1 on error otherwise number of bytes sent
|
|
|
|
*/
|
|
|
|
static ssize_t sendto_sock(n2n_sn_t *sss,
|
|
|
|
SOCKET socket_fd,
|
|
|
|
const struct sockaddr *socket,
|
|
|
|
const uint8_t *pktbuf,
|
|
|
|
size_t pktsize) {
|
|
|
|
|
|
|
|
ssize_t sent = 0;
|
|
|
|
int value = 0;
|
|
|
|
|
|
|
|
// if the connection is tcp, i.e. not the regular sock...
|
|
|
|
if((socket_fd >= 0) && (socket_fd != sss->sock)) {
|
|
|
|
|
drop non portable SOL_TCP / fix MacOS v2 (#680)
* Revert "Fix compilation on MacOS"
in order to another solution
* src: use IPPROTO_TCP in place of SOL_TCP
* use the more specific IPPROTO_TCP type for setsockopt
* fixes build on MacOS
src/edge_utils.c:891:31: error: use of undeclared identifier 'SOL_TCP'; did you mean 'FPP_TCP'?
setsockopt(eee->sock, SOL_TCP, TCP_NODELAY, &value, sizeof(value));
src/edge_utils.c:894:31: error: use of undeclared identifier 'SOL_TCP'
setsockopt(eee->sock, SOL_TCP, TCP_CORK, &value, sizeof(value));
^
src/edge_utils.c:894:40: error: use of undeclared identifier 'TCP_CORK'
setsockopt(eee->sock, SOL_TCP, TCP_CORK, &value, sizeof(value));
* include/n2n.h: drop redefiniton of SOL_TCP for WIN32, as we don't use it anywhere now
* src: only use TCP_CORK on LINUX
* it's LINUX only and fails on Win32 and MacOS (man 7 tcp)
4 years ago
|
|
|
setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));
|
|
|
|
value = 1;
|
drop non portable SOL_TCP / fix MacOS v2 (#680)
* Revert "Fix compilation on MacOS"
in order to another solution
* src: use IPPROTO_TCP in place of SOL_TCP
* use the more specific IPPROTO_TCP type for setsockopt
* fixes build on MacOS
src/edge_utils.c:891:31: error: use of undeclared identifier 'SOL_TCP'; did you mean 'FPP_TCP'?
setsockopt(eee->sock, SOL_TCP, TCP_NODELAY, &value, sizeof(value));
src/edge_utils.c:894:31: error: use of undeclared identifier 'SOL_TCP'
setsockopt(eee->sock, SOL_TCP, TCP_CORK, &value, sizeof(value));
^
src/edge_utils.c:894:40: error: use of undeclared identifier 'TCP_CORK'
setsockopt(eee->sock, SOL_TCP, TCP_CORK, &value, sizeof(value));
* include/n2n.h: drop redefiniton of SOL_TCP for WIN32, as we don't use it anywhere now
* src: only use TCP_CORK on LINUX
* it's LINUX only and fails on Win32 and MacOS (man 7 tcp)
4 years ago
|
|
|
#ifdef LINUX
|
|
|
|
setsockopt(socket_fd, IPPROTO_TCP, TCP_CORK, &value, sizeof(value));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// prepend packet length...
|
|
|
|
uint16_t pktsize16 = htobe16(pktsize);
|
|
|
|
sent = sendto_fd(sss, socket_fd, socket, (uint8_t*)&pktsize16, sizeof(pktsize16));
|
|
|
|
|
|
|
|
if(sent <= 0)
|
|
|
|
return -1;
|
|
|
|
// ...before sending the actual data
|
|
|
|
}
|
|
|
|
|
|
|
|
sent = sendto_fd(sss, socket_fd, socket, pktbuf, pktsize);
|
|
|
|
|
|
|
|
// if the connection is tcp, i.e. not the regular sock...
|
|
|
|
if((socket_fd >= 0) && (socket_fd != sss->sock)) {
|
|
|
|
value = 1; /* value should still be set to 1 */
|
drop non portable SOL_TCP / fix MacOS v2 (#680)
* Revert "Fix compilation on MacOS"
in order to another solution
* src: use IPPROTO_TCP in place of SOL_TCP
* use the more specific IPPROTO_TCP type for setsockopt
* fixes build on MacOS
src/edge_utils.c:891:31: error: use of undeclared identifier 'SOL_TCP'; did you mean 'FPP_TCP'?
setsockopt(eee->sock, SOL_TCP, TCP_NODELAY, &value, sizeof(value));
src/edge_utils.c:894:31: error: use of undeclared identifier 'SOL_TCP'
setsockopt(eee->sock, SOL_TCP, TCP_CORK, &value, sizeof(value));
^
src/edge_utils.c:894:40: error: use of undeclared identifier 'TCP_CORK'
setsockopt(eee->sock, SOL_TCP, TCP_CORK, &value, sizeof(value));
* include/n2n.h: drop redefiniton of SOL_TCP for WIN32, as we don't use it anywhere now
* src: only use TCP_CORK on LINUX
* it's LINUX only and fails on Win32 and MacOS (man 7 tcp)
4 years ago
|
|
|
setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));
|
|
|
|
#ifdef LINUX
|
|
|
|
value = 0;
|
drop non portable SOL_TCP / fix MacOS v2 (#680)
* Revert "Fix compilation on MacOS"
in order to another solution
* src: use IPPROTO_TCP in place of SOL_TCP
* use the more specific IPPROTO_TCP type for setsockopt
* fixes build on MacOS
src/edge_utils.c:891:31: error: use of undeclared identifier 'SOL_TCP'; did you mean 'FPP_TCP'?
setsockopt(eee->sock, SOL_TCP, TCP_NODELAY, &value, sizeof(value));
src/edge_utils.c:894:31: error: use of undeclared identifier 'SOL_TCP'
setsockopt(eee->sock, SOL_TCP, TCP_CORK, &value, sizeof(value));
^
src/edge_utils.c:894:40: error: use of undeclared identifier 'TCP_CORK'
setsockopt(eee->sock, SOL_TCP, TCP_CORK, &value, sizeof(value));
* include/n2n.h: drop redefiniton of SOL_TCP for WIN32, as we don't use it anywhere now
* src: only use TCP_CORK on LINUX
* it's LINUX only and fails on Win32 and MacOS (man 7 tcp)
4 years ago
|
|
|
setsockopt(socket_fd, IPPROTO_TCP, TCP_CORK, &value, sizeof(value));
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return sent;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Send a datagram to a peer whose destination socket is embodied in its sock field of type n2n_sock_t.
|
|
|
|
* It calls sendto_sock to do the final send.
|
|
|
|
*
|
|
|
|
* @return -1 on error otherwise number of bytes sent
|
|
|
|
*/
|
|
|
|
static ssize_t sendto_peer (n2n_sn_t *sss,
|
|
|
|
const struct peer_info *peer,
|
|
|
|
const uint8_t *pktbuf,
|
|
|
|
size_t pktsize) {
|
|
|
|
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
|
|
|
|
if(AF_INET == peer->sock.family) {
|
|
|
|
|
|
|
|
// network order socket
|
|
|
|
struct sockaddr_in socket;
|
|
|
|
fill_sockaddr((struct sockaddr *)&socket, sizeof(socket), &(peer->sock));
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "sent %lu bytes to [%s]",
|
|
|
|
pktsize,
|
|
|
|
sock_to_cstr(sockbuf, &(peer->sock)));
|
|
|
|
|
|
|
|
return sendto_sock(sss,
|
|
|
|
(peer->socket_fd >= 0) ? peer->socket_fd : sss->sock,
|
|
|
|
(const struct sockaddr*)&socket, pktbuf, pktsize);
|
|
|
|
} else {
|
|
|
|
/* AF_INET6 not implemented */
|
|
|
|
errno = EAFNOSUPPORT;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Try and broadcast a message to all edges in the community.
|
|
|
|
*
|
|
|
|
* This will send the exact same datagram to zero or more edges registered to
|
|
|
|
* the supernode.
|
|
|
|
*/
|
|
|
|
static int try_broadcast (n2n_sn_t * sss,
|
|
|
|
const struct sn_community *comm,
|
|
|
|
const n2n_common_t * cmn,
|
|
|
|
const n2n_mac_t srcMac,
|
|
|
|
uint8_t from_supernode,
|
|
|
|
const uint8_t * pktbuf,
|
|
|
|
size_t pktsize) {
|
|
|
|
|
|
|
|
struct peer_info *scan, *tmp;
|
|
|
|
macstr_t mac_buf;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "try_broadcast");
|
|
|
|
|
|
|
|
/* We have to make sure that a broadcast reaches the other supernodes and edges
|
|
|
|
* connected to them. try_broadcast needs a from_supernode parameter: if set,
|
|
|
|
* do forward to edges of community only. If unset, forward to all locally known
|
|
|
|
* nodes of community AND all supernodes associated with the community */
|
|
|
|
|
|
|
|
if (!from_supernode) {
|
|
|
|
HASH_ITER(hh, sss->federation->edges, scan, tmp) {
|
|
|
|
int data_sent_len;
|
|
|
|
|
|
|
|
data_sent_len = sendto_peer(sss, scan, pktbuf, pktsize);
|
|
|
|
|
|
|
|
if(data_sent_len != pktsize) {
|
|
|
|
++(sss->stats.errors);
|
|
|
|
traceEvent(TRACE_WARNING, "multicast %lu to supernode [%s] %s failed %s",
|
|
|
|
pktsize,
|
|
|
|
sock_to_cstr(sockbuf, &(scan->sock)),
|
|
|
|
macaddr_str(mac_buf, scan->mac_addr),
|
|
|
|
strerror(errno));
|
|
|
|
} else {
|
|
|
|
++(sss->stats.broadcast);
|
|
|
|
traceEvent(TRACE_DEBUG, "multicast %lu to supernode [%s] %s",
|
|
|
|
pktsize,
|
|
|
|
sock_to_cstr(sockbuf, &(scan->sock)),
|
|
|
|
macaddr_str(mac_buf, scan->mac_addr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(comm) {
|
|
|
|
HASH_ITER(hh, comm->edges, scan, tmp) {
|
|
|
|
if(memcmp(srcMac, scan->mac_addr, sizeof(n2n_mac_t)) != 0) {
|
|
|
|
/* REVISIT: exclude if the destination socket is where the packet came from. */
|
|
|
|
int data_sent_len;
|
|
|
|
|
|
|
|
data_sent_len = sendto_peer(sss, scan, pktbuf, pktsize);
|
|
|
|
|
|
|
|
if(data_sent_len != pktsize) {
|
|
|
|
++(sss->stats.errors);
|
|
|
|
traceEvent(TRACE_WARNING, "multicast %lu to [%s] %s failed %s",
|
|
|
|
pktsize,
|
|
|
|
sock_to_cstr(sockbuf, &(scan->sock)),
|
|
|
|
macaddr_str(mac_buf, scan->mac_addr),
|
|
|
|
strerror(errno));
|
|
|
|
} else {
|
|
|
|
++(sss->stats.broadcast);
|
|
|
|
traceEvent(TRACE_DEBUG, "multicast %lu to [%s] %s",
|
|
|
|
pktsize,
|
|
|
|
sock_to_cstr(sockbuf, &(scan->sock)),
|
|
|
|
macaddr_str(mac_buf, scan->mac_addr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int try_forward (n2n_sn_t * sss,
|
|
|
|
const struct sn_community *comm,
|
|
|
|
const n2n_common_t * cmn,
|
|
|
|
const n2n_mac_t dstMac,
|
|
|
|
uint8_t from_supernode,
|
|
|
|
const uint8_t * pktbuf,
|
|
|
|
size_t pktsize) {
|
|
|
|
|
|
|
|
struct peer_info * scan;
|
|
|
|
node_supernode_association_t *assoc;
|
|
|
|
macstr_t mac_buf;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
|
|
|
|
HASH_FIND_PEER(comm->edges, dstMac, scan);
|
|
|
|
|
|
|
|
if(NULL != scan) {
|
|
|
|
int data_sent_len;
|
|
|
|
data_sent_len = sendto_peer(sss, scan, pktbuf, pktsize);
|
|
|
|
|
|
|
|
if(data_sent_len == pktsize) {
|
|
|
|
++(sss->stats.fwd);
|
|
|
|
traceEvent(TRACE_DEBUG, "unicast %lu to [%s] %s",
|
|
|
|
pktsize,
|
|
|
|
sock_to_cstr(sockbuf, &(scan->sock)),
|
|
|
|
macaddr_str(mac_buf, scan->mac_addr));
|
|
|
|
} else {
|
|
|
|
++(sss->stats.errors);
|
|
|
|
traceEvent(TRACE_ERROR, "unicast %lu to [%s] %s FAILED (%d: %s)",
|
|
|
|
pktsize,
|
|
|
|
sock_to_cstr(sockbuf, &(scan->sock)),
|
|
|
|
macaddr_str(mac_buf, scan->mac_addr),
|
|
|
|
errno, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if(!from_supernode) {
|
|
|
|
// check if target edge is associated with a certain supernode
|
|
|
|
HASH_FIND(hh, comm->assoc, dstMac, sizeof(n2n_mac_t), assoc);
|
|
|
|
if(assoc) {
|
|
|
|
traceEvent(TRACE_DEBUG, "found mac address associated with a known supernode, forwarding packet to that supernode");
|
|
|
|
sendto_sock(sss, sss->sock,
|
|
|
|
(const struct sockaddr*)&(assoc->sock),
|
|
|
|
pktbuf, pktsize);
|
|
|
|
} else {
|
|
|
|
// forwarding packet to all federated supernodes
|
|
|
|
traceEvent(TRACE_DEBUG, "unknown mac address, broadcasting packet to all federated supernodes");
|
|
|
|
try_broadcast(sss, NULL, cmn, sss->mac_addr, from_supernode, pktbuf, pktsize);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_DEBUG, "unknown mac address in packet from a supernode, dropping the packet");
|
|
|
|
/* Not a known MAC so drop. */
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Initialise some fields of the community structure **/
|
|
|
|
int comm_init (struct sn_community *comm, char *cmn) {
|
|
|
|
|
|
|
|
strncpy((char*)comm->community, cmn, N2N_COMMUNITY_SIZE - 1);
|
|
|
|
comm->community[N2N_COMMUNITY_SIZE - 1] = '\0';
|
|
|
|
comm->is_federation = IS_NO_FEDERATION;
|
|
|
|
|
|
|
|
return 0; /* OK */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Initialise the supernode structure */
|
|
|
|
int sn_init_defaults (n2n_sn_t *sss) {
|
|
|
|
|
|
|
|
int i;
|
|
|
|
size_t idx;
|
|
|
|
|
|
|
|
#ifdef WIN32
|
|
|
|
initWin32();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
pearson_hash_init();
|
|
|
|
|
|
|
|
memset(sss, 0, sizeof(n2n_sn_t));
|
|
|
|
|
|
|
|
sss->daemon = 1; /* By defult run as a daemon. */
|
|
|
|
sss->lport = N2N_SN_LPORT_DEFAULT;
|
|
|
|
sss->mport = N2N_SN_MGMT_PORT;
|
|
|
|
sss->sock = -1;
|
|
|
|
sss->mgmt_sock = -1;
|
|
|
|
sss->min_auto_ip_net.net_addr = inet_addr(N2N_SN_MIN_AUTO_IP_NET_DEFAULT);
|
|
|
|
sss->min_auto_ip_net.net_addr = ntohl(sss->min_auto_ip_net.net_addr);
|
|
|
|
sss->min_auto_ip_net.net_bitlen = N2N_SN_AUTO_IP_NET_BIT_DEFAULT;
|
|
|
|
sss->max_auto_ip_net.net_addr = inet_addr(N2N_SN_MAX_AUTO_IP_NET_DEFAULT);
|
|
|
|
sss->max_auto_ip_net.net_addr = ntohl(sss->max_auto_ip_net.net_addr);
|
|
|
|
sss->max_auto_ip_net.net_bitlen = N2N_SN_AUTO_IP_NET_BIT_DEFAULT;
|
|
|
|
sss->federation = (struct sn_community *)calloc(1, sizeof(struct sn_community));
|
|
|
|
|
|
|
|
/* Initialize the federation */
|
|
|
|
if(sss->federation) {
|
|
|
|
strncpy(sss->federation->community, (char*)FEDERATION_NAME, N2N_COMMUNITY_SIZE - 1);
|
|
|
|
sss->federation->community[N2N_COMMUNITY_SIZE - 1] = '\0';
|
|
|
|
/* enable the flag for federation */
|
|
|
|
sss->federation->is_federation = IS_FEDERATION;
|
|
|
|
sss->federation->purgeable = COMMUNITY_UNPURGEABLE;
|
|
|
|
/* header encryption enabled by default */
|
|
|
|
sss->federation->header_encryption = HEADER_ENCRYPTION_ENABLED;
|
|
|
|
/*setup the encryption key */
|
|
|
|
packet_header_setup_key(sss->federation->community,
|
|
|
|
&(sss->federation->header_encryption_ctx_static),
|
|
|
|
&(sss->federation->header_encryption_ctx_dynamic),
|
|
|
|
&(sss->federation->header_iv_ctx_static),
|
|
|
|
&(sss->federation->header_iv_ctx_dynamic));
|
|
|
|
sss->federation->edges = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
n2n_srand(n2n_seed());
|
|
|
|
|
|
|
|
/* Random auth token */
|
|
|
|
sss->auth.scheme = n2n_auth_simple_id;
|
|
|
|
memrnd(sss->auth.token, N2N_AUTH_ID_TOKEN_SIZE);
|
|
|
|
sss->auth.token_size = N2N_AUTH_ID_TOKEN_SIZE;
|
|
|
|
|
|
|
|
/* Random MAC address */
|
|
|
|
memrnd(sss->mac_addr, N2N_MAC_SIZE);
|
|
|
|
sss->mac_addr[0] &= ~0x01; /* Clear multicast bit */
|
|
|
|
sss->mac_addr[0] |= 0x02; /* Set locally-assigned bit */
|
|
|
|
|
|
|
|
return 0; /* OK */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Initialise the supernode */
|
|
|
|
int sn_init (n2n_sn_t *sss) {
|
|
|
|
|
|
|
|
if(resolve_create_thread(&(sss->resolve_parameter), sss->federation->edges) == 0) {
|
|
|
|
traceEvent(TRACE_NORMAL, "successfully created resolver thread");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Deinitialise the supernode structure and deallocate any memory owned by
|
|
|
|
* it. */
|
|
|
|
void sn_term (n2n_sn_t *sss) {
|
|
|
|
|
|
|
|
struct sn_community *community, *tmp;
|
|
|
|
struct sn_community_regular_expression *re, *tmp_re;
|
|
|
|
n2n_tcp_connection_t *conn, *tmp_conn;
|
|
|
|
node_supernode_association_t *assoc, *tmp_assoc;
|
|
|
|
|
|
|
|
resolve_cancel_thread(sss->resolve_parameter);
|
|
|
|
|
|
|
|
if(sss->sock >= 0) {
|
|
|
|
closesocket(sss->sock);
|
|
|
|
}
|
|
|
|
sss->sock = -1;
|
|
|
|
|
|
|
|
HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn) {
|
|
|
|
shutdown(conn->socket_fd, SHUT_RDWR);
|
|
|
|
closesocket(conn->socket_fd);
|
|
|
|
HASH_DEL(sss->tcp_connections, conn);
|
|
|
|
free(conn);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(sss->tcp_sock >= 0) {
|
|
|
|
shutdown(sss->tcp_sock, SHUT_RDWR);
|
|
|
|
closesocket(sss->tcp_sock);
|
|
|
|
}
|
|
|
|
sss->tcp_sock = -1;
|
|
|
|
|
|
|
|
if(sss->mgmt_sock >= 0) {
|
|
|
|
closesocket(sss->mgmt_sock);
|
|
|
|
}
|
|
|
|
sss->mgmt_sock = -1;
|
|
|
|
|
|
|
|
HASH_ITER(hh, sss->communities, community, tmp) {
|
|
|
|
clear_peer_list(&community->edges);
|
|
|
|
if(NULL != community->header_encryption_ctx_static) {
|
|
|
|
free(community->header_encryption_ctx_static);
|
|
|
|
free(community->header_encryption_ctx_dynamic);
|
|
|
|
}
|
|
|
|
// remove all associations
|
|
|
|
HASH_ITER(hh, community->assoc, assoc, tmp_assoc) {
|
|
|
|
HASH_DEL(community->assoc, assoc);
|
|
|
|
free(assoc);
|
|
|
|
}
|
|
|
|
HASH_DEL(sss->communities, community);
|
|
|
|
free(community);
|
|
|
|
}
|
|
|
|
|
|
|
|
HASH_ITER(hh, sss->rules, re, tmp_re) {
|
|
|
|
HASH_DEL(sss->rules, re);
|
|
|
|
if (NULL != re->rule) {
|
|
|
|
free(re->rule);
|
|
|
|
}
|
|
|
|
free(re);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(sss->community_file)
|
|
|
|
free(sss->community_file);
|
|
|
|
#ifdef WIN32
|
|
|
|
destroyWin32();
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void update_node_supernode_association (struct sn_community *comm,
|
|
|
|
n2n_mac_t *edgeMac, const struct sockaddr_in *sender_sock,
|
|
|
|
time_t now) {
|
|
|
|
|
|
|
|
node_supernode_association_t *assoc;
|
|
|
|
|
|
|
|
HASH_FIND(hh, comm->assoc, edgeMac, sizeof(n2n_mac_t), assoc);
|
|
|
|
if(!assoc) {
|
|
|
|
// create a new association
|
|
|
|
assoc = (node_supernode_association_t*)malloc(sizeof(node_supernode_association_t));
|
|
|
|
if(assoc) {
|
|
|
|
memcpy(&(assoc->mac), edgeMac, sizeof(n2n_mac_t));
|
|
|
|
memcpy((struct sockaddr_in*)&(assoc->sock), sender_sock, sizeof(struct sockaddr_in));
|
|
|
|
assoc->last_seen = now;
|
|
|
|
HASH_ADD(hh, comm->assoc, mac, sizeof(n2n_mac_t), assoc);
|
|
|
|
} else {
|
|
|
|
// already there, update socket and time only
|
|
|
|
memcpy((struct sockaddr_in*)&(assoc->sock), sender_sock, sizeof(struct sockaddr_in));
|
|
|
|
assoc->last_seen = now;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Determine the appropriate lifetime for new registrations.
|
|
|
|
*
|
|
|
|
* If the supernode has been put into a pre-shutdown phase then this lifetime
|
|
|
|
* should not allow registrations to continue beyond the shutdown point.
|
|
|
|
*/
|
|
|
|
static uint16_t reg_lifetime (n2n_sn_t *sss) {
|
|
|
|
|
|
|
|
/* NOTE: UDP firewalls usually have a 30 seconds timeout */
|
|
|
|
return 15;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Verifies authentication tokens from known edges.
|
|
|
|
*
|
|
|
|
* It is called by update_edge and during UNREGISTER_SUPER handling
|
|
|
|
* to verify the stored auth token.
|
|
|
|
*/
|
|
|
|
static int auth_edge (const n2n_auth_t *present, const n2n_auth_t *presented, n2n_auth_t *answer, struct sn_community *community) {
|
|
|
|
|
|
|
|
sn_user_t *user = NULL;
|
|
|
|
|
|
|
|
if(present->scheme == n2n_auth_none) {
|
|
|
|
// n2n_auth_none scheme (set at supernode if cli option '-M')
|
|
|
|
// if required, zero_token answer (not for NAK)
|
|
|
|
if(answer)
|
|
|
|
memset(answer, 0, sizeof(n2n_auth_t));
|
|
|
|
// 0 == (always) successful
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((present->scheme == n2n_auth_simple_id) && (presented->scheme == n2n_auth_simple_id)) {
|
|
|
|
// n2n_auth_simple_id scheme: if required, zero_token answer (not for NAK)
|
|
|
|
if(answer)
|
|
|
|
memset(answer, 0, sizeof(n2n_auth_t));
|
|
|
|
|
|
|
|
// 0 = success (tokens are equal)
|
|
|
|
return (memcmp(present, presented, sizeof(n2n_auth_t)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if((present->scheme == n2n_auth_user_password) && (presented->scheme == n2n_auth_user_password)) {
|
|
|
|
// check if submitted public key is in list of allowed users
|
|
|
|
HASH_FIND(hh, community->allowed_users, &presented->token, sizeof(n2n_private_public_key_t), user);
|
|
|
|
if(user) {
|
|
|
|
if(answer) {
|
|
|
|
memcpy(answer, presented, sizeof(n2n_auth_t));
|
|
|
|
|
|
|
|
// return a double-encrypted challenge (just encrypt again) in the (first half of) public key field so edge can verify
|
|
|
|
memcpy(answer->token, answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, N2N_AUTH_CHALLENGE_SIZE);
|
|
|
|
speck_128_encrypt(answer->token, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
|
|
|
|
// decrypt the challenge using user's shared secret
|
|
|
|
speck_128_decrypt(answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
// xor-in the community dynamic key
|
|
|
|
memxor(answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, community->dynamic_key, N2N_AUTH_CHALLENGE_SIZE);
|
|
|
|
// xor-in the user's shared secret
|
|
|
|
memxor(answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, user->shared_secret, N2N_AUTH_CHALLENGE_SIZE);
|
|
|
|
// encrypt it using user's shared secret
|
|
|
|
speck_128_encrypt(answer->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
// user in list? success! (we will see if edge can handle the key for further com)
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if not successful earlier: failure
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// provides the current / a new local auth token
|
|
|
|
// REVISIT: behavior should depend on some local auth scheme setting (to be implemented)
|
|
|
|
static int get_local_auth (n2n_sn_t *sss, n2n_auth_t *auth) {
|
|
|
|
|
|
|
|
// n2n_auth_simple_id scheme
|
|
|
|
memcpy(auth, &(sss->auth), sizeof(n2n_auth_t));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// handles an incoming (remote) auth token from a so far unknown edge,
|
|
|
|
// takes action as required by auth scheme, and
|
|
|
|
// could provide an answer auth token for use in REGISTER_SUPER_ACK
|
|
|
|
static int handle_remote_auth (n2n_sn_t *sss, const n2n_auth_t *remote_auth,
|
|
|
|
n2n_auth_t *answer_auth,
|
|
|
|
struct sn_community *community) {
|
|
|
|
|
|
|
|
sn_user_t *user = NULL;
|
|
|
|
|
|
|
|
if((NULL == community->allowed_users) != (remote_auth->scheme != n2n_auth_user_password)) {
|
|
|
|
// received token's scheme does not match expected scheme
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(remote_auth->scheme) {
|
|
|
|
// we do not handle n2n_auth_none because the edge always edge always uses either id or user/password
|
|
|
|
// auth_none is sn-internal only (skipping MAC/IP address spoofing protection)
|
|
|
|
case n2n_auth_simple_id:
|
|
|
|
// zero_token answer
|
|
|
|
memset(answer_auth, 0, sizeof(n2n_auth_t));
|
|
|
|
return 0;
|
|
|
|
case n2n_auth_user_password:
|
|
|
|
// check if submitted public key is in list of allowed users
|
|
|
|
HASH_FIND(hh, community->allowed_users, &remote_auth->token, sizeof(n2n_private_public_key_t), user);
|
|
|
|
if(user) {
|
|
|
|
memcpy(answer_auth, remote_auth, sizeof(n2n_auth_t));
|
|
|
|
|
|
|
|
// return a double-encrypted challenge (just encrypt again) in the (first half of) public key field so edge can verify
|
|
|
|
memcpy(answer_auth->token, answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, N2N_AUTH_CHALLENGE_SIZE);
|
|
|
|
speck_128_encrypt(answer_auth->token, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
|
|
|
|
// wrap dynamic key for transmission
|
|
|
|
// decrypt the challenge using user's shared secret
|
|
|
|
speck_128_decrypt(answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
// xor-in the community dynamic key
|
|
|
|
memxor(answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, community->dynamic_key, N2N_AUTH_CHALLENGE_SIZE);
|
|
|
|
// xor-in the user's shared secret
|
|
|
|
memxor(answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, user->shared_secret, N2N_AUTH_CHALLENGE_SIZE);
|
|
|
|
// encrypt it using user's shared secret
|
|
|
|
speck_128_encrypt(answer_auth->token + N2N_PRIVATE_PUBLIC_KEY_SIZE, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if not successful earlier: failure
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Update the edge table with the details of the edge which contacted the
|
|
|
|
* supernode. */
|
|
|
|
static int update_edge (n2n_sn_t *sss,
|
|
|
|
const n2n_REGISTER_SUPER_t* reg,
|
|
|
|
struct sn_community *comm,
|
|
|
|
const n2n_sock_t *sender_sock,
|
|
|
|
const SOCKET socket_fd,
|
|
|
|
n2n_auth_t *answer_auth,
|
|
|
|
int skip_add,
|
|
|
|
time_t now) {
|
|
|
|
|
|
|
|
macstr_t mac_buf;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
struct peer_info *scan, *iter, *tmp;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "update_edge for %s [%s]",
|
|
|
|
macaddr_str(mac_buf, reg->edgeMac),
|
|
|
|
sock_to_cstr(sockbuf, sender_sock));
|
|
|
|
|
|
|
|
HASH_FIND_PEER(comm->edges, reg->edgeMac, scan);
|
|
|
|
|
|
|
|
// if unknown, make sure it is also not known by IP address
|
|
|
|
if(NULL == scan) {
|
|
|
|
HASH_ITER(hh,comm->edges,iter,tmp) {
|
|
|
|
if(iter->dev_addr.net_addr == reg->dev_addr.net_addr) {
|
|
|
|
scan = iter;
|
|
|
|
HASH_DEL(comm->edges, scan);
|
|
|
|
memcpy(scan->mac_addr, reg->edgeMac, sizeof(n2n_mac_t));
|
|
|
|
HASH_ADD_PEER(comm->edges, scan);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(NULL == scan) {
|
|
|
|
/* Not known */
|
|
|
|
if(handle_remote_auth(sss, &(reg->auth), answer_auth, comm) == 0) {
|
|
|
|
if(skip_add == SN_ADD) {
|
|
|
|
scan = (struct peer_info *) calloc(1, sizeof(struct peer_info)); /* deallocated in purge_expired_nodes */
|
|
|
|
memcpy(&(scan->mac_addr), reg->edgeMac, sizeof(n2n_mac_t));
|
|
|
|
scan->dev_addr.net_addr = reg->dev_addr.net_addr;
|
|
|
|
scan->dev_addr.net_bitlen = reg->dev_addr.net_bitlen;
|
|
|
|
memcpy((char*)scan->dev_desc, reg->dev_desc, N2N_DESC_SIZE);
|
|
|
|
memcpy(&(scan->sock), sender_sock, sizeof(n2n_sock_t));
|
|
|
|
scan->socket_fd = socket_fd;
|
|
|
|
memcpy(&(scan->last_cookie), reg->cookie, sizeof(N2N_COOKIE_SIZE));
|
|
|
|
scan->last_valid_time_stamp = initial_time_stamp();
|
|
|
|
|
|
|
|
// store the submitted auth token
|
|
|
|
memcpy(&(scan->auth), &(reg->auth), sizeof(n2n_auth_t));
|
|
|
|
// manually set to type 'auth_none' if cli option disables MAC/IP address spoofing protection
|
|
|
|
// for id based auth communities. This will be obsolete when handling public keys only (v4.0?)
|
|
|
|
if((reg->auth.scheme == n2n_auth_simple_id) && (sss->override_spoofing_protection))
|
|
|
|
scan->auth.scheme = n2n_auth_none;
|
|
|
|
|
|
|
|
HASH_ADD_PEER(comm->edges, scan);
|
|
|
|
|
|
|
|
traceEvent(TRACE_INFO, "created edge %s ==> %s",
|
|
|
|
macaddr_str(mac_buf, reg->edgeMac),
|
|
|
|
sock_to_cstr(sockbuf, sender_sock));
|
|
|
|
}
|
|
|
|
ret = update_edge_new_sn;
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_INFO, "authentication failed");
|
|
|
|
ret = update_edge_auth_fail;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Known */
|
|
|
|
if(auth_edge(&(scan->auth), &(reg->auth), answer_auth, comm) == 0) {
|
|
|
|
if(!sock_equal(sender_sock, &(scan->sock))) {
|
|
|
|
memcpy(&(scan->sock), sender_sock, sizeof(n2n_sock_t));
|
|
|
|
scan->socket_fd = socket_fd;
|
|
|
|
memcpy(&(scan->last_cookie), reg->cookie, sizeof(N2N_COOKIE_SIZE));
|
|
|
|
|
|
|
|
traceEvent(TRACE_INFO, "updated edge %s ==> %s",
|
|
|
|
macaddr_str(mac_buf, reg->edgeMac),
|
|
|
|
sock_to_cstr(sockbuf, sender_sock));
|
|
|
|
ret = update_edge_sock_change;
|
|
|
|
} else {
|
|
|
|
memcpy(&(scan->last_cookie), reg->cookie, sizeof(N2N_COOKIE_SIZE));
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "edge unchanged %s ==> %s",
|
|
|
|
macaddr_str(mac_buf, reg->edgeMac),
|
|
|
|
sock_to_cstr(sockbuf, sender_sock));
|
|
|
|
|
|
|
|
ret = update_edge_no_change;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_INFO, "authentication failed");
|
|
|
|
ret = update_edge_auth_fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if((scan != NULL) && (ret != update_edge_auth_fail)) {
|
|
|
|
scan->last_seen = now;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** checks if a certain ip address is still available, i.e. not used by any other edge of a given community */
|
|
|
|
static int ip_addr_available (struct sn_community *comm, n2n_ip_subnet_t *ip_addr) {
|
|
|
|
|
|
|
|
int success = 1;
|
|
|
|
struct peer_info *peer, *tmp_peer;
|
|
|
|
|
|
|
|
// prerequisite: list of peers is sorted according to peer's tap ip address
|
|
|
|
HASH_ITER(hh, comm->edges, peer, tmp_peer) {
|
|
|
|
if(peer->dev_addr.net_addr > ip_addr->net_addr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(peer->dev_addr.net_addr == ip_addr->net_addr) {
|
|
|
|
success = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static signed int peer_tap_ip_sort (struct peer_info *a, struct peer_info *b) {
|
|
|
|
|
|
|
|
uint32_t a_host_id = a->dev_addr.net_addr & (~bitlen2mask(a->dev_addr.net_bitlen));
|
|
|
|
uint32_t b_host_id = b->dev_addr.net_addr & (~bitlen2mask(b->dev_addr.net_bitlen));
|
|
|
|
|
|
|
|
return ((signed int)a_host_id - (signed int)b_host_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** The IP address assigned to the edge by the auto ip address function of sn. */
|
|
|
|
static int assign_one_ip_addr (struct sn_community *comm, n2n_desc_t dev_desc, n2n_ip_subnet_t *ip_addr) {
|
|
|
|
|
|
|
|
uint32_t tmp, success, net_id, mask, max_host, host_id = 1;
|
|
|
|
dec_ip_bit_str_t ip_bit_str = {'\0'};
|
|
|
|
|
|
|
|
mask = bitlen2mask(comm->auto_ip_net.net_bitlen);
|
|
|
|
net_id = comm->auto_ip_net.net_addr & mask;
|
|
|
|
max_host = ~mask;
|
|
|
|
|
|
|
|
// sorting is a prerequisite for more efficient availabilitiy check
|
|
|
|
HASH_SORT(comm->edges, peer_tap_ip_sort);
|
|
|
|
|
|
|
|
// first proposal derived from hash of mac address
|
|
|
|
tmp = pearson_hash_32(dev_desc, sizeof(n2n_desc_t)) & max_host;
|
|
|
|
if(tmp == 0) tmp++; /* avoid 0 host */
|
|
|
|
if(tmp == max_host) tmp--; /* avoid broadcast address */
|
|
|
|
tmp |= net_id;
|
|
|
|
|
|
|
|
// candidate
|
|
|
|
ip_addr->net_bitlen = comm->auto_ip_net.net_bitlen;
|
|
|
|
|
|
|
|
// check for availability starting from proposal, then downwards, ...
|
|
|
|
for(host_id = tmp; host_id > net_id; host_id--) {
|
|
|
|
ip_addr->net_addr = host_id;
|
|
|
|
success = ip_addr_available(comm, ip_addr);
|
|
|
|
if(success) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// ... then upwards
|
|
|
|
if(!success) {
|
|
|
|
for(host_id = tmp + 1; host_id < (net_id + max_host); host_id++) {
|
|
|
|
ip_addr->net_addr = host_id;
|
|
|
|
success = ip_addr_available(comm, ip_addr);
|
|
|
|
if(success) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(success) {
|
|
|
|
traceEvent(TRACE_INFO, "assign IP %s to tap adapter of edge", ip_subnet_to_str(ip_bit_str, ip_addr));
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_WARNING, "no assignable IP to edge tap adapter");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** checks if a certain sub-network is still available, i.e. does not cut any other community's sub-network */
|
|
|
|
int subnet_available (n2n_sn_t *sss,
|
|
|
|
struct sn_community *comm,
|
|
|
|
uint32_t net_id,
|
|
|
|
uint32_t mask) {
|
|
|
|
|
|
|
|
struct sn_community *cmn, *tmpCmn;
|
|
|
|
int success = 1;
|
|
|
|
|
|
|
|
HASH_ITER(hh, sss->communities, cmn, tmpCmn) {
|
|
|
|
if(cmn == comm) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if(cmn->is_federation == IS_FEDERATION) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if((net_id <= (cmn->auto_ip_net.net_addr + ~bitlen2mask(cmn->auto_ip_net.net_bitlen)))
|
|
|
|
&&(net_id + ~mask >= cmn->auto_ip_net.net_addr)) {
|
|
|
|
success = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** The IP address range (subnet) assigned to the community by the auto ip address function of sn. */
|
|
|
|
int assign_one_ip_subnet (n2n_sn_t *sss,
|
|
|
|
struct sn_community *comm) {
|
|
|
|
|
|
|
|
uint32_t net_id, net_id_i, mask, net_increment;
|
|
|
|
uint32_t no_subnets;
|
|
|
|
uint8_t success;
|
|
|
|
in_addr_t net;
|
|
|
|
|
|
|
|
mask = bitlen2mask(sss->min_auto_ip_net.net_bitlen);
|
|
|
|
// number of possible sub-networks
|
|
|
|
no_subnets = (sss->max_auto_ip_net.net_addr - sss->min_auto_ip_net.net_addr);
|
|
|
|
no_subnets >>= (32 - sss->min_auto_ip_net.net_bitlen);
|
|
|
|
no_subnets += 1;
|
|
|
|
|
|
|
|
// proposal for sub-network to choose
|
|
|
|
net_id = pearson_hash_32((const uint8_t *)comm->community, N2N_COMMUNITY_SIZE) % no_subnets;
|
|
|
|
net_id = sss->min_auto_ip_net.net_addr + (net_id << (32 - sss->min_auto_ip_net.net_bitlen));
|
|
|
|
|
|
|
|
// check for availability starting from net_id, then downwards, ...
|
|
|
|
net_increment = (~mask+1);
|
|
|
|
for(net_id_i = net_id; net_id_i >= sss->min_auto_ip_net.net_addr; net_id_i -= net_increment) {
|
|
|
|
success = subnet_available(sss, comm, net_id_i, mask);
|
|
|
|
if(success) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// ... then upwards
|
|
|
|
if(!success) {
|
|
|
|
for(net_id_i = net_id + net_increment; net_id_i <= sss->max_auto_ip_net.net_addr; net_id_i += net_increment) {
|
|
|
|
success = subnet_available(sss, comm, net_id_i, mask);
|
|
|
|
if(success) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(success) {
|
|
|
|
comm->auto_ip_net.net_addr = net_id_i;
|
|
|
|
comm->auto_ip_net.net_bitlen = sss->min_auto_ip_net.net_bitlen;
|
|
|
|
net = htonl(comm->auto_ip_net.net_addr);
|
|
|
|
traceEvent(TRACE_INFO, "assigned sub-network %s/%u to community '%s'",
|
|
|
|
inet_ntoa(*(struct in_addr *) &net),
|
|
|
|
comm->auto_ip_net.net_bitlen,
|
|
|
|
comm->community);
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
comm->auto_ip_net.net_addr = 0;
|
|
|
|
comm->auto_ip_net.net_bitlen = 0;
|
|
|
|
traceEvent(TRACE_WARNING, "no assignable sub-network left for community '%s'",
|
|
|
|
comm->community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/***
|
|
|
|
*
|
|
|
|
* For a given packet, find the apporopriate internal last valid time stamp for lookup
|
|
|
|
* and verify it (and also update, if applicable).
|
|
|
|
*/
|
|
|
|
static int find_edge_time_stamp_and_verify (struct peer_info * edges,
|
|
|
|
peer_info_t *sn, n2n_mac_t mac,
|
|
|
|
uint64_t stamp, int allow_jitter) {
|
|
|
|
|
|
|
|
uint64_t *previous_stamp = NULL;
|
|
|
|
|
|
|
|
if(sn) {
|
|
|
|
previous_stamp = &(sn->last_valid_time_stamp);
|
|
|
|
} else {
|
|
|
|
struct peer_info *edge;
|
|
|
|
HASH_FIND_PEER(edges, mac, edge);
|
|
|
|
|
|
|
|
if(edge) {
|
|
|
|
// time_stamp_verify_and_update allows the pointer a previous stamp to be NULL
|
|
|
|
// if it is a (so far) unknown edge
|
|
|
|
previous_stamp = &(edge->last_valid_time_stamp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// failure --> 0; success --> 1
|
|
|
|
return time_stamp_verify_and_update(stamp, previous_stamp, allow_jitter);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int re_register_and_purge_supernodes (n2n_sn_t *sss, struct sn_community *comm, time_t *p_last_re_reg_and_purge, time_t now, uint8_t forced) {
|
|
|
|
|
|
|
|
time_t time;
|
|
|
|
struct peer_info *peer, *tmp;
|
|
|
|
|
|
|
|
if(!forced) {
|
|
|
|
if((now - (*p_last_re_reg_and_purge)) < RE_REG_AND_PURGE_FREQUENCY) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// purge long-time-not-seen supernodes
|
|
|
|
purge_expired_nodes(&(comm->edges), sss->sock, &sss->tcp_connections, p_last_re_reg_and_purge,
|
|
|
|
RE_REG_AND_PURGE_FREQUENCY, LAST_SEEN_SN_INACTIVE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(comm != NULL) {
|
|
|
|
HASH_ITER(hh,comm->edges,peer,tmp) {
|
|
|
|
|
|
|
|
time = now - peer->last_seen;
|
|
|
|
|
|
|
|
if(!forced) {
|
|
|
|
if(time <= LAST_SEEN_SN_ACTIVE) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* re-register (send REGISTER_SUPER) */
|
|
|
|
uint8_t pktbuf[N2N_PKT_BUF_SIZE] = {0};
|
|
|
|
size_t idx;
|
|
|
|
/* ssize_t sent; */
|
|
|
|
n2n_common_t cmn;
|
|
|
|
n2n_cookie_t cookie;
|
|
|
|
n2n_REGISTER_SUPER_t reg;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
|
|
|
|
memset(&cmn, 0, sizeof(cmn));
|
|
|
|
memset(®, 0, sizeof(reg));
|
|
|
|
|
|
|
|
cmn.ttl = N2N_DEFAULT_TTL;
|
|
|
|
cmn.pc = n2n_register_super;
|
|
|
|
cmn.flags = N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
memcpy(cmn.community, comm->community, N2N_COMMUNITY_SIZE);
|
|
|
|
|
|
|
|
memrnd(cookie, N2N_COOKIE_SIZE);
|
|
|
|
memcpy(reg.cookie, cookie, N2N_COOKIE_SIZE);
|
|
|
|
memcpy(peer->last_cookie, cookie, N2N_COOKIE_SIZE);
|
|
|
|
|
|
|
|
reg.dev_addr.net_addr = ntohl(peer->dev_addr.net_addr);
|
|
|
|
reg.dev_addr.net_bitlen = mask2bitlen(ntohl(peer->dev_addr.net_bitlen));
|
|
|
|
get_local_auth(sss, &(reg.auth));
|
|
|
|
|
|
|
|
reg.key_time = sss->dynamic_key_time;
|
|
|
|
|
|
|
|
idx = 0;
|
|
|
|
encode_mac(reg.edgeMac, &idx, sss->mac_addr);
|
|
|
|
|
|
|
|
idx = 0;
|
|
|
|
encode_REGISTER_SUPER(pktbuf, &idx, &cmn, ®);
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "send REGISTER_SUPER to %s",
|
|
|
|
sock_to_cstr(sockbuf, &(peer->sock)));
|
|
|
|
|
|
|
|
packet_header_encrypt(pktbuf, idx, idx,
|
|
|
|
comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
|
|
|
|
time_stamp());
|
|
|
|
|
|
|
|
/* sent = */ sendto_peer(sss, peer, pktbuf, idx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0; /* OK */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int purge_expired_communities (n2n_sn_t *sss,
|
|
|
|
time_t* p_last_purge,
|
|
|
|
time_t now) {
|
|
|
|
|
|
|
|
struct sn_community *comm, *tmp_comm;
|
|
|
|
node_supernode_association_t *assoc, *tmp_assoc;
|
|
|
|
size_t num_reg = 0;
|
|
|
|
size_t num_assoc = 0;
|
|
|
|
|
|
|
|
if((now - (*p_last_purge)) < PURGE_REGISTRATION_FREQUENCY) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "purging old communities and edges");
|
|
|
|
|
|
|
|
HASH_ITER(hh, sss->communities, comm, tmp_comm) {
|
|
|
|
// federation is taken care of in re_register_and_purge_supernodes()
|
|
|
|
if(comm->is_federation == IS_FEDERATION)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// purge the community's local peers
|
|
|
|
num_reg += purge_peer_list(&comm->edges, sss->sock, &sss->tcp_connections, now - REGISTRATION_TIMEOUT);
|
|
|
|
|
|
|
|
// purge the community's associated peers (connected to other supernodes)
|
|
|
|
HASH_ITER(hh, comm->assoc, assoc, tmp_assoc) {
|
|
|
|
if(comm->assoc->last_seen < (now - 3 * REGISTRATION_TIMEOUT)) {
|
|
|
|
HASH_DEL(comm->assoc, assoc);
|
|
|
|
free(assoc);
|
|
|
|
num_assoc++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if((comm->edges == NULL) && (comm->purgeable == COMMUNITY_PURGEABLE)) {
|
|
|
|
traceEvent(TRACE_INFO, "purging idle community %s", comm->community);
|
|
|
|
if(NULL != comm->header_encryption_ctx_static) {
|
|
|
|
/* this should not happen as 'purgeable' and thus only communities w/o encrypted header here */
|
|
|
|
free(comm->header_encryption_ctx_static);
|
|
|
|
free(comm->header_iv_ctx_static);
|
|
|
|
free(comm->header_encryption_ctx_dynamic);
|
|
|
|
free(comm->header_iv_ctx_dynamic);
|
|
|
|
}
|
|
|
|
// remove all associations
|
|
|
|
HASH_ITER(hh, comm->assoc, assoc, tmp_assoc) {
|
|
|
|
HASH_DEL(comm->assoc, assoc);
|
|
|
|
free(assoc);
|
|
|
|
}
|
|
|
|
HASH_DEL(sss->communities, comm);
|
|
|
|
free(comm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(*p_last_purge) = now;
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "purge_expired_communities removed %ld locally registered edges and %ld remotely associated edges",
|
|
|
|
num_reg, num_assoc);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int number_enc_packets_sort (struct sn_community *a, struct sn_community *b) {
|
|
|
|
|
|
|
|
// comparison function for sorting communities in descending order of their
|
|
|
|
// number_enc_packets-fields
|
|
|
|
return (b->number_enc_packets - a->number_enc_packets);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int sort_communities (n2n_sn_t *sss,
|
|
|
|
time_t* p_last_sort,
|
|
|
|
time_t now) {
|
|
|
|
|
|
|
|
struct sn_community *comm, *tmp;
|
|
|
|
|
|
|
|
if((now - (*p_last_sort)) < SORT_COMMUNITIES_INTERVAL) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this routine gets periodically called as defined in SORT_COMMUNITIES_INTERVAL
|
|
|
|
// it sorts the communities in descending order of their number_enc_packets-fields...
|
|
|
|
HASH_SORT(sss->communities, number_enc_packets_sort);
|
|
|
|
|
|
|
|
// ... and afterward resets the number_enc__packets-fields to zero
|
|
|
|
// (other models could reset it to half of their value to respect history)
|
|
|
|
HASH_ITER(hh, sss->communities, comm, tmp) {
|
|
|
|
comm->number_enc_packets = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
(*p_last_sort) = now;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int process_mgmt (n2n_sn_t *sss,
|
|
|
|
const struct sockaddr_in *sender_sock,
|
|
|
|
const uint8_t *mgmt_buf,
|
|
|
|
size_t mgmt_size,
|
|
|
|
time_t now) {
|
|
|
|
|
|
|
|
char resbuf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
size_t ressize = 0;
|
|
|
|
uint32_t num_edges = 0;
|
|
|
|
uint32_t num_comm = 0;
|
|
|
|
uint32_t num = 0;
|
|
|
|
struct sn_community *community, *tmp;
|
|
|
|
struct peer_info *peer, *tmpPeer;
|
|
|
|
macstr_t mac_buf;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
char time_buf[10]; /* 9 digits + 1 terminating zero */
|
|
|
|
dec_ip_bit_str_t ip_bit_str = {'\0'};
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "process_mgmt");
|
|
|
|
|
|
|
|
// process input, if any
|
|
|
|
if((0 == memcmp(mgmt_buf, "help", 4)) || (0 == memcmp(mgmt_buf, "?", 1))) {
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"Help for supernode management console:\n"
|
|
|
|
"\thelp | This help message\n"
|
|
|
|
"\treload_communities | Reloads communities and user's public keys\n"
|
|
|
|
"\t<enter> | Display status and statistics\n");
|
|
|
|
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
|
|
|
|
return 0; /* no status output afterwards */
|
|
|
|
}
|
|
|
|
|
|
|
|
if(0 == memcmp(mgmt_buf, "reload_communities", 18)) {
|
|
|
|
if(!sss->community_file) {
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"No community file provided (-c command line option)\n");
|
|
|
|
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
|
|
|
|
return 0; /* no status output afterwards */
|
|
|
|
}
|
|
|
|
traceEvent(TRACE_NORMAL, "'reload_communities' command");
|
|
|
|
|
|
|
|
if(load_allowed_sn_community(sss)) {
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"Error while re-loading community file (not found or no valid content)\n");
|
|
|
|
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
|
|
|
|
return 0; /* no status output afterwards */
|
|
|
|
}
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"OK.\n");
|
|
|
|
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
|
|
|
|
return 0; /* no status output afterwards */
|
|
|
|
}
|
|
|
|
|
|
|
|
// output current status
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
" ### | TAP | MAC | EDGE | HINT | LAST SEEN\n");
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"========================================================================================================\n");
|
|
|
|
HASH_ITER(hh, sss->communities, community, tmp) {
|
|
|
|
if(num_comm)
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"--------------------------------------------------------------------------------------------------------\n");
|
|
|
|
num_comm++;
|
|
|
|
num_edges += HASH_COUNT(community->edges);
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"%s '%s'\n",
|
|
|
|
(community->is_federation) ? "FEDERATION" :
|
|
|
|
((community->purgeable == COMMUNITY_UNPURGEABLE) ? "FIXED NAME COMMUNITY" : "COMMUNITY"),
|
|
|
|
community->community);
|
|
|
|
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
|
|
|
|
ressize = 0;
|
|
|
|
|
|
|
|
num = 0;
|
|
|
|
HASH_ITER(hh, community->edges, peer, tmpPeer) {
|
|
|
|
sprintf (time_buf, "%9u", (unsigned int)(now - peer->last_seen));
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"%4u | %-19s | %-17s | %-21s %-3s | %-15s | %9s\n",
|
|
|
|
++num,
|
|
|
|
(peer->dev_addr.net_addr == 0) ? ((peer->purgeable == SN_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),
|
|
|
|
sock_to_cstr(sockbuf, &(peer->sock)),
|
|
|
|
((peer->socket_fd >= 0) && (peer->socket_fd != sss->sock)) ? "TCP" : "",
|
|
|
|
peer->dev_desc,
|
|
|
|
(peer->last_seen) ? time_buf : "");
|
|
|
|
|
|
|
|
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
|
|
|
|
ressize = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"========================================================================================================\n");
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"uptime %lu | ", (now - sss->start_time));
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"edges %u | ",
|
|
|
|
num_edges);
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"reg_sup %u | ",
|
|
|
|
(unsigned int) sss->stats.reg_super);
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"reg_nak %u | ",
|
|
|
|
(unsigned int) sss->stats.reg_super_nak);
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"errors %u \n",
|
|
|
|
(unsigned int) sss->stats.errors);
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"fwd %u | ",
|
|
|
|
(unsigned int) sss->stats.fwd);
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"broadcast %u | ",
|
|
|
|
(unsigned int) sss->stats.broadcast);
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"cur_cmnts %u\n", HASH_COUNT(sss->communities));
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"last_fwd %lu sec ago | ",
|
|
|
|
(long unsigned int) (now - sss->stats.last_fwd));
|
|
|
|
|
|
|
|
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
|
|
|
|
"last reg %lu sec ago\n\n",
|
|
|
|
(long unsigned int) (now - sss->stats.last_reg_super));
|
|
|
|
|
|
|
|
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int sendto_mgmt (n2n_sn_t *sss,
|
|
|
|
const struct sockaddr_in *sender_sock,
|
|
|
|
const uint8_t *mgmt_buf,
|
|
|
|
size_t mgmt_size) {
|
|
|
|
|
|
|
|
ssize_t r = sendto(sss->mgmt_sock, mgmt_buf, mgmt_size, 0 /*flags*/,
|
|
|
|
(struct sockaddr *)sender_sock, sizeof (struct sockaddr_in));
|
|
|
|
|
|
|
|
if(r <= 0) {
|
|
|
|
++(sss->stats.errors);
|
|
|
|
traceEvent (TRACE_ERROR, "sendto_mgmt : sendto failed. %s", strerror (errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Examine a datagram and determine what to do with it.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
static int process_udp (n2n_sn_t * sss,
|
|
|
|
const struct sockaddr_in *sender_sock,
|
|
|
|
const SOCKET socket_fd,
|
|
|
|
uint8_t * udp_buf,
|
|
|
|
size_t udp_size,
|
|
|
|
time_t now) {
|
|
|
|
|
|
|
|
n2n_common_t cmn; /* common fields in the packet header */
|
|
|
|
size_t rem;
|
|
|
|
size_t idx;
|
|
|
|
size_t msg_type;
|
|
|
|
uint8_t from_supernode;
|
|
|
|
peer_info_t *sn = NULL;
|
|
|
|
n2n_sock_t sender;
|
|
|
|
macstr_t mac_buf;
|
|
|
|
macstr_t mac_buf2;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
char buf[32];
|
|
|
|
uint8_t hash_buf[16] = {0}; /* always size of 16 (max) despite the actual value of N2N_REG_SUP_HASH_CHECK_LEN (<= 16) */
|
|
|
|
|
|
|
|
struct sn_community *comm, *tmp;
|
|
|
|
uint32_t header_enc = 0; /* 1 == encrypted by static key, 2 == encrypted by dynamic key */
|
|
|
|
uint64_t stamp;
|
|
|
|
int skip_add;
|
|
|
|
time_t any_time = 0;
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "processing incoming UDP packet [len: %lu][sender: %s:%u]",
|
|
|
|
udp_size, intoa(ntohl(sender_sock->sin_addr.s_addr), buf, sizeof(buf)),
|
|
|
|
ntohs(sender_sock->sin_port));
|
|
|
|
|
|
|
|
/* check if header is unencrypted. the following check is around 99.99962 percent reliable.
|
|
|
|
* it heavily relies on the structure of packet's common part
|
|
|
|
* changes to wire.c:encode/decode_common need to go together with this code */
|
|
|
|
if(udp_size < 24) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped a packet too short to be valid");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if((udp_buf[23] == (uint8_t)0x00) // null terminated community name
|
|
|
|
&& (udp_buf[00] == N2N_PKT_VERSION) // correct packet version
|
|
|
|
&& ((be16toh(*(uint16_t*)&(udp_buf[02])) & N2N_FLAGS_TYPE_MASK) <= MSG_TYPE_MAX_TYPE) // message type
|
|
|
|
&& ( be16toh(*(uint16_t*)&(udp_buf[02])) < N2N_FLAGS_OPTIONS) // flags
|
|
|
|
) {
|
|
|
|
/* most probably unencrypted */
|
|
|
|
/* make sure, no downgrading happens here and no unencrypted packets can be
|
|
|
|
* injected in a community which definitely deals with encrypted headers */
|
|
|
|
HASH_FIND_COMMUNITY(sss->communities, (char *)&udp_buf[04], comm);
|
|
|
|
if(comm) {
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped a packet with unencrypted header "
|
|
|
|
"addressed to community '%s' which uses encrypted headers",
|
|
|
|
comm->community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_UNKNOWN) {
|
|
|
|
traceEvent(TRACE_INFO, "locked community '%s' to "
|
|
|
|
"unencrypted headers", comm->community);
|
|
|
|
/* set 'no encryption' in case it is not set yet */
|
|
|
|
comm->header_encryption = HEADER_ENCRYPTION_NONE;
|
|
|
|
comm->header_encryption_ctx_static = NULL;
|
|
|
|
comm->header_encryption_ctx_dynamic = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* most probably encrypted */
|
|
|
|
/* cycle through the known communities (as keys) to eventually decrypt */
|
|
|
|
HASH_ITER(hh, sss->communities, comm, tmp) {
|
|
|
|
/* skip the definitely unencrypted communities */
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_NONE) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// match with static (1) or dynamic (2) ctx?
|
|
|
|
// check dynamic first as it is identical to static in normal header encryption mode
|
|
|
|
if(packet_header_decrypt(udp_buf, udp_size,
|
|
|
|
comm->community,
|
|
|
|
comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
|
|
|
|
&stamp)) {
|
|
|
|
header_enc = 2;
|
|
|
|
}
|
|
|
|
if(!header_enc) {
|
|
|
|
pearson_hash_128(hash_buf, udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN));
|
|
|
|
header_enc = packet_header_decrypt(udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN), comm->community,
|
|
|
|
comm->header_encryption_ctx_static, comm->header_iv_ctx_static, &stamp);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(header_enc) {
|
|
|
|
// time stamp verification follows in the packet specific section as it requires to determine the
|
|
|
|
// sender from the hash list by its MAC, this all depends on packet type and packet structure
|
|
|
|
// (MAC is not always in the same place)
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_UNKNOWN) {
|
|
|
|
traceEvent(TRACE_INFO, "locked community '%s' to "
|
|
|
|
"encrypted headers", comm->community);
|
|
|
|
/* set 'encrypted' in case it is not set yet */
|
|
|
|
comm->header_encryption = HEADER_ENCRYPTION_ENABLED;
|
|
|
|
}
|
|
|
|
// count the number of encrypted packets for sorting the communities from time to time
|
|
|
|
// for the HASH_ITER a few lines above gets faster for the more busy communities
|
|
|
|
(comm->number_enc_packets)++;
|
|
|
|
// no need to test further communities
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!header_enc) {
|
|
|
|
// no matching key/community
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped a packet with seemingly encrypted header "
|
|
|
|
"for which no matching community which uses encrypted headers was found");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Use decode_common() to determine the kind of packet then process it:
|
|
|
|
*
|
|
|
|
* REGISTER_SUPER adds an edge and generate a return REGISTER_SUPER_ACK
|
|
|
|
*
|
|
|
|
* REGISTER, REGISTER_ACK and PACKET messages are forwarded to their
|
|
|
|
* destination edge. If the destination is not known then PACKETs are
|
|
|
|
* broadcast.
|
|
|
|
*/
|
|
|
|
|
|
|
|
rem = udp_size; /* Counts down bytes of packet to protect against buffer overruns. */
|
|
|
|
idx = 0; /* marches through packet header as parts are decoded. */
|
|
|
|
|
|
|
|
if(decode_common(&cmn, udp_buf, &rem, &idx) < 0) {
|
|
|
|
traceEvent(TRACE_ERROR, "failed to decode common section");
|
|
|
|
return -1; /* failed to decode packet */
|
|
|
|
}
|
|
|
|
|
|
|
|
msg_type = cmn.pc; /* packet code */
|
|
|
|
|
|
|
|
// special case for user/pw auth
|
|
|
|
// community's auth scheme and message type need to match the used key (dynamic)
|
|
|
|
if(comm) {
|
|
|
|
if((comm->allowed_users)
|
|
|
|
&& (msg_type != MSG_TYPE_REGISTER_SUPER)
|
|
|
|
&& (msg_type != MSG_TYPE_REGISTER_SUPER_ACK)
|
|
|
|
&& (msg_type != MSG_TYPE_REGISTER_SUPER_NAK)) {
|
|
|
|
if(header_enc != 2) {
|
|
|
|
traceEvent(TRACE_WARNING, "dropped packet encrypted with static key where expecting dynamic key");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* REVISIT: when UDP/IPv6 is supported we will need a flag to indicate which
|
|
|
|
* IP transport version the packet arrived on. May need to UDP sockets. */
|
|
|
|
memset(&sender, 0, sizeof(n2n_sock_t));
|
|
|
|
sender.family = AF_INET; /* UDP socket was opened PF_INET v4 */
|
|
|
|
sender.port = ntohs(sender_sock->sin_port);
|
|
|
|
memcpy(&(sender.addr.v4), &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
|
|
|
|
|
|
|
|
from_supernode = cmn.flags & N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
if(from_supernode) {
|
|
|
|
skip_add = SN_ADD_SKIP;
|
|
|
|
sn = add_sn_to_list_by_mac_or_sock (&(sss->federation->edges), &sender, null_mac, &skip_add);
|
|
|
|
// only REGISTER_SUPER allowed from unknown supernodes
|
|
|
|
if((!sn) && (msg_type != MSG_TYPE_REGISTER_SUPER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped incoming data from unknown supernode");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(cmn.ttl < 1) {
|
|
|
|
traceEvent(TRACE_WARNING, "expired TTL");
|
|
|
|
return 0; /* Don't process further */
|
|
|
|
}
|
|
|
|
|
|
|
|
--(cmn.ttl); /* The value copied into all forwarded packets. */
|
|
|
|
|
|
|
|
switch(msg_type) {
|
|
|
|
case MSG_TYPE_PACKET: {
|
|
|
|
/* PACKET from one edge to another edge via supernode. */
|
|
|
|
|
|
|
|
/* pkt will be modified in place and recoded to an output of potentially
|
|
|
|
* different size due to addition of the socket.*/
|
|
|
|
n2n_PACKET_t pkt;
|
|
|
|
n2n_common_t cmn2;
|
|
|
|
uint8_t encbuf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
size_t encx = 0;
|
|
|
|
int unicast; /* non-zero if unicast */
|
|
|
|
uint8_t * rec_buf; /* either udp_buf or encbuf */
|
|
|
|
|
|
|
|
if(!comm) {
|
|
|
|
traceEvent(TRACE_DEBUG, "PACKET with unknown community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sss->stats.last_fwd = now;
|
|
|
|
decode_PACKET(&pkt, &cmn, udp_buf, &rem, &idx);
|
|
|
|
|
|
|
|
// already checked for valid comm
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
if(!find_edge_time_stamp_and_verify(comm->edges, sn, pkt.srcMac, stamp, TIME_STAMP_ALLOW_JITTER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped PACKET due to time stamp error");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unicast = (0 == is_multi_broadcast(pkt.dstMac));
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "RX PACKET (%s) %s -> %s %s",
|
|
|
|
(unicast ? "unicast" : "multicast"),
|
|
|
|
macaddr_str(mac_buf, pkt.srcMac),
|
|
|
|
macaddr_str(mac_buf2, pkt.dstMac),
|
|
|
|
(from_supernode ? "from sn" : "local"));
|
|
|
|
|
|
|
|
if(!from_supernode) {
|
|
|
|
memcpy(&cmn2, &cmn, sizeof(n2n_common_t));
|
|
|
|
|
|
|
|
/* We are going to add socket even if it was not there before */
|
|
|
|
cmn2.flags |= N2N_FLAGS_SOCKET | N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
|
|
|
|
pkt.sock.family = AF_INET;
|
|
|
|
pkt.sock.port = ntohs(sender_sock->sin_port);
|
|
|
|
memcpy(pkt.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
|
|
|
|
|
|
|
|
rec_buf = encbuf;
|
|
|
|
/* Re-encode the header. */
|
|
|
|
encode_PACKET(encbuf, &encx, &cmn2, &pkt);
|
|
|
|
|
|
|
|
uint16_t oldEncx = encx;
|
|
|
|
|
|
|
|
/* Copy the original payload unchanged */
|
|
|
|
encode_buf(encbuf, &encx, (udp_buf + idx), (udp_size - idx));
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
// in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size
|
|
|
|
packet_header_encrypt(rec_buf, oldEncx + (NULL != comm->allowed_users) * min(encx - oldEncx, N2N_SPECK_IVEC_SIZE), encx,
|
|
|
|
comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
|
|
|
|
time_stamp());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Already from a supernode. Nothing to modify, just pass to
|
|
|
|
* destination. */
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "Rx PACKET fwd unmodified");
|
|
|
|
|
|
|
|
rec_buf = udp_buf;
|
|
|
|
encx = udp_size;
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
// in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size
|
|
|
|
packet_header_encrypt(rec_buf, idx + (NULL != comm->allowed_users) * min(encx - idx, N2N_SPECK_IVEC_SIZE), encx,
|
|
|
|
comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
|
|
|
|
time_stamp());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Common section to forward the final product. */
|
|
|
|
if(unicast) {
|
|
|
|
try_forward(sss, comm, &cmn, pkt.dstMac, from_supernode, rec_buf, encx);
|
|
|
|
} else {
|
|
|
|
try_broadcast(sss, comm, &cmn, pkt.srcMac, from_supernode, rec_buf, encx);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_TYPE_REGISTER: {
|
|
|
|
/* Forwarding a REGISTER from one edge to the next */
|
|
|
|
|
|
|
|
n2n_REGISTER_t reg;
|
|
|
|
n2n_common_t cmn2;
|
|
|
|
uint8_t encbuf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
size_t encx = 0;
|
|
|
|
int unicast; /* non-zero if unicast */
|
|
|
|
uint8_t * rec_buf; /* either udp_buf or encbuf */
|
|
|
|
|
|
|
|
if(!comm) {
|
|
|
|
traceEvent(TRACE_DEBUG, "REGISTER from unknown community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sss->stats.last_fwd = now;
|
|
|
|
decode_REGISTER(®, &cmn, udp_buf, &rem, &idx);
|
|
|
|
|
|
|
|
// already checked for valid comm
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
if(!find_edge_time_stamp_and_verify(comm->edges, sn, reg.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped REGISTER due to time stamp error");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unicast = (0 == is_multi_broadcast(reg.dstMac));
|
|
|
|
|
|
|
|
if(unicast) {
|
|
|
|
traceEvent(TRACE_DEBUG, "Rx REGISTER %s -> %s %s",
|
|
|
|
macaddr_str(mac_buf, reg.srcMac),
|
|
|
|
macaddr_str(mac_buf2, reg.dstMac),
|
|
|
|
((cmn.flags & N2N_FLAGS_FROM_SUPERNODE) ? "from sn" : "local"));
|
|
|
|
|
|
|
|
if(0 == (cmn.flags & N2N_FLAGS_FROM_SUPERNODE)) {
|
|
|
|
memcpy(&cmn2, &cmn, sizeof(n2n_common_t));
|
|
|
|
|
|
|
|
/* We are going to add socket even if it was not there before */
|
|
|
|
cmn2.flags |= N2N_FLAGS_SOCKET | N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
|
|
|
|
reg.sock.family = AF_INET;
|
|
|
|
reg.sock.port = ntohs(sender_sock->sin_port);
|
|
|
|
memcpy(reg.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
|
|
|
|
|
|
|
|
/* Re-encode the header. */
|
|
|
|
encode_REGISTER(encbuf, &encx, &cmn2, ®);
|
|
|
|
|
|
|
|
rec_buf = encbuf;
|
|
|
|
} else {
|
|
|
|
/* Already from a supernode. Nothing to modify, just pass to
|
|
|
|
* destination. */
|
|
|
|
|
|
|
|
rec_buf = udp_buf;
|
|
|
|
encx = udp_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(rec_buf, encx, encx,
|
|
|
|
comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
|
|
|
|
time_stamp());
|
|
|
|
}
|
|
|
|
try_forward(sss, comm, &cmn, reg.dstMac, from_supernode, rec_buf, encx); /* unicast only */
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_ERROR, "Rx REGISTER with multicast destination");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_TYPE_REGISTER_ACK: {
|
|
|
|
traceEvent(TRACE_DEBUG, "Rx REGISTER_ACK (not implemented) should not be via supernode");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_TYPE_REGISTER_SUPER: {
|
|
|
|
n2n_REGISTER_SUPER_t reg;
|
|
|
|
n2n_REGISTER_SUPER_ACK_t ack;
|
|
|
|
n2n_REGISTER_SUPER_NAK_t nak;
|
|
|
|
n2n_common_t cmn2;
|
|
|
|
uint8_t ackbuf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
uint8_t payload_buf[REG_SUPER_ACK_PAYLOAD_SPACE];
|
|
|
|
n2n_REGISTER_SUPER_ACK_payload_t *payload;
|
|
|
|
size_t encx = 0;
|
|
|
|
struct sn_community_regular_expression *re, *tmp_re;
|
|
|
|
struct peer_info *peer, *tmp_peer, *p;
|
|
|
|
int8_t allowed_match = -1;
|
|
|
|
uint8_t match = 0;
|
|
|
|
int match_length = 0;
|
|
|
|
n2n_ip_subnet_t ipaddr;
|
|
|
|
int num = 0;
|
|
|
|
int skip;
|
|
|
|
int ret_value;
|
|
|
|
sn_user_t *user = NULL;
|
|
|
|
|
|
|
|
memset(&ack, 0, sizeof(n2n_REGISTER_SUPER_ACK_t));
|
|
|
|
memset(&nak, 0, sizeof(n2n_REGISTER_SUPER_NAK_t));
|
|
|
|
|
|
|
|
/* Edge/supernode requesting registration with us. */
|
|
|
|
sss->stats.last_reg_super=now;
|
|
|
|
++(sss->stats.reg_super);
|
|
|
|
decode_REGISTER_SUPER(®, &cmn, udp_buf, &rem, &idx);
|
|
|
|
|
|
|
|
if(comm) {
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
if(!find_edge_time_stamp_and_verify(comm->edges, sn, reg.edgeMac, stamp, TIME_STAMP_NO_JITTER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped REGISTER_SUPER due to time stamp error");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Before we move any further, we need to check if the requested
|
|
|
|
community is allowed by the supernode. In case it is not we do
|
|
|
|
not report any message back to the edge to hide the supernode
|
|
|
|
existance (better from the security standpoint)
|
|
|
|
*/
|
|
|
|
|
|
|
|
if(!comm && sss->lock_communities) {
|
|
|
|
HASH_ITER(hh, sss->rules, re, tmp_re) {
|
|
|
|
allowed_match = re_matchp(re->rule, (const char *)cmn.community, &match_length);
|
|
|
|
|
|
|
|
if((allowed_match != -1)
|
|
|
|
&& (match_length == strlen((const char *)cmn.community)) // --- only full matches allowed (remove, if also partial matches wanted)
|
|
|
|
&& (allowed_match == 0)) { // --- only full matches allowed (remove, if also partial matches wanted)
|
|
|
|
match = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(match != 1) {
|
|
|
|
traceEvent(TRACE_INFO, "discarded registration with unallowed community '%s'",
|
|
|
|
(char*)cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!comm && (!sss->lock_communities || (match == 1))) {
|
|
|
|
comm = (struct sn_community*)calloc(1, sizeof(struct sn_community));
|
|
|
|
|
|
|
|
if(comm) {
|
|
|
|
comm_init(comm, (char *)cmn.community);
|
|
|
|
/* new communities introduced by REGISTERs could not have had encrypted header... */
|
|
|
|
comm->header_encryption = HEADER_ENCRYPTION_NONE;
|
|
|
|
comm->header_encryption_ctx_static = NULL;
|
|
|
|
comm->header_encryption_ctx_dynamic = NULL;
|
|
|
|
/* ... and also are purgeable during periodic purge */
|
|
|
|
comm->purgeable = COMMUNITY_PURGEABLE;
|
|
|
|
comm->number_enc_packets = 0;
|
|
|
|
HASH_ADD_STR(sss->communities, community, comm);
|
|
|
|
|
|
|
|
traceEvent(TRACE_INFO, "new community: %s", comm->community);
|
|
|
|
assign_one_ip_subnet(sss, comm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!comm) {
|
|
|
|
traceEvent(TRACE_INFO, "discarded registration with unallowed community '%s'",
|
|
|
|
(char*)cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// hash check (user/pw auth only)
|
|
|
|
if(comm->allowed_users) {
|
|
|
|
// check if submitted public key is in list of allowed users
|
|
|
|
HASH_FIND(hh, comm->allowed_users, ®.auth.token, sizeof(n2n_private_public_key_t), user);
|
|
|
|
if(user) {
|
|
|
|
speck_128_encrypt(hash_buf, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
if(memcmp(hash_buf, udp_buf + udp_size - N2N_REG_SUP_HASH_CHECK_LEN /* length has already been checked */, N2N_REG_SUP_HASH_CHECK_LEN)) {
|
|
|
|
traceEvent(TRACE_INFO, "Rx REGISTER_SUPER with wrong hash");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_INFO, "Rx REGISTER_SUPER from unknown user");
|
|
|
|
// continue and let auth check do the rest (otherwise, no NAK is sent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cmn2.ttl = N2N_DEFAULT_TTL;
|
|
|
|
cmn2.pc = n2n_register_super_ack;
|
|
|
|
cmn2.flags = N2N_FLAGS_SOCKET | N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
memcpy(cmn2.community, cmn.community, sizeof(n2n_community_t));
|
|
|
|
|
|
|
|
memcpy(&(ack.cookie), &(reg.cookie), sizeof(n2n_cookie_t));
|
|
|
|
memcpy(ack.srcMac, sss->mac_addr, sizeof(n2n_mac_t));
|
|
|
|
|
|
|
|
if(comm->is_federation != IS_FEDERATION) { /* alternatively, do not send zero tap ip address in federation REGISTER_SUPER */
|
|
|
|
if((reg.dev_addr.net_addr == 0) || (reg.dev_addr.net_addr == 0xFFFFFFFF) || (reg.dev_addr.net_bitlen == 0) ||
|
|
|
|
((reg.dev_addr.net_addr & 0xFFFF0000) == 0xA9FE0000 /* 169.254.0.0 */)) {
|
|
|
|
memset(&ipaddr, 0, sizeof(n2n_ip_subnet_t));
|
|
|
|
assign_one_ip_addr(comm, reg.dev_desc, &ipaddr);
|
|
|
|
ack.dev_addr.net_addr = ipaddr.net_addr;
|
|
|
|
ack.dev_addr.net_bitlen = ipaddr.net_bitlen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ack.lifetime = reg_lifetime(sss);
|
|
|
|
|
|
|
|
ack.sock.family = AF_INET;
|
|
|
|
ack.sock.port = ntohs(sender_sock->sin_port);
|
|
|
|
memcpy(ack.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
|
|
|
|
|
|
|
|
/* Add sender's data to federation (or update it) */
|
|
|
|
if(comm->is_federation == IS_FEDERATION) {
|
|
|
|
skip_add = SN_ADD;
|
|
|
|
p = add_sn_to_list_by_mac_or_sock(&(sss->federation->edges), &(ack.sock), reg.edgeMac, &skip_add);
|
|
|
|
p->last_seen = now;
|
|
|
|
// communication with other supernodes happens via standard udp port
|
|
|
|
p->socket_fd = sss->sock;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip random numbers of supernodes before payload assembling, calculating an appropriate random_number.
|
|
|
|
* That way, all supernodes have a chance to be propagated with REGISTER_SUPER_ACK. */
|
|
|
|
skip = HASH_COUNT(sss->federation->edges) - (int)(REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE / REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE);
|
|
|
|
skip = (skip < 0) ? 0 : n2n_rand_sqr(skip);
|
|
|
|
|
|
|
|
/* Assembling supernode list for REGISTER_SUPER_ACK payload */
|
|
|
|
payload = (n2n_REGISTER_SUPER_ACK_payload_t*)payload_buf;
|
|
|
|
HASH_ITER(hh, sss->federation->edges, peer, tmp_peer) {
|
|
|
|
if(skip) {
|
|
|
|
skip--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if(peer->sock.family == AF_INVALID)
|
|
|
|
continue; /* do not add unresolved supernodes to payload */
|
|
|
|
if(memcmp(&(peer->sock), &(ack.sock), sizeof(n2n_sock_t)) == 0) continue; /* a supernode doesn't add itself to the payload */
|
|
|
|
if((now - peer->last_seen) >= LAST_SEEN_SN_NEW) continue; /* skip long-time-not-seen supernodes.
|
|
|
|
* We need to allow for a little extra time because supernodes sometimes exceed
|
|
|
|
* their SN_ACTIVE time before they get re-registred to. */
|
|
|
|
if(((++num)*REG_SUPER_ACK_PAYLOAD_ENTRY_SIZE) > REG_SUPER_ACK_PAYLOAD_SPACE) break; /* no more space available in REGISTER_SUPER_ACK payload */
|
|
|
|
memcpy(&(payload->sock), &(peer->sock), sizeof(n2n_sock_t));
|
|
|
|
memcpy(payload->mac, peer->mac_addr, sizeof(n2n_mac_t));
|
|
|
|
// shift to next payload entry
|
|
|
|
payload++;
|
|
|
|
}
|
|
|
|
ack.num_sn = num;
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "Rx REGISTER_SUPER for %s [%s]",
|
|
|
|
macaddr_str(mac_buf, reg.edgeMac),
|
|
|
|
sock_to_cstr(sockbuf, &(ack.sock)));
|
|
|
|
|
|
|
|
// check authentication
|
|
|
|
ret_value = update_edge_no_change;
|
|
|
|
if(comm->is_federation != IS_FEDERATION) { /* REVISIT: auth among supernodes is not implemented yet */
|
|
|
|
if(cmn.flags & N2N_FLAGS_SOCKET) {
|
|
|
|
ret_value = update_edge(sss, ®, comm, &(ack.sock), socket_fd, &(ack.auth), SN_ADD_SKIP, now);
|
|
|
|
} else {
|
|
|
|
// do not add in case of null mac (edge asking for ip address)
|
|
|
|
ret_value = update_edge(sss, ®, comm, &(ack.sock), socket_fd, &(ack.auth), is_null_mac(reg.edgeMac) ? SN_ADD_SKIP : SN_ADD, now);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ret_value == update_edge_auth_fail) {
|
|
|
|
// send REGISTER_SUPER_NAK
|
|
|
|
cmn2.pc = n2n_register_super_nak;
|
|
|
|
memcpy(&(nak.cookie), &(reg.cookie), sizeof(n2n_cookie_t));
|
|
|
|
memcpy(nak.srcMac, reg.edgeMac, sizeof(n2n_mac_t));
|
|
|
|
|
|
|
|
encode_REGISTER_SUPER_NAK(ackbuf, &encx, &cmn2, &nak);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(ackbuf, encx, encx,
|
|
|
|
comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
|
|
|
|
time_stamp());
|
|
|
|
// if user-password-auth
|
|
|
|
if(comm->allowed_users) {
|
|
|
|
encode_buf(ackbuf, &encx, hash_buf /* no matter what content */, N2N_REG_SUP_HASH_CHECK_LEN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sendto_sock(sss, socket_fd, (struct sockaddr *)sender_sock, ackbuf, encx);
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "Tx REGISTER_SUPER_NAK for %s",
|
|
|
|
macaddr_str(mac_buf, reg.edgeMac));
|
|
|
|
} else {
|
|
|
|
// if this is not already forwarded from a supernode, ...
|
|
|
|
if(!(cmn.flags & N2N_FLAGS_SOCKET)) {
|
|
|
|
// ... forward to all other supernodes (note try_broadcast()'s behavior with
|
|
|
|
// NULL comm and from_supernode parameter)
|
|
|
|
// exception: do not forward auto ip draw
|
|
|
|
if(!is_null_mac(reg.edgeMac)) {
|
|
|
|
reg.sock.family = AF_INET;
|
|
|
|
reg.sock.port = ntohs(sender_sock->sin_port);
|
|
|
|
memcpy(reg.sock.addr.v4, &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
|
|
|
|
|
|
|
|
cmn2.pc = n2n_register_super;
|
|
|
|
encode_REGISTER_SUPER(ackbuf, &encx, &cmn2, ®);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(ackbuf, encx, encx,
|
|
|
|
comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
|
|
|
|
time_stamp());
|
|
|
|
// if user-password-auth
|
|
|
|
if(comm->allowed_users) {
|
|
|
|
// append an encrypted packet hash
|
|
|
|
pearson_hash_128(hash_buf, ackbuf, encx);
|
|
|
|
// same 'user' as above
|
|
|
|
speck_128_encrypt(hash_buf, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
encode_buf(ackbuf, &encx, hash_buf, N2N_REG_SUP_HASH_CHECK_LEN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
try_broadcast(sss, NULL, &cmn, reg.edgeMac, from_supernode, ackbuf, encx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// dynamic key time handling if appropriate
|
|
|
|
ack.key_time = 0;
|
|
|
|
if(comm->is_federation == IS_FEDERATION) {
|
|
|
|
if(reg.key_time > sss->dynamic_key_time) {
|
|
|
|
traceEvent(TRACE_DEBUG, "setting new key time");
|
|
|
|
// have all edges re_register (using old dynamic key)
|
|
|
|
send_re_register_super(sss);
|
|
|
|
// set new key time
|
|
|
|
sss->dynamic_key_time = reg.key_time;
|
|
|
|
// calculate new dynamic keys for all communities
|
|
|
|
calculate_dynamic_keys(sss);
|
|
|
|
// force re-register with all supernodes
|
|
|
|
re_register_and_purge_supernodes(sss, sss->federation, &any_time, now, 1 /* forced */);
|
|
|
|
}
|
|
|
|
ack.key_time = sss->dynamic_key_time;
|
|
|
|
}
|
|
|
|
|
|
|
|
// send REGISTER_SUPER_ACK
|
|
|
|
encx = 0;
|
|
|
|
cmn2.pc = n2n_register_super_ack;
|
|
|
|
|
|
|
|
encode_REGISTER_SUPER_ACK(ackbuf, &encx, &cmn2, &ack, payload_buf);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(ackbuf, encx, encx,
|
|
|
|
comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
|
|
|
|
time_stamp());
|
|
|
|
// if user-password-auth
|
|
|
|
if(comm->allowed_users) {
|
|
|
|
// append an encrypted packet hash
|
|
|
|
pearson_hash_128(hash_buf, ackbuf, encx);
|
|
|
|
// same 'user' as above
|
|
|
|
speck_128_encrypt(hash_buf, (speck_context_t*)user->shared_secret_ctx);
|
|
|
|
encode_buf(ackbuf, &encx, hash_buf, N2N_REG_SUP_HASH_CHECK_LEN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sendto_sock(sss, socket_fd, (struct sockaddr *)sender_sock, ackbuf, encx);
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "Tx REGISTER_SUPER_ACK for %s [%s]",
|
|
|
|
macaddr_str(mac_buf, reg.edgeMac),
|
|
|
|
sock_to_cstr(sockbuf, &(ack.sock)));
|
|
|
|
} else {
|
|
|
|
// this is an edge with valid authentication registering with another supernode, so ...
|
|
|
|
// 1- ... associate it with that other supernode
|
|
|
|
update_node_supernode_association(comm, &(reg.edgeMac), sender_sock, now);
|
|
|
|
// 2- ... we can delete it from regular list if present (can happen)
|
|
|
|
HASH_FIND_PEER(comm->edges, reg.edgeMac, peer);
|
|
|
|
if(peer != NULL) {
|
|
|
|
if((peer->socket_fd != sss->sock) && (peer->socket_fd >= 0)) {
|
|
|
|
n2n_tcp_connection_t *conn;
|
|
|
|
HASH_FIND_INT(sss->tcp_connections, &(peer->socket_fd), conn);
|
|
|
|
close_tcp_connection(sss, conn); /* also deletes the peer */
|
|
|
|
} else {
|
|
|
|
HASH_DEL(comm->edges, peer);
|
|
|
|
free(peer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_TYPE_UNREGISTER_SUPER: {
|
|
|
|
n2n_UNREGISTER_SUPER_t unreg;
|
|
|
|
struct peer_info *peer;
|
|
|
|
int auth;
|
|
|
|
|
|
|
|
|
|
|
|
memset(&unreg, 0, sizeof(n2n_UNREGISTER_SUPER_t));
|
|
|
|
|
|
|
|
if(!comm) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped UNREGISTER_SUPER with unknown community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((from_supernode == 1) || (comm->is_federation == IS_FEDERATION)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped UNREGISTER_SUPER: should not come from a supernode or federation.");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
decode_UNREGISTER_SUPER(&unreg, &cmn, udp_buf, &rem, &idx);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
if(!find_edge_time_stamp_and_verify(comm->edges, sn, unreg.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped UNREGISTER_SUPER due to time stamp error");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "Rx UNREGISTER_SUPER from %s",
|
|
|
|
macaddr_str(mac_buf, unreg.srcMac));
|
|
|
|
|
|
|
|
HASH_FIND_PEER(comm->edges, unreg.srcMac, peer);
|
|
|
|
if(peer != NULL) {
|
|
|
|
if((auth = auth_edge(&(peer->auth), &unreg.auth, NULL, comm)) == 0) {
|
|
|
|
if((peer->socket_fd != sss->sock) && (peer->socket_fd >= 0)) {
|
|
|
|
n2n_tcp_connection_t *conn;
|
|
|
|
HASH_FIND_INT(sss->tcp_connections, &(peer->socket_fd), conn);
|
|
|
|
close_tcp_connection(sss, conn); /* also deletes the peer */
|
|
|
|
} else {
|
|
|
|
HASH_DEL(comm->edges, peer);
|
|
|
|
free(peer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_TYPE_REGISTER_SUPER_ACK: {
|
|
|
|
n2n_REGISTER_SUPER_ACK_t ack;
|
|
|
|
struct peer_info *scan, *tmp;
|
|
|
|
n2n_sock_str_t sockbuf1;
|
|
|
|
n2n_sock_str_t sockbuf2;
|
|
|
|
macstr_t mac_buf1;
|
|
|
|
n2n_sock_t sender;
|
|
|
|
n2n_sock_t *orig_sender;
|
|
|
|
int i;
|
|
|
|
uint8_t dec_tmpbuf[REG_SUPER_ACK_PAYLOAD_SPACE];
|
|
|
|
n2n_REGISTER_SUPER_ACK_payload_t *payload;
|
|
|
|
|
|
|
|
memset(&sender, 0, sizeof(n2n_sock_t));
|
|
|
|
sender.family = AF_INET;
|
|
|
|
sender.port = ntohs(sender_sock->sin_port);
|
|
|
|
memcpy(&(sender.addr.v4), &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
|
|
|
|
orig_sender = &sender;
|
|
|
|
|
|
|
|
memset(&ack, 0, sizeof(n2n_REGISTER_SUPER_ACK_t));
|
|
|
|
|
|
|
|
if(!comm) {
|
|
|
|
traceEvent(TRACE_DEBUG, "REGISTER_SUPER_ACK with unknown community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if((from_supernode == 0) || (comm->is_federation == IS_NO_FEDERATION)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped REGISTER_SUPER_ACK, should not come from an edge or regular community");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
decode_REGISTER_SUPER_ACK(&ack, &cmn, udp_buf, &rem, &idx, dec_tmpbuf);
|
|
|
|
orig_sender = &(ack.sock);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
if(!find_edge_time_stamp_and_verify(comm->edges, sn, ack.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped REGISTER_SUPER_ACK due to time stamp error");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_ACK from MAC %s [%s] (external %s)",
|
|
|
|
macaddr_str(mac_buf1, ack.srcMac),
|
|
|
|
sock_to_cstr(sockbuf1, &sender),
|
|
|
|
sock_to_cstr(sockbuf2, orig_sender));
|
|
|
|
|
|
|
|
skip_add = SN_ADD_SKIP;
|
|
|
|
scan = add_sn_to_list_by_mac_or_sock(&(sss->federation->edges), &sender, ack.srcMac, &skip_add);
|
|
|
|
if(scan != NULL) {
|
|
|
|
scan->last_seen = now;
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped REGISTER_SUPER_ACK due to an unknown supernode");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(0 == memcmp(ack.cookie, scan->last_cookie, N2N_COOKIE_SIZE)) {
|
|
|
|
|
|
|
|
payload = (n2n_REGISTER_SUPER_ACK_payload_t *)dec_tmpbuf;
|
|
|
|
for(i = 0; i < ack.num_sn; i++) {
|
|
|
|
skip_add = SN_ADD;
|
|
|
|
tmp = add_sn_to_list_by_mac_or_sock(&(sss->federation->edges), &(payload->sock), payload->mac, &skip_add);
|
|
|
|
// other supernodes communicate via standard udp socket
|
|
|
|
tmp->socket_fd = sss->sock;
|
|
|
|
|
|
|
|
if(skip_add == SN_ADD_ADDED) {
|
|
|
|
tmp->last_seen = now - LAST_SEEN_SN_NEW;
|
|
|
|
}
|
|
|
|
|
|
|
|
// shift to next payload entry
|
|
|
|
payload++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ack.key_time > sss->dynamic_key_time) {
|
|
|
|
traceEvent(TRACE_DEBUG, "setting new key time");
|
|
|
|
// have all edges re_register (using old dynamic key)
|
|
|
|
send_re_register_super(sss);
|
|
|
|
// set new key time
|
|
|
|
sss->dynamic_key_time = ack.key_time;
|
|
|
|
// calculate new dynamic keys for all communities
|
|
|
|
calculate_dynamic_keys(sss);
|
|
|
|
// force re-register with all supernodes
|
|
|
|
re_register_and_purge_supernodes(sss, sss->federation, &any_time, now, 1 /* forced */);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_ACK with wrong or old cookie");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_TYPE_REGISTER_SUPER_NAK: {
|
|
|
|
n2n_REGISTER_SUPER_NAK_t nak;
|
|
|
|
uint8_t nakbuf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
size_t encx = 0;
|
|
|
|
struct peer_info *peer;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
macstr_t mac_buf;
|
|
|
|
n2n_sock_t sender;
|
|
|
|
|
|
|
|
memset(&sender, 0, sizeof(n2n_sock_t));
|
|
|
|
sender.family = AF_INET;
|
|
|
|
sender.port = ntohs(sender_sock->sin_port);
|
|
|
|
memcpy(&(sender.addr.v4), &(sender_sock->sin_addr.s_addr), IPV4_SIZE);
|
|
|
|
|
|
|
|
memset(&nak, 0, sizeof(n2n_REGISTER_SUPER_NAK_t));
|
|
|
|
|
|
|
|
if(!comm) {
|
|
|
|
traceEvent(TRACE_DEBUG, "REGISTER_SUPER_NAK with unknown community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
decode_REGISTER_SUPER_NAK(&nak, &cmn, udp_buf, &rem, &idx);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
if(!find_edge_time_stamp_and_verify(comm->edges, sn, nak.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "process_udp dropped REGISTER_SUPER_NAK due to time stamp error");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_NAK from %s [%s]",
|
|
|
|
macaddr_str(mac_buf, nak.srcMac),
|
|
|
|
sock_to_cstr(sockbuf, &sender));
|
|
|
|
|
|
|
|
HASH_FIND_PEER(comm->edges, nak.srcMac, peer);
|
|
|
|
if(comm->is_federation == IS_NO_FEDERATION) {
|
|
|
|
if(peer != NULL) {
|
|
|
|
// this is a NAK for one of the edges conencted to this supernode, forward,
|
|
|
|
// i.e. re-assemble (memcpy from udpbuf to nakbuf could be sufficient as well)
|
|
|
|
|
|
|
|
// use incoming cmn (with already decreased TTL)
|
|
|
|
// NAK (cookie, srcMac, auth) remains unchanged
|
|
|
|
|
|
|
|
encode_REGISTER_SUPER_NAK(nakbuf, &encx, &cmn, &nak);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(nakbuf, encx, encx,
|
|
|
|
comm->header_encryption_ctx_static, comm->header_iv_ctx_static,
|
|
|
|
time_stamp());
|
|
|
|
// if user-password-auth
|
|
|
|
if(comm->allowed_users) {
|
|
|
|
encode_buf(nakbuf, &encx, hash_buf /* no matter what content */, N2N_REG_SUP_HASH_CHECK_LEN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sendto_peer(sss, peer, nakbuf, encx);
|
|
|
|
|
|
|
|
if((peer->socket_fd != sss->sock) && (peer->socket_fd >= 0)) {
|
|
|
|
n2n_tcp_connection_t *conn;
|
|
|
|
HASH_FIND_INT(sss->tcp_connections, &(peer->socket_fd), conn);
|
|
|
|
close_tcp_connection(sss, conn); /* also deletes the peer */
|
|
|
|
} else {
|
|
|
|
HASH_DEL(comm->edges, peer);
|
|
|
|
free(peer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_TYPE_QUERY_PEER: {
|
|
|
|
n2n_QUERY_PEER_t query;
|
|
|
|
uint8_t encbuf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
size_t encx = 0;
|
|
|
|
n2n_common_t cmn2;
|
|
|
|
n2n_PEER_INFO_t pi;
|
|
|
|
struct sn_community_regular_expression *re, *tmp_re;
|
|
|
|
int8_t allowed_match = -1;
|
|
|
|
uint8_t match = 0;
|
|
|
|
int match_length = 0;
|
|
|
|
|
|
|
|
if(!comm && sss->lock_communities) {
|
|
|
|
HASH_ITER(hh, sss->rules, re, tmp_re) {
|
|
|
|
allowed_match = re_matchp(re->rule, (const char *)cmn.community, &match_length);
|
|
|
|
|
|
|
|
if((allowed_match != -1)
|
|
|
|
&& (match_length == strlen((const char *)cmn.community)) // --- only full matches allowed (remove, if also partial matches wanted)
|
|
|
|
&& (allowed_match == 0)) { // --- only full matches allowed (remove, if also partial matches wanted)
|
|
|
|
match = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(match != 1) {
|
|
|
|
traceEvent(TRACE_DEBUG, "QUERY_PEER from unknown community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!comm && sss->lock_communities && (match == 0)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "QUERY_PEER from not allowed community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
decode_QUERY_PEER( &query, &cmn, udp_buf, &rem, &idx );
|
|
|
|
|
|
|
|
// to answer a PING, it is sufficient if the provided communtiy would be a valid one, there does not
|
|
|
|
// neccessarily need to be a comm entry present, e.g. because there locally are no edges of the
|
|
|
|
// community connected (several supernodes in a federation setup)
|
|
|
|
if(comm) {
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
if(!find_edge_time_stamp_and_verify(comm->edges, sn, query.srcMac, stamp, TIME_STAMP_ALLOW_JITTER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped QUERY_PEER due to time stamp error");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(is_null_mac(query.targetMac)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "Rx PING from %s",
|
|
|
|
macaddr_str(mac_buf, query.srcMac));
|
|
|
|
|
|
|
|
cmn2.ttl = N2N_DEFAULT_TTL;
|
|
|
|
cmn2.pc = n2n_peer_info;
|
|
|
|
cmn2.flags = N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
memcpy(cmn2.community, cmn.community, sizeof(n2n_community_t));
|
|
|
|
|
|
|
|
pi.aflags = 0;
|
|
|
|
memcpy(pi.mac, query.targetMac, sizeof(n2n_mac_t));
|
|
|
|
memcpy(pi.srcMac, sss->mac_addr, sizeof(n2n_mac_t));
|
|
|
|
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);
|
|
|
|
|
|
|
|
encode_PEER_INFO(encbuf, &encx, &cmn2, &pi);
|
|
|
|
|
|
|
|
if(comm) {
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx_dynamic,
|
|
|
|
comm->header_iv_ctx_dynamic,
|
|
|
|
time_stamp());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sendto_sock(sss, socket_fd, (struct sockaddr *)sender_sock, encbuf, encx);
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "Tx PONG to %s",
|
|
|
|
macaddr_str(mac_buf, query.srcMac));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_DEBUG, "Rx QUERY_PEER from %s for %s",
|
|
|
|
macaddr_str(mac_buf, query.srcMac),
|
|
|
|
macaddr_str(mac_buf2, query.targetMac));
|
|
|
|
|
|
|
|
struct peer_info *scan;
|
|
|
|
|
|
|
|
// as opposed to the special case 'PING', proper QUERY_PEER processing requires a locally actually present community entry
|
|
|
|
if(!comm) {
|
|
|
|
traceEvent(TRACE_DEBUG, "QUERY_PEER with unknown community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
HASH_FIND_PEER(comm->edges, query.targetMac, scan);
|
|
|
|
if(scan) {
|
|
|
|
cmn2.ttl = N2N_DEFAULT_TTL;
|
|
|
|
cmn2.pc = n2n_peer_info;
|
|
|
|
cmn2.flags = N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
memcpy(cmn2.community, cmn.community, sizeof(n2n_community_t));
|
|
|
|
|
|
|
|
pi.aflags = 0;
|
|
|
|
memcpy(pi.srcMac, query.srcMac, sizeof(n2n_mac_t));
|
|
|
|
memcpy(pi.mac, query.targetMac, sizeof(n2n_mac_t));
|
|
|
|
pi.sock = scan->sock;
|
|
|
|
|
|
|
|
encode_PEER_INFO(encbuf, &encx, &cmn2, &pi);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx_dynamic,
|
|
|
|
comm->header_iv_ctx_dynamic,
|
|
|
|
time_stamp());
|
|
|
|
}
|
|
|
|
// back to sender, be it edge or supernode (which will forward to edge)
|
|
|
|
sendto_sock(sss, socket_fd, (struct sockaddr *)sender_sock, encbuf, encx);
|
|
|
|
|
|
|
|
traceEvent(TRACE_DEBUG, "Tx PEER_INFO to %s",
|
|
|
|
macaddr_str(mac_buf, query.srcMac));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if(from_supernode) {
|
|
|
|
traceEvent(TRACE_DEBUG, "QUERY_PEER on unknown edge from supernode %s, dropping the packet",
|
|
|
|
macaddr_str(mac_buf, query.srcMac));
|
|
|
|
} else {
|
|
|
|
traceEvent(TRACE_DEBUG, "QUERY_PEER from unknown edge %s, forwarding to all other supernodes",
|
|
|
|
macaddr_str(mac_buf, query.srcMac));
|
|
|
|
|
|
|
|
memcpy(&cmn2, &cmn, sizeof(n2n_common_t));
|
|
|
|
cmn2.flags |= N2N_FLAGS_FROM_SUPERNODE;
|
|
|
|
|
|
|
|
encode_QUERY_PEER(encbuf, &encx, &cmn2, &query);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(encbuf, encx, encx, comm->header_encryption_ctx_dynamic,
|
|
|
|
comm->header_iv_ctx_dynamic,
|
|
|
|
time_stamp());
|
|
|
|
}
|
|
|
|
|
|
|
|
try_broadcast(sss, NULL, &cmn, query.srcMac, from_supernode, encbuf, encx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_TYPE_PEER_INFO: {
|
|
|
|
n2n_PEER_INFO_t pi;
|
|
|
|
uint8_t encbuf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
size_t encx = 0;
|
|
|
|
struct peer_info *peer;
|
|
|
|
|
|
|
|
if(!comm) {
|
|
|
|
traceEvent(TRACE_DEBUG, "PEER_INFO with unknown community %s", cmn.community);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
decode_PEER_INFO(&pi, &cmn, udp_buf, &rem, &idx);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
if(!find_edge_time_stamp_and_verify(comm->edges, sn, pi.srcMac, stamp, TIME_STAMP_NO_JITTER)) {
|
|
|
|
traceEvent(TRACE_DEBUG, "dropped PEER_INFO due to time stamp error");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
traceEvent(TRACE_INFO, "Rx PEER_INFO from %s [%s]",
|
|
|
|
macaddr_str(mac_buf, pi.srcMac),
|
|
|
|
sock_to_cstr(sockbuf, &sender));
|
|
|
|
|
|
|
|
HASH_FIND_PEER(comm->edges, pi.srcMac, peer);
|
|
|
|
if(peer != NULL) {
|
|
|
|
if((comm->is_federation == IS_NO_FEDERATION) && (!is_null_mac(pi.srcMac))) {
|
|
|
|
// snoop on the information to use for supernode forwarding (do not wait until first remote REGISTER_SUPER)
|
|
|
|
update_node_supernode_association(comm, &(pi.mac), sender_sock, now);
|
|
|
|
|
|
|
|
// this is a PEER_INFO for one of the edges conencted to this supernode, forward,
|
|
|
|
// i.e. re-assemble (memcpy of udpbuf to encbuf could be sufficient as well)
|
|
|
|
|
|
|
|
// use incoming cmn (with already decreased TTL)
|
|
|
|
// PEER_INFO remains unchanged
|
|
|
|
|
|
|
|
encode_PEER_INFO(encbuf, &encx, &cmn, &pi);
|
|
|
|
|
|
|
|
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
|
|
|
packet_header_encrypt(encbuf, encx, encx,
|
|
|
|
comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
|
|
|
|
time_stamp());
|
|
|
|
}
|
|
|
|
|
|
|
|
sendto_peer(sss, peer, encbuf, encx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* Not a known message type */
|
|
|
|
traceEvent(TRACE_WARNING, "unable to handle packet type %d: ignored", (signed int)msg_type);
|
|
|
|
} /* switch(msg_type) */
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Long lived processing entry point. Split out from main to simply
|
|
|
|
* daemonisation on some platforms. */
|
|
|
|
int run_sn_loop (n2n_sn_t *sss, int *keep_running) {
|
|
|
|
|
|
|
|
uint8_t pktbuf[N2N_SN_PKTBUF_SIZE];
|
|
|
|
time_t last_purge_edges = 0;
|
|
|
|
time_t last_sort_communities = 0;
|
|
|
|
time_t last_re_reg_and_purge = 0;
|
|
|
|
|
|
|
|
sss->start_time = time(NULL);
|
|
|
|
|
|
|
|
while(*keep_running) {
|
|
|
|
int rc;
|
|
|
|
ssize_t bread;
|
|
|
|
int max_sock;
|
|
|
|
fd_set socket_mask;
|
|
|
|
n2n_tcp_connection_t *conn, *next, *tmp_conn;
|
|
|
|
|
|
|
|
SOCKET tmp_sock;
|
|
|
|
n2n_sock_str_t sockbuf;
|
|
|
|
struct timeval wait_time;
|
|
|
|
time_t before, now = 0;
|
|
|
|
|
|
|
|
FD_ZERO(&socket_mask);
|
|
|
|
|
|
|
|
FD_SET(sss->sock, &socket_mask);
|
|
|
|
#ifdef N2N_HAVE_TCP
|
|
|
|
FD_SET(sss->tcp_sock, &socket_mask);
|
|
|
|
#endif
|
|
|
|
FD_SET(sss->mgmt_sock, &socket_mask);
|
|
|
|
|
|
|
|
max_sock = MAX(MAX(sss->sock, sss->mgmt_sock), sss->tcp_sock);
|
|
|
|
|
|
|
|
#ifdef N2N_HAVE_TCP
|
|
|
|
// add the tcp connections' sockets
|
|
|
|
HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn) {
|
|
|
|
//socket descriptor
|
|
|
|
FD_SET(conn->socket_fd, &socket_mask);
|
|
|
|
if(conn->socket_fd > max_sock)
|
|
|
|
max_sock = conn->socket_fd;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
wait_time.tv_sec = 10;
|
|
|
|
wait_time.tv_usec = 0;
|
|
|
|
|
|
|
|
before = time(NULL);
|
|
|
|
|
|
|
|
rc = select(max_sock + 1, &socket_mask, NULL, NULL, &wait_time);
|
|
|
|
|
|
|
|
now = time(NULL);
|
|
|
|
|
|
|
|
if(rc > 0) {
|
|
|
|
|
|
|
|
// external udp
|
|
|
|
if(FD_ISSET(sss->sock, &socket_mask)) {
|
|
|
|
struct sockaddr_in sender_sock;
|
|
|
|
socklen_t i;
|
|
|
|
|
|
|
|
i = sizeof(sender_sock);
|
|
|
|
bread = recvfrom(sss->sock, pktbuf, N2N_SN_PKTBUF_SIZE, 0 /*flags*/,
|
|
|
|
(struct sockaddr *)&sender_sock, (socklen_t *)&i);
|
|
|
|
|
|
|
|
if((bread < 0)
|
|
|
|
#ifdef WIN32
|
|
|
|
&& (WSAGetLastError() != WSAECONNRESET)
|
|
|
|
#endif
|
|
|
|
) {
|
|
|
|
/* For UDP bread of zero just means no data (unlike TCP). */
|
|
|
|
/* The fd is no good now. Maybe we lost our interface. */
|
|
|
|
traceEvent(TRACE_ERROR, "recvfrom() failed %d errno %d (%s)", bread, errno, strerror(errno));
|
|
|
|
#ifdef WIN32
|
|
|
|
traceEvent(TRACE_ERROR, "WSAGetLastError(): %u", WSAGetLastError());
|
|
|
|
#endif
|
|
|
|
*keep_running = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a datagram to process...
|
|
|
|
if(bread > 0) {
|
|
|
|
// ...and the datagram has data (not just a header)
|
|
|
|
process_udp(sss, &sender_sock, sss->sock, pktbuf, bread, now);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef N2N_HAVE_TCP
|
|
|
|
// the so far known tcp connections
|
|
|
|
|
|
|
|
// beware: current conn and other items of the connection list may be found
|
|
|
|
// due for deletion while processing packets. Even OTHER connections, e.g. if
|
|
|
|
// forwarding to another edge node fails. connections due for deletion will
|
|
|
|
// not immediately be deleted but marked 'inactive' for later deletion
|
|
|
|
HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn) {
|
|
|
|
// do not process entries that have been marked inactive, those will be deleted
|
|
|
|
// immediately after this loop
|
|
|
|
if(conn->inactive)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(FD_ISSET(conn->socket_fd, &socket_mask)) {
|
|
|
|
|
|
|
|
struct sockaddr_in sender_sock;
|
|
|
|
socklen_t i;
|
|
|
|
|
|
|
|
i = sizeof(sender_sock);
|
|
|
|
bread = recvfrom(conn->socket_fd,
|
|
|
|
conn->buffer + conn->position, conn->expected - conn->position, 0 /*flags*/,
|
|
|
|
(struct sockaddr *)&sender_sock, (socklen_t *)&i);
|
|
|
|
|
|
|
|
if(bread <= 0) {
|
|
|
|
traceEvent(TRACE_INFO, "closing tcp connection to [%s]", sock_to_cstr(sockbuf, (n2n_sock_t*)&sender_sock));
|
|
|
|
traceEvent(TRACE_DEBUG, "recvfrom() returns %d and sees errno %d (%s)", bread, errno, strerror(errno));
|
|
|
|
#ifdef WIN32
|
|
|
|
traceEvent(TRACE_DEBUG, "WSAGetLastError(): %u", WSAGetLastError());
|
|
|
|
#endif
|
|
|
|
close_tcp_connection(sss, conn);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
conn->position += bread;
|
|
|
|
|
|
|
|
if(conn->position == conn->expected) {
|
|
|
|
if(conn->position == sizeof(uint16_t)) {
|
|
|
|
// the prepended length has been read, preparing for the packet
|
|
|
|
conn->expected += be16toh(*(uint16_t*)(conn->buffer));
|
|
|
|
if(conn->expected > N2N_SN_PKTBUF_SIZE) {
|
|
|
|
traceEvent(TRACE_INFO, "closing tcp connection to [%s]", sock_to_cstr(sockbuf, (n2n_sock_t*)&sender_sock));
|
|
|
|
traceEvent(TRACE_DEBUG, "too many bytes in tcp packet expected");
|
|
|
|
close_tcp_connection(sss, conn);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// full packet read, handle it
|
|
|
|
process_udp(sss, (struct sockaddr_in*)&(conn->sock), conn->socket_fd,
|
|
|
|
conn->buffer + sizeof(uint16_t), conn->position - sizeof(uint16_t), now);
|
|
|
|
|
|
|
|
// reset, await new prepended length
|
|
|
|
conn->expected = sizeof(uint16_t);
|
|
|
|
conn->position = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove inactive / already closed tcp connections from list
|
|
|
|
HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn) {
|
|
|
|
if(conn->inactive) {
|
|
|
|
HASH_DEL(sss->tcp_connections, conn);
|
|
|
|
free(conn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// accept new incoming tcp connection
|
|
|
|
if(FD_ISSET(sss->tcp_sock, &socket_mask)) {
|
|
|
|
struct sockaddr_in sender_sock;
|
|
|
|
socklen_t i;
|
|
|
|
|
|
|
|
i = sizeof(sender_sock);
|
|
|
|
if((HASH_COUNT(sss->tcp_connections) + 4) < FD_SETSIZE) {
|
|
|
|
tmp_sock = accept(sss->tcp_sock, (struct sockaddr *)&sender_sock, (socklen_t *)&i);
|
|
|
|
if(tmp_sock >= 0) {
|
|
|
|
conn = (n2n_tcp_connection_t*)malloc(sizeof(n2n_tcp_connection_t));
|
|
|
|
if(conn) {
|
|
|
|
conn->socket_fd = tmp_sock;
|
|
|
|
memcpy(&(conn->sock), &sender_sock, sizeof(struct sockaddr_in));
|
|
|
|
conn->inactive = 0;
|
|
|
|
conn->expected = sizeof(uint16_t);
|
|
|
|
conn->position = 0;
|
|
|
|
HASH_ADD_INT(sss->tcp_connections, socket_fd, conn);
|
|
|
|
traceEvent(TRACE_INFO, "accepted incoming TCP connection from [%s]",
|
|
|
|
sock_to_cstr(sockbuf, (n2n_sock_t*)&sender_sock));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// no space to store the socket for a new connection, close immediately
|
|
|
|
traceEvent(TRACE_DEBUG, "denied incoming TCP connection from [%s] due to max connections limit hit",
|
|
|
|
sock_to_cstr(sockbuf, (n2n_sock_t*)&sender_sock));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif /* N2N_HAVE_TCP */
|
|
|
|
|
|
|
|
// handle management port input
|
|
|
|
if(FD_ISSET(sss->mgmt_sock, &socket_mask)) {
|
|
|
|
struct sockaddr_in sender_sock;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
i = sizeof(sender_sock);
|
|
|
|
bread = recvfrom(sss->mgmt_sock, pktbuf, N2N_SN_PKTBUF_SIZE, 0 /*flags*/,
|
|
|
|
(struct sockaddr *)&sender_sock, (socklen_t *)&i);
|
|
|
|
|
|
|
|
if(bread <= 0) {
|
|
|
|
traceEvent(TRACE_ERROR, "recvfrom() failed %d errno %d (%s)", bread, errno, strerror(errno));
|
|
|
|
*keep_running = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we have a datagram to process
|
|
|
|
process_mgmt(sss, &sender_sock, pktbuf, bread, now);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
if(((now - before) < wait_time.tv_sec) && (*keep_running)){
|
|
|
|
// this is no real timeout, something went wrong with one of the tcp connections (probably)
|
|
|
|
// close them all, edges will re-open if they detect closure
|
|
|
|
traceEvent(TRACE_DEBUG, "falsly claimed timeout, assuming issue with tcp connection, closing them all");
|
|
|
|
HASH_ITER(hh, sss->tcp_connections, conn, tmp_conn)
|
|
|
|
close_tcp_connection(sss, conn);
|
|
|
|
} else
|
|
|
|
traceEvent(TRACE_DEBUG, "timeout");
|
|
|
|
}
|
|
|
|
|
|
|
|
re_register_and_purge_supernodes(sss, sss->federation, &last_re_reg_and_purge, now, 0 /* not forced */);
|
|
|
|
purge_expired_communities(sss, &last_purge_edges, now);
|
|
|
|
sort_communities(sss, &last_sort_communities, now);
|
|
|
|
resolve_check(sss->resolve_parameter, 0 /* presumably, no special resolution requirement */, now);
|
|
|
|
} /* while */
|
|
|
|
|
|
|
|
sn_term(sss);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|