Merge branches 'mdw/knock' and 'mdw/ipv6' into bleeding
authorMark Wooding <mdw@distorted.org.uk>
Fri, 25 Jan 2019 12:08:24 +0000 (12:08 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Fri, 25 Jan 2019 12:08:24 +0000 (12:08 +0000)
* mdw/knock:
  Add notion of `ephemeral' associations and a goodbye protocol.
  Add new `knock' protocol.
  server/{keyexch,peer}.c: Maybe key-exchange messages come out of the blue.
  server/keyexch.c (kx_message): Squish vertically.
  server/keyexch.c: Abstract out the common message-handling behaviour.
  server/keymgmt.c: Track and find keys by their 32-bit IDs.
  server/test.c: Add a program to assist unit tests.
  server/servutil.c: Add utilities for plain asymmetric encryption.
  server/servutil.c: Add utilities for simple leaky-bucket rate limiting.
  server/keyexch.c: Rename kx_init => kx_setup.
  server/: Augment challenges to allow a payload.
  server/chal.c: Capture `master->algs.bulk' in a variable.
  server/chal.c: Rename bulk => bchal.
  server/: Expose and enhance the bulk-key-derivation protocol.

* mdw/ipv6: (64 commits)
  contrib/greet.in: Accept IPv6 addresses.
  contrib/tripe-ipif.in: Fixing for IPv6.
  svc/conntrack.in: Add IPv6 support.
  svc/conntrack.in: Split out a base class from `InetAddress'.
  svc/conntrack.in: Contemplate multiple address families.
  svc/conntrack.in: Allow multiple networks in a peer pattern.
  svc/conntrack.in (kickpeers): Refactor and reformat the search loop.
  svc/conntrack.in (kickpeers): Rename `map' variable.
  svc/conntrack.in: Process peer patterns in order.
  svc/conntrack.in: Maintain config groups in a dictionary.
  svc/conntrack.in: Make an `InetAddress' class to do address wrangling.
  svc/conntrack.in: Factor out network parsing.
  svc/conntrack.in: Gather address hacking functions into a new section.
  svc/conntrack.in: Introduce a function for parsing address strings.
  svc/conntrack.in (strmask): Consistently return a string object.
  svc/conntrack.in: Fix netmask parsing.
  svc/conntrack.in: Leave time for network configuration to settle.
  svc/conntrack.in: Hoist `netupdown' above `kickpeers'.
  server/, mon/: Introduce transport of TrIPE over IPv6.
  server/addrmap.c (hash): Visually tighten the arithmetic.
  ...

25 files changed:
configure.ac
contrib/greet.in
contrib/tripe-ipif.in
mon/tripemon.in
pathmtu/pathmtu.1.in
pathmtu/pathmtu.c
peerdb/peers.in.5.in
peerdb/tripe-newpeers.in
pkstream/pkstream.1.in
pkstream/pkstream.c
proxy/tripe-mitm.8.in
proxy/tripe-mitm.c
py/tripe.py.in
server/Makefile.am
server/addrmap.c
server/admin.c
server/peer.c
server/servutil.c
server/tripe-admin.5.in
server/tripe.8.in
server/tripe.c
server/tripe.h
svc/conntrack.8.in
svc/conntrack.in
svc/tripe-ifup.in

index 84fcf5a..887f81d 100644 (file)
@@ -63,6 +63,27 @@ case "$host_os" in
     ;;
 esac
 
+AC_ARG_WITH([adns],
+  AS_HELP_STRING([--with-adns],
+                [use ADNS library for background name resolution]),
+  [want_adns=$withval],
+  [want_adns=auto])
+case $want_adns in
+  no) ;;
+  *) AC_CHECK_LIB([adns], [adns_submit], [have_adns=yes], [have_adns=no]) ;;
+esac
+AC_SUBST([ADNS_LIBS])
+case $want_adns,$have_adns in
+  yes,no)
+    AC_MSG_ERROR([ADNS library not found but explicitly requested])
+    ;;
+  yes,yes | auto,yes)
+    ADNS_LIBS="-ladns"
+    AC_DEFINE([HAVE_LIBADNS], [1],
+             [Define if the GNU adns library is available.])
+    ;;
+esac
+
 PKG_CHECK_MODULES([mLib], [mLib >= 2.2.1])
 PKG_CHECK_MODULES([catacomb], [catacomb >= 2.2.2-38])
 
index c84efdc..7bc678a 100644 (file)
@@ -9,9 +9,12 @@ from sys import argv
 def db64(s):
   return (s + '='*((-len(s))%4)).decode('base64')
 
-addr, chal = (lambda _, h, p, c: ((h, int(p)), db64(c)))(*argv)
-sk = S.socket(S.AF_INET, S.SOCK_DGRAM)
-sk.connect(addr)
+ai, chal = (lambda _, h, p, c:
+            (S.getaddrinfo(h, p, S.AF_UNSPEC, S.SOCK_DGRAM, S.IPPROTO_UDP,
+                             S.AI_NUMERICHOST | S.AI_NUMERICSERV)[0],
+             db64(c)))(*argv)
+sk = S.socket(ai[0], S.SOCK_DGRAM)
+sk.connect(ai[4])
 
 pkt = '\x25' + chal
 sk.send(pkt)
index d0118a4..3d2aa82 100755 (executable)
@@ -48,7 +48,9 @@
 ### field is used (a) by the accompanying `ipif-peers' script to set up the
 ### peer association, and (b) to determine the correct MTU to set; it
 ### should have the form ADDRESS[:PORT], where the PORT defaults to 4070 if
-### it's not given explicitly.
+### it's not given explicitly, and an IPv6 ADDRESS is enclosed in square
+### brackets (because of the stupid syntax decision to use colons in IPv6
+### address literals).
 ###
 ### Having done all of that, and having configured userv-ipif correctly,
 ### you should set TRIPE_SLIPIF=.../tripe-ipif and everything should just
@@ -108,18 +110,28 @@ case "$remote_ext" in
     addr=$(tripectl addr $peer)
     set -- $addr
     case $1 in
-      INET) remote_ext=$2 ;;
+      INET | INET6) remote_af=$1 remote_ext=$2 ;;
       *) echo >&2 "$quis: unexpected address family \`$1'"; exit 1 ;;
     esac
     ;;
+  \[*\]:*)
+    remote_af=INET6
+    remote_ext=${remote_ext#\[}
+    remote_ext=${remote_ext%\]:*}
+    ;;
   *:*)
+    remote_af=INET
     remote_ext=${remote_ext%:*}
     ;;
 esac
 
 ## Determine the MTU based on the path.
 pmtu=$(pathmtu $remote_ext)
-mtu=$(( $pmtu - 29 - $overhead ))
+case $remote_af in
+  INET) iphdrsz=20 ;;
+  INET6) iphdrsz=40 ;;
+esac
+mtu=$(( $pmtu - $iphdrsz - 8 - $overhead - 1 ))
 
 ## Obtain the tunnel and run it.
 now=$(date +"%Y-%m-%d %H:%M:%S")
index 8666575..11ee6dc 100644 (file)
@@ -323,13 +323,19 @@ class Peer (MonitorObject):
 
   def _setaddr(me, addr):
     """Set the peer's address."""
-    if addr[0] == 'INET':
-      ipaddr, port = addr[1:]
+    if addr[0] in ['INET', 'INET6']:
+      af, ipaddr, port = addr
       try:
-        name = S.gethostbyaddr(ipaddr)[0]
-        me.addr = 'INET %s:%s [%s]' % (name, port, ipaddr)
-      except S.herror:
-        me.addr = 'INET %s:%s' % (ipaddr, port)
+        name, _ = S.getnameinfo((ipaddr, int(port)),
+                                S.NI_NUMERICSERV | S.NI_NAMEREQD)
+      except S.gaierror:
+        me.addr = '%s %s%s%s:%s' % (af,
+                                    af == 'INET6' and '[' or '',
+                                    ipaddr,
+                                    af == 'INET6' and ']' or '',
+                                    port)
+      else:
+        me.addr = '%s %s:%s [%s]' % (af, name, port, ipaddr)
     else:
       me.addr = ' '.join(addr)
 
@@ -1042,6 +1048,8 @@ class AddPeerDialog (MyDialog):
     * e_name, e_addr, e_port, c_keepalive, l_tunnel: widgets in the dialog
   """
 
+  AFS = ['ANY', 'INET', 'INET6']
+
   def __init__(me):
     """Initialize the dialogue."""
     MyDialog.__init__(me, 'Add peer',
@@ -1057,8 +1065,13 @@ class AddPeerDialog (MyDialog):
     me.e_name = table.labelled('Name',
                                ValidatingEntry(r'^[^\s:]+$', '', 16),
                                width = 3)
+    me.l_af = table.labelled('Family', combo_box_text(),
+                             newlinep = True, width = 3)
+    for af in me.AFS:
+      me.l_af.append_text(af)
+    me.l_af.set_active(0)
     me.e_addr = table.labelled('Address',
-                               ValidatingEntry(r'^[a-zA-Z0-9.-]+$', '', 24),
+                               ValidatingEntry(r'^[a-zA-Z0-9.-:]+$', '', 24),
                                newlinep = True)
     me.e_port = table.labelled('Port',
                                ValidatingEntry(numericvalidate(0, 65535),
@@ -1110,7 +1123,9 @@ class AddPeerDialog (MyDialog):
     """Handle an OK press: create the peer."""
     try:
       t = me.l_tunnel.get_active()
+      afix = me.l_af.get_active()
       me._addpeer(me.e_name.get_text(),
+                  me.AFS[afix],
                   me.e_addr.get_text(),
                   me.e_port.get_text(),
                   keepalive = (me.c_keepalive.get_active() and
index eca637a..32fe121 100644 (file)
@@ -37,6 +37,7 @@ pathmtu \- discover path MTU to a given host
 .SH "SYNOPSIS"
 .
 .B pathmtu
+.RB [ \-46v ]
 .RB [ \-H
 .IR header ]
 .RB [ \-m
@@ -104,13 +105,19 @@ Command-line options are as follows.
 Writes a brief description of the command-line options available to
 standard output and exits with status 0.
 .TP
-.B "\-v, \-\-version"
+.B "\-V, \-\-version"
 Writes tripe's version number to standard output and exits with status
 0.
 .TP
 .B "\-u, \-\-usage"
 Writes a brief usage summary to standard output and exits with status 0.
 .TP
+.B "\-4"
+Look up hostnames only as IPv4 addresses.
+.TP
+.B "\-6"
+Look up hostnames only as IPv6 addresses.
+.TP
 .BI "\-g, \-\-growth=" factor
 Sets the retransmit interval growth factor.  Each time a packet is
 retransmitted,
@@ -156,6 +163,12 @@ assumes that the timeout means that the remote host
 .I did
 receive the packet.  The default timeout is 8 seconds.
 .TP
+.B "\-v, \-\-verbose"
+Write a running human-readable commentary to standard error about the
+progress of the operation.  Usually,
+.B pathmtu
+does its job silently unless there are errors.
+.TP
 .BI "\-H, \-\-header=" header
 Sets the packet header, in hexadecimal.  If you set an explicit port
 number, it may be worth setting the packet header too, so as not to
index 32f67a1..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>
@@ -105,6 +107,48 @@ 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();
+  }
+}
+
+/* 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();
+  }
+}
+
 /*----- Main algorithm skeleton -------------------------------------------*/
 
 struct param {
@@ -115,7 +159,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 {
@@ -222,12 +266,19 @@ 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
@@ -365,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
 
@@ -385,13 +437,19 @@ 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;
 };
 
@@ -402,20 +460,60 @@ 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;
 
-  /* 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
@@ -434,10 +532,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;
@@ -470,51 +567,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;
   }
@@ -531,45 +674,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
@@ -605,7 +803,9 @@ 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)
@@ -614,17 +814,36 @@ static int linux_setup(void *stv, int sk, const struct param *pp)
   int i, mtu;
   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_PROBE;
-  if (setsockopt(st->sk, IPPROTO_IP, IP_MTU_DISCOVER, &i, sizeof(i)))
+  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. */
@@ -641,7 +860,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);
 }
@@ -668,7 +887,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);
   }
@@ -695,7 +914,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");
 }
 
@@ -711,13 +930,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\
@@ -734,11 +956,9 @@ 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
@@ -746,18 +966,27 @@ 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();
 
   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' },
@@ -767,7 +996,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);
@@ -784,6 +1013,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;
@@ -808,25 +1039,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));
index ac27b49..d638b07 100644 (file)
@@ -83,14 +83,33 @@ is replaced by the value assigned to the given
 .IR key .
 .hP \*o
 An occurrence of
-.BI $[ host ]
+.BI $ flags [ host ]
 is replaced by the IP address of the named
 .IR host .
 Note that
 .I host
 may itself contain
 .BI $( key )
-substitutions.
+substitutions.  The
+.I flags
+consist of zero or more of the following characters:
+.RB ` 4 '
+looks up the
+.IR host 's
+IPv4 address(es);
+.RB ` 6 '
+looks up the
+.IR host 's
+IPv6 address(es);
+and
+.RB ` * '
+returns all of the found addresses, separated by spaces, rather than
+just the first one.  If neither address family is requested, then
+.RB ` 46 '
+is assumed.  IPv6 address lookup of names, rather than address literals,
+depends on the external
+.BR adnshost (1)
+program; if it is not present then only IPv4 lookups will be performed.
 .PP
 There is a simple concept of
 .I inheritance
index 81e62f7..92c1a07 100644 (file)
@@ -32,8 +32,12 @@ import mLib as M
 from optparse import OptionParser
 import cdb as CDB
 from sys import stdin, stdout, exit, argv
+import subprocess as SUB
 import re as RX
 import os as OS
+import errno as E
+import fcntl as F
+import socket as S
 from cStringIO import StringIO
 
 ###--------------------------------------------------------------------------
@@ -60,7 +64,52 @@ class ResolverFailure (ExpectedError):
   def __str__(me):
     return "failed to resolve `%s': %s" % (me.host, me.msg)
 
-class BulkResolver (object):
+class ResolvingHost (object):
+  """
+  A host name which is being looked up by a bulk-resolver instance.
+
+  Most notably, this is where the flag-handling logic lives for the
+  $FLAGS[HOSTNAME] syntax.
+  """
+
+  def __init__(me, name):
+    """Make a new resolving-host object for the host NAME."""
+    me.name = name
+    me.addr = { 'INET': [], 'INET6': [] }
+    me.failure = None
+
+  def addaddr(me, af, addr):
+    """
+    Add the address ADDR with address family AF.
+
+    The address family may be `INET' or `INET6'.
+    """
+    me.addr[af].append(addr)
+
+  def failed(me, msg):
+    """
+    Report that resolution of this host failed, with a human-readable MSG.
+    """
+    me.failure = msg
+
+  def get(me, flags):
+    """Return a list of addresses according to the FLAGS string."""
+    if me.failure is not None: raise ResolverFailure(me.name, me.failure)
+    aa = []
+    a4 = me.addr['INET']
+    a6 = me.addr['INET6']
+    all, any = False, False
+    for ch in flags:
+      if ch == '*': all = True
+      elif ch == '4': aa += a4; any = True
+      elif ch == '6': aa += a6; any = True
+      else: raise ValueError("unknown address-resolution flag `%s'" % ch)
+    if not any: aa = a4 + a6
+    if not aa: raise ResolverFailure(me.name, 'no matching addresses found')
+    if not all: aa = [aa[0]]
+    return aa
+
+class BaseBulkResolver (object):
   """
   Resolve a number of DNS names in parallel.
 
@@ -78,36 +127,193 @@ class BulkResolver (object):
 
   def __init__(me):
     """Initialize the resolver."""
-    me._resolvers = {}
     me._namemap = {}
 
-  def prepare(me, host):
-    """Prime the resolver to resolve the name HOST."""
-    if host not in me._resolvers:
-      me._resolvers[host] = M.SelResolveByName \
-                            (host,
-                             lambda name, alias, addr:
-                               me._resolved(host, addr[0]),
-                             lambda: me._resolved(host, None))
+  def prepare(me, name):
+    """Prime the resolver to resolve the given host NAME."""
+    if name not in me._namemap:
+      me._namemap[name] = host = ResolvingHost(name)
+      try:
+        ailist = S.getaddrinfo(name, None, S.AF_UNSPEC, S.SOCK_DGRAM, 0,
+                               S.AI_NUMERICHOST | S.AI_NUMERICSERV)
+      except S.gaierror:
+        me._prepare(host, name)
+      else:
+        for af, skty, proto, cname, sa in ailist:
+          if af == S.AF_INET: host.addaddr('INET', sa[0])
+          elif af == S.AF_INET6: host.addaddr('INET6', sa[0])
+
+  def lookup(me, name, flags):
+    """Fetch the address corresponding to the host NAME."""
+    return me._namemap[name].get(flags)
+
+class BresBulkResolver (BaseBulkResolver):
+  """
+  A BulkResolver using mLib's `bres' background resolver.
+
+  This is always available (and might use ADNS), but only does IPv4.
+  """
+
+  def __init__(me):
+    super(BresBulkResolver, me).__init__()
+    """Initialize the resolver."""
+    me._noutstand = 0
+
+  def _prepare(me, host, name):
+    """Arrange to resolve a NAME, reporting the results to HOST."""
+    host._resolv = M.SelResolveByName(
+      name,
+      lambda cname, alias, addr: me._resolved(host, cname, addr),
+      lambda: me._resolved(host, None, []))
+    me._noutstand += 1
 
   def run(me):
     """Run the background DNS resolver until it's finished."""
-    while me._resolvers:
-      M.select()
+    while me._noutstand: M.select()
 
-  def lookup(me, host):
+  def _resolved(me, host, cname, addr):
+    """Callback function: remember that ADDRs are the addresses for HOST."""
+    if not addr:
+      host.failed('(unknown failure)')
+    else:
+      if cname is not None: host.name = cname
+      for a in addr: host.addaddr('INET', a)
+    host._resolv = None
+    me._noutstand -= 1
+
+class AdnsBulkResolver (BaseBulkResolver):
+  """
+  A BulkResolver using ADNS, via the `adnshost' command-line tool.
+
+  This can do simultaneous IPv4 and IPv6 lookups and is quite shiny.
+  """
+
+  def __init__(me):
+    """Initialize the resolver."""
+
+    super(AdnsBulkResolver, me).__init__()
+
+    ## Start the external resolver process.
+    me._kid = SUB.Popen(['adnshost', '-afs'],
+                        stdin = SUB.PIPE, stdout = SUB.PIPE)
+
+    ## Set up the machinery for feeding input to the resolver.
+    me._in = me._kid.stdin
+    M.fdflags(me._in, fbic = OS.O_NONBLOCK, fxor = OS.O_NONBLOCK)
+    me._insel = M.SelFile(me._in.fileno(), M.SEL_WRITE, me._write)
+    me._inbuf, me._inoff, me._inlen = '', 0, 0
+    me._idmap = {}
+    me._nextid = 0
+
+    ## Set up the machinery for collecting the resolver's output.
+    me._out = me._kid.stdout
+    M.fdflags(me._out, fbic = OS.O_NONBLOCK, fxor = OS.O_NONBLOCK)
+    me._outline = M.SelLineBuffer(me._out,
+                                  lineproc = me._hostline, eofproc = me._eof)
+    me._outline.enable()
+
+    ## It's not finished yet.
+    me._done = False
+
+  def _prepare(me, host, name):
+    """Arrange for the resolver to resolve the name NAME."""
+
+    ## Work out the next job id, and associate that with the host record.
+    host.id = me._nextid; me._nextid += 1
+    me._namemap[name] = me._idmap[host.id] = host
+
+    ## Feed the name to the resolver process.
+    me._inbuf += name + '\n'
+    me._inlen += len(name) + 1
+    if not me._insel.activep: me._insel.enable()
+    while me._inoff < me._inlen: M.select()
+
+  def _write(me):
+    """Write material from `_inbuf' to the resolver when it's ready."""
+
+    ## Try to feed some more material to the resolver.
+    try: n = OS.write(me._in.fileno(), me._inbuf[me._inoff:])
+    except OSError, e:
+      if e.errno == E.EAGAIN or e.errno == E.EWOULDBLOCK: return
+      else: raise
+
+    ## If we're done, then clear the buffer.
+    me._inoff += n
+    if me._inoff >= me._inlen:
+      me._insel.disable()
+      me._inbuf, me._inoff, me._inlen = '', 0, 0
+
+  def _eof(me):
+    """Notice that the resolver has finished."""
+    me._outline.disable()
+    me._done = True
+    me._kid.wait()
+
+  def run(me):
     """
-    Fetch the address corresponding to HOST.
+    Tell the resolver it has all of our input now, and wait for it to finish.
     """
-    addr = me._namemap[host]
-    if addr is None:
-      raise ResolverFailure(host, '(unknown failure)')
-    return addr
-
-  def _resolved(me, host, addr):
-    """Callback function: remember that ADDR is the address for HOST."""
-    me._namemap[host] = addr
-    del me._resolvers[host]
+    me._in.close()
+    while not me._done: M.select()
+    if me._idmap:
+      raise Exception('adnshost failed to process all the requests')
+
+  def _hostline(me, line):
+    """Handle a host line from the resolver."""
+
+    ## Parse the line into fields.
+    (id, nrrs, stty, stocde, stmsg, owner, cname, ststr), _ = \
+        M.split(line, quotep = True)
+    id, nrrs = int(id), int(nrrs)
+
+    ## Find the right record.
+    host = me._idmap[id]
+    if stty != 'ok': host.failed(ststr)
+
+    ## Stash away the canonical name of the host.
+    host.name = cname == '$' and owner or cname
+
+    ## If there are no record lines to come, then remove this record from the
+    ## list of outstanding jobs.  Otherwise, switch to the handler for record
+    ## lines.
+    if not nrrs:
+      del me._idmap[id]
+    else:
+      me._outline.lineproc = me._rrline
+      me._nrrs = nrrs
+      me._outhost = host
+
+  def _rrline(me, line):
+    """Handle a record line from the resolver."""
+
+    ## Parse the line into fields.
+    ww, _ = M.split(line, quotep = True)
+    owner, type, af = ww[:3]
+
+    ## If this is an address record, and it looks like an interesting address
+    ## type, then stash the address.
+    if type == 'A' and (af == 'INET' or af == 'INET6'):
+      me._outhost.addaddr(af, ww[3])
+
+    ## Update the parser state.  If there are no more records for this job
+    ## then mark the job as done and switch back to expecting a host line.
+    me._nrrs -= 1
+    if not me._nrrs:
+      me._outline.lineproc = me._hostline
+      del me._idmap[me._outhost.id]
+      me._outhost = None
+
+## Select a bulk resolver.  If `adnshost' exists then we might as well use
+## it.
+BulkResolver = BresBulkResolver
+try:
+  p = SUB.Popen(['adnshost', '--version'],
+                stdin = SUB.PIPE, stdout = SUB.PIPE, stderr = SUB.PIPE)
+  _out, _err = p.communicate()
+  st = p.wait()
+  if st == 0: BulkResolver = AdnsBulkResolver
+except OSError:
+  pass
 
 ###--------------------------------------------------------------------------
 ### The configuration parser.
@@ -133,8 +339,9 @@ RX_CONT = RX.compile(r'''(?x) ^ \s+
 ## Match a $(VAR) configuration variable reference; group 1 is the VAR.
 RX_REF = RX.compile(r'(?x) \$ \( ([^)]+) \)')
 
-## Match a $[HOST] name resolution reference; group 1 is the HOST.
-RX_RESOLVE = RX.compile(r'(?x) \$ \[ ([^]]+) \]')
+## Match a $FLAGS[HOST] name resolution reference; group 1 are the flags;
+## group 2 is the HOST.
+RX_RESOLVE = RX.compile(r'(?x) \$ ([46*]*) \[ ([^]]+) \]')
 
 class ConfigSyntaxError (ExpectedError):
   def __init__(me, fname, lno, msg):
@@ -212,17 +419,17 @@ class ConfigSection (object):
 
   def _expand(me, string, resolvep):
     """
-    Expands $(...) and (optionally) $[...] placeholders in STRING.
+    Expands $(...) and (optionally) $FLAGS[...] placeholders in STRING.
 
     RESOLVEP is a boolean switch: do we bother to tax the resolver or not?
     This is turned off by MyConfigParser's resolve() method while it's
     collecting hostnames to be resolved.
     """
-    string = RX_REF.sub \
-             (lambda m: me.get(m.group(1), resolvep), string)
+    string = RX_REF.sub(lambda m: me.get(m.group(1), resolvep), string)
     if resolvep:
-      string = RX_RESOLVE.sub(lambda m: me._cp._resolver.lookup(m.group(1)),
-                              string)
+      string = RX_RESOLVE.sub(
+        lambda m: ' '.join(me._cp._resolver.lookup(m.group(2), m.group(1))),
+        string)
     return string
 
   def _parents(me):
@@ -352,8 +559,10 @@ class MyConfigParser (object):
     * It recognizes `$(VAR)' references to configuration variables during
       expansion and processes them correctly.
 
-    * It recognizes `$[HOST]' name-resolver requests and handles them
-      correctly.
+    * It recognizes `$FLAGS[HOST]' name-resolver requests and handles them
+      correctly.  FLAGS consists of characters `4' (IPv4 addresses), `6'
+      (IPv6 addresses), and `*' (all, space-separated, rather than just the
+      first).
 
     * Its parsing behaviour is well-defined.
 
@@ -466,7 +675,7 @@ class MyConfigParser (object):
       for key in sec.items():
         value = sec.get(key, resolvep = False)
         for match in RX_RESOLVE.finditer(value):
-          me._resolver.prepare(match.group(1))
+          me._resolver.prepare(match.group(2))
     me._resolver.run()
 
 ###--------------------------------------------------------------------------
index cdf8dc5..757e5cd 100644 (file)
@@ -37,6 +37,7 @@ pkstream \- forward UDP packets over streams
 .SH "SYNOPSIS"
 .
 .B pkstream
+.RB [ \-46 ]
 .RB [ \-l
 .IR port ]
 .RB [ \-p
@@ -97,6 +98,12 @@ version number to standard output and exits with status 0.
 .B "\-u, \-\-usage"
 Writes a brief usage summary to standard output and exits with status 0.
 .TP
+.B "\-4"
+Look up hostnames only as IPv4 addresses.
+.TP
+.B "\-6"
+Look up hostnames only as IPv6 addresses.
+.TP
 .BI "\-l, \-\-listen=" port
 Listen for connections on the given TCP
 .IR port .
index 4e565e0..8b2a058 100644 (file)
@@ -45,6 +45,7 @@
 
 #include <mLib/alloc.h>
 #include <mLib/bits.h>
+#include <mLib/darray.h>
 #include <mLib/dstr.h>
 #include <mLib/fdflags.h>
 #include <mLib/mdwopt.h>
 
 /*----- Data structures ---------------------------------------------------*/
 
+typedef union addr {
+  struct sockaddr sa;
+  struct sockaddr_in sin;
+  struct sockaddr_in6 sin6;
+} addr;
+
+DA_DECL(addr_v, addr);
+DA_DECL(str_v, const char *);
+
 typedef struct pk {
   struct pk *next;                     /* Next packet in the chain */
   octet *p, *o;                                /* Buffer start and current posn */
@@ -73,9 +83,10 @@ typedef struct pkstream {
 } pkstream;
 
 typedef struct connwait {
-  sel_file a;                          /* Selector */
-  struct sockaddr_in me;               /* Who I'm meant to be */
-  struct in_addr peer;                 /* Who my peer is */
+  unsigned f;                          /* Various flags */
+#define cwf_port 1u                    /*   Port is defined => listen */
+  sel_file *sfv;                       /* Selectors */
+  addr_v me, peer;                    /* Who I'm meant to be; who peer is */
 } connwait;
 
 /*----- Static variables --------------------------------------------------*/
@@ -83,7 +94,7 @@ typedef struct connwait {
 static sel_state sel;
 static connwait cw;
 static int fd_udp;
-static size_t pk_nmax = 128, pk_szmax = 1024 * 1024;
+static size_t pk_nmax = 128, pk_szmax = 1024*1024;
 
 /*----- Main code ---------------------------------------------------------*/
 
@@ -93,6 +104,111 @@ static int nonblockify(int fd)
 static int cloexec(int fd)
   { return (fdflags(fd, 0, 0, FD_CLOEXEC, FD_CLOEXEC)); }
 
+static socklen_t addrsz(const addr *a)
+{
+  switch (a->sa.sa_family) {
+    case AF_INET: return sizeof(a->sin);
+    case AF_INET6: return sizeof(a->sin6);
+    default: abort();
+  }
+}
+
+static int knownafp(int af)
+{
+  switch (af) {
+    case AF_INET: case AF_INET6: return (1);
+    default: return (0);
+  }
+}
+
+static int initsock(int fd, int af)
+{
+  int yes = 1;
+
+  switch (af) {
+    case AF_INET: break;
+    case AF_INET6:
+      if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)))
+       return (-1);
+      break;
+    default: abort();
+  }
+  return (0);
+}
+
+static const char *addrstr(const addr *a)
+{
+  static char buf[128];
+  socklen_t n = sizeof(buf);
+
+  if (getnameinfo(&a->sa, addrsz(a), buf, n, 0, 0, NI_NUMERICHOST))
+    return ("<addrstr failed>");
+  return (buf);
+}
+
+static int addreq(const addr *a, const addr *b)
+{
+  if (a->sa.sa_family != b->sa.sa_family) return (0);
+  switch (a->sa.sa_family) {
+    case AF_INET:
+      return (a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr);
+    case AF_INET6:
+      return (!memcmp(a->sin6.sin6_addr.s6_addr,
+                     b->sin6.sin6_addr.s6_addr,
+                     16) &&
+             a->sin6.sin6_scope_id == b->sin6.sin6_scope_id);
+    default:
+      abort();
+  }
+}
+
+static void initaddr(addr *a, int af)
+{
+  a->sa.sa_family = af;
+  switch (af) {
+    case AF_INET:
+      a->sin.sin_addr.s_addr = INADDR_ANY;
+      a->sin.sin_port = 0;
+      break;
+    case AF_INET6:
+      memset(a->sin6.sin6_addr.s6_addr, 0, 16);
+      a->sin6.sin6_port = 0;
+      a->sin6.sin6_flowinfo = 0;
+      a->sin6.sin6_scope_id = 0;
+      break;
+    default:
+      abort();
+  }
+}
+
+#define caf_addr 1u
+#define caf_port 2u
+static void copyaddr(addr *a, const struct sockaddr *sa, unsigned f)
+{
+  const struct sockaddr_in *sin;
+  const struct sockaddr_in6 *sin6;
+
+  a->sa.sa_family = sa->sa_family;
+  switch (sa->sa_family) {
+    case AF_INET:
+      sin = (const struct sockaddr_in *)sa;
+      if (f&caf_addr) a->sin.sin_addr = sin->sin_addr;
+      if (f&caf_port) a->sin.sin_port = sin->sin_port;
+      break;
+    case AF_INET6:
+      sin6 = (const struct sockaddr_in6 *)sa;
+      if (f&caf_addr) {
+       a->sin6.sin6_addr = sin6->sin6_addr;
+       a->sin6.sin6_scope_id = sin6->sin6_scope_id;
+      }
+      if (f&caf_port) a->sin6.sin6_port = sin6->sin6_port;
+      /* ??? flowinfo? */
+      break;
+    default:
+      abort();
+  }
+}
+
 static void dolisten(void);
 
 static void doclose(pkstream *p)
@@ -101,20 +217,16 @@ static void doclose(pkstream *p)
   close(p->w.fd);
   close(p->p.reader.fd);
   selpk_destroy(&p->p);
-  if (!(p->f & PKF_FULL))
-    sel_rmfile(&p->r);
-  if (p->npk)
-    sel_rmfile(&p->w);
+  if (!(p->f&PKF_FULL)) sel_rmfile(&p->r);
+  if (p->npk) sel_rmfile(&p->w);
   for (pk = p->pks; pk; pk = ppk) {
     ppk = pk->next;
     xfree(pk->p);
     xfree(pk);
   }
   xfree(p);
-  if (cw.me.sin_port != 0)
-    dolisten();
-  else
-    exit(0);
+  if (cw.f&cwf_port) dolisten();
+  else exit(0);
 }
 
 static void rdtcp(octet *b, size_t sz, pkbuf *pk, size_t *k, void *vp)
@@ -122,10 +234,7 @@ static void rdtcp(octet *b, size_t sz, pkbuf *pk, size_t *k, void *vp)
   pkstream *p = vp;
   size_t pksz;
 
-  if (!sz) {
-    doclose(p);
-    return;
-  }
+  if (!sz) { doclose(p); return; }
   pksz = LOAD16(b);
   if (pksz + 2 == sz) {
     DISCARD(write(fd_udp, b + 2, pksz));
@@ -151,8 +260,7 @@ static void wrtcp(int fd, unsigned mode, void *vp)
   }
 
   if ((n = writev(fd, iov, i)) < 0) {
-    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
-      return;
+    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
     moan("couldn't write to TCP socket: %s", strerror(errno));
     doclose(p);
     return;
@@ -173,14 +281,9 @@ static void wrtcp(int fd, unsigned mode, void *vp)
     }
   }
   p->pks = pk;
-  if (!pk) {
-    p->pk_tail = &p->pks;
-    sel_rmfile(&p->w);
-  }
-  if ((p->f & PKF_FULL) && p->npk < pk_nmax && p->szpk < pk_szmax) {
-    p->f &= ~PKF_FULL;
-    sel_addfile(&p->r);
-  }
+  if (!pk) { p->pk_tail = &p->pks; sel_rmfile(&p->w); }
+  if ((p->f&PKF_FULL) && p->npk < pk_nmax && p->szpk < pk_szmax)
+    { p->f &= ~PKF_FULL; sel_addfile(&p->r); }
 }
 
 static void rdudp(int fd, unsigned mode, void *vp)
@@ -205,15 +308,12 @@ static void rdudp(int fd, unsigned mode, void *vp)
   pk->n = n + 2;
   *p->pk_tail = pk;
   p->pk_tail = &pk->next;
-  if (!p->npk)
-    sel_addfile(&p->w);
+  if (!p->npk) sel_addfile(&p->w);
   sel_force(&p->w);
   p->npk++;
   p->szpk += n + 2;
-  if (p->npk >= pk_nmax || p->szpk >= pk_szmax) {
-    sel_rmfile(&p->r);
-    p->f |= PKF_FULL;
-  }
+  if (p->npk >= pk_nmax || p->szpk >= pk_szmax)
+    { sel_rmfile(&p->r); p->f |= PKF_FULL; }
 }
 
 static void dofwd(int fd_in, int fd_out)
@@ -233,82 +333,109 @@ static void dofwd(int fd_in, int fd_out)
 static void doaccept(int fd_s, unsigned mode, void *p)
 {
   int fd;
-  struct sockaddr_in sin;
-  socklen_t sz = sizeof(sin);
+  addr a;
+  socklen_t sz = sizeof(a);
+  size_t i, n;
 
-  if ((fd = accept(fd_s, (struct sockaddr *)&sin, &sz)) < 0) {
-    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
-      return;
+  if ((fd = accept(fd_s, &a.sa, &sz)) < 0) {
+    if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
     moan("couldn't accept incoming connection: %s", strerror(errno));
     return;
   }
-  if (cw.peer.s_addr != INADDR_ANY &&
-      cw.peer.s_addr != sin.sin_addr.s_addr) {
-    close(fd);
-    moan("rejecting connection from %s", inet_ntoa(sin.sin_addr));
-    return;
-  }
+  n = DA_LEN(&cw.peer);
+  if (!n) goto match;
+  for (i = 0; i < n; i++) if (addreq(&a, &DA(&cw.peer)[i])) goto match;
+  moan("rejecting connection from %s", addrstr(&a));
+  close(fd); return;
+match:
   if (nonblockify(fd) || cloexec(fd)) {
-    close(fd);
     moan("couldn't accept incoming connection: %s", strerror(errno));
-    return;
+    close(fd); return;
   }
   dofwd(fd, fd);
-  close(fd_s);
-  sel_rmfile(&cw.a);
+  n = DA_LEN(&cw.me);
+  for (i = 0; i < n; i++) { close(cw.sfv[i].fd); sel_rmfile(&cw.sfv[i]); }
 }
 
-static void dolisten(void)
+static void dolisten1(const addr *a, sel_file *sf)
 {
   int fd;
   int opt = 1;
 
-  if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ||
+  if ((fd = socket(a->sa.sa_family, SOCK_STREAM, IPPROTO_TCP)) < 0 ||
       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) ||
-      bind(fd, (struct sockaddr *)&cw.me, sizeof(cw.me)) ||
+      initsock(fd, a->sa.sa_family) ||
+      bind(fd, &a->sa, addrsz(a)) ||
       listen(fd, 1) || nonblockify(fd) || cloexec(fd))
     die(1, "couldn't set up listening socket: %s", strerror(errno));
-  sel_initfile(&sel, &cw.a, fd, SEL_READ, doaccept, 0);
-  sel_addfile(&cw.a);
+  sel_initfile(&sel, sf, fd, SEL_READ, doaccept, 0);
+  sel_addfile(sf);
 }
 
-static void parseaddr(const char *pp, struct in_addr *a, unsigned short *pt)
+static void dolisten(void)
 {
-  char *p = xstrdup(pp);
-  char *q = 0;
-  if (a && pt) {
-    strtok(p, ":");
-    q = strtok(0, "");
-    if (!q)
-      die(1, "missing port number in address `%s'", p);
-  } else if (pt) {
-    q = p;
+  size_t i, n;
+
+  n = DA_LEN(&cw.me);
+  for (i = 0; i < n; i++)
+    dolisten1(&DA(&cw.me)[i], &cw.sfv[i]);
+}
+
+static void pushaddrs(addr_v *av, const struct addrinfo *ailist)
+{
+  const struct addrinfo *ai;
+  size_t i, n;
+
+  for (ai = ailist, n = 0; ai; ai = ai->ai_next)
+    if (knownafp(ai->ai_family)) n++;
+  DA_ENSURE(av, n);
+  for (i = DA_LEN(av), ai = ailist; ai; ai = ai->ai_next) {
+    if (!knownafp(ai->ai_family)) continue;
+    initaddr(&DA(av)[i], ai->ai_family);
+    copyaddr(&DA(av)[i++], ai->ai_addr, caf_addr | caf_port);
   }
+  DA_EXTEND(av, n);
+}
 
-  if (a) {
-    struct hostent *h;
-    if ((h = gethostbyname(p)) == 0)
-      die(1, "unknown host `%s'", p);
-    memcpy(a, h->h_addr, sizeof(*a));
+#define paf_parse 1u
+static void parseaddr(const struct addrinfo *aihint,
+                     const char *host, const char *svc, unsigned f,
+                     struct addrinfo **ai_out)
+{
+  char *alloc = 0, *sep;
+  int err;
+
+  if (f&paf_parse) {
+    alloc = xstrdup(host);
+    if (alloc[0] != '[') {
+      if ((sep = strchr(alloc, ':')) == 0)
+       die(1, "missing port number in address `%s'", host);
+      host = alloc; *sep = 0; svc = sep + 1;
+    } else {
+      if ((sep = strchr(alloc, ']')) == 0 || sep[1] != ':')
+       die(1, "bad syntax in address `%s:'", host);
+      host = alloc + 1; *sep = 0; svc = sep + 2;
+    }
   }
 
-  if (pt) {
-    struct servent *s;
-    char *qq;
-    unsigned long n;
-    if ((s = getservbyname(q, "tcp")) != 0)
-      *pt = s->s_port;
-    else if ((n = strtoul(q, &qq, 0)) == 0 || *qq || n > 0xffff)
-      die(1, "bad port number `%s'", q);
+  err = getaddrinfo(host, svc, aihint, ai_out);
+  if (err) {
+    if (host && svc) {
+      die(1, "failed to resolve hostname `%s', service `%s': %s",
+         host, svc, gai_strerror(err));
+    } else if (host)
+      die(1, "failed to resolve hostname `%s': %s", host, gai_strerror(err));
     else
-      *pt = htons(n);
+      die(1, "failed to resolve service `%s': %s", svc, gai_strerror(err));
   }
+
+  xfree(alloc);
 }
 
 static void usage(FILE *fp)
 {
   pquis(fp,
-       "Usage: $ [-l PORT] [-b ADDR] [-p ADDR] [-c ADDR:PORT]\n\
+       "Usage: $ [-46] [-l PORT] [-b ADDR] [-p ADDR] [-c ADDR:PORT]\n\
        ADDR:PORT ADDR:PORT\n");
 }
 
@@ -327,6 +454,8 @@ Options:\n\
 -v, --version          Display version number.\n\
 -u, --usage            Display pointless usage message.\n\
 \n\
+-4, --ipv4             Restrict to IPv4 only.\n\
+-6, --ipv6             Restrict to IPv6 only.\n\
 -l, --listen=PORT      Listen for connections to TCP PORT.\n\
 -p, --peer=ADDR                Only accept connections from IP ADDR.\n\
 -b, --bind=ADDR                Bind to ADDR before connecting.\n\
@@ -340,29 +469,29 @@ stdout; though it can use TCP sockets instead.\n\
 int main(int argc, char *argv[])
 {
   unsigned f = 0;
-  unsigned short pt;
-  struct sockaddr_in connaddr, bindaddr;
-  struct sockaddr_in udp_me, udp_peer;
+  str_v bindhosts = DA_INIT, peerhosts = DA_INIT;
+  const char *bindsvc = 0;
+  addr bindaddr;
+  const char *connhost = 0;
+  struct addrinfo aihint = { 0 }, *ai, *ailist;
+  int af = AF_UNSPEC;
+  int fd = -1;
   int len = 65536;
+  size_t i, n;
 
 #define f_bogus 1u
 
+  cw.f = 0;
+
   ego(argv[0]);
-  bindaddr.sin_family = AF_INET;
-  bindaddr.sin_addr.s_addr = INADDR_ANY;
-  bindaddr.sin_port = 0;
-  connaddr.sin_family = AF_INET;
-  connaddr.sin_addr.s_addr = INADDR_ANY;
-  cw.me.sin_family = AF_INET;
-  cw.me.sin_addr.s_addr = INADDR_ANY;
-  cw.me.sin_port = 0;
-  cw.peer.s_addr = INADDR_ANY;
   sel_init(&sel);
   for (;;) {
     static struct option opt[] = {
       { "help",                        0,              0,      'h' },
       { "version",             0,              0,      'v' },
       { "usage",               0,              0,      'u' },
+      { "ipv4",                        0,              0,      '4' },
+      { "ipv6",                        0,              0,      '6' },
       { "listen",              OPTF_ARGREQ,    0,      'l' },
       { "peer",                        OPTF_ARGREQ,    0,      'p' },
       { "bind",                        OPTF_ARGREQ,    0,      'b' },
@@ -371,70 +500,125 @@ int main(int argc, char *argv[])
     };
     int i;
 
-    i = mdwopt(argc, argv, "hvul:p:b:c:", opt, 0, 0, 0);
+    i = mdwopt(argc, argv, "hvu46l:p:b:c:", opt, 0, 0, 0);
     if (i < 0)
       break;
     switch (i) {
-      case 'h':
-       help(stdout);
-       exit(0);
-      case 'v':
-       version(stdout);
-       exit(0);
-      case 'u':
-       usage(stdout);
-       exit(0);
-      case 'l':
-       parseaddr(optarg, 0, &pt);
-       cw.me.sin_port = pt;
-       break;
-      case 'p':
-       parseaddr(optarg, &cw.peer, 0);
-       break;
-      case 'b':
-       parseaddr(optarg, &bindaddr.sin_addr, 0);
-       cw.me.sin_addr = bindaddr.sin_addr;
-       break;
-      case 'c':
-       parseaddr(optarg, &connaddr.sin_addr, &pt);
-       connaddr.sin_port = pt;
-       break;
-      default:
-       f |= f_bogus;
-       break;
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+      case '4': af = AF_INET; break;
+      case '6': af = AF_INET6; break;
+      case 'l': bindsvc = optarg; break;
+      case 'p': DA_PUSH(&peerhosts, optarg); break;
+      case 'b': DA_PUSH(&bindhosts, optarg); break;
+      case 'c': connhost = optarg; break;
+      default: f |= f_bogus; break;
+    }
+  }
+  if (optind + 2 != argc || (f&f_bogus)) { usage(stderr); exit(1); }
+
+  if (DA_LEN(&bindhosts) && !bindsvc && !connhost)
+    die(1, "bind addr only makes sense when listening or connecting");
+  if (DA_LEN(&peerhosts) && !bindsvc)
+    die(1, "peer addr only makes sense when listening");
+  if (bindsvc && connhost)
+    die(1, "can't listen and connect");
+
+  aihint.ai_family = af;
+  DA_CREATE(&cw.me); DA_CREATE(&cw.peer);
+
+  n = DA_LEN(&bindhosts);
+  if (n || bindsvc) {
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+    if (!n) {
+      parseaddr(&aihint, 0, bindsvc, 0, &ailist);
+      pushaddrs(&cw.me, ailist);
+      freeaddrinfo(ailist);
+    } else if (!bindsvc) {
+      if (n != 1) die(1, "can only bind to one address as client");
+      parseaddr(&aihint, DA(&bindhosts)[0], 0, 0, &ailist);
+      for (ai = ailist; ai && !knownafp(ai->ai_family); ai = ai->ai_next);
+      if (!ai)
+       die(1, "no usable addresses returned for `%s'", DA(&bindhosts)[0]);
+      initaddr(&bindaddr, ai->ai_family);
+      copyaddr(&bindaddr, ai->ai_addr, caf_addr);
+      aihint.ai_family = ai->ai_family;
+      freeaddrinfo(ailist);
+    } else for (i = 0; i < n; i++) {
+      parseaddr(&aihint, DA(&bindhosts)[i], bindsvc, 0, &ailist);
+      pushaddrs(&cw.me, ailist);
+      freeaddrinfo(ailist);
+    }
+    if (bindsvc) {
+      cw.f |= cwf_port;
+      n = DA_LEN(&cw.me);
+      cw.sfv = xmalloc(n*sizeof(*cw.sfv));
     }
   }
-  if (optind + 2 != argc || (f & f_bogus)) {
-    usage(stderr);
-    exit(1);
+
+  n = DA_LEN(&peerhosts);
+  if (n) {
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG;
+    for (i = 0; i < n; i++) {
+      parseaddr(&aihint, DA(&peerhosts)[i], 0, 0, &ailist);
+      pushaddrs(&cw.peer, ailist);
+      freeaddrinfo(ailist);
+    }
+    if (!DA_LEN(&cw.peer)) die(1, "no usable peer addresses");
   }
 
-  udp_me.sin_family = udp_peer.sin_family = AF_INET;
-  parseaddr(argv[optind], &udp_me.sin_addr, &pt);
-  udp_me.sin_port = pt;
-  parseaddr(argv[optind + 1], &udp_peer.sin_addr, &pt);
-  udp_peer.sin_port = pt;
+  if (connhost) {
+    aihint.ai_socktype = SOCK_STREAM;
+    aihint.ai_protocol = IPPROTO_TCP;
+    aihint.ai_flags = AI_ADDRCONFIG;
+    parseaddr(&aihint, connhost, 0, paf_parse, &ailist);
+
+    for (ai = ailist; ai; ai = ai->ai_next) {
+      if ((fd = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
+         !initsock(fd, ai->ai_family) &&
+         (!DA_LEN(&bindhosts) ||
+          !bind(fd, &bindaddr.sa, addrsz(&bindaddr))) &&
+         !connect(fd, ai->ai_addr, ai->ai_addrlen))
+       goto conn_tcp;
+      if (fd >= 0) close(fd);
+    }
+    die(1, "couldn't connect to TCP server: %s", strerror(errno));
+  conn_tcp:
+    if (nonblockify(fd) || cloexec(fd))
+      die(1, "couldn't connect to TCP server: %s", strerror(errno));
+  }
 
-  if ((fd_udp = socket(PF_INET, SOCK_DGRAM, 0)) < 0 ||
-      bind(fd_udp, (struct sockaddr *)&udp_me, sizeof(udp_me)) ||
-      connect(fd_udp, (struct sockaddr *)&udp_peer, sizeof(udp_peer)) ||
+  aihint.ai_family = af;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_protocol = IPPROTO_UDP;
+  aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+  parseaddr(&aihint, argv[optind], 0, paf_parse, &ailist);
+  for (ai = ailist; ai && !knownafp(ai->ai_family); ai = ai->ai_next);
+  if (!ai) die(1, "no usable addresses returned for `%s'", argv[optind]);
+  if ((fd_udp = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP)) < 0 ||
+      initsock(fd_udp, ai->ai_family) ||
+      nonblockify(fd_udp) || cloexec(fd_udp) ||
       setsockopt(fd_udp, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
       setsockopt(fd_udp, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len)) ||
-      nonblockify(fd_udp) || cloexec(fd_udp))
+      bind(fd_udp, ai->ai_addr, ai->ai_addrlen))
     die(1, "couldn't set up UDP socket: %s", strerror(errno));
-
-  if (cw.me.sin_port != 0)
-    dolisten();
-  else if (connaddr.sin_addr.s_addr != INADDR_ANY) {
-    int fd;
-    if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 ||
-       bind(fd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) ||
-       connect(fd, (struct sockaddr *)&connaddr, sizeof(connaddr)) ||
-       nonblockify(fd) || cloexec(fd))
-      die(1, "couldn't connect to TCP server: %s", strerror(errno));
-    dofwd(fd, fd);
-  } else
-    dofwd(STDIN_FILENO, STDOUT_FILENO);
+  freeaddrinfo(ailist);
+  aihint.ai_family = ai->ai_family;
+  aihint.ai_flags = AI_ADDRCONFIG;
+  parseaddr(&aihint, argv[optind + 1], 0, paf_parse, &ailist);
+  for (ai = ailist; ai; ai = ai->ai_next)
+    if (!connect(fd_udp, ai->ai_addr, ai->ai_addrlen)) goto conn_udp;
+  die(1, "couldn't set up UDP socket: %s", strerror(errno));
+conn_udp:
+
+  if (bindsvc) dolisten();
+  else if (connhost) dofwd(fd, fd);
+  else dofwd(STDIN_FILENO, STDOUT_FILENO);
 
   for (;;) {
     if (sel_select(&sel) && errno != EINTR)
index cab0fb2..e939919 100644 (file)
@@ -58,6 +58,9 @@ The command line contains a sequence of directives, each of which has
 the form
 .IB command : arg \c
 .BR : ...
+(The delimiter character can be changed using the
+.B \-d
+command-line option.)
 A list of directives can be stored in a file, one per line, and included
 using the
 .B include
@@ -76,6 +79,12 @@ successfully.
 .B "\-u, \-\-usage"
 Write a usage message to standard output, and exit successfully.
 .TP
+.BI "\-d, \-\-delimiter=" char
+Use
+.I char
+as the delimiter to separate argument names in directives, rather than
+.RB ` : '.
+.TP
 .BI "\-k, \-\-keyring=" file
 Read keys from
 .IR file .
@@ -85,6 +94,14 @@ in the current directory.
 .SS "Directives"
 A directive is ignored if it is empty, or if its first character is a
 .RB ` # '.
+Directives consist of a name followed by zero or more arguments,
+separated by a delimiter character.  The default delimiter is
+.RB ` : ',
+but this can be overridden using the
+.B \-d
+option (see above); this manual uses
+.RB ` : '
+consistently as the delimiter character.
 The following directives are recognized.
 .TP
 .BI peer: name : local-port : remote-addr : remote-port
@@ -102,9 +119,11 @@ Both
 .I local-port
 and
 .I remote-port
-must be numbers;
+may be numbers or UDP service names;
 .I remote-addr
-may be a hostname or an IP address in dotted-quad format.  Exactly two
+may be a hostname, an IPv4 address in dotted-quad format, or an IPv6
+address in hex-and-colons format (this last obviously requires selecting
+a different delimeter character).  Exactly two
 .B peer
 directives must be present.  The one first registered is the
 .I left
@@ -114,6 +133,16 @@ peer.  The two peers must use
 .I different
 local ports.
 .TP
+.BI peer4: name : local-port : remote-addr : remote-port
+As for
+.I peer
+(see above), but force the use of IPv4.
+.TP
+.BI peer6: name : local-port : remote-addr : remote-port
+As for
+.I peer
+(see above), but force the use of IPv6.
+.TP
 .BI include: file
 Read more directives from
 .IR file .
index bd57306..6acf2a1 100644 (file)
@@ -62,9 +62,9 @@
 #include <catacomb/mprand.h>
 #include <catacomb/dh.h>
 
+#include <catacomb/chacha20.h>
 #include <catacomb/noise.h>
 #include <catacomb/rand.h>
-#include <catacomb/rc4.h>
 
 #include "util.h"
 
@@ -97,6 +97,7 @@ static peer peers[2];
 static unsigned npeer = 0;
 static key_file keys;
 static grand *rng;
+static const char *delim = ":";
 
 #define PASS(f, buf, sz) ((f) ? (f)->func((f), (buf), (sz)) : (void)0)
 #define RND(i) (rng->ops->range(rng, (i)))
@@ -114,46 +115,52 @@ static void dopacket(int fd, unsigned mode, void *vv)
   }
 }
 
-static void addpeer(unsigned ac, char **av)
+static void addpeer_common(const char *cmd, int af, unsigned ac, char **av)
 {
-  struct hostent *h;
-  struct sockaddr_in sin;
-  int len = PKBUFSZ;
+  struct addrinfo aihint = { 0 }, *ai0, *ai1;
+  int len = PKBUFSZ, yes = 1;
+  int err;
   peer *p;
   int fd;
 
-  if (ac != 4)
-    die(1, "syntax: peer:NAME:PORT:ADDR:PORT");
-  if (npeer >= 2)
-    die(1, "enough peers already");
-  if (!key_bytag(&keys, av[0]))
-    die(1, "no key named `%s'", av[0]);
+  if (ac != 4) die(1, "syntax: %s:NAME:PORT:ADDR:PORT", cmd);
+  if (!key_bytag(&keys, av[0])) die(1, "no key named `%s'", av[0]);
   p = &peers[npeer++];
   p->name = xstrdup(av[0]);
-  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+  aihint.ai_family = af;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_flags = AI_ADDRCONFIG;
+  if ((err = getaddrinfo(av[2], av[3], &aihint, &ai1)) != 0)
+    die(1, "getaddrinfo(`%s', `%s'): %s", av[2], av[3], gai_strerror(err));
+  aihint.ai_family = ai1->ai_family;
+  aihint.ai_flags = AI_ADDRCONFIG | AI_PASSIVE;
+  if ((err = getaddrinfo(0, av[1], &aihint, &ai0)) != 0)
+    die(1, "getaddrinfo(passive, `%s'): %s", av[1], gai_strerror(err));
+  if ((fd = socket(ai1->ai_family, SOCK_DGRAM, ai1->ai_protocol)) < 0)
     die(1, "socket: %s", strerror(errno));
-  fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
-  memset(&sin, 0, sizeof(sin));
-  sin.sin_family = AF_INET;
-  sin.sin_addr.s_addr = INADDR_ANY;
-  sin.sin_port = htons(atoi(av[1]));
-  if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
+  if (ai1->ai_family == AF_INET6) {
+    if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)))
+      die(1, "setsockopt: %s", strerror(errno));
+  }
+  if (bind(fd, ai0->ai_addr, ai0->ai_addrlen))
     die(1, "bind: %s", strerror(errno));
-  memset(&sin, 0, sizeof(sin));
-  sin.sin_family = AF_INET;
-  if ((h = gethostbyname(av[2])) == 0)
-    die(1, "gethostbyname `%s'", av[2]);
   if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
       setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len)))
     die(1, "setsockopt: %s", strerror(errno));
-  memcpy(&sin.sin_addr, h->h_addr, sizeof(sin.sin_addr));
-  sin.sin_port = htons(atoi(av[3]));
-  if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)))
+  if (connect(fd, ai1->ai_addr, ai1->ai_addrlen))
     die(1, "connect: %s", strerror(errno));
   sel_initfile(&sel, &p->sf, fd, SEL_READ, dopacket, p);
   sel_addfile(&p->sf);
+  freeaddrinfo(ai0); freeaddrinfo(ai1);
 }
 
+static void addpeer(unsigned ac, char **av)
+  { addpeer_common("peer", AF_UNSPEC, ac, av); }
+static void addpeer4(unsigned ac, char **av)
+  { addpeer_common("peer4", AF_INET, ac, av); }
+static void addpeer6(unsigned ac, char **av)
+  { addpeer_common("peer6", AF_INET6, ac, av); }
+
 /*----- Fork filter -------------------------------------------------------*/
 
 typedef struct forknode {
@@ -184,8 +191,7 @@ static void dofork(filter *f, const octet *buf, size_t sz)
 static void addfork(filter *f, unsigned ac, char **av)
 {
   forkfilt *ff;
-  if (ac != 1)
-    die(1, "syntax: filt:fork:NAME");
+  if (ac != 1) die(1, "syntax: filt:fork:NAME");
   ff = CREATE(forkfilt);
   ff->name = xstrdup(av[0]);
   ff->fn = 0;
@@ -201,23 +207,18 @@ static void nextfork(unsigned ac, char **av)
   forknode *fn, **ffn;
   peer *p;
 
-  if (ac < 1)
-    die(1, "syntax: next:NAME:...");
+  if (ac < 1) die(1, "syntax: next:NAME:...");
   for (i = 0; i < 2; i++) {
     p = &peers[i];
     for (f = p->f; f; f = f->next) {
-      if (f->func != dofork)
-       continue;
+      if (f->func != dofork) continue;
       ff = f->state;
-      for (j = 0; j < ac; j++) {
-       if (strcmp(av[j], ff->name) == 0)
-         goto match;
-      }
+      for (j = 0; j < ac; j++)
+       if (strcmp(av[j], ff->name) == 0) goto match;
       continue;
     match:
       fn = CREATE(forknode);
-      for (ffn = &ff->fn; *ffn; ffn = &(*ffn)->next)
-       ;
+      for (ffn = &ff->fn; *ffn; ffn = &(*ffn)->next);
       fn->f = f->next;
       f->next = 0;
       fn->next = 0;
@@ -248,13 +249,10 @@ static void docorrupt(filter *f, const octet *buf, size_t sz)
 static void addcorrupt(filter *f, unsigned ac, char **av)
 {
   corrupt *c;
-  if (ac > 1)
-    die(1, "syntax: filt:corrupt[:P-CORRUPT]");
+  if (ac > 1) die(1, "syntax: filt:corrupt[:P-CORRUPT]");
   c = CREATE(corrupt);
-  if (ac > 0)
-    c->p_corrupt = atoi(av[0]);
-  else
-    c->p_corrupt = 5;
+  if (ac > 0) c->p_corrupt = atoi(av[0]);
+  else c->p_corrupt = 5;
   f->state = c;
   f->func = docorrupt;
 }
@@ -269,22 +267,17 @@ static void dodrop(filter *f, const octet *buf, size_t sz)
 {
   drop *d = f->state;
 
-  if (!RND(d->p_drop))
-    puts("drop packet");
-  else
-    PASS(f->next, buf, sz);
+  if (!RND(d->p_drop)) puts("drop packet");
+  else PASS(f->next, buf, sz);
 }
 
 static void adddrop(filter *f, unsigned ac, char **av)
 {
   drop *d;
-  if (ac > 1)
-    die(1, "syntax: filt:drop[:P-DROP]");
+  if (ac > 1) die(1, "syntax: filt:drop[:P-DROP]");
   d = CREATE(drop);
-  if (ac > 0)
-    d->p_drop = atoi(av[0]);
-  else
-    d->p_drop = 5;
+  if (ac > 0) d->p_drop = atoi(av[0]);
+  else d->p_drop = 5;
   f->state = d;
   f->func = dodrop;
 }
@@ -333,6 +326,8 @@ static void dsend(delaynode *dn, unsigned force)
 {
   delay *d = dn->d;
   delaynode *ddn;
+  unsigned i;
+
   fputs(" send...\n", stdout);
   assert(dn->buf);
   PASS(d->f->next, dn->buf, dn->sz);
@@ -355,7 +350,7 @@ static void dsend(delaynode *dn, unsigned force)
       ddn->flag = 0;
       printf(" move id %u from slot %u to slot %u", ddn->seq, ddn->i, dn->i);
     }
-    { unsigned i; for (i = 0; i < d->n; i++) assert(d->q[i].buf); }
+    for (i = 0; i < d->n; i++) assert(d->q[i].buf);
     fputs(" remove", stdout);
   }
 }
@@ -400,19 +395,14 @@ static void adddelay(filter *f, unsigned ac, char **av)
   delay *d;
   unsigned i;
 
-  if (ac < 1 || ac > 3)
-    die(1, "syntax: filt:delay:QLEN[:MILLIS:P-REPLAY]");
+  if (ac < 1 || ac > 3) die(1, "syntax: filt:delay:QLEN[:MILLIS:P-REPLAY]");
   d = CREATE(delay);
   d->max = atoi(av[0]);
-  if (ac > 1)
-    d->t = strtoul(av[1], 0, 10);
-  else
-    d->t = 100;
+  if (ac > 1) d->t = strtoul(av[1], 0, 10);
+  else d->t = 100;
   d->t *= 1000;
-  if (ac > 2)
-    d->p_replay = atoi(av[2]);
-  else
-    d->p_replay = 20;
+  if (ac > 2) d->p_replay = atoi(av[2]);
+  else d->p_replay = 20;
   d->n = 0;
   d->q = xmalloc(d->max * sizeof(delaynode));
   d->f = f;
@@ -436,8 +426,7 @@ static void dosend(filter *f, const octet *buf, size_t sz)
 
 static void addsend(filter *f, unsigned ac, char **av)
 {
-  if (ac)
-    die(1, "syntax: filt:send");
+  if (ac) die(1, "syntax: filt:send");
   f->func = dosend;
 }
 
@@ -457,21 +446,16 @@ static void dofilter(peer *from, peer *to, unsigned ac, char **av)
 {
   filter **ff, *f = CREATE(filter);
   const struct filtab *ft;
-  if (ac < 1)
-    die(1, "syntax: {l,r,}filt:NAME:...");
+  if (ac < 1) die(1, "syntax: {l,r,}filt:NAME:...");
   f->next = 0;
   f->p_from = from;
   f->p_to = to;
   f->state = 0;
-  for (ff = &from->f; *ff; ff = &(*ff)->next)
-    ;
+  for (ff = &from->f; *ff; ff = &(*ff)->next);
   *ff = f;
-  for (ft = filtab; ft->name; ft++) {
-    if (strcmp(av[0], ft->name) == 0) {
-      ft->func(f, ac - 1, av + 1);
-      return;
-    }
-  }
+  for (ft = filtab; ft->name; ft++)
+    if (strcmp(av[0], ft->name) == 0)
+      { ft->func(f, ac - 1, av + 1); return; }
   die(1, "unknown filter `%s'", av[0]);
 }
 
@@ -500,8 +484,7 @@ static void floodtimer(struct timeval *tv, void *vv)
   sz /= 2;
 
   rng->ops->fill(rng, buf, sz);
-  if (f->type < 0x100)
-    buf[0] = f->type;
+  if (f->type < 0x100) buf[0] = f->type;
   puts("flood packet");
   PASS(f->p->f, buf, sz);
   setflood(f);
@@ -518,22 +501,15 @@ static void setflood(flood *f)
 static void doflood(peer *p, unsigned ac, char **av)
 {
   flood *f;
-  if (ac > 3)
-    die(1, "syntax: flood[:TYPE:MILLIS:SIZE]");
+  if (ac > 3) die(1, "syntax: flood[:TYPE:MILLIS:SIZE]");
   f = CREATE(flood);
   f->p = p;
-  if (ac > 0)
-    f->type = strtoul(av[0], 0, 16);
-  else
-    f->type = 0x100;
-  if (ac > 1)
-    f->t = atoi(av[1]);
-  else
-    f->t = 10;
-  if (ac > 2)
-    f->sz = atoi(av[2]);
-  else
-    f->sz = 128;
+  if (ac > 0) f->type = strtoul(av[0], 0, 16);
+  else f->type = 0x100;
+  if (ac > 1) f->t = atoi(av[1]);
+  else f->t = 10;
+  if (ac > 2) f->sz = atoi(av[2]);
+  else f->sz = 128;
   f->t *= 1000;
   setflood(f);
 }
@@ -568,15 +544,11 @@ static void include(unsigned ac, char **av)
 {
   FILE *fp;
   dstr d = DSTR_INIT;
-  if (!ac)
-    die(1, "syntax: include:FILE:...");
+  if (!ac) die(1, "syntax: include:FILE:...");
   while (*av) {
     if ((fp = fopen(*av, "r")) == 0)
       die(1, "fopen `%s': %s", *av, strerror(errno));
-    while (dstr_putline(&d, fp) != EOF) {
-      parse(d.buf);
-      DRESET(&d);
-    }
+    while (dstr_putline(&d, fp) != EOF) { parse(d.buf); DRESET(&d); }
     fclose(fp);
     av++;
   }
@@ -587,6 +559,8 @@ const struct cmdtab {
   void (*func)(unsigned /*ac*/, char **/*av*/);
 } cmdtab[] = {
   { "peer",    addpeer },
+  { "peer4",   addpeer4 },
+  { "peer6",   addpeer6 },
   { "include", include },
   { "filt",    addfilter },
   { "lfilt",   addlfilter },
@@ -606,12 +580,11 @@ static void parse(char *p)
   unsigned c = 0;
   const struct cmdtab *ct;
 
-  p = strtok(p, ":");
-  if (!p || *p == '#')
-    return;
+  p = strtok(p, delim);
+  if (!p || *p == '#') return;
   do {
     v[c++] = p;
-    p = strtok(0, ":");
+    p = strtok(0, delim);
   } while (p && c < AVMAX - 1);
   v[c] = 0;
   for (ct = cmdtab; ct->name; ct++) {
@@ -629,7 +602,7 @@ static void version(FILE *fp)
   { pquis(fp, "$, TrIPE version " VERSION "\n"); }
 
 static void usage(FILE *fp)
-  { pquis(fp, "Usage: $ [-k KEYRING] DIRECTIVE...\n"); }
+  { pquis(fp, "Usage: $ [-d CHAR] [-k KEYRING] DIRECTIVE...\n"); }
 
 static void help(FILE *fp)
 {
@@ -643,10 +616,11 @@ Options:\n\
 -v, --version          Show the version number.\n\
 -u, --usage            Show terse usage summary.\n\
 \n\
+-d, --delimiter=CHAR   Use CHAR rather than `:' as delimiter.\n\
 -k, --keyring=FILE     Fetch keys from FILE.\n\
 \n\
 Directives:\n\
-  peer:NAME:LOCAL-PORT:REMOTE-ADDR:REMOTE-PORT\n\
+  peer{,4,6}:NAME:LOCAL-PORT:REMOTE-ADDR:REMOTE-PORT\n\
   include:FILE\n\
   {,l,r}filt:FILTER:ARGS:...\n\
   next:TAG\n\
@@ -666,7 +640,8 @@ int main(int argc, char *argv[])
   const char *kfname = "keyring.pub";
   int i;
   unsigned f = 0;
-  char buf[16];
+  char buf[32];
+  static octet zero[CHACHA_NONCESZ];
 
 #define f_bogus 1u
 
@@ -676,44 +651,35 @@ int main(int argc, char *argv[])
       { "help",                0,              0,      'h' },
       { "version",     0,              0,      'v' },
       { "usage",       0,              0,      'u' },
+      { "delimiter",   OPTF_ARGREQ,    0,      'd' },
       { "keyring",     OPTF_ARGREQ,    0,      'k' },
       { 0,             0,              0,      0 }
     };
-    if ((i = mdwopt(argc, argv, "hvuk:", opt, 0, 0, 0)) < 0)
-      break;
+    if ((i = mdwopt(argc, argv, "hvud:k:", opt, 0, 0, 0)) < 0) break;
     switch (i) {
-      case 'h':
-       help(stdout);
-       exit(0);
-      case 'v':
-       version(stdout);
-       exit(0);
-      case 'u':
-       usage(stdout);
-       exit(0);
-      case 'k':
-       kfname = optarg;
-       break;
-      default:
-       f |= f_bogus;
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+      case 'd':
+       if (!optarg[0] || optarg[1])
+         die(1, "delimiter must be a single character");
+       delim = optarg;
        break;
+      case 'k': kfname = optarg; break;
+      default: f |= f_bogus; break;
     }
   }
-  if (f & f_bogus) {
-    usage(stderr);
-    exit(1);
-  }
+  if (f & f_bogus) { usage(stderr); exit(1); }
+
   rand_noisesrc(RAND_GLOBAL, &noise_source);
-  rand_seed(RAND_GLOBAL, 160);
+  rand_seed(RAND_GLOBAL, 256);
   rand_get(RAND_GLOBAL, buf, sizeof(buf));
-  rng = rc4_rand(buf, sizeof(buf));
+  rng = chacha20_rand(buf, sizeof(buf), zero);
   sel_init(&sel);
   if (key_open(&keys, kfname, KOPEN_READ, key_moan, 0))
     die(1, "couldn't open `%s': %s", kfname, strerror(errno));
-  for (i = optind; i < argc; i++)
-    parse(argv[i]);
-  if (npeer != 2)
-    die(1, "need two peers");
+  for (i = optind; i < argc; i++) parse(argv[i]);
+  if (npeer != 2) die(1, "need two peers");
   for (;;) {
     if (sel_select(&sel) && errno != EINTR)
       die(1, "select failed: %s", strerror(errno));
index 0126dc5..a9be668 100644 (file)
@@ -879,8 +879,10 @@ class TripeCommandDispatcher (TripeConnection):
                                *['PING'] +
                                _kwopts(kw, ['timeout']) +
                                [peer]))
-  def port(me):
-    return _oneline(me.command('PORT', filter = _tokenjoin))
+  def port(me, af = None):
+    return _oneline(me.command('PORT',
+                               *((af is not None) and [af] or []),
+                               filter = _tokenjoin))
   def quit(me):
     return _simple(me.command('QUIT'))
   def reload(me):
index 2a1ff28..f569b45 100644 (file)
@@ -28,7 +28,8 @@ sbin_PROGRAMS          =
 noinst_PROGRAMS                 =
 man_MANS                =
 
-LDADD                   = $(libtripe) $(libpriv) $(catacomb_LIBS)
+LDADD                   = $(libtripe) $(libpriv) \
+                               $(catacomb_LIBS) $(ADNS_LIBS)
 
 ###--------------------------------------------------------------------------
 ### The main server.
index 140d0b6..76a358e 100644 (file)
@@ -74,11 +74,21 @@ void am_destroy(addrmap *m)
 
 static uint32 hash(const addr *a)
 {
+  size_t i;
+  uint32 h;
+
   switch (a->sa.sa_family) {
     case AF_INET:
-      return (U32((AF_INET * 0x4eaac1b7ul) +
-                 (a->sin.sin_addr.s_addr * 0xa5dbc837) +
-                 (a->sin.sin_port * 0x3b049e83)));
+      return (U32(0x4eaac1b7ul*AF_INET +
+                 0xa5dbc837ul*a->sin.sin_addr.s_addr +
+                 0x3b049e83ul*a->sin.sin_port));
+    case AF_INET6:
+      for (i = 0, h = 0; i < 16; i++)
+       h = 0x6bd26a67ul*h + a->sin6.sin6_addr.s6_addr[i];
+      return (U32(0x4eaac1b7ul*AF_INET6 +
+                 0xa5dbc837ul*h +
+                 0x1d94eab4ul*a->sin6.sin6_scope_id +
+                 0x3b049e83ul*a->sin6.sin6_port));
     default:
       abort();
   }
@@ -99,6 +109,10 @@ static int addreq(const addr *a, const addr *b)
     case AF_INET:
       return (a->sin.sin_addr.s_addr == b->sin.sin_addr.s_addr &&
              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) &&
+             a->sin6.sin6_port == b->sin6.sin6_port);
     default:
       abort();
   }
index 54883af..87bb905 100644 (file)
@@ -61,6 +61,10 @@ static const trace_opt w_opts[] = {
 
 /*----- Static variables --------------------------------------------------*/
 
+#ifdef HAVE_LIBADNS
+  static adns_state ads;
+  sel_hook hook;
+#endif
 static admin *admins;
 static admin *a_dead;
 static sel_file sock;
@@ -272,14 +276,19 @@ void a_vformat(dstr *d, const char *fmt, va_list *ap)
     } else if (*fmt == '?') {
       if (strcmp(fmt, "?ADDR") == 0) {
        const addr *a = va_arg(*ap, const addr *);
-       switch (a->sa.sa_family) {
-         case AF_INET:
-           u_quotify(d, "INET");
-           u_quotify(d, inet_ntoa(a->sin.sin_addr));
-           dstr_putf(d, " %u", (unsigned)ntohs(a->sin.sin_port));
-           break;
-         default:
-           abort();
+       char name[NI_MAXHOST], serv[NI_MAXSERV];
+       int ix, err;
+       if ((err = getnameinfo(&a->sa, addrsz(a),
+                              name, sizeof(name), serv, sizeof(serv),
+                              (NI_NUMERICHOST | NI_NUMERICSERV |
+                               NI_DGRAM)))) {
+         dstr_putf(d, " E%d", err);
+         u_quotify(d, gai_strerror(err));
+       } else {
+         ix = afix(a->sa.sa_family); assert(ix >= 0);
+         u_quotify(d, aftab[ix].name);
+         u_quotify(d, name);
+         u_quotify(d, serv);
        }
       } else if (strcmp(fmt, "?B64") == 0) {
        const octet *p = va_arg(*ap, const octet *);
@@ -1006,6 +1015,101 @@ static void a_svcrelease(admin_service *svc)
 
 /*----- Name resolution operations ----------------------------------------*/
 
+#ifdef HAVE_LIBADNS
+
+/* --- @before_select@ --- *
+ *
+ * Arguments:  @sel_state *s@ = the @sel@ multiplexor (unused)
+ *             @sel_args *a@ = input to @select@, to be updated
+ *             @void *p@ = a context pointer (unused)
+ *
+ * Returns:    ---
+ *
+ * Use:                An I/O multiplexor hook, called just before waiting for I/O
+ *             events.
+ *
+ *             Currently its main purpose is to wire ADNS into the event
+ *             loop.
+ */
+
+static void before_select(sel_state *s, sel_args *a, void *p)
+{
+  struct timeval now;
+  adns_query q;
+  adns_answer *n;
+  admin_resop *r;
+  int any = 0;
+
+  /* --- Check for name-resolution progress --- *
+   *
+   * If there is any, then clobber the timeout: one of the resolver
+   * callbacks might have renewed its interest in a file descriptor, but too
+   * late to affect this @select@ call.
+   *
+   * I think, strictly, this is an mLib bug, but it's cheap enough to hack
+   * around here.  Fixing it will wait for mLib 3.
+   */
+
+  for (;;) {
+    q = 0;
+    if (adns_check(ads, &q, &n, &p)) break;
+    r = p;
+    any = 1;
+    if (n->status != adns_s_ok) {
+      T( trace(T_ADMIN, "admin: resop %s failed: %s",
+              BGTAG(r), adns_strerror(n->status)); )
+      a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+      r->func(r, ARES_FAIL);
+    } else {
+      T( trace(T_ADMIN, "admin: resop %s ok", BGTAG(r)); )
+      assert(n->type == adns_r_addr);
+      assert(n->nrrs > 0);
+      assert(n->rrs.addr[0].len <= sizeof(r->sa));
+      memcpy(&r->sa, &n->rrs.addr[0].addr, n->rrs.addr[0].len);
+      setport(&r->sa, r->port);
+      r->func(r, ARES_OK);
+    }
+    free(n);
+    sel_rmtimer(&r->t);
+    xfree(r->addr);
+    a_bgrelease(&r->bg);
+  }
+
+  if (any) { a->tvp = &a->tv; a->tv.tv_sec = 0; a->tv.tv_usec = 0; }
+
+  gettimeofday(&now, 0);
+  adns_beforeselect(ads, &a->maxfd,
+                   &a->fd[SEL_READ], &a->fd[SEL_WRITE], &a->fd[SEL_EXC],
+                   &a->tvp, &a->tv, &now);
+}
+
+/* --- @after_select@ --- *
+ *
+ * Arguments:  @sel_state *s@ = the @sel@ multiplexor (unused)
+ *             @sel_args *a@ = input to @select@, to be updated
+ *             @void *p@ = a context pointer (unused)
+ *
+ * Returns:    ---
+ *
+ * Use:                An I/O multiplexor hook, called just after waiting for I/O
+ *             events.
+ *
+ *             Currently its main purpose is to wire ADNS into the event
+ *             loop.
+ */
+
+static void after_select(sel_state *s, sel_args *a, void *p)
+{
+  struct timeval now;
+
+  gettimeofday(&now, 0);
+  adns_afterselect(ads, a->maxfd,
+                  &a->fd[SEL_READ], &a->fd[SEL_WRITE], &a->fd[SEL_EXC],
+                  &now);
+}
+
+#else
+
 /* --- @a_resolved@ --- *
  *
  * Arguments:  @struct hostent *h@ = pointer to resolved hostname
@@ -1020,13 +1124,17 @@ static void a_resolved(struct hostent *h, void *v)
 {
   admin_resop *r = v;
 
-  T( trace(T_ADMIN, "admin: resop %s resolved", BGTAG(r)); )
   QUICKRAND;
   if (!h) {
+    T( trace(T_ADMIN, "admin: resop %s failed: %s",
+            BGTAG(r), hstrerror(h_errno)); )
     a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
     r->func(r, ARES_FAIL);
   } else {
+    T( trace(T_ADMIN, "admin: resop %s ok", BGTAG(r)); )
+    r->sa.sin.sin_family = AF_INET;
     memcpy(&r->sa.sin.sin_addr, h->h_addr, sizeof(struct in_addr));
+    setport(&r->sa, r->port);
     r->func(r, ARES_OK);
   }
   sel_rmtimer(&r->t);
@@ -1034,6 +1142,8 @@ static void a_resolved(struct hostent *h, void *v)
   a_bgrelease(&r->bg);
 }
 
+#endif
+
 /* --- @a_restimer@ --- *
  *
  * Arguments:  @struct timeval *tv@ = timer
@@ -1051,7 +1161,11 @@ static void a_restimer(struct timeval *tv, void *v)
   T( trace(T_ADMIN, "admin: resop %s timeout", BGTAG(r)); )
   a_bgfail(&r->bg, "resolver-timeout", "%s", r->addr, A_END);
   r->func(r, ARES_FAIL);
+#ifdef HAVE_LIBADNS
+  adns_cancel(r->q);
+#else
   bres_abort(&r->r);
+#endif
   xfree(r->addr);
   a_bgrelease(&r->bg);
 }
@@ -1073,7 +1187,11 @@ static void a_rescancel(admin_bgop *bg)
   r->func(r, ARES_FAIL);
   sel_rmtimer(&r->t);
   xfree(r->addr);
+#ifdef HAVE_LIBADNS
+  adns_cancel(r->q);
+#else
   bres_abort(&r->r);
+#endif
 }
 
 /* --- @a_resolve@ --- *
@@ -1096,20 +1214,39 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
 {
   struct timeval tv;
   unsigned long pt;
+  int af = AF_UNSPEC;
+  const char *fam = "ANY";
   char *p;
-  int i = 0;
+  int i = 0, j;
+  struct addrinfo *ai, *ailist, aihint = { 0 };
+#ifdef HAVE_LIBADNS
+  int err;
+  adns_queryflags qf;
+#endif
 
   /* --- Fill in the easy bits of address --- */
 
   r->bg.tag = "<starting>";
   r->addr = 0;
   r->func = func;
-  if (mystrieq(av[i], "inet")) i++;
+  if (mystrieq(av[i], "any"))
+    { fam = "ANY"; af = AF_UNSPEC; i++; }
+  else for (j = 0; j < NADDRFAM; j++) {
+    if (mystrieq(av[i], aftab[j].name)) {
+      if (udpsock[j].fd < 0) {
+       a_fail(a, "disabled-address-family", "%s", aftab[j].name, A_END);
+       goto fail;
+      }
+      fam = aftab[j].name;
+      af = aftab[j].af;
+      i++;
+      break;
+    }
+  }
   if (ac - i != 1 && ac - i != 2) {
-    a_fail(a, "bad-addr-syntax", "[inet] ADDRESS [PORT]", A_END);
+    a_fail(a, "bad-addr-syntax", "[FAMILY] ADDRESS [PORT]", A_END);
     goto fail;
   }
-  r->sa.sin.sin_family = AF_INET;
   r->addr = xstrdup(av[i]);
   if (!av[i + 1])
     pt = TRIPE_PORT;
@@ -1128,7 +1265,7 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
     a_fail(a, "invalid-port", "%lu", pt, A_END);
     goto fail;
   }
-  r->sa.sin.sin_port = htons(pt);
+  r->port = pt;
 
   /* --- Report backgrounding --- *
    *
@@ -1138,14 +1275,33 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
 
   if (a_bgadd(a, &r->bg, tag, a_rescancel))
     goto fail;
-  T( trace(T_ADMIN, "admin: %u, resop %s, hostname `%s'",
-          a->seq, BGTAG(r), r->addr); )
+  T( trace(T_ADMIN, "admin: %u, resop %s, hostname `%s', family `%s'",
+          a->seq, BGTAG(r), r->addr, fam); )
 
   /* --- If the name is numeric, do it the easy way --- */
 
-  if (inet_aton(av[i], &r->sa.sin.sin_addr)) {
-    T( trace(T_ADMIN, "admin: resop %s done the easy way", BGTAG(r)); )
-    func(r, ARES_OK);
+  aihint.ai_family = af;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_protocol = IPPROTO_UDP;
+  aihint.ai_flags = AI_NUMERICHOST;
+  if (!getaddrinfo(av[i], 0, &aihint, &ailist)) {
+    for (ai = ailist; ai; ai = ai->ai_next) {
+      if ((j = afix(ai->ai_family)) >= 0 && udpsock[j].fd >= 0)
+       break;
+    }
+    if (!ai) {
+      T( trace(T_ADMIN, "admin: resop %s failed: "
+              "no suitable addresses returned", BGTAG(r)); )
+      a_bgfail(&r->bg, "resolve-error", "%s" , r->addr, A_END);
+      func(r, ARES_FAIL);
+    } else {
+      T( trace(T_ADMIN, "admin: resop %s done the easy way", BGTAG(r)); )
+      assert(ai->ai_addrlen <= sizeof(r->sa));
+      memcpy(&r->sa, ai->ai_addr, ai->ai_addrlen);
+      setport(&r->sa, r->port);
+      func(r, ARES_OK);
+    }
+    freeaddrinfo(ailist);
     xfree(r->addr);
     a_bgrelease(&r->bg);
     return;
@@ -1156,13 +1312,43 @@ static void a_resolve(admin *a, admin_resop *r, const char *tag,
   gettimeofday(&tv, 0);
   tv.tv_sec += T_RESOLVE;
   sel_addtimer(&sel, &r->t, &tv, a_restimer, r);
+#ifdef HAVE_LIBADNS
+  qf = adns_qf_search;
+  for (j = 0; j < NADDRFAM; j++) {
+    if ((af == AF_UNSPEC || af == aftab[i].af) && udpsock[j].fd >= 0)
+      qf |= aftab[j].qf;
+  }
+  if ((err = adns_submit(ads, r->addr, adns_r_addr, qf, r, &r->q)) != 0) {
+    T( trace(T_ADMIN, "admin: resop %s adns_submit failed: %s",
+            BGTAG(r), strerror(err)); )
+    a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+    goto fail_release;
+  }
+#else
+  if (af != AF_UNSPEC && af != AF_INET) {
+    T( trace(T_ADMIN, "admin: resop %s failed: unsupported address family",
+            BGTAG(r)); )
+    a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+    goto fail_release;
+  }
+  if (udpsock[AFIX_INET].fd < 0) {
+    a_bgfail(&r->bg, "disabled-address-family", "INET", A_END);
+    goto fail_release;
+  }
   bres_byname(&r->r, r->addr, a_resolved, r);
+#endif
   return;
 
 fail:
   func(r, ARES_FAIL);
   if (r->addr) xfree(r->addr);
   xfree(r);
+  return;
+
+fail_release:
+  func(r, ARES_FAIL);
+  xfree(r->addr);
+  a_bgrelease(&r->bg);
 }
 
 /*----- Option parsing ----------------------------------------------------*/
@@ -1675,7 +1861,27 @@ static void acmd_warn(admin *a, unsigned ac, char *av[])
   { alertcmd(a, AF_WARN, AF_WARN, "WARN", av); }
 
 static void acmd_port(admin *a, unsigned ac, char *av[])
-  { a_info(a, "%u", p_port(), A_END); a_ok(a); }
+{
+  int i;
+
+  if (ac) {
+    for (i = 0; i < NADDRFAM; i++)
+      if (mystrieq(av[0], aftab[i].name)) goto found;
+    a_fail(a, "unknown-address-family", "%s", av[0], A_END);
+    return;
+  found:
+    if (udpsock[i].fd < 0) {
+      a_fail(a, "disabled-address-family", "%s", aftab[i].name, A_END);
+      return;
+    }
+  } else {
+    for (i = 0; i < NADDRFAM; i++)
+      if (udpsock[i].fd >= 0) goto found;
+    abort();
+  }
+  a_info(a, "%u", p_port(i), A_END);
+  a_ok(a);
+}
 
 static void acmd_daemon(admin *a, unsigned ac, char *av[])
 {
@@ -1838,7 +2044,6 @@ static void acmd_addr(admin *a, unsigned ac, char *av[])
 
   if ((p = a_findpeer(a, av[0])) != 0) {
     ad = p_addr(p);
-    assert(ad->sa.sa_family == AF_INET);
     a_info(a, "?ADDR", ad, A_END);
     a_ok(a);
   }
@@ -1988,7 +2193,7 @@ static const acmd acmdtab[] = {
   { "notify",  "MESSAGE ...",          1,      0xffff, acmd_notify },
   { "peerinfo",        "PEER",                 1,      1,      acmd_peerinfo },
   { "ping",    "[OPTIONS] PEER",       1,      0xffff, acmd_ping },
-  { "port",    0,                      0,      0,      acmd_port },
+  { "port",    "[FAMILY]",             0,      1,      acmd_port },
   { "quit",    0,                      0,      0,      acmd_quit },
   { "reload",  0,                      0,      0,      acmd_reload },
   { "servinfo",        0,                      0,      0,      acmd_servinfo },
@@ -2297,6 +2502,9 @@ void a_init(const char *name, uid_t u, gid_t g, mode_t m)
   struct sigaction sa;
   size_t sz;
   mode_t omask;
+#ifdef HAVE_LIBADNS
+  int err;
+#endif
 
   /* --- Create services table --- */
 
@@ -2363,7 +2571,17 @@ again:
   sel_initfile(&sel, &sock, fd, SEL_READ, a_accept, 0);
   sel_addfile(&sock);
   sockname = name;
+#ifdef HAVE_LIBADNS
+  if ((err = adns_init(&ads,
+                      (adns_if_permit_ipv4 | adns_if_permit_ipv6 |
+                       adns_if_noserverwarn | adns_if_nosigpipe |
+                       adns_if_noautosys),
+                      0)) != 0)
+    die(EXIT_FAILURE, "failed to initialize ADNS: %s", strerror(errno));
+  sel_addhook(&sel, &hook, before_select, after_select, 0);
+#else
   bres_init(&sel);
+#endif
   T( trace_custom(a_trace, 0);
      trace(T_ADMIN, "admin: enabled custom tracing"); )
   flags |= F_INIT;
index 606b203..a8099e4 100644 (file)
 
 #include "tripe.h"
 
+/*----- Global state ------------------------------------------------------*/
+
+sel_file udpsock[NADDRFAM];
+
 /*----- Static variables --------------------------------------------------*/
 
 static sym_table byname;
 static addrmap byaddr;
-static sel_file sock;
 static unsigned nmobile;
 
 /*----- Tunnel table ------------------------------------------------------*/
@@ -181,6 +184,7 @@ int p_updateaddr(peer *p, const addr *a)
 {
   peer *q;
   peer_byaddr *pa, *qa;
+  int ix;
   unsigned f;
 
   /* --- Figure out how to proceed --- *
@@ -197,6 +201,7 @@ int p_updateaddr(peer *p, const addr *a)
     T( trace(T_PEER, "peer: updating address for `%s'", p_name(p)); )
     am_remove(&byaddr, p->byaddr);
     p->byaddr = pa; p->spec.sa = *a; pa->p = p;
+    p->afix = afix(p->spec.sa.sa.sa_family); assert(p->afix >= 0);
     a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
     return (0);
   } else {
@@ -205,6 +210,7 @@ int p_updateaddr(peer *p, const addr *a)
             p_name(p), p_name(q)); )
     q->byaddr = qa; qa->p = q; q->spec.sa = p->spec.sa;
     p->byaddr = pa; pa->p = p; p->spec.sa = *a;
+    ix = p->afix; p->afix = q->afix; q->afix = ix;
     a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
     a_notify("NEWADDR", "?PEER", q, "?ADDR", &q->spec.sa, A_END);
     return (0);
@@ -330,6 +336,10 @@ static void p_read(int fd, unsigned mode, void *v)
   ssize_t n;
   int ch;
   buf b, bb;
+#ifndef NTRACE
+  int ix = -1;
+  char name[NI_MAXHOST], svc[NI_MAXSERV];
+#endif
 
   /* --- Read the data --- */
 
@@ -340,14 +350,18 @@ static void p_read(int fd, unsigned mode, void *v)
     a_warn("PEER", "-", "socket-read-error", "?ERRNO", A_END);
     return;
   }
+  IF_TRACING(T_PEER, {
+    ix = afix(a.sa.sa_family);
+    getnameinfo(&a.sa, sz, name, sizeof(name), svc, sizeof(svc),
+               NI_NUMERICHOST | NI_NUMERICSERV);
+  })
 
   /* --- If the packet is a greeting, don't check peers --- */
 
   if (n && buf_i[0] == (MSG_MISC | MISC_GREET)) {
     IF_TRACING(T_PEER, {
-      trace(T_PEER, "peer: greeting received from INET %s %u",
-           inet_ntoa(a.sin.sin_addr),
-           (unsigned)ntohs(a.sin.sin_port));
+      trace(T_PEER, "peer: greeting received from %s %s %s",
+           aftab[ix].name, name, svc);
       trace_block(T_PACKET, "peer: greeting contents", buf_i, n);
     })
     buf_init(&b, buf_i, n);
@@ -373,11 +387,11 @@ static void p_read(int fd, unsigned mode, void *v)
   IF_TRACING(T_PEER, {
     if (p) {
       trace(T_PEER,
-           "peer: packet received from `%s' from address INET %s %d",
-           p_name(p), inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port));
+           "peer: packet received from `%s' from address %s %s %s",
+           p_name(p), aftab[ix].name, name, svc);
     } else {
-      trace(T_PEER, "peer: packet received from unknown address INET %s %d",
-           inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port));
+      trace(T_PEER, "peer: packet received from unknown address %s %s %s",
+           aftab[ix].name, name, svc);
     }
     trace_block(T_PACKET, "peer: packet contents", buf_i, n);
   })
@@ -543,7 +557,7 @@ static int p_dotxend(peer *p)
   }
   IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet",
                                 BBASE(&p->b), BLEN(&p->b)); )
-  if (sendto(sock.fd, BBASE(&p->b), BLEN(&p->b),
+  if (sendto(udpsock[p->afix].fd, BBASE(&p->b), BLEN(&p->b),
             0, &p->spec.sa.sa, sasz) < 0) {
     a_warn("PEER", "?PEER", p, "socket-write-error", "?ERRNO", A_END);
     return (0);
@@ -799,45 +813,68 @@ const addr *p_addr(peer *p) { return (&p->spec.sa); }
 
 /* --- @p_init@ --- *
  *
- * Arguments:  @struct in_addr addr@ = address to bind to
- *             @unsigned port@ = port number to listen to
+ * Arguments:  @struct addrinfo *ailist@ = addresses to bind to
  *
  * Returns:    ---
  *
  * Use:                Initializes the peer system; creates the socket.
  */
 
-void p_init(struct in_addr addr, unsigned port)
+void p_init(struct addrinfo *ailist)
 {
   int fd;
-  struct sockaddr_in sin;
   int len = PKBUFSZ;
+  int yes = 1;
+  int i;
+  struct addrinfo *ai;
+  unsigned port, lastport = 0;
+  addr a;
+  socklen_t sz;
 
-  /* --- Note on socket buffer sizes --- *
-   *
-   * For some bizarre reason, Linux 2.2 (at least) doubles the socket buffer
-   * sizes I pass to @setsockopt@.  I'm not putting special-case code here
-   * for Linux: BSD (at least TCPv2) does what I tell it rather than second-
-   * guessing me.
-   */
-
-  if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
-    die(EXIT_FAILURE, "socket creation failed: %s", strerror(errno));
-  BURN(sin);
-  sin.sin_family = AF_INET;
-  sin.sin_addr = addr;
-  sin.sin_port = htons(port);
-  if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
-    die(EXIT_FAILURE, "bind failed: %s", strerror(errno));
-  if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
-      setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) {
-    die(EXIT_FAILURE, "failed to set socket buffer sizes: %s",
-       strerror(errno));
+  for (i = 0; i < NADDRFAM; i++) udpsock[i].fd = -1;
+
+  for (ai = ailist; ai; ai = ai->ai_next) {
+    if ((i = afix(ai->ai_family)) < 0) continue;
+    if (udpsock[i].fd != -1) continue;
+
+    /* --- Note on socket buffer sizes --- *
+     *
+     * For some bizarre reason, Linux 2.2 (at least) doubles the socket
+     * buffer sizes I pass to @setsockopt@.  I'm not putting special-case
+     * code here for Linux: BSD (at least TCPv2) does what I tell it rather
+     * than second-guessing me.
+     */
+
+    if ((fd = socket(ai->ai_family, SOCK_DGRAM, 0)) < 0)
+      die(EXIT_FAILURE, "socket creation failed: %s", strerror(errno));
+    if (i == AFIX_INET6 &&
+       setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes))) {
+      die(EXIT_FAILURE, "failed to set IPv6-only state: %s",
+         strerror(errno));
+    }
+    assert(ai->ai_addrlen <= sizeof(a));
+    memcpy(&a, ai->ai_addr, ai->ai_addrlen);
+    if ((port = getport(&a)) == 0 && lastport) setport(&a, lastport);
+    if (bind(fd, &a.sa, addrsz(&a)))
+      die(EXIT_FAILURE, "bind failed: %s", strerror(errno));
+    if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
+       setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) {
+      die(EXIT_FAILURE, "failed to set socket buffer sizes: %s",
+         strerror(errno));
+    }
+    fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+    sel_initfile(&sel, &udpsock[i], fd, SEL_READ, p_read, 0);
+    sel_addfile(&udpsock[i]);
+    T( trace(T_PEER, "peer: created %s socket", aftab[i].name); )
+    if (!port) {
+      sz = sizeof(a);
+      if (getsockname(fd, &a.sa, &sz)) {
+       die(EXIT_FAILURE, "failed to read local socket address: %s",
+           strerror(errno));
+      }
+      lastport = getport(&a);
+    }
   }
-  fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
-  sel_initfile(&sel, &sock, fd, SEL_READ, p_read, 0);
-  sel_addfile(&sock);
-  T( trace(T_PEER, "peer: created socket"); )
 
   sym_create(&byname);
   am_create(&byaddr);
@@ -845,20 +882,19 @@ void p_init(struct in_addr addr, unsigned port)
 
 /* --- @p_port@ --- *
  *
- * Arguments:  ---
+ * Arguments:  @int i@ = address family index to retrieve
  *
  * Returns:    Port number used for socket.
  */
 
-unsigned p_port(void)
+unsigned p_port(int i)
 {
   addr a;
   socklen_t sz = sizeof(addr);
 
-  if (getsockname(sock.fd, &a.sa, &sz))
+  if (getsockname(udpsock[i].fd, &a.sa, &sz))
     die(EXIT_FAILURE, "couldn't read port number: %s", strerror(errno));
-  assert(a.sa.sa_family == AF_INET);
-  return (ntohs(a.sin.sin_port));
+  return (getport(&a));
 }
 
 /* --- @p_keepalive@ --- *
@@ -932,6 +968,7 @@ peer *p_create(peerspec *spec)
   p->ks = 0;
   p->pings = 0;
   p->ifname = 0;
+  p->afix = afix(p->spec.sa.sa.sa_family); assert(p->afix >= 0);
   memset(&p->st, 0, sizeof(stats));
   p->st.t_start = time(0);
   if (!(tops->flags & TUNF_PRIVOPEN))
index 72b5b80..703e448 100644 (file)
@@ -295,6 +295,34 @@ int mystrieq(const char *x, const char *y)
   }
 }
 
+/*----- Address handling --------------------------------------------------*/
+
+const struct addrfam aftab[] = {
+#ifdef HAVE_LIBADNS
+#  define DEF(af, qf) { AF_##af, #af, adns_qf_##qf },
+#else
+#  define DEF(af, qf) { AF_##af, #af },
+#endif
+  ADDRFAM(DEF)
+#undef DEF
+};
+
+/* --- @afix@ --- *
+ *
+ * Arguments:  @int af@ = an address family code
+ *
+ * Returns:    The index of the address family's record in @aftab@, or @-1@.
+ */
+
+int afix(int af)
+{
+  int i;
+
+  for (i = 0; i < NADDRFAM; i++)
+    if (af == aftab[i].af) return (i);
+  return (-1);
+}
+
 /* --- @addrsz@ --- *
  *
  * Arguments:  @const addr *a@ = a network address
@@ -306,6 +334,35 @@ socklen_t addrsz(const addr *a)
 {
   switch (a->sa.sa_family) {
     case AF_INET: return (sizeof(a->sin));
+    case AF_INET6: return (sizeof(a->sin6));
+    default: abort();
+  }
+}
+
+/* --- @getport@, @setport@ --- *
+ *
+ * Arguments:  @addr *a@ = a network address
+ *             @unsigned port@ = port number to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Retrieves or sets the port number in an address structure.
+ */
+
+unsigned getport(addr *a)
+{
+  switch (a->sa.sa_family) {
+    case AF_INET: return (ntohs(a->sin.sin_port)); break;
+    case AF_INET6: return (ntohs(a->sin6.sin6_port)); break;
+    default: abort();
+  }
+}
+
+void setport(addr *a, unsigned port)
+{
+  switch (a->sa.sa_family) {
+    case AF_INET: a->sin.sin_port = htons(port); break;
+    case AF_INET6: a->sin6.sin6_port = htons(port); break;
     default: abort();
   }
 }
index c81dc11..f066ae6 100644 (file)
@@ -251,21 +251,50 @@ the meanings of the subsequent tokens depend on the address family.
 Address family tokens are not case-sensitive on input; on output, they
 are always in upper-case.
 .PP
-At present, only one address family is understood.
+The following address families are recognized.
+.TP
+.BI "ANY " address " \fR[" port \fR]
+An address and port number for any supported address family.  On output,
+.B tripe
+never uses this form.  On input, the
+.I address
+is examined: if it is a numeric address for some recognized address
+family, then it is interpreted as such; otherwise it is looked up using
+the DNS (in the background).  The background resolver's address-sorting
+rules apply, and
+.B tripe
+simply takes the first address in the returned list which is of a
+supported address family.  Symbolic port numbers are permitted; if
+omitted, the default port 4070 is used.
 .TP
 .BI "INET " address " \fR[" port \fR]
 An Internet socket, naming an IPv4 address and UDP port.  On output, the
-address is always in numeric dotted-quad form, and the port is given as
-a plain number.  On input, DNS hostnames and symbolic port names are
-permitted; if omitted, the default port 4070 is used.  Name resolution
-does not block the main server, but will block the requesting client,
-unless the command is run in the background.
+.I address
+is always in numeric dotted-quad form, and the
+.I port
+is given as a plain decimal number.  On input, DNS hostnames and
+symbolic port names are permitted; if omitted, the default port 4070 is
+used.
+.TP
+.BI "INET6 " address " \fR[" port \fR]
+An Internet socket, naming an IPv6 address and UDP port.  On output, the
+.I address
+is always in numeric hex-and-colons form, and the
+.I port
+is given as a plain decimal number.  On input, DNS hostnames and
+symbolic port names may be permitted, depending on how
+.B tripe
+was compiled; if omitted, the default port 4070 is used.
 .PP
 If, on input, no recognized address family token is found, the following
 tokens are assumed to represent an
-.B INET
+.B ANY
 address.  Addresses output by the server always have an address family
-token.
+token, and do not use
+.BR ANY .
+.PP
+Name resolution never blocks the main server, but will block the
+requesting client, unless the command is run in the background.
 .SS "Key-value output"
 Some commands (e.g.,
 .B STATS
@@ -507,12 +536,16 @@ tunnel interface.  If
 is the MTU of the path to the peer, then the tunnel MTU should be
 .IP
 .I MTU
-\- 29 \-
+\-
+.I header-length
+\- 9 \-
 .I bulk-overhead
 .PP
-allowing 20 bytes of IP header, 8 bytes of UDP header, a packet type
-octet, and the bulk-crypto transform overhead (which includes the
-sequence number).
+allowing
+.I header-length
+= 20 (IPv4) or 40 (IPv6) bytes of IP header, 8 bytes of UDP header, a
+packet type octet, and the bulk-crypto transform overhead (which
+includes the sequence number).
 .RE
 .SP
 .BI "BGCANCEL " tag
@@ -717,12 +750,18 @@ given, seconds are assumed.
 .RE
 .SP
 .B "PORT"
+.RI [ family ]
 Emits an
 .B INFO
 line containing just the number of the UDP port used by the
 .B tripe
-server.  If you've allowed your server to allocate a port dynamically,
-this is how to find out which one it chose.
+server, for the given address
+.I family
+(or one chosen arbitrarily if omitted -- though
+.B tripe
+tries to use the same port number consistently so this is not a likely
+problem in practice).  If you've allowed your server to allocate a port
+dynamically, this is how to find out which one it chose.
 .SP
 .B "RELOAD"
 Instructs the server to recheck its keyring files.  The server checks
@@ -1048,6 +1087,15 @@ An unknown watch option was requested.
 An error occurred during the attempt to become a daemon, as reported by
 .IR message .
 .SP
+.BI "disabled-address-family " afam
+(For
+.B ADD
+and
+.BR PORT .)
+The address family
+.I afam
+is supported, but was disabled using command-line arguments.
+.SP
 .BI "invalid-port " number
 (For
 .BR ADD .)
@@ -1133,6 +1181,13 @@ is available, which does not meet the stated requirements.
 .I tag
 is already the tag of an outstanding job.
 .SP
+.BI "unknown-address-family " afam
+(For
+.BR PORT .)
+The address family
+.I afam
+is unrecognized.
+.SP
 .BI "unknown-command " token
 The command
 .I token
index aaaf267..8b78682 100644 (file)
@@ -37,7 +37,7 @@ tripe \- a simple VPN daemon
 .SH "SYNOPSIS"
 .
 .B tripe
-.RB [ \-DF ]
+.RB [ \-46DF ]
 .RB [ \-d
 .IR dir ]
 .RB [ \-b
@@ -165,6 +165,15 @@ Writes to standard output a list of the configured tunnel drivers, one
 per line, and exits with status 0.  This is intended for the use of the
 start-up script, so that it can check that it will actually work.
 .TP
+.B "\-4, \-\-ipv4"
+Use only IPv4 addresses.  The server will resolve names only to IPv4
+addresses, and not attempt to create IPv6 sockets.
+.TP
+.B "\-6, \-\-ipv6"
+Use only IPv6 addresses.  The server will resolve names only to IPv6
+addresses, and not attempt to create IPv4 sockets.  Note that v6-mapped
+IPv4 addresses won't work either.
+.TP
 .B "\-D, \-\-daemon"
 Dissociates from its terminal and starts running in the background after
 completing the initialization procedure described above.  If running as
index d50757b..b843885 100644 (file)
@@ -91,6 +91,8 @@ Options:\n\
 -u, --usage            Display pointless usage message.\n\
     --tunnels          Display IP tunnel drivers and exit.\n\
 \n\
+-4, --ipv4             Transport over IPv4 only.\n\
+-6, --ipv6             Transport over IPv6 only.\n\
 -D, --daemon           Run in the background.\n\
 -F, --foreground       Quit when stdin reports end-of-file.\n\
 -d, --directory=DIR    Switch to directory DIR [default " CONFIGDIR "].\n\
@@ -119,11 +121,11 @@ int main(int argc, char *argv[])
   int csockmode = 0600;
   const char *dir = CONFIGDIR;
   const char *p;
-  unsigned port = TRIPE_PORT;
-  struct in_addr baddr = { INADDR_ANY };
+  const char *bindhost = 0, *bindsvc = STR(TRIPE_PORT);
+  struct addrinfo aihint = { 0 }, *ailist;
   unsigned f = 0;
   int i;
-  int selerr = 0;
+  int err, selerr = 0;
   unsigned af;
   struct timeval tv;
   uid_t u = -1;
@@ -141,6 +143,7 @@ int main(int argc, char *argv[])
   if ((p = getenv("TRIPESOCK")) != 0)
     csock = p;
   tun_default = tunnels[0];
+  aihint.ai_family = AF_UNSPEC;
 
   for (;;) {
     static const struct option opts[] = {
@@ -149,6 +152,8 @@ int main(int argc, char *argv[])
       { "usage",       0,              0,      'u' },
       { "tunnels",     0,              0,      '0' },
 
+      { "ipv4",                0,              0,      '4' },
+      { "ipv6",                0,              0,      '6' },
       { "daemon",      0,              0,      'D' },
       { "foreground",  0,              0,      'F' },
       { "uid",         OPTF_ARGREQ,    0,      'U' },
@@ -171,7 +176,7 @@ int main(int argc, char *argv[])
       { 0,             0,              0,      0 }
     };
 
-    i = mdwopt(argc, argv, "hvuDFU:G:b:n:p:d:k:K:t:a:m:" T("T:"),
+    i = mdwopt(argc, argv, "hvu46DFU:G:b:n:p:d:k:K:t:a:m:" T("T:"),
               opts, 0, 0, 0);
     if (i < 0)
       break;
@@ -186,6 +191,12 @@ int main(int argc, char *argv[])
        usage(stdout);
        exit(0);
 
+      case '4':
+       aihint.ai_family = AF_INET;
+       break;
+      case '6':
+       aihint.ai_family = AF_INET6;
+       break;
       case 'D':
        f |= f_daemon;
        break;
@@ -199,25 +210,12 @@ int main(int argc, char *argv[])
        f |= f_foreground;
        break;
 
-      case 'b': {
-       struct hostent *h = gethostbyname(optarg);
-       if (!h)
-         die(EXIT_FAILURE, "unknown host name `%s'", optarg);
-       memcpy(&baddr, h->h_addr, sizeof(struct in_addr));
-      } break;
-      case 'p': {
-       char *p;
-       unsigned long i = strtoul(optarg, &p, 0);
-       if (*p) {
-         struct servent *s = getservbyname(optarg, "udp");
-         if (!s)
-           die(EXIT_FAILURE, "unknown service name `%s'", optarg);
-         i = ntohs(s->s_port);
-       }
-       if (i >= 65536)
-         die(EXIT_FAILURE, "bad port number %lu", i);
-       port = i;
-      } break;
+      case 'b':
+       bindhost = optarg;
+       break;
+      case 'p':
+       bindsvc = optarg;
+       break;
       case 'n': {
        int i;
        for (i = 0;; i++) {
@@ -273,6 +271,17 @@ int main(int argc, char *argv[])
   if (!(~f & (f_daemon | f_foreground)))
     die(EXIT_FAILURE, "foreground operation for a daemon is silly");
 
+  aihint.ai_protocol = IPPROTO_UDP;
+  aihint.ai_socktype = SOCK_DGRAM;
+  aihint.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+  if ((err = getaddrinfo(bindhost, bindsvc, &aihint, &ailist)) != 0) {
+    die(EXIT_FAILURE, "couldn't resolve hostname %c%s%c, port `%s': %s",
+       bindhost ? '`' : '<',
+       bindhost ? bindhost : "nil",
+       bindhost ? '\'' : '>',
+       bindsvc, gai_strerror(err));
+  }
+
   if (chdir(dir)) {
     die(EXIT_FAILURE, "can't set current directory to `%s': %s",
        dir, strerror(errno));
@@ -285,7 +294,7 @@ int main(int argc, char *argv[])
   signal(SIGPIPE, SIG_IGN);
   for (i = 0; tunnels[i]; i++)
     tunnels[i]->init();
-  p_init(baddr, port);
+  p_init(ailist); freeaddrinfo(ailist);
   if (!(f & f_daemon)) {
     af = AF_WARN;
 #ifndef NTRACE
index a2907be..10a03f5 100644 (file)
 #include <pwd.h>
 #include <grp.h>
 
+#ifdef HAVE_LIBADNS
+#  define ADNS_FEATURE_MANYAF
+#  include <adns.h>
+#endif
+
 #include <mLib/alloc.h>
 #include <mLib/arena.h>
 #include <mLib/base64.h>
-#include <mLib/bres.h>
+#ifndef HAVE_LIBADNS
+#  include <mLib/bres.h>
+#endif
 #include <mLib/codec.h>
 #include <mLib/daemonize.h>
 #include <mLib/dstr.h>
@@ -428,6 +435,27 @@ extern const bulkops bulktab[];
 
 /*----- Data structures ---------------------------------------------------*/
 
+/* --- The address-family table --- */
+
+#define ADDRFAM(_)                                                     \
+  _(INET,      want_ipv4)                                              \
+  _(INET6,     want_ipv6)
+
+enum {
+#define ENUM(af, qf) AFIX_##af,
+  ADDRFAM(ENUM)
+#undef ENUM
+  NADDRFAM
+};
+
+extern const struct addrfam {
+  int af;
+  const char *name;
+#ifdef HAVE_LIBADNS
+  adns_queryflags qf;
+#endif
+} aftab[NADDRFAM];
+
 /* --- Socket addresses --- *
  *
  * A magic union of supported socket addresses.
@@ -436,6 +464,7 @@ extern const bulkops bulktab[];
 typedef union addr {
   struct sockaddr sa;
   struct sockaddr_in sin;
+  struct sockaddr_in6 sin6;
 } addr;
 
 /* --- Mapping keyed on addresses --- */
@@ -635,6 +664,7 @@ typedef struct peer {
   peer_byaddr *byaddr;                 /* Lookup-by-address block */
   struct ping *pings;                  /* Pings we're waiting for */
   peerspec spec;                       /* Specifications for this peer */
+  int afix;                            /* Index of address family */
   tunnel *t;                           /* Tunnel for local packets */
   char *ifname;                                /* Interface name for tunnel */
   keyset *ks;                          /* List head for keysets */
@@ -691,9 +721,14 @@ typedef struct admin_bgop {
 typedef struct admin_resop {
   admin_bgop bg;                       /* Background operation header */
   char *addr;                          /* Hostname to be resolved */
+#ifdef HAVE_LIBADNS
+  adns_query q;
+#else
   bres_client r;                       /* Background resolver task */
+#endif
   sel_timer t;                         /* Timer for resolver */
   addr sa;                             /* Socket address */
+  unsigned port;                       /* Port number chosen */
   size_t sasz;                         /* Socket address size */
   void (*func)(struct admin_resop *, int); /* Handler */
 } admin_resop;
@@ -777,6 +812,7 @@ extern sel_state sel;                       /* Global I/O event state */
 extern octet buf_i[PKBUFSZ], buf_o[PKBUFSZ], buf_t[PKBUFSZ], buf_u[PKBUFSZ];
 extern const tunnel_ops *tunnels[];    /* Table of tunnels (0-term) */
 extern const tunnel_ops *tun_default;  /* Default tunnel to use */
+extern sel_file udpsock[NADDRFAM];     /* The master UDP sockets */
 extern kdata *master;                  /* Default private key */
 extern const char *tag_priv;           /* Default private key tag */
 
@@ -1600,24 +1636,23 @@ extern const addr *p_addr(peer */*p*/);
 
 /* --- @p_init@ --- *
  *
- * Arguments:  @struct in_addr addr@ = address to bind to
- *             @unsigned port@ = port number to listen to
+ * Arguments:  @struct addrinfo *ailist@ = addresses to bind to
  *
  * Returns:    ---
  *
  * Use:                Initializes the peer system; creates the socket.
  */
 
-extern void p_init(struct in_addr /*addr*/, unsigned /*port*/);
+extern void p_init(struct addrinfo */*ailist*/);
 
 /* --- @p_port@ --- *
  *
- * Arguments:  ---
+ * Arguments:  @int i@ = address family index to retrieve
  *
  * Returns:    Port number used for socket.
  */
 
-unsigned p_port(void);
+extern unsigned p_port(int /*i*/);
 
 /* --- @p_create@ --- *
  *
@@ -1778,6 +1813,15 @@ extern const char *timestr(time_t /*t*/);
 
 extern int mystrieq(const char */*x*/, const char */*y*/);
 
+/* --- @afix@ --- *
+ *
+ * Arguments:  @int af@ = an address family code
+ *
+ * Returns:    The index of the address family's record in @aftab@, or @-1@.
+ */
+
+extern int afix(int af);
+
 /* --- @addrsz@ --- *
  *
  * Arguments:  @const addr *a@ = a network address
@@ -1787,6 +1831,19 @@ extern int mystrieq(const char */*x*/, const char */*y*/);
 
 extern socklen_t addrsz(const addr */*a*/);
 
+/* --- @getport@, @setport@ --- *
+ *
+ * Arguments:  @addr *a@ = a network address
+ *             @unsigned port@ = port number to set
+ *
+ * Returns:    ---
+ *
+ * Use:                Retrieves or sets the port number in an address structure.
+ */
+
+extern unsigned getport(addr */*a*/);
+extern void setport(addr */*a*/, unsigned /*port*/);
+
 /* --- @seq_reset@ --- *
  *
  * Arguments:  @seqwin *s@ = sequence-checking window
index 713a70b..b38f668 100644 (file)
@@ -85,22 +85,24 @@ followed by peer definitions, each of which looks like this:
 .B =
 .RI [ remote-addr ]
 .IB network / mask
+\&...
 .PP
 This means that the peer
 .I tag
-should be selected if the host's current IP address is within the
-network indicated by
+should be selected if the host's current IP address is within one of the
+networks indicated by
 .IB network / mask \fR.  
-Here,
+Here, a
 .I network
-is an IP address in dotted-quad form, and
+is an IPv4 or IPv6 address in dotted-quad form, and
 .I mask
-is a netmask, either in dotted-quad form, or as a number of 1-bits.
-Only one peer in each group may be connected at any given time; if a
-change is needed, any existing peer in the group is killed before
-connecting the new one.  If no match is found in a particular group,
-then no peers in the group are connected.  Strange and unhelpful things
-will happen if you put the same peer in several different groups.
+is a netmask, either in dotted-quad form (for IPv4), or as a prefix
+length (i.e., the number of initial 1-bits).  Only one peer in each
+group may be connected at any given time; if a change is needed, any
+existing peer in the group is killed before connecting the new one.  If
+no match is found in a particular group, then no peers in the group are
+connected.  Strange and unhelpful things will happen if you put the same
+peer in several different groups.
 .PP
 The tags
 .B down
@@ -113,36 +115,29 @@ is useful for detecting a `home' network, where a VPN is unnecessary
 The notion of `current IP address' is somewhat vague.  The
 .B conntrack
 service calculates it as the source address that the host would put on
-an IP packet sent to an arbitrarily chosen remote address.  The default
-remote address is 1.2.3.4 (which is unlikely ever to be assigned); this
-should determine an IP address on the network interface closest to the
-default gateway.  You can influence this process in two ways.  Firstly,
-you can change the default remote address used by adding a line
+an IP packet sent to a particular remote address; note that this is
+entirely hypothetical, and no actual packets are transmitted.  The
+default remote addresses are 1.2.3.4 (for IPv4, which is unlikely ever
+to be assigned), and 2001::1 (for IPv6); this should determine an IP
+address on the network interface closest to the default gateway.  You
+can influence this process in two ways.  Firstly, you can change the
+default remote address used by adding one or more lines
 .IP
 .B "test-addr ="
 .I remote-addr
+\&...
 .PP
 before the first peer group section.  Secondly, you can specify a
 particular
 .I remote-addr
 to use when checking whether a particular peer is applicable.
 .PP
-The peer definitions can be in any order.  They are checked
-most-specific first, and searching stops as soon as a match is found.
-Therefore a default definition can be added as
-.IP
-.I tag
-.B =
-.B 0/0
-.PP
-without fear of overriding any more specific definitions.  For avoidance
-of doubt, one peer definition is
-.I more specific
-than another if either the former has a specified
-.I remote-addr
-and the latter has not, or the former is wholly contained within the
-latter.  (Overlapping definitions are not recommended, and will be
-processed in an arbitrary order.)
+The peer definitions in each group are checked in the order given, and
+searching stops as soon as a match is found.  (In older versions of
+.BR conntrack ,
+definitions were processed according to a most-specific-first order, but
+that doesn't provide an ordering between IPv4 and IPv6 networks, which
+is important; so this has been changed.)
 .PP
 Peers are connected using the
 .BR connect (8)
index 3368295..28e4b0b 100644 (file)
@@ -36,11 +36,13 @@ import socket as S
 import mLib as M
 import tripe as T
 import dbus as D
+import re as RX
 for i in ['mainloop', 'mainloop.glib']:
   __import__('dbus.%s' % i)
 try: from gi.repository import GLib as G
 except ImportError: import gobject as G
 from struct import pack, unpack
+from cStringIO import StringIO
 
 SM = T.svcmgr
 ##__import__('rmcr').__debug = True
@@ -53,46 +55,139 @@ class struct (object):
   def __init__(me, **kw):
     me.__dict__.update(kw)
 
-def toposort(cmp, things):
-  """
-  Generate the THINGS in an order consistent with a given partial order.
-
-  The function CMP(X, Y) should return true if X must precede Y, and false if
-  it doesn't care.  If X and Y are equal then it should return false.
+def loadb(s):
+  n = 0
+  for ch in s: n = 256*n + ord(ch)
+  return n
 
-  The THINGS may be any finite iterable; it is converted to a list
-  internally.
-  """
+def storeb(n, wd = None):
+  if wd is None: wd = n.bit_length()
+  s = StringIO()
+  for i in xrange((wd - 1)&-8, -8, -8): s.write(chr((n >> i)&0xff))
+  return s.getvalue()
 
-  ## Make sure we can index the THINGS, and prepare an ordering table.
-  ## What's going on?  The THINGS might not have a helpful equality
-  ## predicate, so it's easier to work with indices.  The ordering table will
-  ## remember which THINGS (by index) are considered greater than other
-  ## things.
-  things = list(things)
-  n = len(things)
-  order = [{} for i in xrange(n)]
-  rorder = [{} for i in xrange(n)]
-  for i in xrange(n):
-    for j in xrange(n):
-      if i != j and cmp(things[i], things[j]):
-        order[j][i] = True
-        rorder[i][j] = True
-
-  ## Now we can do the sort.
-  out = []
-  while True:
-    done = True
-    for i in xrange(n):
-      if order[i] is not None:
-        done = False
-        if len(order[i]) == 0:
-          for j in rorder[i]:
-            del order[j][i]
-          yield things[i]
-          order[i] = None
-    if done:
-      break
+###--------------------------------------------------------------------------
+### Address manipulation.
+###
+### I think this is the most demanding application, in terms of address
+### hacking, in the entire TrIPE suite.  At least we don't have to do it in
+### C.
+
+class BaseAddress (object):
+  def __init__(me, addrstr, maskstr = None):
+    me._setaddr(addrstr)
+    if maskstr is None:
+      me.mask = -1
+    elif maskstr.isdigit():
+      me.mask = (1 << me.NBITS) - (1 << me.NBITS - int(maskstr))
+    else:
+      me._setmask(maskstr)
+    if me.addr&~me.mask:
+      raise ValueError('network contains bits set beyond mask')
+  def _addrstr_to_int(me, addrstr):
+    try: return loadb(S.inet_pton(me.AF, addrstr))
+    except S.error: raise ValueError('bad address syntax')
+  def _int_to_addrstr(me, n):
+    return S.inet_ntop(me.AF, storeb(me.addr, me.NBITS))
+  def _setmask(me, maskstr):
+    raise ValueError('only prefix masked supported')
+  def _maskstr(me):
+    raise ValueError('only prefix masked supported')
+  def sockaddr(me, port = 0):
+    if me.mask != -1: raise ValueError('not a simple address')
+    return me._sockaddr(port)
+  def __str__(me):
+    addrstr = me._addrstr()
+    if me.mask == -1:
+      return addrstr
+    else:
+      inv = me.mask ^ ((1 << me.NBITS) - 1)
+      if (inv&(inv + 1)) == 0:
+        return '%s/%d' % (addrstr, me.NBITS - inv.bit_length())
+      else:
+        return '%s/%s' % (addrstr, me._maskstr())
+  def withinp(me, net):
+    if type(net) != type(me): return False
+    if (me.mask&net.mask) != net.mask: return False
+    if (me.addr ^ net.addr)&net.mask: return False
+    return me._withinp(net)
+  def eq(me, other):
+    if type(me) != type(other): return False
+    if me.mask != other.mask: return False
+    if me.addr != other.addr: return False
+    return me._eq(other)
+  def _withinp(me, net):
+    return True
+  def _eq(me, other):
+    return True
+
+class InetAddress (BaseAddress):
+  AF = S.AF_INET
+  AFNAME = 'IPv4'
+  NBITS = 32
+  def _addrstr_to_int(me, addrstr):
+    try: return loadb(S.inet_aton(addrstr))
+    except S.error: raise ValueError('bad address syntax')
+  def _setaddr(me, addrstr):
+    me.addr = me._addrstr_to_int(addrstr)
+  def _setmask(me, maskstr):
+    me.mask = me._addrstr_to_int(maskstr)
+  def _addrstr(me):
+    return me._int_to_addrstr(me.addr)
+  def _maskstr(me):
+    return me._int_to_addrstr(me.mask)
+  def _sockaddr(me, port = 0):
+    return (me._addrstr(), port)
+  @classmethod
+  def from_sockaddr(cls, sa):
+    addr, port = (lambda a, p: (a, p))(*sa)
+    return cls(addr), port
+
+class Inet6Address (BaseAddress):
+  AF = S.AF_INET6
+  AFNAME = 'IPv6'
+  NBITS = 128
+  def _setaddr(me, addrstr):
+    pc = addrstr.find('%')
+    if pc == -1:
+      me.addr = me._addrstr_to_int(addrstr)
+      me.scope = 0
+    else:
+      me.addr = me._addrstr_to_int(addrstr[:pc])
+      ais = S.getaddrinfo(addrstr, 0, S.AF_INET6, S.SOCK_DGRAM, 0,
+                          S.AI_NUMERICHOST | S.AI_NUMERICSERV)
+      me.scope = ais[0][4][3]
+  def _addrstr(me):
+    addrstr = me._int_to_addrstr(me.addr)
+    if me.scope == 0:
+      return addrstr
+    else:
+      name, _ = S.getnameinfo((addrstr, 0, 0, me.scope),
+                              S.NI_NUMERICHOST | S.NI_NUMERICSERV)
+      return name
+  def _sockaddr(me, port = 0):
+    return (me._addrstr(), port, 0, me.scope)
+  @classmethod
+  def from_sockaddr(cls, sa):
+    addr, port, _, scope = (lambda a, p, f = 0, s = 0: (a, p, f, s))(*sa)
+    me = cls(addr)
+    me.scope = scope
+    return me, port
+  def _withinp(me, net):
+    return net.scope == 0 or me.scope == net.scope
+  def _eq(me, other):
+    return me.scope == other.scope
+
+def parse_address(addrstr, maskstr = None):
+  if addrstr.find(':') >= 0: return Inet6Address(addrstr, maskstr)
+  else: return InetAddress(addrstr, maskstr)
+
+def parse_net(netstr):
+  try: sl = netstr.index('/')
+  except ValueError: raise ValueError('missing mask')
+  return parse_address(netstr[:sl], netstr[sl + 1:])
+
+def straddr(a): return a is None and '#<none>' or str(a)
 
 ###--------------------------------------------------------------------------
 ### Parse the configuration file.
@@ -102,18 +197,32 @@ def toposort(cmp, things):
 ## this service are largely going to be satellite notes, I don't think
 ## scalability's going to be a problem.
 
+TESTADDRS = [InetAddress('1.2.3.4'), Inet6Address('2001::1')]
+
+CONFSYNTAX = [
+  ('COMMENT', RX.compile(r'^\s*($|[;#])')),
+  ('GRPHDR', RX.compile(r'^\s*\[(.*)\]\s*$')),
+  ('ASSGN', RX.compile(r'\s*([\w.-]+)\s*[:=]\s*(|\S|\S.*\S)\s*$'))]
+
+class ConfigError (Exception):
+  def __init__(me, file, lno, msg):
+    me.file = file
+    me.lno = lno
+    me.msg = msg
+  def __str__(me):
+    return '%s:%d: %s' % (me.file, me.lno, me.msg)
+
 class Config (object):
   """
   Represents a configuration file.
 
   The most interesting thing is probably the `groups' slot, which stores a
   list of pairs (NAME, PATTERNS); the NAME is a string, and the PATTERNS a
-  list of (TAG, PEER, ADDR, MASK) triples.  The implication is that there
-  should be precisely one peer with a name matching NAME-*, and that it
-  should be NAME-TAG, where (TAG, PEER, ADDR, MASK) is the first triple such
-  that the host's primary IP address (if PEER is None -- or the IP address it
-  would use for communicating with PEER) is within the network defined by
-  ADDR/MASK.
+  list of (TAG, PEER, NETS) triples.  The implication is that there should be
+  precisely one peer from the set, and that it should be named TAG, where
+  (TAG, PEER, NETS) is the first triple such that the host's primary IP
+  address (if PEER is None -- or the IP address it would use for
+  communicating with PEER) is within one of the networks defined by NETS.
   """
 
   def __init__(me, file):
@@ -136,86 +245,136 @@ class Config (object):
     Internal function to update the configuration from the underlying file.
     """
 
-    ## Read the configuration.  We have no need of the fancy substitutions,
-    ## so turn them all off.
-    cp = RawConfigParser()
-    cp.read(me._file)
     if T._debug: print '# reread config'
 
-    ## Save the test address.  Make sure it's vaguely sensible.  The default
-    ## is probably good for most cases, in fact, since that address isn't
-    ## actually in use.  Note that we never send packets to the test address;
-    ## we just use it to discover routing information.
-    if cp.has_option('DEFAULT', 'test-addr'):
-      testaddr = cp.get('DEFAULT', 'test-addr')
-      S.inet_aton(testaddr)
-    else:
-      testaddr = '1.2.3.4'
-
-    ## Scan the configuration file and build the groups structure.
-    groups = []
-    for sec in cp.sections():
-      pats = []
-      for tag in cp.options(sec):
-        spec = cp.get(sec, tag).split()
-
-        ## Parse the entry into peer and network.
-        if len(spec) == 1:
-          peer = None
-          net = spec[0]
-        else:
-          peer, net = spec
-
-        ## Syntax of a net is ADDRESS/MASK, where ADDRESS is a dotted-quad,
-        ## and MASK is either a dotted-quad or a single integer N indicating
-        ## a mask with N leading ones followed by trailing zeroes.
-        slash = net.index('/')
-        addr, = unpack('>L', S.inet_aton(net[:slash]))
-        if net.find('.', slash + 1) >= 0:
-          mask, = unpack('>L', S.inet_aton(net[:slash]))
+    ## Initial state.
+    testaddrs = {}
+    groups = {}
+    grpname = None
+    grplist = []
+
+    ## Open the file and start reading.
+    with open(me._file) as f:
+      lno = 0
+      for line in f:
+        lno += 1
+        for tag, rx in CONFSYNTAX:
+          m = rx.match(line)
+          if m: break
         else:
-          n = int(net[slash + 1:], 10)
-          mask = (1 << 32) - (1 << 32 - n)
-        pats.append((tag, peer, addr & mask, mask))
-
-      ## Annoyingly, RawConfigParser doesn't preserve the order of options.
-      ## In order to make things vaguely sane, we topologically sort the
-      ## patterns so that more specific patterns are checked first.
-      pats = list(toposort(lambda (t, p, a, m), (tt, pp, aa, mm): \
-                             (p and not pp) or \
-                             (p == pp and m == (m | mm) and aa == (a & mm)),
-                           pats))
-      groups.append((sec, pats))
+          raise ConfigError(me._file, lno, 'failed to parse line: %r' % line)
+
+        if tag == 'COMMENT':
+          ## A comment.  Ignore it and hope it goes away.
+
+          continue
+
+        elif tag == 'GRPHDR':
+          ## A group header.  Flush the old group and start a new one.
+          newname = m.group(1)
+
+          if grpname is not None: groups[grpname] = grplist
+          if newname in groups:
+            raise ConfigError(me._file, lno,
+                              "duplicate group name `%s'" % newname)
+          grpname = newname
+          grplist = []
+
+        elif tag == 'ASSGN':
+           ## An assignment.  Deal with it.
+          name, value = m.group(1), m.group(2)
+
+          if grpname is None:
+            ## We're outside of any group, so this is a global configuration
+            ## tweak.
+
+            if name == 'test-addr':
+              for astr in value.split():
+                try:
+                  a = parse_address(astr)
+                except Exception, e:
+                  raise ConfigError(me._file, lno,
+                                    "invalid IP address `%s': %s" %
+                                    (astr, e))
+                if a.AF in testaddrs:
+                  raise ConfigError(me._file, lno,
+                                    'duplicate %s test-address' % a.AFNAME)
+                testaddrs[a.AF] = a
+            else:
+              raise ConfigError(me._file, lno,
+                                "unknown global option `%s'" % name)
+
+          else:
+            ## Parse a pattern and add it to the group.
+            spec = value.split()
+            i = 0
+
+            ## Check for an explicit target address.
+            if i >= len(spec) or spec[i].find('/') >= 0:
+              peer = None
+              af = None
+            else:
+              try:
+                peer = parse_address(spec[i])
+              except Exception, e:
+                raise ConfigError(me._file, lno,
+                                  "invalid IP address `%s': %s" %
+                                  (spec[i], e))
+              af = peer.AF
+              i += 1
+
+            ## Parse the list of local networks.
+            nets = []
+            while i < len(spec):
+              try:
+                net = parse_net(spec[i])
+              except Exception, e:
+                raise ConfigError(me._file, lno,
+                                  "invalid IP network `%s': %s" %
+                                  (spec[i], e))
+              else:
+                nets.append(net)
+              i += 1
+            if not nets:
+              raise ConfigError(me._file, lno, 'no networks defined')
+
+            ## Make sure that the addresses are consistent.
+            for net in nets:
+              if af is None:
+                af = net.AF
+              elif net.AF != af:
+                raise ConfigError(me._file, lno,
+                                  "net %s doesn't match" % net)
+
+            ## Add this entry to the list.
+            grplist.append((name, peer, nets))
+
+    ## Fill in the default test addresses if necessary.
+    for a in TESTADDRS: testaddrs.setdefault(a.AF, a)
 
     ## Done.
-    me.testaddr = testaddr
+    if grpname is not None: groups[grpname] = grplist
+    me.testaddrs = testaddrs
     me.groups = groups
 
 ### This will be a configuration file.
 CF = None
 
-def straddr(a): return a is None and '#<none>' or S.inet_ntoa(pack('>L', a))
-def strmask(m):
-  for i in xrange(33):
-    if m == 0xffffffff ^ ((1 << (32 - i)) - 1): return i
-  return straddr(m)
-
 def cmd_showconfig():
-  T.svcinfo('test-addr=%s' % CF.testaddr)
+  T.svcinfo('test-addr=%s' %
+            ' '.join(str(a)
+                     for a in sorted(CF.testaddrs.itervalues(),
+                                     key = lambda a: a.AFNAME)))
 def cmd_showgroups():
-  for sec, pats in CF.groups:
-    T.svcinfo(sec)
+  for g in sorted(CF.groups.iterkeys()):
+    T.svcinfo(g)
 def cmd_showgroup(g):
-  for s, p in CF.groups:
-    if s == g:
-      pats = p
-      break
-  else:
-    raise T.TripeJobError('unknown-group', g)
-  for t, p, a, m in pats:
+  try: pats = CF.groups[g]
+  except KeyError: raise T.TripeJobError('unknown-group', g)
+  for t, p, nn in pats:
     T.svcinfo('peer', t,
-              'target', p or '(default)',
-              'net', '%s/%s' % (straddr(a), strmask(m)))
+              'target', p and str(p) or '(default)',
+              'net', ' '.join(map(str, nn)))
 
 ###--------------------------------------------------------------------------
 ### Responding to a network up/down event.
@@ -224,24 +383,54 @@ def localaddr(peer):
   """
   Return the local IP address used for talking to PEER.
   """
-  sk = S.socket(S.AF_INET, S.SOCK_DGRAM)
+  sk = S.socket(peer.AF, S.SOCK_DGRAM)
   try:
     try:
-      sk.connect((peer, 1))
-      addr, _ = sk.getsockname()
-      addr, = unpack('>L', S.inet_aton(addr))
-      return addr
+      sk.connect(peer.sockaddr(1))
+      addr = sk.getsockname()
+      return type(peer).from_sockaddr(addr)[0]
     except S.error:
       return None
   finally:
     sk.close()
 
 _kick = T.Queue()
+_delay = None
+
+def cancel_delay():
+  global _delay
+  if _delay is not None:
+    if T._debug: print '# cancel delayed kick'
+    G.source_remove(_delay)
+    _delay = None
+
+def netupdown(upness, reason):
+  """
+  Add or kill peers according to whether the network is up or down.
+
+  UPNESS is true if the network is up, or false if it's down.
+  """
+
+  _kick.put((upness, reason))
+
+def delay_netupdown(upness, reason):
+  global _delay
+  cancel_delay()
+  def _func():
+    global _delay
+    if T._debug: print '# delayed %s: %s' % (upness, reason)
+    _delay = None
+    netupdown(upness, reason)
+    return False
+  if T._debug: print '# delaying %s: %s' % (upness, reason)
+  _delay = G.timeout_add(2000, _func)
+
 def kickpeers():
   while True:
     upness, reason = _kick.get()
     if T._debug: print '# kickpeers %s: %s' % (upness, reason)
     select = []
+    cancel_delay()
 
     ## Make sure the configuration file is up-to-date.  Don't worry if we
     ## can't do anything useful.
@@ -254,53 +443,52 @@ def kickpeers():
     ## Find the current list of peers.
     peers = SM.list()
 
-    ## Work out the primary IP address.
+    ## Work out the primary IP addresses.
+    locals = {}
     if upness:
-      addr = localaddr(CF.testaddr)
-      if addr is None:
-        upness = False
-    else:
-      addr = None
+      for af, remote in CF.testaddrs.iteritems():
+        local = localaddr(remote)
+        if local is not None: locals[af] = local
+      if not locals: upness = False
     if not T._debug: pass
-    elif addr: print '#   local address = %s' % straddr(addr)
-    else: print '#   offline'
+    elif not locals: print '#   offline'
+    else:
+      for local in locals.itervalues():
+        print '#   local %s address = %s' % (local.AFNAME, local)
 
     ## Now decide what to do.
     changes = []
-    for g, pp in CF.groups:
+    for g, pp in CF.groups.iteritems():
       if T._debug: print '#   check group %s' % g
 
       ## Find out which peer in the group ought to be active.
-      ip = None
-      map = {}
+      statemap = {}
       want = None
-      for t, p, a, m in pp:
-        if p is None or not upness:
-          ipq = addr
-        else:
-          ipq = localaddr(p)
+      matchp = False
+      for t, p, nn in pp:
+        af = nn[0].AF
+        if p is None or not upness: ip = locals.get(af)
+        else: ip = localaddr(p)
         if T._debug:
-          info = 'peer=%s; target=%s; net=%s/%s; local=%s' % (
-            t, p or '(default)', straddr(a), strmask(m), straddr(ipq))
-        if upness and ip is None and \
-              ipq is not None and (ipq & m) == a:
+          info = 'peer = %s; target = %s; nets = %s; local = %s' % (
+            t, p or '(default)', ', '.join(map(str, nn)), straddr(ip))
+        if upness and not matchp and \
+           ip is not None and any(ip.withinp(n) for n in nn):
           if T._debug: print '#     %s: SELECTED' % info
-          map[t] = 'up'
+          statemap[t] = 'up'
           select.append('%s=%s' % (g, t))
-          if t == 'down' or t.startswith('down/'):
-            want = None
-          else:
-            want = t
-          ip = ipq
+          if t == 'down' or t.startswith('down/'): want = None
+          else: want = t
+          matchp = True
         else:
-          map[t] = 'down'
+          statemap[t] = 'down'
           if T._debug: print '#     %s: skipped' % info
 
       ## Shut down the wrong ones.
       found = False
-      if T._debug: print '#   peer-map = %r' % map
+      if T._debug: print '#   peer-map = %r' % statemap
       for p in peers:
-        what = map.get(p, 'leave')
+        what = statemap.get(p, 'leave')
         if what == 'up':
           found = True
           if T._debug: print '#   peer %s: already up' % p
@@ -332,15 +520,6 @@ def kickpeers():
       SM.notify('conntrack', upness and 'up' or 'down', *select + reason)
       for c in changes: c()
 
-def netupdown(upness, reason):
-  """
-  Add or kill peers according to whether the network is up or down.
-
-  UPNESS is true if the network is up, or false if it's down.
-  """
-
-  _kick.put((upness, reason))
-
 ###--------------------------------------------------------------------------
 ### NetworkManager monitor.
 
@@ -391,13 +570,13 @@ class NetworkManagerMonitor (object):
 
   def _nm_state(me, state):
     if state in NM_CONNSTATES:
-      netupdown(True, ['nm', 'connected'])
+      delay_netupdown(True, ['nm', 'connected'])
     else:
-      netupdown(False, ['nm', 'disconnected'])
+      delay_netupdown(False, ['nm', 'disconnected'])
 
   def _nm_connchange(me, props):
-    if props.get('Default', False):
-      netupdown(True, ['nm', 'default-connection-change'])
+    if props.get('Default', False) or props.get('Default6', False):
+      delay_netupdown(True, ['nm', 'default-connection-change'])
 
 ##--------------------------------------------------------------------------
 ### Connman monitor.
@@ -429,7 +608,7 @@ class ConnManMonitor (object):
 
   def _cm_state(me, prop, value):
     if prop != 'State': return
-    netupdown(value == 'online', ['connman', value])
+    delay_netupdown(value == 'online', ['connman', value])
 
 ###--------------------------------------------------------------------------
 ### Maemo monitor.
@@ -470,10 +649,10 @@ class MaemoICdMonitor (object):
   def _icd_state(me, iap, ty, state, hunoz):
     if state == 'CONNECTED':
       me._iap = iap
-      netupdown(True, ['icd', 'connected', iap])
+      delay_netupdown(True, ['icd', 'connected', iap])
     elif state == 'IDLE' and iap == me._iap:
       me._iap = None
-      netupdown(False, ['icd', 'idle'])
+      delay_netupdown(False, ['icd', 'idle'])
 
 ###--------------------------------------------------------------------------
 ### D-Bus connection tracking.
@@ -585,8 +764,9 @@ def init():
   DBM.addmon(NetworkManagerMonitor())
   DBM.addmon(ConnManMonitor())
   DBM.addmon(MaemoICdMonitor())
-  G.timeout_add_seconds(30, lambda: (netupdown(True, ['interval-timer'])
-                                     or True))
+  G.timeout_add_seconds(30, lambda: (_delay is not None or
+                                     netupdown(True, ['interval-timer']) or
+                                     True))
 
 def parse_options():
   """
index 032142c..e9e9cb9 100644 (file)
@@ -46,11 +46,16 @@ fi
 peer=$1 ifname=$2 family=$3; shift 3
 
 ## Parse the address family.
+case "$family" in
+  INET) ipsz=20 ;;
+  INET6) ipsz=40 ;;
+  *) echo >&2 "$0: unknown address family $family"; exit 1 ;;
+esac
 case "$family,$#" in
-  INET,1) addr=$1 port=4070 ;;
-  INET,2) addr=$1 port=$2 ;;
-  INET,*) echo >&2 "$0: bad INET address"; exit 1 ;;
-  *)      echo >&2 "$0: unknown address family $family"; exit 1 ;;
+  INET,1 | INET6,1) addr=$1 port=4070 ;;
+  INET,2 | INET6,2) addr=$1 port=$2 ;;
+  INET,* | INET6,*) echo >&2 "$0: bad $family address"; exit 1 ;;
+  *) echo >&2 "$0: unknown address family $family"; exit 1 ;;
 esac
 
 ###--------------------------------------------------------------------------
@@ -137,7 +142,7 @@ case $haveaddr4,$haveaddr6 in
        mtu=$P_MTU;;
       *)
        pathmtu=$(pathmtu "$addr")
-       mtu=$(expr "$pathmtu" - 29 - $A_BULK_OVERHEAD)
+       mtu=$(( $pathmtu - $ipsz - 9 - $A_BULK_OVERHEAD ))
        ;;
     esac
     try ip link set dev "$ifname" up mtu "$mtu"