From: Mark Wooding Date: Fri, 25 Jan 2019 12:08:24 +0000 (+0000) Subject: Merge branches 'mdw/knock' and 'mdw/ipv6' into bleeding X-Git-Tag: 1.5.0~41 X-Git-Url: https://git.distorted.org.uk/~mdw/tripe/commitdiff_plain/4a3882945f605704ede113a9fe98cd19a92363a7?hp=067aa5f013dd6108e81c1df0c2ed19491802bc69 Merge branches 'mdw/knock' and 'mdw/ipv6' into bleeding * 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. ... --- diff --git a/configure.ac b/configure.ac index 84fcf5a9..887f81d3 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/contrib/greet.in b/contrib/greet.in index c84efdcb..7bc678aa 100644 --- a/contrib/greet.in +++ b/contrib/greet.in @@ -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) diff --git a/contrib/tripe-ipif.in b/contrib/tripe-ipif.in index d0118a45..3d2aa824 100755 --- a/contrib/tripe-ipif.in +++ b/contrib/tripe-ipif.in @@ -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") diff --git a/mon/tripemon.in b/mon/tripemon.in index 8666575a..11ee6dc0 100644 --- a/mon/tripemon.in +++ b/mon/tripemon.in @@ -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 diff --git a/pathmtu/pathmtu.1.in b/pathmtu/pathmtu.1.in index eca637ac..32fe121f 100644 --- a/pathmtu/pathmtu.1.in +++ b/pathmtu/pathmtu.1.in @@ -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 diff --git a/pathmtu/pathmtu.c b/pathmtu/pathmtu.c index 32f67a11..4eba80d6 100644 --- a/pathmtu/pathmtu.c +++ b/pathmtu/pathmtu.c @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include #include @@ -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)); diff --git a/peerdb/peers.in.5.in b/peerdb/peers.in.5.in index ac27b49c..d638b078 100644 --- a/peerdb/peers.in.5.in +++ b/peerdb/peers.in.5.in @@ -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 diff --git a/peerdb/tripe-newpeers.in b/peerdb/tripe-newpeers.in index 81e62f75..92c1a070 100644 --- a/peerdb/tripe-newpeers.in +++ b/peerdb/tripe-newpeers.in @@ -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() ###-------------------------------------------------------------------------- diff --git a/pkstream/pkstream.1.in b/pkstream/pkstream.1.in index cdf8dc5e..757e5cde 100644 --- a/pkstream/pkstream.1.in +++ b/pkstream/pkstream.1.in @@ -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 . diff --git a/pkstream/pkstream.c b/pkstream/pkstream.c index 4e565e00..8b2a058c 100644 --- a/pkstream/pkstream.c +++ b/pkstream/pkstream.c @@ -45,6 +45,7 @@ #include #include +#include #include #include #include @@ -57,6 +58,15 @@ /*----- 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 (""); + 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) diff --git a/proxy/tripe-mitm.8.in b/proxy/tripe-mitm.8.in index cab0fb2b..e939919e 100644 --- a/proxy/tripe-mitm.8.in +++ b/proxy/tripe-mitm.8.in @@ -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 . diff --git a/proxy/tripe-mitm.c b/proxy/tripe-mitm.c index bd57306d..6acf2a1d 100644 --- a/proxy/tripe-mitm.c +++ b/proxy/tripe-mitm.c @@ -62,9 +62,9 @@ #include #include +#include #include #include -#include #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)); diff --git a/py/tripe.py.in b/py/tripe.py.in index 0126dc58..a9be6687 100644 --- a/py/tripe.py.in +++ b/py/tripe.py.in @@ -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): diff --git a/server/Makefile.am b/server/Makefile.am index 2a1ff28a..f569b455 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -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. diff --git a/server/addrmap.c b/server/addrmap.c index 140d0b6e..76a358ea 100644 --- a/server/addrmap.c +++ b/server/addrmap.c @@ -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(); } diff --git a/server/admin.c b/server/admin.c index 54883afa..87bb9053 100644 --- a/server/admin.c +++ b/server/admin.c @@ -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 = ""; 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; diff --git a/server/peer.c b/server/peer.c index 606b2038..a8099e41 100644 --- a/server/peer.c +++ b/server/peer.c @@ -27,11 +27,14 @@ #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)) diff --git a/server/servutil.c b/server/servutil.c index 72b5b80e..703e448e 100644 --- a/server/servutil.c +++ b/server/servutil.c @@ -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(); } } diff --git a/server/tripe-admin.5.in b/server/tripe-admin.5.in index c81dc111..f066ae6d 100644 --- a/server/tripe-admin.5.in +++ b/server/tripe-admin.5.in @@ -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 diff --git a/server/tripe.8.in b/server/tripe.8.in index aaaf2678..8b786828 100644 --- a/server/tripe.8.in +++ b/server/tripe.8.in @@ -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 diff --git a/server/tripe.c b/server/tripe.c index d50757bc..b8438857 100644 --- a/server/tripe.c +++ b/server/tripe.c @@ -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 diff --git a/server/tripe.h b/server/tripe.h index a2907be1..10a03f5f 100644 --- a/server/tripe.h +++ b/server/tripe.h @@ -62,10 +62,17 @@ #include #include +#ifdef HAVE_LIBADNS +# define ADNS_FEATURE_MANYAF +# include +#endif + #include #include #include -#include +#ifndef HAVE_LIBADNS +# include +#endif #include #include #include @@ -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 diff --git a/svc/conntrack.8.in b/svc/conntrack.8.in index 713a70b3..b38f6681 100644 --- a/svc/conntrack.8.in +++ b/svc/conntrack.8.in @@ -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) diff --git a/svc/conntrack.in b/svc/conntrack.in index 3368295a..28e4b0b3 100644 --- a/svc/conntrack.in +++ b/svc/conntrack.in @@ -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 '#' 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 '#' 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(): """ diff --git a/svc/tripe-ifup.in b/svc/tripe-ifup.in index 032142c5..e9e9cb92 100644 --- a/svc/tripe-ifup.in +++ b/svc/tripe-ifup.in @@ -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"