Add support for recvmmsg().
[tinc] / src / net_packet.c
index 4c06045..e716144 100644 (file)
@@ -454,6 +454,13 @@ bool receive_tcppacket_sptps(connection_t *c, const char *data, int len) {
                return true;
        }
 
+       if(!to->status.reachable) {
+               /* This can happen in the form of a race condition
+                  if the node just became unreachable. */
+               logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot relay TCP packet from %s (%s) because the destination, %s (%s), is unreachable", from->name, from->hostname, to->name, to->hostname);
+               return true;
+       }
+
        /* Help the sender reach us over UDP.
           Note that we only do this if we're the destination or the static relay;
           otherwise every hop would initiate its own UDP info message, resulting in elevated chatter. */
@@ -1190,15 +1197,13 @@ static void try_tx_sptps(node_t *n, bool mtu) {
 
        node_t *via = (n->via == myself) ? n->nexthop : n->via;
 
-       /* If the static relay doesn't support SPTPS, everything goes via TCP anyway. */
-
-       if((via->options >> 24) < 4)
-               return;
+       /* If we do have a static relay, try everything with that one instead, if it supports relaying. */
 
-       /* If we do have a static relay, try everything with that one instead. */
-
-       if(via != n)
-               return try_tx_sptps(via, mtu);
+       if(via != n) {
+               if((via->options >> 24) < 4)
+                       return;
+               return try_tx(via, mtu);
+       }
 
        /* Otherwise, try to establish UDP connectivity. */
 
@@ -1210,7 +1215,7 @@ static void try_tx_sptps(node_t *n, bool mtu) {
           while we try to establish direct connectivity. */
 
        if(!n->status.udp_confirmed && n != n->nexthop && (n->nexthop->options >> 24) >= 4)
-               try_tx_sptps(n->nexthop, mtu);
+               try_tx(n->nexthop, mtu);
 }
 
 static void try_tx_legacy(node_t *n, bool mtu) {
@@ -1235,6 +1240,8 @@ static void try_tx_legacy(node_t *n, bool mtu) {
 }
 
 void try_tx(node_t *n, bool mtu) {
+       if(!n->status.reachable)
+               return;
        if(n->status.sptps)
                try_tx_sptps(n, mtu);
        else
@@ -1245,8 +1252,12 @@ void send_packet(node_t *n, vpn_packet_t *packet) {
        // If it's for myself, write it to the tun/tap device.
 
        if(n == myself) {
-               if(overwrite_mac)
+               if(overwrite_mac) {
                         memcpy(DATA(packet), mymac.x, ETH_ALEN);
+                        // Use an arbitrary fake source address.
+                        memcpy(DATA(packet) + ETH_ALEN, DATA(packet), ETH_ALEN);
+                        DATA(packet)[ETH_ALEN * 2 - 1] ^= 0xFF;
+               }
                n->out_packets++;
                n->out_bytes += packet->len;
                devops.write(packet);
@@ -1271,7 +1282,7 @@ void send_packet(node_t *n, vpn_packet_t *packet) {
 
        if(n->status.sptps) {
                send_sptps_packet(n, packet);
-               try_tx_sptps(n, true);
+               try_tx(n, true);
                return;
        }
 
@@ -1291,7 +1302,7 @@ void send_packet(node_t *n, vpn_packet_t *packet) {
        }
 
        send_udppacket(via, packet);
-       try_tx_legacy(via, true);
+       try_tx(via, true);
 }
 
 void broadcast_packet(const node_t *from, vpn_packet_t *packet) {
@@ -1382,42 +1393,27 @@ static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) {
        return match;
 }
 
-void handle_incoming_vpn_data(void *data, int flags) {
-       listen_socket_t *ls = data;
-       vpn_packet_t pkt;
+static void handle_incoming_vpn_packet(listen_socket_t *ls, vpn_packet_t *pkt, sockaddr_t *addr) {
        char *hostname;
        node_id_t nullid = {};
-       sockaddr_t addr = {};
-       socklen_t addrlen = sizeof addr;
        node_t *from, *to;
        bool direct = false;
 
-       pkt.offset = 0;
-       int len = recvfrom(ls->udp.fd, DATA(&pkt), MAXSIZE, 0, &addr.sa, &addrlen);
-
-       if(len <= 0 || len > MAXSIZE) {
-               if(!sockwouldblock(sockerrno))
-                       logger(DEBUG_ALWAYS, LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno));
-               return;
-       }
-
-       pkt.len = len;
-
-       sockaddrunmap(&addr); /* Some braindead IPv6 implementations do stupid things. */
+       sockaddrunmap(addr); /* Some braindead IPv6 implementations do stupid things. */
 
        // Try to figure out who sent this packet.
 
-       node_t *n = lookup_node_udp(&addr);
+       node_t *n = lookup_node_udp(addr);
 
        if(n && !n->status.udp_confirmed)
                n = NULL; // Don't believe it if we don't have confirmation yet.
 
        if(!n) {
                // It might be from a 1.1 node, which might have a source ID in the packet.
-               pkt.offset = 2 * sizeof(node_id_t);
-               from = lookup_node_id(SRCID(&pkt));
-               if(from && !memcmp(DSTID(&pkt), &nullid, sizeof nullid) && from->status.sptps) {
-                       if(sptps_verify_datagram(&from->sptps, DATA(&pkt), pkt.len - 2 * sizeof(node_id_t)))
+               pkt->offset = 2 * sizeof(node_id_t);
+               from = lookup_node_id(SRCID(pkt));
+               if(from && !memcmp(DSTID(pkt), &nullid, sizeof nullid) && from->status.sptps) {
+                       if(sptps_verify_datagram(&from->sptps, DATA(pkt), pkt->len - 2 * sizeof(node_id_t)))
                                n = from;
                        else
                                goto skip_harder;
@@ -1425,42 +1421,49 @@ void handle_incoming_vpn_data(void *data, int flags) {
        }
 
        if(!n) {
-               pkt.offset = 0;
-               n = try_harder(&addr, &pkt);
+               pkt->offset = 0;
+               n = try_harder(addr, pkt);
        }
 
 skip_harder:
        if(!n) {
                if(debug_level >= DEBUG_PROTOCOL) {
-                       hostname = sockaddr2hostname(&addr);
+                       hostname = sockaddr2hostname(addr);
                        logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet from unknown source %s", hostname);
                        free(hostname);
                }
                return;
        }
 
-       pkt.offset = 0;
+       pkt->offset = 0;
 
        if(n->status.sptps) {
                bool relay_enabled = (n->options >> 24) >= 4;
                if (relay_enabled) {
-                       pkt.offset = 2 * sizeof(node_id_t);
-                       pkt.len -= pkt.offset;
+                       pkt->offset = 2 * sizeof(node_id_t);
+                       pkt->len -= pkt->offset;
                }
 
-               if(!memcmp(DSTID(&pkt), &nullid, sizeof nullid) || !relay_enabled) {
+               if(!memcmp(DSTID(pkt), &nullid, sizeof nullid) || !relay_enabled) {
                        direct = true;
                        from = n;
                        to = myself;
                } else {
-                       from = lookup_node_id(SRCID(&pkt));
-                       to = lookup_node_id(DSTID(&pkt));
+                       from = lookup_node_id(SRCID(pkt));
+                       to = lookup_node_id(DSTID(pkt));
                }
                if(!from || !to) {
                        logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet from %s (%s) with unknown source and/or destination ID", n->name, n->hostname);
                        return;
                }
 
+               if(!to->status.reachable) {
+                       /* This can happen in the form of a race condition
+                          if the node just became unreachable. */
+                       logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot relay packet from %s (%s) because the destination, %s (%s), is unreachable", from->name, from->hostname, to->name, to->hostname);
+                       return;
+               }
+
                /* The packet is supposed to come from the originator or its static relay
                   (i.e. with no dynamic relays in between).
                   If it did not, "help" the static relay by sending it UDP info.
@@ -1473,8 +1476,8 @@ skip_harder:
                /* If we're not the final recipient, relay the packet. */
 
                if(to != myself) {
-                       send_sptps_data(to, from, 0, DATA(&pkt), pkt.len);
-                       try_tx_sptps(to, true);
+                       send_sptps_data(to, from, 0, DATA(pkt), pkt->len);
+                       try_tx(to, true);
                        return;
                }
        } else {
@@ -1482,12 +1485,12 @@ skip_harder:
                from = n;
        }
 
-       if(!receive_udppacket(from, &pkt))
+       if(!receive_udppacket(from, pkt))
                return;
 
        n->sock = ls - listen_socket;
-       if(direct && sockaddrcmp(&addr, &n->address))
-               update_node_udp(n, &addr);
+       if(direct && sockaddrcmp(addr, &n->address))
+               update_node_udp(n, addr);
 
        /* If the packet went through a relay, help the sender find the appropriate MTU
           through the relay path. */
@@ -1496,6 +1499,66 @@ skip_harder:
                send_mtu_info(myself, n, MTU);
 }
 
+void handle_incoming_vpn_data(void *data, int flags) {
+       listen_socket_t *ls = data;
+
+#ifdef HAVE_RECVMMSG
+#define MAX_MSG 64
+       vpn_packet_t pkt[MAX_MSG];
+       sockaddr_t addr[MAX_MSG];
+       struct mmsghdr msg[MAX_MSG];
+       struct iovec iov[MAX_MSG];
+
+       for(int i = 0; i < MAX_MSG; i++) {
+               pkt[i].offset = 0;
+
+               iov[i] = (struct iovec){
+                       .iov_base = DATA(&pkt[i]),
+                       .iov_len = MAXSIZE,
+               };
+
+               msg[i].msg_hdr = (struct msghdr){
+                       .msg_name = &addr[i].sa,
+                       .msg_namelen = sizeof addr[i],
+                       .msg_iov = &iov[i],
+                       .msg_iovlen = 1,
+               };
+       }
+
+       int num = recvmmsg(ls->udp.fd, msg, MAX_MSG, MSG_DONTWAIT, NULL);
+
+       if(num < 0) {
+               if(!sockwouldblock(sockerrno))
+                       logger(DEBUG_ALWAYS, LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno));
+               return;
+       }
+
+       for(int i = 0; i < num; i++) {
+               pkt[i].len = msg[i].msg_len;
+               if(pkt[i].len <= 0 || pkt[i].len > MAXSIZE)
+                       continue;
+               handle_incoming_vpn_packet(ls, &pkt[i], &addr[i]);
+       }
+#else
+       vpn_packet_t pkt;
+       sockaddr_t addr = {};
+       socklen_t addrlen = sizeof addr;
+
+       pkt.offset = 0;
+       int len = recvfrom(ls->udp.fd, DATA(&pkt), MAXSIZE, 0, &addr.sa, &addrlen);
+
+       if(len <= 0 || len > MAXSIZE) {
+               if(!sockwouldblock(sockerrno))
+                       logger(DEBUG_ALWAYS, LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno));
+               return;
+       }
+
+       pkt.len = len;
+
+       handle_incoming_vpn_packet(ls, &pkt, &addr);
+#endif
+}
+
 void handle_device_data(void *data, int flags) {
        vpn_packet_t packet;
        packet.offset = DEFAULT_PACKET_OFFSET;