chiark / gitweb /
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 84fcf5a90bd9a4a7a223fb0560642cc8fd4b0464..887f81d31ee46cc512658bf0449727ff598f9234 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 c84efdcb195c58353df7f440e902a11a448f0c9d..7bc678aaa24317e68846b99be0a72110cefdb226 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 d0118a457ed26f1ec17261031b08fde7a2fa24db..3d2aa824a21de507898cf46454c66bc70c2c604e 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 8666575a1fba5a6c5b182688341cd0638458b1b5..11ee6dc0b79aa410445647c450f712196c707afc 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 eca637ac3636956e40b9cd28b28a9d4b09316f38..32fe121f06c2a9448697823d28e1a1f026fa6f3a 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 32f67a11f340f728ba760f8af06457c2192d2232..4eba80d6da8d8166547b625c46e869689b7e2673 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 ac27b49cabd5c819363e958d35ff2a037b7f28ce..d638b07821da4b04247558f924aba83c2368d159 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 81e62f75fa8c2727f226a10c4e6ae563151b678d..92c1a0700587b448c3a19c44657ee12fb1ca2c1e 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 cdf8dc5ed1a3cbd4a35997cc160047b4778bcf4a..757e5cdea6301d2ebf9a6535ab385c50c016e178 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 4e565e00da4357d97c624406e809b8751f9044aa..8b2a058c316b2523bb43aa0aa402534096c001a7 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 cab0fb2b74cae87d498537d6090a0eb226d5036c..e939919e1f58ebcd06f460835cfa040e71f4f789 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 bd57306d7f08c203e0d2085a10ba62973353363d..6acf2a1df4b996ccc3bafe99da87ffcfe4fe336e 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 0126dc586991c1357f71e0679b85c3c34cba0b72..a9be66872cfbbc0b1e4223272238db4cbef9719c 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 2a1ff28ac20d2caac630c843a4069a540d93e9c1..f569b4559bbf3c96f08e41a8ca705fcea757b1a2 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 140d0b6ea0a324e1d724a532613a6f8fdc38bcb1..76a358ea34eec387e54fe419615169f593ad8a35 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 54883afa448cf15926c78c5e5d0c8748ea0276d1..87bb90530653529c39902db80e770584d8fef373 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 606b2038d0c76f93a64af77d539566786de0fe7f..a8099e41adb7776a026f283b10bf625d810eb65c 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 72b5b80e229961c136d526d3653a6ac3a73b1ff5..703e448ec8dcea95cf9405ff5378e1aebe1e97d4 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 c81dc111b84e1d4677e8633da27e7f26596a89aa..f066ae6dcd280a97e9e5347ee004fb3584d0df6f 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 aaaf2678b99a8cedb07f9909cc829e449cbce11f..8b7868283ad1ea8920c39acaf173a508edd81f76 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 d50757bc2baf2d1c2e1ae45ea7d895924ee08ef7..b84388573d1e02dd4b038d210733543ddd9be806 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 a2907be1a6bff9a030d9dd4879c8eed1e96e14e6..10a03f5fd79900201b2878a97a1bd76d32b00503 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 713a70b3c4640243a5524f84978fc36fc86fb003..b38f6681dacc96291a7b7d320a9ebabab5f190a4 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 3368295a46ec8d743dee349a503243dcca6b55ac..28e4b0b3321bedad801ca71fda6a7fe11bc4c1cc 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 032142c5716f845e773db8357ac23e0a38ce0c90..e9e9cb929bde385bea97be14f8188808a938d6f8 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"