X-Git-Url: https://git.distorted.org.uk/~mdw/tripe/blobdiff_plain/88510d86bc0300f9a30ac9904826e75eaa4e791c..HEAD:/pathmtu/pathmtu.c diff --git a/pathmtu/pathmtu.c b/pathmtu/pathmtu.c index d347b0dd..9c5c8294 100644 --- a/pathmtu/pathmtu.c +++ b/pathmtu/pathmtu.c @@ -9,29 +9,25 @@ * * This file is part of Trivial IP Encryption (TrIPE). * - * TrIPE is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * TrIPE is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your + * option) any later version. * - * TrIPE is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * TrIPE is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. * * You should have received a copy of the GNU General Public License - * along with TrIPE; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * along with TrIPE. If not, see . */ /*----- Header files ------------------------------------------------------*/ -#if defined(linux) -# define _BSD_SOURCE -#endif - #include "config.h" +#include #include #include #include @@ -51,11 +47,15 @@ #include #include #include +#include +#include #include -#include -#include -#include +#ifdef HAVE_GETIFADDRS +# include +# include +# include +#endif #include #include @@ -109,6 +109,31 @@ static double s2f(const char *s, const char *what) static void f2tv(struct timeval *tv, double t) { tv->tv_sec = t; tv->tv_usec = (t - tv->tv_sec)*MILLION; } +union addr { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +}; + +/* Check whether an address family is even slightly supported. */ +static int addrfamok(int af) +{ + switch (af) { + case AF_INET: case AF_INET6: return (1); + default: return (0); + } +} + +/* Return the size of a socket address. */ +static size_t addrsz(const union addr *a) +{ + switch (a->sa.sa_family) { + case AF_INET: return (sizeof(a->sin)); + case AF_INET6: return (sizeof(a->sin6)); + default: abort(); + } +} + /*----- Main algorithm skeleton -------------------------------------------*/ struct param { @@ -119,7 +144,7 @@ struct param { double timeout; /* Retransmission timeout */ int seqoff; /* Offset to write sequence number */ const struct probe_ops *pops; /* Probe algorithm description */ - struct sockaddr_in sin; /* Destination address */ + union addr a; /* Destination address */ }; struct probestate { @@ -226,18 +251,27 @@ static int pathmtu(const struct param *pp) /* Build and connect a UDP socket. We'll need this to know the local port * number to use if nothing else. Set other stuff up. */ - if ((sk = socket(PF_INET, SOCK_DGRAM, 0)) < 0) goto fail_0; - if (connect(sk, (struct sockaddr *)&pp->sin, sizeof(pp->sin))) goto fail_1; + if ((sk = socket(pp->a.sa.sa_family, SOCK_DGRAM, IPPROTO_UDP)) < 0) + goto fail_0; + if (connect(sk, &pp->a.sa, addrsz(&pp->a))) goto fail_1; st = xmalloc(pp->pops->statesz); if ((mtu = pp->pops->setup(st, sk, pp)) < 0) goto fail_2; ps.pp = pp; ps.q = rand() & 0xffff; - lo = 576; hi = mtu; + switch (pp->a.sa.sa_family) { + case AF_INET: lo = 576; break; + case AF_INET6: lo = 1280; break; + default: abort(); + } + hi = mtu; + if (hi < lo) { errno = EMSGSIZE; return (-1); } /* And now we do a thing which is sort of like a binary search, except that * we also take explicit clues as establishing a new upper bound, and we * try to hug that initially. */ for (;;) { + assert(lo <= mtu && mtu <= hi); + if (pp->f & F_VERBOSE) moan("probe: %d <= %d <= %d", lo, mtu, hi); rc = probe(&ps, st, mtu); switch (rc) { @@ -295,19 +329,36 @@ static int pathmtu(const struct param *pp) if (pp->f & F_VERBOSE) moan("probe returned: found correct MTU"); goto done; } - if (pp->f & F_VERBOSE) moan("probe returned: guessing higher"); lo = mtu; + + /* Now we must make a new guess, between lo and hi. We know that lo + * is good; but we're not so sure about hi here. We know that hi > + * lo, so this will find an approximate midpoint, greater than lo and + * no more than hi. + */ + if (pp->f & F_VERBOSE) moan("probe returned: guessing higher"); mtu += (hi - lo + 1)/2; break; case RC_LOWER: lower: - if (mtu == lo) { + /* If this didn't work, and we're already at the bottom of our + * possible range, then something has gone horribly wrong. + */ + assert(lo < mtu); + hi = mtu - 1; + if (lo == hi) { if (pp->f & F_VERBOSE) moan("error returned: found correct MTU"); + mtu = lo; goto done; } + + /* We must make a new guess, between lo and hi. We're probably + * fairly sure that lo will succeed, since either it's the minimum + * MTU or we've tested it already; but we're not quite sure about hi, + * so we want to aim high. + */ if (pp->f & F_VERBOSE) moan("error returned: guessing lower"); - hi = mtu - 1; mtu -= (hi - lo + 1)/2; break; @@ -337,8 +388,10 @@ fail_0: /*----- Doing it the hard way ---------------------------------------------*/ +#ifdef HAVE_GETIFADDRS + #if defined(linux) || defined(__OpenBSD__) -#define IPHDR_SANE +# define IPHDR_SANE #endif #ifdef IPHDR_SANE @@ -350,9 +403,27 @@ fail_0: #endif static int rawicmp = -1, rawudp = -1, rawerr = 0; +static int rawicmp6 = -1, rawudp6 = -1, rawerr6 = 0; #define IPCK_INIT 0xffff +/* Compare two addresses. Maybe compare the port numbers too. */ +#define AEF_PORT 1u +static int addreq(const union addr *a, const union addr *b, unsigned f) +{ + switch (a->sa.sa_family) { + case AF_INET: + return (a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr && + (!(f&AEF_PORT) || a->sin.sin_port == b->sin.sin_port)); + case AF_INET6: + return (!memcmp(a->sin6.sin6_addr.s6_addr, + b->sin6.sin6_addr.s6_addr, 16) && + (!(f&AEF_PORT) || a->sin6.sin6_port == b->sin6.sin6_port)); + default: + abort(); + } +} + /* Compute an IP checksum over some data. This is a restartable interface: * initialize A to `IPCK_INIT' for the first call. */ @@ -370,37 +441,83 @@ static unsigned ipcksum(const void *buf, size_t n, unsigned a) /* TCP/UDP pseudoheader structure. */ struct phdr { struct in_addr ph_src, ph_dst; - u_char ph_z, ph_p; - u_short ph_len; + 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 { - struct sockaddr_in me, sin; + union addr me, a; int sk, rawicmp, rawudp; + uint16_t srcport, dstport; unsigned q; }; static int raw_setup(void *stv, int sk, const struct param *pp) { struct raw_state *st = stv; - size_t sz; + socklen_t sz; int i, mtu = -1; struct ifaddrs *ifa, *ifaa, *ifap; struct ifreq ifr; + struct icmp6_filter f6; - /* If we couldn't acquire raw sockets, we fail here. */ - if (rawerr) { errno = rawerr; goto fail_0; } - st->rawicmp = rawicmp; st->rawudp = rawudp; st->sk = sk; + /* 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; + } /* Initialize the sequence number. */ st->q = rand() & 0xffff; /* Snaffle the local and remote address and port number. */ - st->sin = pp->sin; + st->a = pp->a; sz = sizeof(st->me); - if (getsockname(sk, (struct sockaddr *)&st->me, &sz)) + if (getsockname(sk, &st->me.sa, &sz)) goto fail_0; + /* 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(); + } + /* There isn't a portable way to force the DF flag onto a packet through * UDP, or even through raw IP, unless we write the entire IP header * ourselves. This is somewhat annoying, especially since we have an @@ -419,10 +536,9 @@ static int raw_setup(void *stv, int sk, const struct param *pp) for (i = 0; i < 2; i++) { for (ifap = 0, ifa = ifaa; ifa; ifa = ifa->ifa_next) { if (!(ifa->ifa_flags & IFF_UP) || !ifa->ifa_addr || - ifa->ifa_addr->sa_family != AF_INET || + ifa->ifa_addr->sa_family != st->me.sa.sa_family || (i == 0 && - ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr != - st->me.sin_addr.s_addr) || + !addreq((union addr *)ifa->ifa_addr, &st->me, 0)) || (i == 1 && ifap && strcmp(ifap->ifa_name, ifa->ifa_name) == 0) || strlen(ifa->ifa_name) >= sizeof(ifr.ifr_name)) continue; @@ -455,51 +571,97 @@ 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; - /* Build the IP header. */ - ip = (struct ip *)b; - ip->ip_v = 4; - ip->ip_hl = sizeof(*ip)/4; - ip->ip_tos = IPTOS_RELIABILITY; - ip->ip_len = sane_htons(mtu); - STEP(st->q); ip->ip_id = htons(st->q); - ip->ip_off = sane_htons(0 | IP_DF); - ip->ip_ttl = 64; - ip->ip_p = IPPROTO_UDP; - ip->ip_sum = 0; - ip->ip_src = st->me.sin_addr; - ip->ip_dst = st->sin.sin_addr; - - /* Build a UDP packet in the output buffer. */ - udp = (struct udphdr *)(ip + 1); - udp->uh_sport = st->me.sin_port; - udp->uh_dport = st->sin.sin_port; - udp->uh_ulen = htons(mtu - sizeof(*ip)); - udp->uh_sum = 0; - - /* Copy the payload. */ - p = (unsigned char *)(udp + 1); - memcpy(p, buf, mtu - (p - b)); - - /* Calculate the UDP checksum. */ - ph.ph_src = ip->ip_src; - ph.ph_dst = ip->ip_dst; - ph.ph_z = 0; - ph.ph_p = IPPROTO_UDP; - ph.ph_len = udp->uh_ulen; - ck = IPCK_INIT; - ck = ipcksum(&ph, sizeof(ph), ck); - ck = ipcksum(udp, mtu - sizeof(*ip), ck); - udp->uh_sum = htons(ck); + switch (st->a.sa.sa_family) { + + case AF_INET: + + /* Build the IP header. */ + ip = (struct ip *)b; + ip->ip_v = 4; + ip->ip_hl = sizeof(*ip)/4; + ip->ip_tos = IPTOS_RELIABILITY; + ip->ip_len = sane_htons(mtu); + STEP(st->q); ip->ip_id = htons(st->q); + ip->ip_off = sane_htons(0 | IP_DF); + ip->ip_ttl = 64; + ip->ip_p = IPPROTO_UDP; + ip->ip_sum = 0; + ip->ip_src = st->me.sin.sin_addr; + ip->ip_dst = st->a.sin.sin_addr; + + /* Build a UDP packet in the output buffer. */ + udp = (struct udphdr *)(ip + 1); + udp->uh_sport = st->srcport; + udp->uh_dport = st->dstport; + udp->uh_ulen = htons(mtu - sizeof(*ip)); + udp->uh_sum = 0; + + /* Copy the payload. */ + p = (unsigned char *)(udp + 1); + memcpy(p, buf, mtu - (p - b)); + + /* Calculate the UDP checksum. */ + ph.ph_src = ip->ip_src; + ph.ph_dst = ip->ip_dst; + ph.ph_z = 0; + ph.ph_p = IPPROTO_UDP; + ph.ph_len = udp->uh_ulen; + ck = IPCK_INIT; + ck = ipcksum(&ph, sizeof(ph), ck); + ck = ipcksum(udp, mtu - sizeof(*ip), ck); + udp->uh_sum = htons(ck); + + 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(); + } /* Send the whole thing off. If we're too big for the interface then we * might need to trim immediately. */ - if (sendto(st->rawudp, b, mtu, 0, - (struct sockaddr *)&st->sin, sizeof(st->sin)) < 0) { + if (sendto(st->rawudp, b, mtu, 0, &st->a.sa, addrsz(&st->a)) < 0) { if (errno == EMSGSIZE) return (RC_LOWER); else goto fail_0; } @@ -516,45 +678,100 @@ 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; /* An ICMP packet: see what's inside. */ if (FD_ISSET(st->rawicmp, fd_in)) { if ((n = read(st->rawicmp, b, sizeof(b))) < 0) goto fail_0; - ip = (struct ip *)b; - if (n < sizeof(*ip) || n < sizeof(4*ip->ip_hl) || - ip->ip_v != 4 || ip->ip_p != IPPROTO_ICMP) - goto skip_icmp; - n -= sizeof(4*ip->ip_hl); - - icmp = (struct icmp *)(b + 4*ip->ip_hl); - if (n < sizeof(*icmp) || icmp->icmp_type != ICMP_UNREACH) - goto skip_icmp; - n -= offsetof(struct icmp, icmp_ip); - - ip = &icmp->icmp_ip; - if (n < sizeof(*ip) || - ip->ip_p != IPPROTO_UDP || ip->ip_hl != sizeof(*ip)/4 || - ip->ip_id != htons(st->q) || - ip->ip_src.s_addr != st->me.sin_addr.s_addr || - ip->ip_dst.s_addr != st->sin.sin_addr.s_addr) - goto skip_icmp; - n -= sizeof(*ip); - - udp = (struct udphdr *)(ip + 1); - if (n < sizeof(udp) || udp->uh_sport != st->me.sin_port || - udp->uh_dport != st->sin.sin_port) - goto skip_icmp; - n -= sizeof(*udp); - - if (icmp->icmp_code == ICMP_UNREACH_PORT) return (RC_HIGHER); - else if (icmp->icmp_code != ICMP_UNREACH_NEEDFRAG) goto skip_icmp; - else if (icmp->icmp_nextmtu) return (htons(icmp->icmp_nextmtu)); - else return (RC_LOWER); + switch (st->me.sa.sa_family) { + + case AF_INET: + + ip = (struct ip *)b; + if (n < sizeof(*ip) || n < sizeof(4*ip->ip_hl) || + ip->ip_v != 4 || ip->ip_p != IPPROTO_ICMP) + goto skip_icmp; + n -= sizeof(4*ip->ip_hl); + + icmp = (struct icmp *)(b + 4*ip->ip_hl); + if (n < sizeof(*icmp) || icmp->icmp_type != ICMP_UNREACH) + goto skip_icmp; + n -= offsetof(struct icmp, icmp_ip); + + ip = &icmp->icmp_ip; + if (n < sizeof(*ip) || + ip->ip_p != IPPROTO_UDP || ip->ip_hl != sizeof(*ip)/4 || + ip->ip_id != htons(st->q) || + ip->ip_src.s_addr != st->me.sin.sin_addr.s_addr || + ip->ip_dst.s_addr != st->a.sin.sin_addr.s_addr) + goto skip_icmp; + n -= sizeof(*ip); + + udp = (struct udphdr *)(ip + 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 (icmp->icmp_code == ICMP_UNREACH_PORT) return (RC_HIGHER); + else if (icmp->icmp_code != ICMP_UNREACH_NEEDFRAG) goto skip_icmp; + else if (icmp->icmp_nextmtu) return (htons(icmp->icmp_nextmtu)); + else return (RC_LOWER); + + 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(); + } } + skip_icmp:; /* If we got a reply to the current probe then we're good. If we got an @@ -581,6 +798,8 @@ static const struct probe_ops raw_ops = { #undef OPS_CHAIN #define OPS_CHAIN &raw_ops +#endif + /*----- Doing the job on Linux --------------------------------------------*/ #if defined(linux) @@ -590,26 +809,47 @@ static const struct probe_ops raw_ops = { #endif struct linux_state { + int sol, so_mtu_discover, so_mtu; int sk; + size_t hdrlen; }; static int linux_setup(void *stv, int sk, const struct param *pp) { struct linux_state *st = stv; int i, mtu; - size_t sz; + socklen_t sz; + + /* Check that the address is OK. */ + switch (pp->a.sa.sa_family) { + case AF_INET: + st->sol = IPPROTO_IP; + st->so_mtu_discover = IP_MTU_DISCOVER; + st->so_mtu = IP_MTU; + st->hdrlen = 28; + break; + case AF_INET6: + st->sol = IPPROTO_IPV6; + st->so_mtu_discover = IPV6_MTU_DISCOVER; + st->so_mtu = IPV6_MTU; + st->hdrlen = 48; + break; + default: + errno = EPFNOSUPPORT; + return (-1); + } /* Snaffle the UDP socket. */ st->sk = sk; /* Turn on kernel path-MTU discovery and force DF on. */ - i = IP_PMTUDISC_DO; - if (setsockopt(st->sk, IPPROTO_IP, IP_MTU_DISCOVER, &i, sizeof(i))) + i = IP_PMTUDISC_PROBE; + if (setsockopt(st->sk, st->sol, st->so_mtu_discover, &i, sizeof(i))) return (-1); /* Read the initial MTU guess back and report it. */ sz = sizeof(mtu); - if (getsockopt(st->sk, IPPROTO_IP, IP_MTU, &mtu, &sz)) + if (getsockopt(st->sk, st->sol, st->so_mtu, &mtu, &sz)) return (-1); /* Done. */ @@ -626,7 +866,7 @@ static int linux_xmit(void *stv, int mtu) struct linux_state *st = stv; /* Write the packet. */ - if (write(st->sk, buf, mtu - 28) >= 0) return (RC_OK); + if (write(st->sk, buf, mtu - st->hdrlen) >= 0) return (RC_OK); else if (errno == EMSGSIZE) return (RC_LOWER); else return (RC_FAIL); } @@ -635,7 +875,7 @@ static int linux_selproc(void *stv, fd_set *fd_in, struct probestate *ps) { struct linux_state *st = stv; int mtu; - size_t sz; + socklen_t sz; ssize_t n; unsigned char b[65536]; @@ -653,7 +893,7 @@ static int linux_selproc(void *stv, fd_set *fd_in, struct probestate *ps) errno == ECONNREFUSED || errno == EHOSTUNREACH) return (RC_HIGHER); sz = sizeof(mtu); - if (getsockopt(st->sk, IPPROTO_IP, IP_MTU, &mtu, &sz)) + if (getsockopt(st->sk, st->sol, st->so_mtu, &mtu, &sz)) return (RC_FAIL); return (mtu); } @@ -680,7 +920,7 @@ static void version(FILE *fp) static void usage(FILE *fp) { - pquis(fp, "Usage: $ [-H HEADER] [-m METHOD]\n\ + pquis(fp, "Usage: $ [-46v] [-H HEADER] [-m METHOD]\n\ [-r SECS] [-g FACTOR] [-t SECS] HOST [PORT]\n"); } @@ -696,13 +936,16 @@ static void help(FILE *fp) Options in full:\n\ \n\ -h, --help Show this help text.\n\ --v, --version Show version number.\n\ +-V, --version Show version number.\n\ -u, --usage Show brief usage message.\n\ \n\ +-4, --ipv4 Restrict to IPv4 only.\n\ +-6, --ipv6 Restrict to IPv6 only.\n\ -g, --growth=FACTOR Growth factor for retransmit interval.\n\ -m, --method=METHOD Use METHOD to probe for MTU.\n\ -r, --retransmit=SECS Retransmit if no reply after SEC.\n\ -t, --timeout=SECS Give up expecting a reply after SECS.\n\ +-v, --verbose Write a running commentary to stderr.\n\ -H, --header=HEX Packet header, in hexadecimal.\n\ \n\ Probe methods:\n\ @@ -719,30 +962,39 @@ int main(int argc, char *argv[]) hex_ctx hc; dstr d = DSTR_INIT; size_t sz; - int i; - unsigned long u; - char *q; - struct hostent *h; - struct servent *s; + int i, err; + struct addrinfo aihint = { 0 }, *ailist, *ai; + const char *host, *svc = "7"; unsigned f = 0; #define f_bogus 1u +#ifdef HAVE_GETIFADDRS 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; +#endif if (setuid(getuid())) abort(); ego(argv[0]); fillbuffer(buf, sizeof(buf)); - pp.sin.sin_port = htons(7); + + aihint.ai_family = AF_UNSPEC; + aihint.ai_protocol = IPPROTO_UDP; + aihint.ai_socktype = SOCK_DGRAM; + aihint.ai_flags = AI_ADDRCONFIG; for (;;) { static const struct option opts[] = { { "help", 0, 0, 'h' }, { "version", 0, 0, 'V' }, { "usage", 0, 0, 'u' }, + { "ipv4", 0, 0, '4' }, + { "ipv6", 0, 0, '6' }, { "header", OPTF_ARGREQ, 0, 'H' }, { "growth", OPTF_ARGREQ, 0, 'g' }, { "method", OPTF_ARGREQ, 0, 'm' }, @@ -752,7 +1004,7 @@ int main(int argc, char *argv[]) { 0, 0, 0, 0 } }; - i = mdwopt(argc, argv, "hVu" "H:g:m:r:t:v", opts, 0, 0, 0); + i = mdwopt(argc, argv, "hVu" "46H:g:m:r:t:v", opts, 0, 0, 0); if (i < 0) break; switch (i) { case 'h': help(stdout); exit(0); @@ -769,6 +1021,8 @@ int main(int argc, char *argv[]) pp.seqoff = sz; break; + case '4': aihint.ai_family = AF_INET; break; + case '6': aihint.ai_family = AF_INET6; break; case 'g': pp.regr = s2f(optarg, "retransmit growth factor"); break; case 'r': pp.retx = s2f(optarg, "retransmit interval"); break; case 't': pp.timeout = s2f(optarg, "timeout"); break; @@ -793,25 +1047,17 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } - if ((h = gethostbyname(*argv)) == 0) - die(EXIT_FAILURE, "unknown host `%s': %s", *argv, hstrerror(h_errno)); - if (h->h_addrtype != AF_INET) - die(EXIT_FAILURE, "unsupported address family for host `%s'", *argv); - memcpy(&pp.sin.sin_addr, h->h_addr, sizeof(struct in_addr)); - argv++; argc--; - - if (*argv) { - errno = 0; - u = strtoul(*argv, &q, 0); - if (!errno && !*q) - pp.sin.sin_port = htons(u); - else if ((s = getservbyname(*argv, "udp")) == 0) - die(EXIT_FAILURE, "unknown UDP service `%s'", *argv); - else - pp.sin.sin_port = s->s_port; + host = argv[0]; + if (argv[1]) svc = argv[1]; + if ((err = getaddrinfo(host, svc, &aihint, &ailist)) != 0) { + die(EXIT_FAILURE, "unknown host `%s' or service `%s': %s", + host, svc, gai_strerror(err)); } + for (ai = ailist; ai && !addrfamok(ai->ai_family); ai = ai->ai_next); + if (!ai) die(EXIT_FAILURE, "no supported address families for `%s'", host); + assert(ai->ai_addrlen <= sizeof(pp.a)); + memcpy(&pp.a, ai->ai_addr, ai->ai_addrlen); - pp.sin.sin_family = AF_INET; i = pathmtu(&pp); if (i < 0) die(EXIT_FAILURE, "failed to discover MTU: %s", strerror(errno));