diff --git a/include/n2n_typedefs.h b/include/n2n_typedefs.h index 2e7162a..1bb8c6a 100644 --- a/include/n2n_typedefs.h +++ b/include/n2n_typedefs.h @@ -671,18 +671,28 @@ typedef struct sn_stats { time_t last_reg_super; /* Time when last REGISTER_SUPER was received. */ } sn_stats_t; -struct sn_community { - char community[N2N_COMMUNITY_SIZE]; - uint8_t is_federation; /* if not-zero, then the current community is the federation of supernodes */ - uint8_t purgeable; /* indicates purgeable community (fixed-name, predetermined (-c parameter) communties usually are unpurgeable) */ - uint8_t header_encryption; /* Header encryption indicator. */ - he_context_t *header_encryption_ctx; /* Header encryption cipher context. */ - he_context_t *header_iv_ctx; /* Header IV ecnryption cipher context, REMOVE as soon as seperate fields for checksum and replay protection available */ - struct peer_info *edges; /* Link list of registered edges. */ - int64_t number_enc_packets; /* Number of encrypted packets handled so far, required for sorting from time to time */ - n2n_ip_subnet_t auto_ip_net; /* Address range of auto ip address service. */ +typedef struct node_supernode_association { + + n2n_mac_t mac; /* mac address of an edge */ + const struct sockaddr_in sock; /* network order socket of that edge's supernode */ + time_t last_seen; /* time mark to keep track of purging requirements */ UT_hash_handle hh; /* makes this structure hashable */ +} node_supernode_association_t; + +struct sn_community { + char community[N2N_COMMUNITY_SIZE]; + uint8_t is_federation; /* if not-zero, then the current community is the federation of supernodes */ + uint8_t purgeable; /* indicates purgeable community (fixed-name, predetermined (-c parameter) communties usually are unpurgeable) */ + uint8_t header_encryption; /* Header encryption indicator. */ + he_context_t *header_encryption_ctx; /* Header encryption cipher context. */ + he_context_t *header_iv_ctx; /* Header IV ecnryption cipher context, REMOVE as soon as seperate fields for checksum and replay protection available */ + struct peer_info *edges; /* Link list of registered edges. */ + node_supernode_association_t *assoc; /* list of other edges from this community and their supernodes */ + int64_t number_enc_packets; /* Number of encrypted packets handled so far, required for sorting from time to time */ + n2n_ip_subnet_t auto_ip_net; /* Address range of auto ip address service. */ + + UT_hash_handle hh; /* makes this structure hashable */ }; /* Typedef'd pointer to get abstract datatype. */ diff --git a/src/sn_utils.c b/src/sn_utils.c index adfe9fa..0777ab8 100644 --- a/src/sn_utils.c +++ b/src/sn_utils.c @@ -20,13 +20,6 @@ #define HASH_FIND_COMMUNITY(head, name, out) HASH_FIND_STR(head, name, out) -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); static ssize_t sendto_peer (n2n_sn_t *sss, const struct peer_info *peer, @@ -38,14 +31,6 @@ static int sendto_mgmt (n2n_sn_t *sss, const uint8_t *mgmt_buf, size_t mgmt_size); -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); - static uint16_t reg_lifetime (n2n_sn_t *sss); static int update_edge (n2n_sn_t *sss, @@ -81,54 +66,6 @@ static int process_udp (n2n_sn_t *sss, /* ************************************** */ -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; - 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) { - /* Forwarding packet to all federated supernodes. */ - traceEvent(TRACE_DEBUG, "Unknown MAC. Broadcasting packet to all federated supernodes."); - try_broadcast(sss, NULL, cmn, sss->mac_addr, from_supernode, pktbuf, pktsize); - } else { - traceEvent(TRACE_DEBUG, "try_forward unknown MAC. Dropping the packet."); - /* Not a known MAC so drop. */ - return -2; - } - } - - return 0; -} - static void close_tcp_connection(n2n_sn_t *sss, n2n_tcp_connection_t *conn) { @@ -299,9 +236,9 @@ static int try_broadcast (n2n_sn_t * sss, 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 and all supernodes */ + * 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) { @@ -356,6 +293,65 @@ static int try_broadcast (n2n_sn_t * sss, } +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, "try_forward 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, "try_forward sees 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, "try_forward sees 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) { @@ -435,6 +431,7 @@ 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; if(sss->sock >= 0) { closesocket(sss->sock); @@ -465,6 +462,11 @@ void sn_term (n2n_sn_t *sss) { if(NULL != community->header_encryption_ctx) { free(community->header_encryption_ctx); } + // 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); } @@ -484,6 +486,29 @@ void sn_term (n2n_sn_t *sss) { #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. * @@ -903,8 +928,10 @@ static int purge_expired_communities (n2n_sn_t *sss, time_t* p_last_purge, time_t now) { - struct sn_community *comm, *tmp; + 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; @@ -912,25 +939,42 @@ static int purge_expired_communities (n2n_sn_t *sss, traceEvent(TRACE_DEBUG, "Purging old communities and edges"); - HASH_ITER(hh, sss->communities, comm, tmp) { + 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) { /* this should not happen as 'purgeable' and thus only communities w/o encrypted header here */ free(comm->header_encryption_ctx); } + // 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, "Remove %ld edges", num_reg); + traceEvent(TRACE_DEBUG, "purge_expired_communities removed %ld locally registered edges and %ld remotely associated edges", + num_reg, num_assoc); return 0; } @@ -1405,6 +1449,7 @@ static int process_udp (n2n_sn_t * sss, struct sn_community *fed; struct sn_community_regular_expression *re, *tmp_re; struct peer_info *peer, *tmp_peer, *p; + node_supernode_association_t *assoc; int8_t allowed_match = -1; uint8_t match = 0; int match_length = 0; @@ -1565,7 +1610,6 @@ static int process_udp (n2n_sn_t * sss, 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; @@ -1602,8 +1646,10 @@ static int process_udp (n2n_sn_t * sss, 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 we can delete it here if present (can happen) + // 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)) { @@ -1986,6 +2032,9 @@ static int process_udp (n2n_sn_t * sss, 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)