Clamp MSS of IPv4 SYN packets.
[tinc] / src / route.c
index 5c69671..8297731 100644 (file)
@@ -48,6 +48,7 @@ static const size_t ip6_size = sizeof(struct ip6_hdr);
 static const size_t icmp6_size = sizeof(struct icmp6_hdr);
 static const size_t ns_size = sizeof(struct nd_neighbor_solicit);
 static const size_t opt_size = sizeof(struct nd_opt_hdr);
+#define max(a, b) ((a) > (b) ? (a) : (b))
 
 /* RFC 1071 */
 
@@ -92,6 +93,53 @@ static bool checklength(node_t *source, vpn_packet_t *packet, length_t length) {
                return true;
 }
 
+void clamp_mss(node_t *source, node_t *via, vpn_packet_t *packet) {
+       int i;
+       int len = ((packet->data[46] >> 4) - 5) * 4;
+
+       for(int i = 0; i < len;) {
+               if(packet->data[54 + i] == 0)
+                       break;
+
+               if(packet->data[54 + i] == 1) {
+                       i++;
+                       continue;
+               }
+
+               if(i > len - 2 || i > len - packet->data[55 + i])
+                       break;
+
+               if(packet->data[54 + i] != 2) {
+                       if(packet->data[55 + i] < 2)
+                               break;
+                       i += packet->data[55 + i];
+                       continue;
+               }
+
+               if(packet->data[55] != 4)
+                       break;
+
+               uint16_t oldmss = packet->data[56 + i] << 8 | packet->data[57 + i];
+               uint16_t newmss = via->mtu - 54;
+               uint16_t csum = packet->data[50] << 8 | packet->data[51];
+
+               if(oldmss <= newmss)
+                       break;
+               
+               ifdebug(TRAFFIC) logger(LOG_INFO, "Clamping MSS of packet from %s to %s to %d", source->name, via->name, newmss);
+
+               packet->data[56 + i] = newmss >> 8;
+               packet->data[57 + i] = newmss & 0xff;
+               csum ^= 0xffff;
+               csum -= oldmss;
+               csum += newmss;
+               csum ^= 0xffff;
+               packet->data[50] = csum >> 8;
+               packet->data[51] = csum & 0xff;
+               break;
+       }
+}
+
 static void swap_mac_addresses(vpn_packet_t *packet) {
        mac_t tmp;
        memcpy(&tmp, &packet->data[0], sizeof tmp);
@@ -104,7 +152,7 @@ static void learn_mac(mac_t *address) {
        avl_node_t *node;
        connection_t *c;
 
-       subnet = lookup_subnet_mac(address);
+       subnet = lookup_subnet_mac(myself, address);
 
        /* If we don't know this MAC address yet, store it */
 
@@ -315,10 +363,10 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) {
 
        via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via;
        
-       if(via && packet->len > via->mtu && via != myself) {
+       if(via && packet->len > max(via->mtu, 590) && via != myself) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
                if(packet->data[20] & 0x40) {
-                       packet->len = via->mtu;
+                       packet->len = max(via->mtu, 590);
                        route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED);
                } else {
                        fragment_ipv4_packet(via, packet);
@@ -327,6 +375,9 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) {
                return;
        }
 
+       if(via && via != myself && packet->data[23] == 6 && packet->len >= 58 && (packet->data[46] >> 4) > 5)
+               clamp_mss(source, via, packet);
        send_packet(subnet->owner, packet);
 }
 
@@ -458,9 +509,9 @@ static void route_ipv6_unicast(node_t *source, vpn_packet_t *packet) {
 
        via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via;
        
-       if(via && packet->len > via->mtu && via != myself) {
+       if(via && packet->len > max(via->mtu, 1294) && via != myself) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
-               packet->len = via->mtu;
+               packet->len = max(via->mtu, 1294);
                route_ipv6_unreachable(source, packet, ICMP6_PACKET_TOO_BIG, 0);
                return;
        }
@@ -705,7 +756,7 @@ static void route_mac(node_t *source, vpn_packet_t *packet) {
        /* Lookup destination address */
 
        memcpy(&dest, &packet->data[0], sizeof dest);
-       subnet = lookup_subnet_mac(&dest);
+       subnet = lookup_subnet_mac(NULL, &dest);
 
        if(!subnet) {
                broadcast_packet(source, packet);
@@ -724,7 +775,7 @@ static void route_mac(node_t *source, vpn_packet_t *packet) {
        if(via && packet->len > via->mtu && via != myself) {
                ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu);
                uint16_t type = packet->data[12] << 8 | packet->data[13];
-               if(type == ETH_P_IP) {
+               if(type == ETH_P_IP && packet->len > 590) {
                        if(packet->data[20] & 0x40) {
                                packet->len = via->mtu;
                                route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED);
@@ -732,13 +783,16 @@ static void route_mac(node_t *source, vpn_packet_t *packet) {
                                fragment_ipv4_packet(via, packet);
                        }
                        return;
-               } else if(type == ETH_P_IPV6) {
+               } else if(type == ETH_P_IPV6 && packet->len > 1294) {
                        packet->len = via->mtu;
                        route_ipv6_unreachable(source, packet, ICMP6_PACKET_TOO_BIG, 0);
                        return;
                }
        }
 
+       if(via && via != myself && (packet->data[12] << 8 | packet->data[13]) == ETH_P_IP && packet->len >= 58 && packet->data[23] == 6 && (packet->data[46] >> 4) > 5)
+               clamp_mss(source, via, packet);
        send_packet(subnet->owner, packet);
 }