pathmtu/pathmtu.c: Support IPv6 in the `raw' probing strategy.
authorMark Wooding <mdw@distorted.org.uk>
Fri, 15 Sep 2017 00:52:47 +0000 (01:52 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 28 Jun 2018 23:26:40 +0000 (00:26 +0100)
Add code for constructing and parsing IPv6 packets.  This is rather
annoying and fiddly, in ways which are excitingly different from the
ways in which raw sockets are exciting and fiddly with IPv4.

pathmtu/pathmtu.c

index c149479..4eba80d 100644 (file)
@@ -47,6 +47,8 @@
 #include <netinet/in_systm.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
 #include <netinet/udp.h>
 
 #include <net/if.h>
@@ -414,6 +416,7 @@ fail_0:
 #endif
 
 static int rawicmp = -1, rawudp = -1, rawerr = 0;
+static int rawicmp6 = -1, rawudp6 = -1, rawerr6 = 0;
 
 #define IPCK_INIT 0xffff
 
@@ -437,6 +440,11 @@ struct phdr {
   uint8_t ph_z, ph_p;
   uint16_t ph_len;
 };
+struct phdr6 {
+  struct in6_addr ph6_src, ph6_dst;
+  uint32_t ph6_len;
+  uint8_t ph6_z0, ph6_z1, ph6_z2, ph6_nxt;
+};
 
 struct raw_state {
   union addr me, a;
@@ -452,14 +460,30 @@ static int raw_setup(void *stv, int sk, const struct param *pp)
   int i, mtu = -1;
   struct ifaddrs *ifa, *ifaa, *ifap;
   struct ifreq ifr;
+  struct icmp6_filter f6;
 
   /* Check that the address is OK, and that we have the necessary raw
    * sockets.
+   *
+   * For IPv6, also set the filter so we don't get too many useless wakeups.
    */
   switch (pp->a.sa.sa_family) {
     case AF_INET:
       if (rawerr) { errno = rawerr; goto fail_0; }
       st->rawicmp = rawicmp; st->rawudp = rawudp; st->sk = sk;
+      /* IPv4 filtering is available on Linux but isn't portable. */
+      break;
+    case AF_INET6:
+      if (rawerr6) { errno = rawerr6; goto fail_0; }
+      st->rawicmp = rawicmp6; st->rawudp = rawudp6; st->sk = sk;
+      ICMP6_FILTER_SETBLOCKALL(&f6);
+      ICMP6_FILTER_SETPASS(ICMP6_PACKET_TOO_BIG, &f6);
+      ICMP6_FILTER_SETPASS(ICMP6_DST_UNREACH, &f6);
+      if (setsockopt(st->rawicmp, IPPROTO_ICMPV6, ICMP6_FILTER,
+                    &f6, sizeof(f6))) {
+       die(EXIT_FAILURE, "failed to set icmpv6 filter: %s",
+           strerror(errno));
+      }
       break;
     default:
       errno = EPFNOSUPPORT; goto fail_0;
@@ -474,12 +498,18 @@ static int raw_setup(void *stv, int sk, const struct param *pp)
   if (getsockname(sk, &st->me.sa, &sz))
     goto fail_0;
 
-  /* An unfortunate bodge which will make sense in the future. */
+  /* Only now do some fiddling because Linux doesn't like port numbers in
+   * IPv6 raw destination addresses...
+   */
   switch (pp->a.sa.sa_family) {
     case AF_INET:
       st->srcport = st->me.sin.sin_port; st->me.sin.sin_port = 0;
       st->dstport =  st->a.sin.sin_port;  st->a.sin.sin_port = 0;
       break;
+    case AF_INET6:
+      st->srcport = st->me.sin6.sin6_port; st->me.sin6.sin6_port = 0;
+      st->dstport =  st->a.sin6.sin6_port;  st->a.sin6.sin6_port = 0;
+      break;
     default:
       abort();
   }
@@ -537,8 +567,10 @@ static int raw_xmit(void *stv, int mtu)
   struct raw_state *st = stv;
   unsigned char b[65536], *p;
   struct ip *ip;
+  struct ip6_hdr *ip6;
   struct udphdr *udp;
   struct phdr ph;
+  struct phdr6 ph6;
   unsigned ck;
 
   switch (st->a.sa.sa_family) {
@@ -583,6 +615,41 @@ static int raw_xmit(void *stv, int mtu)
 
       break;
 
+    case AF_INET6:
+
+      /* Build the IP header. */
+      ip6 = (struct ip6_hdr *)b;
+      STEP(st->q); ip6->ip6_flow = htonl(0x60000000 | st->q);
+      ip6->ip6_plen = htons(mtu - sizeof(*ip6));
+      ip6->ip6_nxt = IPPROTO_UDP;
+      ip6->ip6_hlim = 64;
+      ip6->ip6_src = st->me.sin6.sin6_addr;
+      ip6->ip6_dst = st->a.sin6.sin6_addr;
+
+      /* Build a UDP packet in the output buffer. */
+      udp = (struct udphdr *)(ip6 + 1);
+      udp->uh_sport = st->srcport;
+      udp->uh_dport = st->dstport;
+      udp->uh_ulen = htons(mtu - sizeof(*ip6));
+      udp->uh_sum = 0;
+
+      /* Copy the payload. */
+      p = (unsigned char *)(udp + 1);
+      memcpy(p, buf, mtu - (p - b));
+
+      /* Calculate the UDP checksum. */
+      ph6.ph6_src = ip6->ip6_src;
+      ph6.ph6_dst = ip6->ip6_dst;
+      ph6.ph6_len = udp->uh_ulen;
+      ph6.ph6_z0 = ph6.ph6_z1 = ph6.ph6_z2 = 0;
+      ph6.ph6_nxt = IPPROTO_UDP;
+      ck = IPCK_INIT;
+      ck = ipcksum(&ph6, sizeof(ph6), ck);
+      ck = ipcksum(udp, mtu - sizeof(*ip6), ck);
+      udp->uh_sum = htons(ck);
+
+      break;
+
     default:
       abort();
   }
@@ -607,7 +674,9 @@ static int raw_selproc(void *stv, fd_set *fd_in, struct probestate *ps)
   struct raw_state *st = stv;
   unsigned char b[65536];
   struct ip *ip;
+  struct ip6_hdr *ip6;
   struct icmp *icmp;
+  struct icmp6_hdr *icmp6;
   struct udphdr *udp;
   const unsigned char *payload;
   ssize_t n;
@@ -656,6 +725,44 @@ static int raw_selproc(void *stv, fd_set *fd_in, struct probestate *ps)
 
        break;
 
+      case AF_INET6:
+       icmp6 = (struct icmp6_hdr *)b;
+       if (n < sizeof(*icmp6) ||
+           (icmp6->icmp6_type != ICMP6_PACKET_TOO_BIG &&
+            icmp6->icmp6_type != ICMP6_DST_UNREACH))
+         goto skip_icmp;
+       n -= sizeof(*icmp6);
+
+       ip6 = (struct ip6_hdr *)(icmp6 + 1);
+       if (n < sizeof(*ip6) || ip6->ip6_nxt != IPPROTO_UDP ||
+           memcmp(ip6->ip6_src.s6_addr,
+                  st->me.sin6.sin6_addr.s6_addr, 16) ||
+           memcmp(ip6->ip6_dst.s6_addr,
+                  st->a.sin6.sin6_addr.s6_addr, 16) ||
+           (ntohl(ip6->ip6_flow)&0xffff) != st->q)
+         goto skip_icmp;
+       n -= sizeof(*ip6);
+
+       udp = (struct udphdr *)(ip6 + 1);
+       if (n < sizeof(*udp) || udp->uh_sport != st->srcport ||
+           udp->uh_dport != st->dstport)
+         goto skip_icmp;
+       n -= sizeof(*udp);
+
+       payload = (const unsigned char *)(udp + 1);
+       if (!mypacketp(ps, payload, n)) goto skip_icmp;
+
+       if (icmp6->icmp6_type == ICMP6_PACKET_TOO_BIG)
+         return (ntohs(icmp6->icmp6_mtu));
+       else switch (icmp6->icmp6_code) {
+           case ICMP6_DST_UNREACH_ADMIN:
+           case ICMP6_DST_UNREACH_NOPORT:
+             return (RC_HIGHER);
+           default:
+             goto skip_icmp;
+         }
+       break;
+
       default:
        abort();
     }
@@ -859,6 +966,9 @@ int main(int argc, char *argv[])
   if ((rawicmp = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0 ||
       (rawudp = socket(PF_INET, SOCK_RAW, IPPROTO_UDP)) < 0)
     rawerr = errno;
+  if ((rawicmp6 = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0 ||
+      (rawudp6 = socket(PF_INET6, SOCK_RAW, IPPROTO_RAW)) < 0)
+    rawerr6 = errno;
   if (setuid(getuid()))
     abort();