Support ToS/DiffServ for IPv6 meta and UDP connections.
[tinc] / src / net_packet.c
index e169e2c..f8c6ac9 100644 (file)
@@ -1,7 +1,7 @@
 /*
     net_packet.c -- Handles in- and outgoing VPN packets
     Copyright (C) 1998-2005 Ivo Timmermans,
-                  2000-2014 Guus Sliepen <guus@tinc-vpn.org>
+                  2000-2016 Guus Sliepen <guus@tinc-vpn.org>
                   2010      Timothy Redaelli <timothy@redaelli.eu>
                   2010      Brandon Black <blblack@gmail.com>
 
@@ -282,9 +282,8 @@ static bool receive_udppacket(node_t *n, vpn_packet_t *inpkt) {
                        }
                        return false;
                }
-               inpkt->offset += 2 * sizeof(node_id_t);
                n->status.udppacket = true;
-               bool result = sptps_receive_data(&n->sptps, DATA(inpkt), inpkt->len - 2 * sizeof(node_id_t));
+               bool result = sptps_receive_data(&n->sptps, DATA(inpkt), inpkt->len);
                n->status.udppacket = false;
 
                if(!result) {
@@ -455,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. */
@@ -615,10 +621,7 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) {
        vpn_packet_t *outpkt;
        int origlen = origpkt->len;
        size_t outlen;
-#if defined(SOL_IP) && defined(IP_TOS)
-       static int priority = 0;
        int origpriority = origpkt->priority;
-#endif
 
        pkt1.offset = DEFAULT_PACKET_OFFSET;
        pkt2.offset = DEFAULT_PACKET_OFFSET;
@@ -713,15 +716,27 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) {
        if(!sa)
                choose_udp_address(n, &sa, &sock);
 
+       if(priorityinheritance && origpriority != listen_socket[sock].priority) {
+               listen_socket[sock].priority = origpriority;
+               switch(sa->sa.sa_family) {
 #if defined(SOL_IP) && defined(IP_TOS)
-       if(priorityinheritance && origpriority != priority
-          && listen_socket[n->sock].sa.sa.sa_family == AF_INET) {
-               priority = origpriority;
-               logger(DEBUG_TRAFFIC, LOG_DEBUG, "Setting outgoing packet priority to %d", priority);
-               if(setsockopt(listen_socket[n->sock].udp.fd, SOL_IP, IP_TOS, &priority, sizeof(priority))) /* SO_PRIORITY doesn't seem to work */
-                       logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", sockstrerror(sockerrno));
-       }
+               case AF_INET:
+                       logger(DEBUG_TRAFFIC, LOG_DEBUG, "Setting IPv4 outgoing packet priority to %d", origpriority);
+                       if(setsockopt(listen_socket[sock].udp.fd, SOL_IP, IP_TOS, &origpriority, sizeof origpriority)) /* SO_PRIORITY doesn't seem to work */
+                               logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", sockstrerror(sockerrno));
+                       break;
 #endif
+#if defined(IPPROTO_IPV6) & defined(IPV6_TCLASS)
+               case AF_INET6:
+                       logger(DEBUG_TRAFFIC, LOG_DEBUG, "Setting IPv6 outgoing packet priority to %d", origpriority);
+                       if(setsockopt(listen_socket[sock].udp.fd, IPPROTO_IPV6, IPV6_TCLASS, &origpriority, sizeof origpriority)) /* SO_PRIORITY doesn't seem to work */
+                               logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", sockstrerror(sockerrno));
+                       break;
+#endif
+               default:
+                       break;
+               }
+       }
 
        if(sendto(listen_socket[sock].udp.fd, SEQNO(inpkt), inpkt->len, 0, &sa->sa, SALEN(sa->sa)) < 0 && !sockwouldblock(sockerrno)) {
                if(sockmsgsize(sockerrno)) {
@@ -1191,15 +1206,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 we do have a static relay, try everything with that one instead, if it supports relaying. */
 
-       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. */
 
@@ -1211,7 +1224,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) {
@@ -1236,6 +1249,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
@@ -1246,8 +1261,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);
@@ -1272,7 +1291,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;
        }
 
@@ -1292,7 +1311,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) {
@@ -1383,42 +1402,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;
@@ -1426,36 +1430,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;
+
        if(n->status.sptps) {
-               pkt.offset = 2 * sizeof(node_id_t);
+               bool relay_enabled = (n->options >> 24) >= 4;
+               if (relay_enabled) {
+                       pkt->offset = 2 * sizeof(node_id_t);
+                       pkt->len -= pkt->offset;
+               }
 
-               if(!memcmp(DSTID(&pkt), &nullid, sizeof nullid)) {
+               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.
@@ -1468,8 +1485,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 - 2 * sizeof(node_id_t));
-                       try_tx_sptps(to, true);
+                       send_sptps_data(to, from, 0, DATA(pkt), pkt->len);
+                       try_tx(to, true);
                        return;
                }
        } else {
@@ -1477,13 +1494,12 @@ skip_harder:
                from = n;
        }
 
-       pkt.offset = 0;
-       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. */
@@ -1492,6 +1508,67 @@ 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
+       static int num = MAX_MSG;
+       static vpn_packet_t pkt[MAX_MSG];
+       static sockaddr_t addr[MAX_MSG];
+       static struct mmsghdr msg[MAX_MSG];
+       static struct iovec iov[MAX_MSG];
+
+       for(int i = 0; i < num; 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,
+               };
+       }
+
+       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;