/** * (C) 2007-20 - ntop.org and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not see see * */ #include "n2n.h" #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, const uint8_t * pktbuf, size_t pktsize); static ssize_t sendto_sock(n2n_sn_t *sss, const n2n_sock_t *sock, 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 int try_broadcast(n2n_sn_t * sss, const struct sn_community *comm, const n2n_common_t * cmn, const n2n_mac_t srcMac, const uint8_t * pktbuf, size_t pktsize); 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, time_t now); 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, uint8_t *udp_buf, size_t udp_size, time_t now); /* ************************************** */ static int try_forward(n2n_sn_t * sss, const struct sn_community *comm, const n2n_common_t * cmn, const n2n_mac_t dstMac, 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_sock(sss, &(scan->sock), 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)); } } else { traceEvent(TRACE_DEBUG, "try_forward unknown MAC"); /* Not a known MAC so drop. */ return(-2); } return(0); } /** Send a datagram to the destination embodied in a n2n_sock_t. * * @return -1 on error otherwise number of bytes sent */ static ssize_t sendto_sock(n2n_sn_t *sss, const n2n_sock_t *sock, const uint8_t *pktbuf, size_t pktsize) { n2n_sock_str_t sockbuf; if (AF_INET == sock->family) { struct sockaddr_in udpsock; udpsock.sin_family = AF_INET; udpsock.sin_port = htons(sock->port); memcpy(&(udpsock.sin_addr.s_addr), &(sock->addr.v4), IPV4_SIZE); traceEvent(TRACE_DEBUG, "sendto_sock %lu to [%s]", pktsize, sock_to_cstr(sockbuf, sock)); return sendto(sss->sock, pktbuf, pktsize, 0, (const struct sockaddr *)&udpsock, sizeof(struct sockaddr_in)); } 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, 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"); 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_sock(sss, &(scan->sock), 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; } /** Initialise the supernode structure */ int sn_init(n2n_sn_t *sss) { #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; n2n_srand (n2n_seed()); return 0; /* OK */ } /** 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; if (sss->sock >= 0) { closesocket(sss->sock); } sss->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) free (community->header_encryption_ctx); 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); } } /** 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; } /** 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, time_t now) { macstr_t mac_buf; n2n_sock_str_t sockbuf; struct peer_info *scan; 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 (NULL == scan) { /* Not known */ scan = (struct peer_info *) calloc(1, sizeof(struct peer_info)); /* deallocated in purge_expired_registrations */ 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(&(scan->sock), sender_sock, sizeof(n2n_sock_t)); scan->last_valid_time_stamp = initial_time_stamp(); HASH_ADD_PEER(comm->edges, scan); traceEvent(TRACE_INFO, "update_edge created %s ==> %s", macaddr_str(mac_buf, reg->edgeMac), sock_to_cstr(sockbuf, sender_sock)); } else { /* Known */ if (!sock_equal(sender_sock, &(scan->sock))) { memcpy(&(scan->sock), sender_sock, sizeof(n2n_sock_t)); traceEvent(TRACE_INFO, "update_edge updated %s ==> %s", macaddr_str(mac_buf, reg->edgeMac), sock_to_cstr(sockbuf, sender_sock)); } else { traceEvent(TRACE_DEBUG, "update_edge unchanged %s ==> %s", macaddr_str(mac_buf, reg->edgeMac), sock_to_cstr(sockbuf, sender_sock)); } } scan->last_seen = now; return 0; } 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_ip_subnet_t *ipaddr) { struct peer_info *peer, *tmpPeer; uint32_t 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; HASH_SORT(comm->edges, peer_tap_ip_sort); HASH_ITER(hh, comm->edges, peer, tmpPeer) { if ((peer->dev_addr.net_addr & bitlen2mask(peer->dev_addr.net_bitlen)) == net_id) { if (host_id >= max_host) { traceEvent(TRACE_WARNING, "No assignable IP to edge tap adapter."); return -1; } if (peer->dev_addr.net_addr == 0) { continue; } if ((peer->dev_addr.net_addr & max_host) == host_id) { ++host_id; } else { break; } } } ipaddr->net_addr = net_id | host_id; ipaddr->net_bitlen = comm->auto_ip_net.net_bitlen; traceEvent(TRACE_INFO, "Assign IP %s to tap adapter of edge.", ip_subnet_to_str(ip_bit_str, ipaddr)); return 0; } /** 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( (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(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, int from_supernode, n2n_mac_t mac, uint64_t stamp) { uint64_t * previous_stamp = NULL; if(!from_supernode) { 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) ); } static int purge_expired_communities(n2n_sn_t *sss, time_t* p_last_purge, time_t now) { struct sn_community *comm, *tmp; size_t num_reg = 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) { num_reg += purge_peer_list(&comm->edges, now - REGISTRATION_TIMEOUT); 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); HASH_DEL(sss->communities, comm); free(comm); } } (*p_last_purge) = now; traceEvent(TRACE_DEBUG, "Remove %ld edges", num_reg); 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 = 0; struct sn_community *community, *tmp; struct peer_info *peer, *tmpPeer; macstr_t mac_buf; n2n_sock_str_t sockbuf; dec_ip_bit_str_t ip_bit_str = {'\0'}; traceEvent(TRACE_DEBUG, "process_mgmt"); ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, " id tun_tap MAC edge last_seen\n"); ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, "---------------------------------------------------------------------------------\n"); HASH_ITER(hh, sss->communities, community, tmp) { num_edges += HASH_COUNT(community->edges); ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, "community: %s\n", community->community); sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); ressize = 0; num = 0; HASH_ITER(hh, community->edges, peer, tmpPeer) { ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, " %-4u %-18s %-17s %-21s %lu\n", ++num, ip_subnet_to_str(ip_bit_str, &peer->dev_addr), macaddr_str(mac_buf, peer->mac_addr), sock_to_cstr(sockbuf, &(peer->sock)), now - peer->last_seen); 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, 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; macstr_t mac_buf; macstr_t mac_buf2; n2n_sock_str_t sockbuf; char buf[32]; struct sn_community *comm, *tmp; uint64_t stamp; const n2n_mac_t null_mac = {0, 0, 0, 0, 0, 0}; /* 00:00:00:00:00:00 */ 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 unenrypted. 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 < 20) { traceEvent(TRACE_DEBUG, "process_udp dropped a packet too short to be valid."); return -1; } if ( (udp_buf[19] == (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, "process_udp 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, "process_udp locked community '%s' to using " "unencrypted headers.", comm->community); /* set 'no encryption' in case it is not set yet */ comm->header_encryption = HEADER_ENCRYPTION_NONE; comm->header_encryption_ctx = NULL; } } } else { /* most probably encrypted */ /* cycle through the known communities (as keys) to eventually decrypt */ uint32_t ret = 0; HASH_ITER (hh, sss->communities, comm, tmp) { /* skip the definitely unencrypted communities */ if (comm->header_encryption == HEADER_ENCRYPTION_NONE) continue; uint16_t checksum = 0; if ( (ret = packet_header_decrypt (udp_buf, udp_size, comm->community, comm->header_encryption_ctx, comm->header_iv_ctx, &stamp, &checksum)) ) { // 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 (checksum != pearson_hash_16 (udp_buf, udp_size)) { traceEvent(TRACE_DEBUG, "process_udp dropped packet due to checksum error."); return -1; } if (comm->header_encryption == HEADER_ENCRYPTION_UNKNOWN) { traceEvent (TRACE_INFO, "process_udp locked community '%s' to using " "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 (!ret) { // no matching key/community traceEvent(TRACE_DEBUG, "process_udp 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 */ from_supernode= cmn.flags & N2N_FLAGS_FROM_SUPERNODE; 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, "process_udp 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, from_supernode, pkt.srcMac, stamp)) { traceEvent(TRACE_DEBUG, "process_udp 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) packet_header_encrypt (rec_buf, oldEncx, comm->header_encryption_ctx, comm->header_iv_ctx, time_stamp (), pearson_hash_16 (rec_buf, encx)); } 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) packet_header_encrypt (rec_buf, idx, comm->header_encryption_ctx, comm->header_iv_ctx, time_stamp (), pearson_hash_16 (rec_buf, udp_size)); } /* Common section to forward the final product. */ if(unicast) try_forward(sss, comm, &cmn, pkt.dstMac, rec_buf, encx); else try_broadcast(sss, comm, &cmn, pkt.srcMac, 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, "process_udp 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, from_supernode, reg.srcMac, stamp)) { traceEvent(TRACE_DEBUG, "process_udp 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, comm->header_encryption_ctx, comm->header_iv_ctx, time_stamp (), pearson_hash_16 (rec_buf, encx)); try_forward(sss, comm, &cmn, reg.dstMac, 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_common_t cmn2; uint8_t ackbuf[N2N_SN_PKTBUF_SIZE]; size_t encx=0; struct sn_community_regular_expression *re, *tmp_re; int8_t allowed_match = -1; uint8_t match = 0; int match_length = 0; n2n_ip_subnet_t ipaddr; memset(&ack, 0, sizeof(n2n_REGISTER_SUPER_ACK_t)); /* Edge 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, from_supernode, reg.edgeMac, stamp)) { traceEvent(TRACE_DEBUG, "process_udp 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: unallowed community '%s'", (char*)cmn.community); return -1; } } if(!comm && (!sss->lock_communities || (match == 1))) { comm = calloc(1, sizeof(struct sn_community)); if(comm) { strncpy(comm->community, (char*)cmn.community, N2N_COMMUNITY_SIZE-1); comm->community[N2N_COMMUNITY_SIZE-1] = '\0'; /* new communities introduced by REGISTERs could not have had encrypted header... */ comm->header_encryption = HEADER_ENCRYPTION_NONE; comm->header_encryption_ctx = 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) { 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.edgeMac, reg.edgeMac, sizeof(n2n_mac_t)); 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 */)) { assign_one_ip_addr(comm, &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); ack.num_sn=0; /* No backup */ traceEvent(TRACE_DEBUG, "Rx REGISTER_SUPER for %s [%s]", macaddr_str(mac_buf, reg.edgeMac), sock_to_cstr(sockbuf, &(ack.sock))); if(memcmp(reg.edgeMac, &null_mac, N2N_MAC_SIZE) != 0){ update_edge(sss, ®, comm, &(ack.sock), now); } encode_REGISTER_SUPER_ACK(ackbuf, &encx, &cmn2, &ack); if (comm->header_encryption == HEADER_ENCRYPTION_ENABLED) packet_header_encrypt (ackbuf, encx, comm->header_encryption_ctx, comm->header_iv_ctx, time_stamp (), pearson_hash_16 (ackbuf, encx)); sendto(sss->sock, ackbuf, encx, 0, (struct sockaddr *)sender_sock, sizeof(struct sockaddr_in)); traceEvent(TRACE_DEBUG, "Tx REGISTER_SUPER_ACK for %s [%s]", macaddr_str(mac_buf, reg.edgeMac), sock_to_cstr(sockbuf, &(ack.sock))); } else { traceEvent(TRACE_INFO, "Discarded registration: unallowed community '%s'", (char*)cmn.community); return -1; } 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; if(!comm) { traceEvent(TRACE_DEBUG, "process_udp QUERY_PEER from unknown community %s", cmn.community); return -1; } decode_QUERY_PEER( &query, &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, from_supernode, query.srcMac, stamp)) { traceEvent(TRACE_DEBUG, "process_udp dropped QUERY_PEER due to time stamp error."); return -1; } } 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; 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.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, comm->header_encryption_ctx, comm->header_iv_ctx, time_stamp (), pearson_hash_16 (encbuf, encx)); sendto( sss->sock, encbuf, encx, 0, (struct sockaddr *)sender_sock, sizeof(struct sockaddr_in) ); traceEvent( TRACE_DEBUG, "Tx PEER_INFO to %s", macaddr_str( mac_buf, query.srcMac ) ); } else { traceEvent( TRACE_DEBUG, "Ignoring QUERY_PEER for unknown edge %s", macaddr_str( mac_buf, query.targetMac ) ); } 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; sss->start_time = time(NULL); while (*keep_running) { int rc; ssize_t bread; int max_sock; fd_set socket_mask; struct timeval wait_time; time_t now = 0; FD_ZERO(&socket_mask); max_sock = MAX(sss->sock, sss->mgmt_sock); FD_SET(sss->sock, &socket_mask); FD_SET(sss->mgmt_sock, &socket_mask); wait_time.tv_sec = 10; wait_time.tv_usec = 0; rc = select(max_sock + 1, &socket_mask, NULL, NULL, &wait_time); now = time(NULL); if (rc > 0) { 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, pktbuf, bread, now); } } 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 { traceEvent(TRACE_DEBUG, "timeout"); } purge_expired_communities(sss, &last_purge_edges, now); sort_communities (sss, &last_sort_communities, now); } /* while */ sn_term(sss); return 0; }