Fix source IP address for ICMP unreachable packets generated by tinc.
[oweals/tinc.git] / src / route.c
index 13f81572c66d504809d3e15282341225823c944f..f046e6e4abb161871511eef014a1fc7a818c3521 100644 (file)
@@ -1,7 +1,7 @@
 /*
     route.c -- routing
     Copyright (C) 2000-2005 Ivo Timmermans,
-                  2000-2012 Guus Sliepen <guus@tinc-vpn.org>
+                  2000-2014 Guus Sliepen <guus@tinc-vpn.org>
 
     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
@@ -197,7 +197,7 @@ static void learn_mac(mac_t *address) {
        /* If we don't know this MAC address yet, store it */
 
        if(!subnet) {
-               ifdebug(TRAFFIC) logger(LOG_INFO, "Learned new MAC address %hx:%hx:%hx:%hx:%hx:%hx",
+               ifdebug(TRAFFIC) logger(LOG_INFO, "Learned new MAC address %x:%x:%x:%x:%x:%x",
                                   address->x[0], address->x[1], address->x[2], address->x[3],
                                   address->x[4], address->x[5]);
 
@@ -258,6 +258,7 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, length_
        struct in_addr ip_src;
        struct in_addr ip_dst;
        uint32_t oldlen;
+       int sockfd;
 
        if(ratelimit(3))
                return;
@@ -275,6 +276,25 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, length_
        ip_src = ip.ip_src;
        ip_dst = ip.ip_dst;
 
+       /* Try to reply with an IP address assigned to the local machine */
+
+       sockfd = socket(AF_INET, SOCK_DGRAM, 0);
+       if (sockfd != -1) {
+               struct sockaddr_in addr;
+               memset(&addr, 0, sizeof(addr));
+               addr.sin_family = AF_INET;
+               addr.sin_addr = ip.ip_src;
+               if (!connect(sockfd, (const struct sockaddr*) &addr, sizeof(addr))) {
+                       memset(&addr, 0, sizeof(addr));
+                       addr.sin_family = AF_INET;
+                       socklen_t addrlen = sizeof(addr);
+                       if (!getsockname(sockfd, (struct sockaddr*) &addr, &addrlen) && addrlen <= sizeof(addr)) {
+                               ip_dst = addr.sin_addr;
+                       }
+               }
+               close(sockfd);
+       }
+
        oldlen = packet->len - ether_size;
 
        if(type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
@@ -340,7 +360,7 @@ static void fragment_ipv4_packet(node_t *dest, vpn_packet_t *packet, length_t et
        todo = ntohs(ip.ip_len) - ip_size;
 
        if(ether_size + ip_size + todo != packet->len) {
-               ifdebug(TRAFFIC) logger(LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%zd)", packet->len, ether_size + ip_size + todo);
+               ifdebug(TRAFFIC) logger(LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%d)", packet->len, (int)(ether_size + ip_size + todo));
                return;
        }
 
@@ -453,6 +473,7 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_
        struct ip6_hdr ip6;
        struct icmp6_hdr icmp6 = {0};
        uint16_t checksum;      
+       int sockfd;
 
        struct {
                struct in6_addr ip6_src;        /* source address */
@@ -477,6 +498,25 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_
        pseudo.ip6_src = ip6.ip6_dst;
        pseudo.ip6_dst = ip6.ip6_src;
 
+       /* Try to reply with an IP address assigned to the local machine */
+
+       sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
+       if (sockfd != -1) {
+               struct sockaddr_in6 addr;
+               memset(&addr, 0, sizeof(addr));
+               addr.sin6_family = AF_INET6;
+               addr.sin6_addr = ip6.ip6_src;
+               if (!connect(sockfd, (const struct sockaddr*) &addr, sizeof(addr))) {
+                       memset(&addr, 0, sizeof(addr));
+                       addr.sin6_family = AF_INET6;
+                       socklen_t addrlen = sizeof(addr);
+                       if (!getsockname(sockfd, (struct sockaddr*) &addr, &addrlen) && addrlen <= sizeof(addr)) {
+                               pseudo.ip6_src = addr.sin6_addr;
+                       }
+               }
+               close(sockfd);
+       }
+
        pseudo.length = packet->len - ether_size;
 
        if(type == ICMP6_PACKET_TOO_BIG)
@@ -836,6 +876,11 @@ static void route_mac(node_t *source, vpn_packet_t *packet) {
        if(forwarding_mode == FMODE_OFF && source != myself && subnet->owner != myself)
                return;
 
+       uint16_t type = packet->data[12] << 8 | packet->data[13];
+
+       if(priorityinheritance && type == ETH_P_IP && packet->len >= ether_size + ip_size)
+               packet->priority = packet->data[15];
+
        // Handle packets larger than PMTU
 
        node_t *via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via;
@@ -845,7 +890,6 @@ 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];
                length_t ethlen = 14;
 
                if(type == ETH_P_8021Q) {
@@ -887,7 +931,7 @@ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) {
                        if(!checklength(source, packet, ethlen + ip_size))
                                return false;
 
-                       if(packet->data[ethlen + 8] < 1) {
+                       if(packet->data[ethlen + 8] <= 1) {
                                if(packet->data[ethlen + 11] != IPPROTO_ICMP || packet->data[ethlen + 32] != ICMP_TIME_EXCEEDED)
                                        route_ipv4_unreachable(source, packet, ethlen, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL);
                                return false;
@@ -910,7 +954,7 @@ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) {
                        if(!checklength(source, packet, ethlen + ip6_size))
                                return false;
 
-                       if(packet->data[ethlen + 7] < 1) {
+                       if(packet->data[ethlen + 7] <= 1) {
                                if(packet->data[ethlen + 6] != IPPROTO_ICMPV6 || packet->data[ethlen + 40] != ICMP6_TIME_EXCEEDED)
                                        route_ipv6_unreachable(source, packet, ethlen, ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT);
                                return false;