Fix source IP address for ICMP unreachable packets generated by tinc.
[tinc] / src / route.c
index 70c5806..3d7b1df 100644 (file)
@@ -259,6 +259,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;
@@ -276,6 +277,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)
@@ -448,7 +468,8 @@ static void route_ipv4(node_t *source, vpn_packet_t *packet) {
 static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, length_t ether_size, uint8_t type, uint8_t code) {
        struct ip6_hdr ip6;
        struct icmp6_hdr icmp6 = {0};
-       uint16_t checksum;
+       uint16_t checksum;      
+       int sockfd;
 
        struct {
                struct in6_addr ip6_src;        /* source address */
@@ -473,6 +494,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)
@@ -784,15 +824,13 @@ static void route_arp(node_t *source, vpn_packet_t *packet) {
        if(subnet->owner == myself)
                return;                                          /* silently ignore */
 
-       memcpy(DATA(packet), DATA(packet) + ETH_ALEN, ETH_ALEN); /* copy destination address */
-       DATA(packet)[ETH_ALEN * 2 - 1] ^= 0xFF;                  /* mangle source address so it looks like it's not from us */
-
        memcpy(&addr, arp.arp_tpa, sizeof addr);                 /* save protocol addr */
        memcpy(arp.arp_tpa, arp.arp_spa, sizeof addr);           /* swap destination and source protocol address */
        memcpy(arp.arp_spa, &addr, sizeof addr);                 /* ... */
 
        memcpy(arp.arp_tha, arp.arp_sha, ETH_ALEN);              /* set target hard/proto addr */
-       memcpy(arp.arp_sha, DATA(packet) + ETH_ALEN, ETH_ALEN);  /* add fake source hard addr */
+       memcpy(arp.arp_sha, DATA(packet) + ETH_ALEN, ETH_ALEN);  /* set source hard/proto addr */
+       arp.arp_sha[ETH_ALEN - 1] ^= 0xFF;                       /* for consistency with route_packet() */
        arp.arp_op = htons(ARPOP_REPLY);
 
        /* Copy structs on stack back to packet */
@@ -904,7 +942,7 @@ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) {
                        if(!checklength(source, packet, ethlen + ip_size))
                                return false;
 
-                       if(DATA(packet)[ethlen + 8] < 1) {
+                       if(DATA(packet)[ethlen + 8] <= 1) {
                                if(DATA(packet)[ethlen + 11] != IPPROTO_ICMP || DATA(packet)[ethlen + 32] != ICMP_TIME_EXCEEDED)
                                        route_ipv4_unreachable(source, packet, ethlen, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL);
                                return false;
@@ -927,7 +965,7 @@ static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) {
                        if(!checklength(source, packet, ethlen + ip6_size))
                                return false;
 
-                       if(DATA(packet)[ethlen + 7] < 1) {
+                       if(DATA(packet)[ethlen + 7] <= 1) {
                                if(DATA(packet)[ethlen + 6] != IPPROTO_ICMPV6 || DATA(packet)[ethlen + 40] != ICMP6_TIME_EXCEEDED)
                                        route_ipv6_unreachable(source, packet, ethlen, ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT);
                                return false;