]> git.lizzy.rs Git - plan9front.git/commitdiff
tinc: implement experimental mash peer to peer VPN from http://www.tinc-vpn.org/
authorcinap_lenrek <cinap_lenrek@felloff.net>
Tue, 31 Oct 2017 21:44:25 +0000 (22:44 +0100)
committercinap_lenrek <cinap_lenrek@felloff.net>
Tue, 31 Oct 2017 21:44:25 +0000 (22:44 +0100)
sys/man/8/tinc [new file with mode: 0644]
sys/src/cmd/ip/mkfile
sys/src/cmd/ip/tinc.c [new file with mode: 0644]

diff --git a/sys/man/8/tinc b/sys/man/8/tinc
new file mode 100644 (file)
index 0000000..18163e8
--- /dev/null
@@ -0,0 +1,119 @@
+.TH TINC 8
+.SH NAME
+tinc - mash peer to peer VPN
+.SH SYNOPSIS
+.B ip/tinc
+[
+.B -d
+] [
+.B -p
+.I maxprocs
+] [
+.B -x
+.I inside
+] [
+.B -o
+.I outside
+] [
+.B -c
+.I confdir
+] [
+.B -n
+.I myname
+]
+.I localip
+.I localmask
+[
+.I hosts...
+]
+.SH DESCRIPTION
+Tinc implements the mash peer to peer VPN protocol from
+.I https://www.tinc-vpn.org/
+as of version 1.0.32. Within a tinc VPN one can reach all
+the subnets of all hosts within the network even when not
+directly connected to the owning host of the subnet.
+.PP
+Each host that is directly connected to us has its own hostfile under
+.IR confdir /hosts/ hostname
+containing its public address, owned subnets, options and RSA public key.
+The hostfile format is the same as the original tinc implementation.
+The
+.I confdir
+is specified with the
+.B -c
+option or defaults to the current working directory.
+Other hosts might exist behind these directly connected nodes but
+this information is distributed automatically within the protocol.
+.PP
+On startup,
+.I tinc
+creates an ip interface with the address
+.I localip
+and network mask
+.I localmask
+on the
+.I inside
+ip stack (specified with
+.B -x
+option) and starts listening for incoming connections on the
+.I outside
+ip stack (specified with the
+.B -o
+option). When optional
+.I hosts
+are specified on the command line, then it will also do outgoing connections
+using the
+.I outside
+ip stack. The
+.I localmask
+usually is a supernet of all the subnets within the VPN. Our own hostname
+.I myhost
+can be specified with
+.B -n
+option or is asssumed to be the
+.I sysname
+when not specified.
+This hosts RSA private key needs to be present in factotum and tagged with
+.BR "service=tinc"
+and
+.BI "host=" myhost .
+.PP
+The options:
+.TP
+.B -d
+Enable debug outout and do not fork to the background.
+.TP
+.B -p
+Limit the number of client processes to
+.IR  maxprocs .
+.TP
+.B -x
+Specifies the
+.I inside
+and
+.I outside
+network stack directory where the tinc ip interface it bound. Defaults to
+.BR /net .
+.TP
+.B -o
+Specifies the
+.I outside
+network stack directory where incoming and outgoing tinc connections
+are made. Defaults to
+.BR inside .
+.TP
+.B -c
+Specifies the configuration directory
+.I confdir
+for the VPN.
+.TP
+.B -n
+Sets our hostname to
+.IR myhost .
+.SH "SEE ALSO"
+.IR rsa (8),
+.IR ip (3)
+.br
+.I https://www.tinc-vpn.org/documentation/
+.SH SOURCE
+.B /sys/src/cmd/ip/tinc.c
index 900bb17f70d6e2c93b3d057d667e8291025fdbc7..702a2e01e2bf4dcd3847d158590b51106022c67a 100644 (file)
@@ -21,6 +21,7 @@ TARG =        6in4\
        telnetd\
        tftpd\
        tftpfs\
+       tinc\
        traceroute\
        torrent\
        udpecho\
diff --git a/sys/src/cmd/ip/tinc.c b/sys/src/cmd/ip/tinc.c
new file mode 100644 (file)
index 0000000..31ac6e3
--- /dev/null
@@ -0,0 +1,1662 @@
+#include <u.h>
+#include <libc.h>
+#include <mp.h>
+#include <libsec.h>
+#include <auth.h>
+#include <ip.h>
+
+enum {
+       OptIndirect     = 1,
+       OptTcpOnly      = 3,
+       OptPmtuDiscov   = 4,
+       OptClampMss     = 8,
+
+       MaxWeight       = 0x7fffffff,
+       AESkeylen       = 256/8,
+       MAClen          = 4,
+
+       Eaddrlen        = 6,
+       EtherType       = 2*Eaddrlen,
+       EtherHdr        = EtherType+2,
+
+       Ip4Hdr          = 20,
+       Ip6Hdr          = 40,
+       UdpHdr          = 8,
+       TcpHdr          = 20,
+
+       /* worst case: UDPv6 over 6in4 over PPPoE */
+       DefPMTU         = 1500-8-Ip4Hdr-Ip6Hdr-UdpHdr-4-AESbsize-MAClen,
+
+       MaxPacket       = 4096,
+
+       /* messages */
+       ID              = 0,
+       META_KEY        = 1,
+       CHALLENGE       = 2,
+       CHAL_REPLY      = 3,
+       ACK             = 4,
+       PING            = 8,
+       PONG            = 9,
+       ADD_SUBNET      = 10,
+       DEL_SUBNET      = 11,
+       ADD_EDGE        = 12,
+       DEL_EDGE        = 13,
+       KEY_CHANGED     = 14,
+       REQ_KEY         = 15,
+       ANS_KEY         = 16,
+       TCP_PACKET      = 17,
+
+       /* openssl crap */
+       EVP_AES256CBC   = 427,
+       EVP_AES256CFB   = 429,
+       EVP_SHA256      = 672,
+};
+
+typedef struct Snet Snet;
+typedef struct Edge Edge;
+typedef struct Ciph Ciph;
+typedef struct Host Host;
+typedef struct Conn Conn;
+
+struct Snet
+{
+       Host    *owner;
+
+       Snet    *next;  /* next subnet on owner */
+       uchar   mask[IPaddrlen];
+       uchar   ip[IPaddrlen];
+       int     prefixlen;
+       int     weight;
+       char    reported;
+       char    deleted;
+};
+
+struct Edge
+{
+       Host    *src;
+       Host    *dst;
+       Edge    *next;  /* next edge on src */
+       Edge    *rev;   /* reverse drection edge */
+
+       uchar   ip[IPaddrlen];
+       int     port;
+
+       int     options;
+       int     weight;
+       char    reported;
+       char    deleted;
+};
+
+struct Ciph
+{
+       void    (*crypt)(uchar*, int, AESstate*);
+       uint    seq;
+       uchar   key[AESkeylen+AESbsize];
+       AESstate cs[1];
+       Lock;
+};
+
+struct Host
+{
+       Host    *next;
+       char    *name;
+       char    *addr;
+
+       Conn    *conn;
+       Host    *from;
+       Edge    *link;
+
+       Snet    *snet;
+
+       uchar   ip[IPaddrlen];
+       int     port;
+
+       int     connected;
+       int     options;
+       int     pmtu;
+       int     udpfd;
+
+       uvlong  ooo;    /* out of order replay window */
+       Ciph    cin[1];
+       Ciph    cout[1];
+
+       RSApub  *rsapub;
+};
+
+struct Conn
+{
+       Host    *host;
+       Edge    *edge;
+
+       int     fd;
+       int     port;
+       uchar   ip[IPaddrlen];
+
+       vlong   pingtime;
+
+       QLock   sendlk;
+
+       Ciph    cin[1];
+       Ciph    cout[1];
+
+       char    *rp;
+       char    *wp;
+       char    buf[MaxPacket+16];
+};
+
+QLock  hostslk;
+Host   *hosts;
+
+Edge   **edges;
+int    nedges;
+Snet   **snet;
+int    nsnet;
+
+int    debug;
+int    maxprocs = 100;
+
+char   *confdir = ".";
+char   *myname = nil;
+Host   *myhost = nil;
+
+Conn   *bcast = (void*)-1;
+Conn   *lconn = nil;
+RWLock netlk;
+
+char   *outside = "/net";
+char   *inside = "/net";
+int    ipifn = 0;
+int    ipcfd = -1;
+int    ipdfd = -1;
+uchar  localip[IPaddrlen];
+uchar  localmask[IPaddrlen];
+
+void   deledge(Edge*);
+void   delsubnet(Snet*);
+void   netrecalc(void);
+
+void   procsetname(char *fmt, ...);
+int    consend(Conn *c, char *fmt, ...);
+void   routepkt(Host *s, uchar *p, int n);
+void   needkey(Host *from);
+void   clearkey(Host *from);
+
+#pragma varargck argpos procsetname 1
+#pragma varargck argpos consend 2
+
+void*
+emalloc(ulong len)
+{
+       void *v = malloc(len);
+       if(v == nil)
+               sysfatal("malloc: %r");
+       setmalloctag(v, getcallerpc(&len));
+       memset(v, 0, len);
+       return v;
+}
+void*
+erealloc(void *v, ulong len)
+{
+       if((v = realloc(v, len)) == nil && len != 0)
+               sysfatal("realloc: %r");
+       setrealloctag(v, getcallerpc(&v));
+       return v;
+}
+char*
+estrdup(char *s)
+{
+       if((s = strdup(s)) == nil)
+               sysfatal("strdup: %r");
+       setmalloctag(s, getcallerpc(&s));
+       return s;
+}
+
+void
+procsetname(char *fmt, ...)
+{
+       int fd, n;
+       char buf[128];
+       va_list arg;
+
+       snprint(buf, sizeof buf, "#p/%d/args", getpid());
+       if((fd = open(buf, OWRITE)) < 0)
+               return;
+       va_start(arg, fmt);
+       n = vsnprint(buf, sizeof buf, fmt, arg);
+       va_end(arg);
+       write(fd, buf, n+1);
+       close(fd);
+}
+
+char*
+fd2dir(int fd, char *dir, int len)
+{
+       char *p;
+
+       *dir = 0;
+       if(fd2path(fd, dir, len) < 0)
+               return nil;
+       p = strrchr(dir, '/');
+       if(p == nil || p == dir)
+               return nil;
+       *p = 0;
+       return dir;
+}
+
+int
+dir2ipport(char *dir, uchar ip[IPaddrlen])
+{
+       NetConnInfo *nci;
+       int port = -1;
+
+       if((nci = getnetconninfo(dir, -1)) == nil)
+               return -1;
+       if(parseip(ip, nci->rsys) != -1)
+               port = atoi(nci->rserv);
+       freenetconninfo(nci);
+       return port;
+}
+
+void
+hangupfd(int fd)
+{
+       char buf[128];
+
+       if(fd < 0 || fd2dir(fd, buf, sizeof(buf)-5) == nil)
+               return;
+       strcat(buf, "/ctl");
+       if((fd = open(buf, OWRITE)) >= 0){
+               hangup(fd);
+               close(fd);
+       }
+}
+
+void
+netlock(Conn *c)
+{
+       if(c != nil) {
+               wlock(&netlk);
+               assert(lconn == nil);
+               lconn = c;
+       } else {
+               rlock(&netlk);
+               assert(lconn == nil);
+       }
+}
+void
+netunlock(Conn *c)
+{
+       if(c != nil){
+               assert(c == lconn);
+               netrecalc();
+               lconn = nil;
+               wunlock(&netlk);
+       }else {
+               assert(lconn == nil);
+               runlock(&netlk);
+       }
+}
+
+int
+edgecmp(Edge *a, Edge *b)
+{
+       int c;
+
+       if((c = a->deleted - b->deleted) != 0)
+               return c;
+       return a->weight - b->weight;
+}
+int
+edgepcmp(void *a, void *b)
+{
+       return edgecmp(*(Edge**)a, *(Edge**)b);
+}
+
+int
+subnetcmp(Snet *a, Snet *b)
+{
+       int c;
+
+       if((c = a->deleted - b->deleted) != 0)
+               return c;
+       if((c = memcmp(b->mask, a->mask, IPaddrlen)) != 0)
+               return c;
+       if((c = memcmp(a->ip, b->ip, IPaddrlen)) != 0)
+               return c;
+       return a->weight - b->weight;
+}
+int
+subnetpcmp(void *a, void *b)
+{
+       return subnetcmp(*(Snet**)a, *(Snet**)b);
+}
+
+Snet*
+lookupnet(uchar ip[IPaddrlen])
+{
+       int i;
+       Snet *t;
+       uchar x[IPaddrlen];
+
+       for(i=0; i<nsnet; i++){
+               t = snet[i];
+               maskip(ip, t->mask, x);
+               if(memcmp(x, t->ip, IPaddrlen) == 0)
+                       return t;
+       }
+       return nil;
+}
+
+void
+reportsubnet(Conn *c, Snet *t)
+{
+       if(c == nil || !(t->deleted || t->owner->connected))
+               return;
+       if(c == bcast){
+               Edge *x;
+
+               if(t->deleted != t->reported)
+                       return;
+               t->reported = !t->deleted;
+               for(x = myhost->link; x != nil; x = x->next)
+                       if(x->dst->conn != lconn && x->dst->from == myhost)
+                               reportsubnet(x->dst->conn, t);
+               return;
+       }
+       if(t->owner == c->host)
+               return;
+       if(t->deleted)
+               consend(c, "%d %x %s %I/%d#%d", DEL_SUBNET, rand(),
+                       t->owner->name, t->ip, t->prefixlen, t->weight);
+       else
+               consend(c, "%d %x %s %I/%d#%d", ADD_SUBNET, rand(), t->owner->name,
+                       t->ip, t->prefixlen, t->weight);
+}
+void
+reportedge(Conn *c, Edge *e)
+{
+       if(c == nil || !(e->deleted || e->src->connected && e->dst->connected))
+               return;
+       if(c == bcast){
+               Edge *x;
+
+               if(e->deleted != e->reported)
+                       return;
+               e->reported = !e->deleted;
+               for(x = myhost->link; x != nil; x = x->next)
+                       if(x->dst->conn != lconn && x->dst->from == myhost)
+                               reportedge(x->dst->conn, e);
+               return;
+       }
+       if(e->src == c->host)
+               return;
+       if(e->deleted){
+               if(e->dst == c->host)
+                       return;
+               consend(c, "%d %x %s %s", DEL_EDGE, rand(),
+                       e->src->name, e->dst->name);
+       } else
+               consend(c, "%d %x %s %s %s %d %x %d", ADD_EDGE, rand(),
+                       e->src->name, e->dst->name,
+                       e->dst->addr, e->dst->port, e->dst->options, e->weight);
+}
+
+void
+netrecalc(void)
+{
+       Host *h;
+       Edge *e;
+       Snet *t;
+       int i;
+
+       if(myhost == nil)
+               return;
+
+       qsort(edges, nedges, sizeof(edges[0]), edgepcmp);
+       while(nedges > 0 && edges[nedges-1]->deleted){
+               reportedge(bcast, edges[--nedges]);
+               free(edges[nedges]);
+               edges[nedges] = nil;
+       }
+       for(h = hosts; h != nil; h = h->next) h->from = nil;
+
+       myhost->from = myhost;
+       myhost->connected = 1;
+
+Loop:
+       for(i=0; i<nedges; i++){
+               e = edges[i];
+               if(e->src->from == nil || (h = e->dst)->from != nil)
+                       continue;
+               memmove(h->ip, e->ip, IPaddrlen);
+               h->port = e->port;
+               h->options = e->options;
+               h->from = e->src;
+               if(h->connected == 0){
+                       h->connected = 1;
+                       for(t = h->snet; t != nil; t = t->next)
+                               t->reported = 0;
+                       e->reported = 0;
+               }
+               goto Loop;
+       }
+
+       for(h = hosts; h != nil; h = h->next){
+               if(h->from == nil && h->connected){
+                       h->connected = 0;
+                       clearkey(h);
+                       while(h->link != nil) {
+                               deledge(h->link->rev);
+                               deledge(h->link);
+                       }
+                       while(h->snet != nil) delsubnet(h->snet);
+               }
+       }
+
+       qsort(snet, nsnet, sizeof(snet[0]), subnetpcmp);
+       for(i = nsnet-1; i >= 0; i--){
+               reportsubnet(bcast, snet[i]);
+               if(snet[i]->deleted){
+                       assert(i == nsnet-1);
+                       nsnet = i;
+                       free(snet[i]);
+                       snet[i] = nil;
+               }
+       }
+
+       qsort(edges, nedges, sizeof(edges[0]), edgepcmp);
+       for(i = nedges-1; i >= 0; i--){
+               reportedge(bcast, edges[i]);
+               if(edges[i]->deleted){
+                       assert(i == nedges-1);
+                       nedges = i;
+                       free(edges[i]);
+                       edges[i] = nil;
+               }
+       }
+}
+
+Snet*
+getsubnet(Host *h, char *s, int new)
+{
+       uchar ip[IPaddrlen], mask[IPaddrlen];
+       Snet *t;
+       char *p, *e;
+       int i, prefixlen, weight;
+
+       if(parseip(ip, s) == -1)
+               return nil;
+
+       weight = 10;
+       prefixlen = 128;
+       if((p = strchr(s, '/')) != nil){
+               if((e = strchr(p+1, '#')) != nil)
+                       *e = 0;
+               prefixlen = atoi(p+1) + (isv4(ip)!=0)*(128-32);
+               if(e != nil){
+                       *e = '#';
+                       weight = atoi(e+1);
+               }
+       }
+       memset(mask, 0, IPaddrlen);
+       for(i=0; i<prefixlen; i++)
+               mask[i/8] |= 0x80>>(i%8);
+       maskip(ip, mask, ip);
+
+       for(t = h->snet; t != nil; t = t->next)
+               if(memcmp(t->ip, ip, IPaddrlen) == 0
+               && memcmp(t->mask, mask, IPaddrlen) == 0){
+                       if(new)
+                               t->weight = weight;
+                       return t;
+               }
+
+       if(!new)
+               return nil;
+
+       t = emalloc(sizeof(Snet));
+       memmove(t->ip, ip, IPaddrlen);
+       memmove(t->mask, mask, IPaddrlen);
+       t->prefixlen = prefixlen - (isv4(ip)!=0)*(128-32);
+       t->weight = weight;
+       t->owner = h;
+       t->next = h->snet;
+       h->snet = t;
+       if((nsnet % 16) == 0)
+               snet = erealloc(snet, sizeof(snet[0])*(nsnet+16));
+       snet[nsnet++] = t;
+       return t;
+}
+
+void
+delsubnet(Snet *t)
+{
+       Snet **tp;
+
+       if(t == nil || t->deleted)
+               return;
+       for(tp = &t->owner->snet; *tp != nil; tp = &(*tp)->next){
+               if(*tp == t){
+                       *tp = t->next;
+                       break;
+               }
+       }
+       t->next = nil;
+       t->deleted = 1;
+}
+
+Edge*
+getedge(Host *src, Host *dst, int new)
+{
+       Edge *e;
+
+       for(e = src->link; e != nil; e = e->next)
+               if(e->dst == dst)
+                       return e;
+       if(!new)
+               return nil;
+       e = emalloc(sizeof(Edge));
+       e->weight = MaxWeight;
+       e->src = src;
+       e->dst = dst;
+       e->next = src->link;
+       src->link = e;
+       if((e->rev = getedge(dst, src, 0)) != nil)
+               e->rev->rev = e;
+       if((nedges % 16) == 0)
+               edges = erealloc(edges, sizeof(edges[0])*(nedges+16));
+       edges[nedges++] = e;
+       return e;
+}
+
+void
+deledge(Edge *e)
+{
+       Edge **ep;
+
+       if(e == nil || e->deleted)
+               return;
+       if(e->rev != nil){
+               if(e->rev->rev != nil){
+                       assert(e->rev->rev == e);
+                       e->rev->rev = nil;
+               }
+               e->rev = nil;
+       }
+       for(ep = &e->src->link; *ep != nil; ep = &((*ep)->next))
+               if(*ep == e){
+                       *ep = e->next;
+                       break;
+               }
+       e->next = nil;
+       e->options = 0;
+       e->weight = MaxWeight;
+       e->port = 0;
+       memset(e->ip, 0, IPaddrlen);
+       e->deleted = 1;
+}
+
+Host*
+gethost(char *name, int new)
+{
+       char buf[8*1024], *s, *e, *x;
+       Host *h;
+       int fd, n;
+
+       if(*name == 0 || *name == '.' || strchr(name, '/') != nil)
+               return nil;
+       qlock(&hostslk);
+       for(h = hosts; h != nil; h = h->next)
+               if(strcmp(h->name, name) == 0)
+                       goto out;
+       snprint(buf, sizeof(buf), "%s/hosts/%s", confdir, name);
+       if((fd = open(buf, OREAD)) < 0){
+               if(!new)
+                       goto out;
+               buf[0] = 0;
+       } else {
+               n = read(fd, buf, sizeof(buf)-1);
+               close(fd);
+               if(n <= 0)
+                       goto out;
+               buf[n] = 0;
+       }
+       h = emalloc(sizeof(Host));
+       h->name = estrdup(name);
+       h->addr = estrdup(name);
+       h->port = 655;
+       h->pmtu = DefPMTU;
+       h->options = OptClampMss;
+       h->udpfd = -1;
+       h->connected = 0;
+       h->next = hosts;
+       hosts = h;
+       if((s = (char*)decodePEM(buf, "RSA PUBLIC KEY", &n, nil)) == nil)
+               goto out;
+       h->rsapub = asn1toRSApub((uchar*)s, n);
+       free(s);
+       if(h->rsapub == nil)
+               goto out;
+       for(s = buf; s != nil; s = e){
+               char *f[2];
+
+               if((e = strchr(s, '\n')) != nil)
+                       *e++ = 0;
+               if((x = strchr(s, '=')) == nil)
+                       continue;
+               *x = ' ';
+               if((n = tokenize(s, f, nelem(f))) != 2)
+                       continue;
+               if(cistrcmp(f[0], "Address") == 0){
+                       free(h->addr);
+                       h->addr = estrdup(f[1]);
+                       continue;
+               }
+               if(cistrcmp(f[0], "IndirectData") == 0){
+                       h->options |= OptIndirect*(cistrcmp(f[1], "yes") == 0);
+                       continue;
+               }
+               if(cistrcmp(f[0], "TCPonly") == 0){
+                       h->options |= OptTcpOnly*(cistrcmp(f[1], "yes") == 0);
+                       continue;
+               }
+               if(cistrcmp(f[0], "ClampMSS") == 0){
+                       h->options &= ~(OptClampMss*(cistrcmp(f[1], "no") == 0));
+                       continue;
+               }
+               if(cistrcmp(f[0], "PMTU") == 0){
+                       h->pmtu = atoi(f[1]);
+                       if(h->pmtu > MaxPacket)
+                               h->pmtu = MaxPacket;
+                       else if(h->pmtu < 512)
+                               h->pmtu = 512;
+                       continue;
+               }
+               if(cistrcmp(f[0], "Port") == 0){
+                       h->port = atoi(f[1]);
+                       continue;
+               }
+               if(cistrcmp(f[0], "Subnet") == 0){
+                       if(myhost == nil)
+                               getsubnet(h, f[1], 1);
+                       continue;
+               }
+       }
+       if(myhost == nil && h->snet == nil){
+               snprint(buf, sizeof(buf), "%I/%d", localip, isv4(localip) ? 32 : 128);
+               getsubnet(h, buf, 1);
+       }
+       parseip(h->ip, h->addr);
+out:
+       qunlock(&hostslk);
+       return h;
+}
+
+Host*
+findhost(uchar ip[IPaddrlen], int port)
+{
+       Host *h;
+
+       qlock(&hostslk);
+       for(h = hosts; h != nil; h = h->next){
+               if(memcmp(ip, h->ip, IPaddrlen) == 0
+               && (port == -1 || port == h->port))
+                       break;
+       }
+       qunlock(&hostslk);
+       return h;
+}
+
+AuthRpc*
+getrsarpc(void)
+{
+       AuthRpc *rpc;
+       int afd, r;
+       char *s;
+       mpint *m;
+
+       if(myhost->rsapub == nil){
+               werrstr("no RSA public key");
+               return nil;
+       }
+       if((afd = open("/mnt/factotum/rpc", ORDWR)) < 0)
+               return nil;
+       if((rpc = auth_allocrpc(afd)) == nil){
+               close(afd);
+               return nil;
+       }
+       m = mpnew(0);
+       s = smprint("proto=rsa service=tinc role=client host=%q", myhost->name);
+       r = auth_rpc(rpc, "start", s, strlen(s));
+       free(s);
+       if(r != ARok){
+               goto Err;
+       }
+       werrstr("no key found");
+       while(auth_rpc(rpc, "read", nil, 0) == ARok){
+               s = rpc->arg;
+               if(strtomp(s, &s, 16, m) == nil)
+                       continue;
+               if(mpcmp(m, myhost->rsapub->n) != 0)
+                       continue;
+               mpfree(m);
+               return rpc;
+       }
+Err:
+       mpfree(m);
+       auth_freerpc(rpc);
+       close(afd);
+       return nil;
+}
+void
+putrsarpc(AuthRpc *rpc)
+{
+       if(rpc == nil)
+               return;
+       close(rpc->afd);
+       auth_freerpc(rpc);
+}
+
+
+int
+conread(Conn *c)
+{
+       int n;
+
+       if(c->rp > c->buf){
+               memmove(c->buf, c->rp, c->wp - c->rp);
+               c->wp -= (c->rp - c->buf);
+               c->rp = c->buf;
+       }
+       if((n = read(c->fd, c->wp, &c->buf[sizeof(c->buf)] - c->wp)) <= 0)
+               return n;
+       if(c->cin->crypt != nil)
+               (*c->cin->crypt)((uchar*)c->wp, n, c->cin->cs);
+       c->wp += n;
+       return n;
+}
+int
+conwrite(Conn *c, char *s, int n)
+{
+       if(c->cout->crypt != nil)
+               (*c->cout->crypt)((uchar*)s, n, c->cout->cs);
+       if(write(c->fd, s, n) != n)
+               return -1;
+       return 0;
+}
+
+int
+conrecv(Conn *c, char **f, int nf)
+{
+       char *s, *e;
+
+       do {
+               if(c->wp > c->rp && (e = memchr(s = c->rp, '\n', c->wp - c->rp)) != nil){
+                       *e++ = 0;
+                       c->rp = e;
+if(debug) fprint(2, "<-%s %s\n", c->host != nil ? c->host->name : "???", s);
+                       return tokenize(s, f, nf);
+               }
+       } while(conread(c) > 0);
+       return 0;
+}
+int
+consend(Conn *c, char *fmt, ...)
+{
+       char buf[1024];
+       va_list a;
+       int n;
+
+       if(c == nil)
+               return -1;
+
+       va_start(a, fmt);
+       n = vsnprint(buf, sizeof(buf)-2, fmt, a);
+       va_end(a);
+
+       buf[n++] = '\n';
+       buf[n] = 0;
+
+       qlock(&c->sendlk);
+if(debug) fprint(2, "->%s %s", c->host != nil ? c->host->name : "???", buf);
+       n = conwrite(c, buf, n);
+       qunlock(&c->sendlk);
+
+       return n;
+}
+
+int
+recvudp(Host *h)
+{
+       uchar buf[4+MaxPacket+AESbsize+MAClen], mac[SHA2_256dlen];
+       AESstate cs[1];
+       uint seq;
+       int n, o;
+
+       if((n = read(h->udpfd, buf, sizeof(buf))) <= 0)
+               return -1;
+       lock(h->cin);
+       if(h->cin->crypt == nil || (n -= MAClen) < AESbsize){
+               unlock(h->cin);
+               return -1;
+       }
+       hmac_sha2_256(buf, n, h->cin->key, sizeof(h->cin->key), mac, nil);
+       if(tsmemcmp(mac, buf+n, MAClen) != 0){
+               unlock(h->cin);
+               return -1;
+       }
+       memmove(cs, h->cin->cs, sizeof(cs));
+       (*h->cin->crypt)(buf, n, cs);
+
+       seq  = buf[0]<<24;
+       seq |= buf[1]<<16;
+       seq |= buf[2]<<8;
+       seq |= buf[3]<<0;
+
+       if((o = (int)(seq - h->cin->seq)) > 0){
+               h->cin->seq = seq;
+               h->ooo = o < 64 ? h->ooo<<o | 1ULL : 0ULL;
+       } else {
+               o = -o;
+               if(o >= 64 || (h->ooo & 1ULL<<o) != 0){
+                       unlock(h->cin);
+                       return 0;
+               }
+               h->ooo |= 1ULL<<o;
+       }
+       unlock(h->cin);
+       if((n -= buf[n-1]) < 4+EtherHdr)
+               return -1;
+       routepkt(h, buf+4, n-4);
+       return 0;
+}
+int
+sendudp(Host *h, uchar *p, int n)
+{
+       uchar buf[4+MaxPacket+AESbsize+SHA2_256dlen];
+       AESstate cs[1];
+       uint seq;
+       int pad;
+
+       if(h->udpfd < 0 || n > MaxPacket || n > h->pmtu)
+               return -1;
+       lock(h->cout);
+       if(h->cout->crypt == nil){
+               unlock(h->cout);
+               needkey(h);
+               return -1;
+       }
+
+       seq = ++h->cout->seq;
+       buf[0] = seq>>24;
+       buf[1] = seq>>16;
+       buf[2] = seq>>8;
+       buf[3] = seq>>0;
+
+       memmove(buf+4, p, n), n += 4;
+       pad = AESbsize - ((uint)n % AESbsize);
+       memset(buf+n, pad, pad), n += pad;
+       memmove(cs, h->cout->cs, sizeof(cs));
+       (*h->cout->crypt)(buf, n, cs);
+       hmac_sha2_256(buf, n, h->cout->key, sizeof(h->cout->key), buf+n, nil);
+       unlock(h->cout);
+       n += MAClen;
+       if(write(h->udpfd, buf, n) != n)
+               return -1;
+       if((seq & 0xFFFFF) == 0) needkey(h);
+       return 0;
+}
+
+int
+sendtcp(Host *h, uchar *p, int n)
+{
+       char buf[24];
+       Conn *c;
+       int m;
+
+       if((c = h->conn) == nil)
+               return -1;
+       m = snprint(buf, sizeof(buf), "17 %d\n", n);
+       qlock(&c->sendlk);
+       if(conwrite(c, buf, m) < 0
+       || conwrite(c, (char*)p, n) < 0)
+               n = -1;
+       else
+               n = 0;
+       qunlock(&c->sendlk);
+       return n;
+}
+
+void
+forward(Host *s, Host *d, uchar *p, int n)
+{
+       if(d->from == nil)
+               return;
+       while(d != s && d != myhost){
+               if(sendudp(d, p, n) == 0)
+                       return;
+               if(sendtcp(d, p, n) == 0)
+                       return;
+               d = d->from;
+       }
+}
+
+int
+updatebyte(int csum, uchar *b, uchar *p, int v)
+{
+       int o;
+
+       o = *p;
+       v &= 255;
+       *p = v;
+       if(((p - b) & 1) == 0){
+               o <<= 8;
+               v <<= 8;
+       }
+       csum += o^0xFFFF;
+       csum += v;
+       while(v = csum >> 16)
+               csum = (csum & 0xFFFF) + v;
+       return csum;
+}
+
+void
+clampmss(Host *d, uchar *p, int n, int o)
+{
+       int oldmss, newmss, csum;
+       uchar *h, *e;
+
+       if(n <= TcpHdr || (p[13]&2) == 0 || (d->options & OptClampMss) == 0)
+               return;
+       if((e = p+(p[12]>>4)*4) > p+n)
+               return;
+       for(h = p+TcpHdr; h+4 <= e && h[1] > 0; h += h[1])
+               if(h[0] == 2 && h[1] == 4)
+                       goto Found;
+       return;
+Found:
+       oldmss = h[2]<<8 | h[3];
+       newmss = myhost->pmtu;
+       if(d->pmtu < newmss)
+               newmss = d->pmtu;
+       newmss -= o + TcpHdr;
+       if(oldmss <= newmss)
+               return;
+if(debug) fprint(2, "clamping tcp mss %d -> %d for %s\n", oldmss, newmss, d->name);
+       csum = (p[16]<<8 | p[17]) ^ 0xFFFF;
+       csum = updatebyte(csum, p, h+2, newmss>>8);
+       csum = updatebyte(csum, p, h+3, newmss);
+       csum ^= 0xFFFF;
+       p[16] = csum>>8;
+       p[17] = csum;
+}
+
+void
+routepkt(Host *s, uchar *p, int n)
+{
+       uchar src[IPaddrlen], dst[IPaddrlen];
+       int o, type;
+       Snet *t;
+
+Ether:
+       if(n <= EtherHdr)
+               return;
+       switch(p[EtherType+0]<<8 | p[EtherType+1]){
+       default:
+               return;
+       case 0x8100:    /* VLAN */
+               memmove(p+4, p, 2*Eaddrlen);
+               p += 4, n -= 4;
+               goto Ether;
+       case 0x0800:    /* IP */
+               break;
+       }
+       switch(p[EtherHdr] & 0xF0){
+       default:
+               return;
+       case 0x40:
+               o = EtherHdr+(p[EtherHdr] & 15)*4;
+               if(n < EtherHdr+Ip4Hdr || n < o)
+                       return;
+               type = p[EtherHdr+9];
+               v4tov6(src, p+EtherHdr+12);
+               v4tov6(dst, p+EtherHdr+16);
+               break;
+       case 0x60:
+               o = EtherHdr+Ip6Hdr;
+               if(n < o)
+                       return;
+               type = p[EtherHdr+6];
+               memmove(src, p+EtherHdr+8, 16);
+               memmove(dst, p+EtherHdr+24, 16);
+               break;
+       }
+       netlock(nil);
+       if((t = lookupnet(dst)) != nil){
+               if(type == 6)   /* TCP */
+                       clampmss(t->owner, p+o, n-o, o);
+               if(t->owner == myhost)
+                       write(ipdfd, p+EtherHdr, n-EtherHdr);
+               else
+                       forward(s, t->owner, p, n);
+       }
+       netunlock(nil);
+}
+
+void
+updateweight(Edge *e, int ms)
+{
+       e->weight = (e->weight + ms) / 2;
+       if(e->weight < 0)
+               e->weight = 0;
+}
+
+int
+metaauth(Conn *c)
+{
+       mpint *m, *h;
+       uchar b[256];
+       AuthRpc *rpc;
+       char *f[8];
+       int n, n1, n2, ms;
+       Edge *e;
+
+       c->pingtime = nsec();
+       if(consend(c, "%d %s 17", ID, myhost->name) < 0)
+               return -1;
+       n = conrecv(c, f, nelem(f));
+       if(n != 3 || atoi(f[0]) != ID || atoi(f[2]) != 17)
+               return -1;
+       if((c->host = gethost(f[1], 0)) == nil
+       || c->host == myhost || c->host->rsapub == nil)
+               return -1;
+
+       n1 = (mpsignif(c->host->rsapub->n)+7)/8;
+       if(n1 < AESkeylen+AESbsize || n1 > sizeof(b))
+               return -1;
+       n2 = (mpsignif(myhost->rsapub->n)+7)/8;
+       if(n2 < AESkeylen+AESbsize || n2 > sizeof(b))
+               return -1;
+
+       m = mpnrand(c->host->rsapub->n, genrandom, nil);
+       mptober(m, b, n1);
+       setupAESstate(c->cout->cs, b+n1-AESkeylen, AESkeylen, b+n1-AESkeylen-AESbsize);
+       rsaencrypt(c->host->rsapub, m, m);
+       mptober(m, b, n1);
+       mpfree(m);
+       if(consend(c, "%d %d %d 0 0 %.*H", META_KEY, EVP_AES256CFB, EVP_SHA256, n1, b) < 0)
+               return -1;
+       c->cout->crypt = aesCFBencrypt;
+
+       n = conrecv(c, f, nelem(f));
+       if(n != 6 || atoi(f[0]) != META_KEY || strlen(f[5]) != 2*n2)
+               return -1;
+       if(atoi(f[1]) != EVP_AES256CFB || atoi(f[2]) != EVP_SHA256){
+               fprint(2, "%s uses unknown cipher/digest agorithms: %s %s\n",
+                       c->host->name, f[1], f[2]);
+               return -1;
+       }
+
+       if((rpc = getrsarpc()) == nil
+       || auth_rpc(rpc, "write", f[5], strlen(f[5])) != ARok
+       || auth_rpc(rpc, "read", nil, 0) != ARok){
+               putrsarpc(rpc);
+               return -1;
+       }
+
+       m = strtomp(rpc->arg, nil, 16, nil);
+       putrsarpc(rpc);
+       mptober(m, b, n2);
+       mpfree(m);
+       setupAESstate(c->cin->cs, b+n2-AESkeylen, AESkeylen, b+n2-AESkeylen-AESbsize);
+       c->cin->crypt = aesCFBdecrypt;
+
+       h = mpnrand(c->host->rsapub->n, genrandom, nil);
+       mptober(h, b, n1);
+       if(consend(c, "%d %.*H", CHALLENGE, n1, b) < 0){
+               mpfree(h);
+               return -1;
+       }
+       sha2_256(b, n1, b, nil);
+       betomp(b, SHA2_256dlen, h);
+
+       n = conrecv(c, f, nelem(f));
+       if(n != 2 || atoi(f[0]) != CHALLENGE){
+               mpfree(h);
+               return -1;
+       }
+       m = strtomp(f[1], nil, 16, nil);
+       mptober(m, b, n2);
+       mpfree(m);
+       sha2_256(b, n2, b, nil);
+       if(consend(c, "%d %.*H", CHAL_REPLY, SHA2_256dlen, b) < 0){
+               mpfree(h);
+               return -1;
+       }
+       n = conrecv(c, f, nelem(f));
+       if(n != 2 || atoi(f[0]) != CHAL_REPLY){
+               mpfree(h);
+               return -1;
+       }
+       m = strtomp(f[1], nil, 16, nil);
+       n = mpcmp(m, h);
+       mpfree(m);
+       mpfree(h);
+       if(n != 0)
+               return -1;
+       ms = (nsec() - c->pingtime)/1000000LL;
+       if(consend(c, "%d %d %d %x", ACK, myhost->port, ms, myhost->options) < 0)
+               return -1;
+       n = conrecv(c, f, nelem(f));
+       if(n != 4 || atoi(f[0]) != ACK)
+               return -1;
+
+       netlock(c);
+       e = getedge(myhost, c->host, 1);
+       memmove(e->ip, c->ip, IPaddrlen);
+       e->port = atoi(f[1]);
+       e->weight = atoi(f[2]);
+       e->options = strtol(f[3], nil, 16);
+       updateweight(e, ms);
+       c->pingtime = 0;
+       c->edge = e;
+       c->host->conn = c;
+       netunlock(c);
+
+       return 0;
+}
+
+Conn*
+nearcon(Host *to)
+{
+       while(to != nil && to != myhost){
+               if(to->conn != nil)
+                       return to->conn;
+               to = to->from;
+       }
+       return nil;
+}
+
+void
+sendkey(Host *to)
+{
+       lock(to->cin);
+       to->ooo = 0;
+       to->cin->seq = 0;
+       genrandom(to->cin->key, sizeof(to->cin->key));
+       setupAESstate(to->cin->cs, to->cin->key, AESkeylen, to->cin->key+AESkeylen);
+       to->cin->crypt = aesCBCdecrypt;
+       unlock(to->cin);
+
+       consend(nearcon(to), "%d %s %s %.*H %d %d %d %d", ANS_KEY,
+               myhost->name, to->name,
+               sizeof(to->cin->key), to->cin->key,
+               EVP_AES256CBC, EVP_SHA256, MAClen, 0);
+}
+void
+needkey(Host *from)
+{
+       consend(nearcon(from), "%d %s %s", REQ_KEY, myhost->name, from->name);
+}
+void
+recvkey(Host *from, char **f)
+{
+       uchar key[sizeof(from->cout->key)];
+       mpint *m;
+
+       if(atoi(f[1]) != EVP_AES256CBC || atoi(f[2]) != EVP_SHA256
+       || atoi(f[3]) != MAClen || atoi(f[4]) != 0){
+               fprint(2, "%s key uses unknown parameters: %s %s %s %s\n",
+                       from->name, f[1], f[2], f[3], f[4]);
+               return;
+       }
+       if(strlen(f[0]) != sizeof(key)*2)
+               return;
+       if((m = strtomp(f[0], nil, 16, nil)) == nil)
+               return;
+       mptober(m, key, sizeof(key));
+       mpfree(m);
+       lock(from->cout);
+       if(tsmemcmp(key, from->cout->key, sizeof(key)) == 0)
+               goto Out;
+       from->cout->seq = 0;
+       memmove(from->cout->key, key, sizeof(key));
+       setupAESstate(from->cout->cs, from->cout->key, AESkeylen, from->cout->key+AESkeylen);
+       from->cout->crypt = aesCBCencrypt;
+Out:
+       unlock(from->cout);
+       memset(key, 0, sizeof(key));
+}
+void
+clearkey(Host *from)
+{
+       lock(from->cout);
+       from->cout->crypt = nil;
+       from->cout->seq = 0;
+       memset(from->cout->cs, 0, sizeof(from->cout->cs));
+       genrandom(from->cout->key, sizeof(from->cout->key));
+       unlock(from->cout);
+}
+
+void
+metapeer(Conn *c)
+{
+       char *f[8];
+       Host *h, *r;
+       Edge *e;
+       Snet *t;
+       int i, n;
+
+       netlock(nil);
+       for(i=0; i<nsnet; i++)
+               reportsubnet(c, snet[i]);
+       for(i=0; i<nedges; i++)
+               reportedge(c, edges[i]);
+       netunlock(nil);
+
+       sendkey(c->host);
+       while((n = conrecv(c, f, nelem(f))) > 0){
+               switch(atoi(f[0])){
+               case PING:
+                       if(consend(c, "%d %x", PONG, rand()) < 0)
+                               return;
+                       continue;
+               case PONG:
+                       netlock(c);
+                       if(c->pingtime != 0){
+                               if((e = getedge(myhost, c->host, 0)) != nil)
+                                       updateweight(e, (nsec() - c->pingtime) / 1000000LL);
+                               c->pingtime = 0;
+                       }
+                       netunlock(c);
+                       continue;
+               case ADD_SUBNET:
+                       if(n != 4 || (h = gethost(f[2], 1)) == nil || h == myhost)
+                               break;
+                       netlock(c);
+                       getsubnet(h, f[3], 1);
+                       netunlock(c);
+                       continue;
+               case DEL_SUBNET:
+                       if(n != 4 || (h = gethost(f[2], 0)) == nil || h == myhost)
+                               break;
+                       netlock(c);
+                       if((t = getsubnet(h, f[3], 0)) != nil)
+                               delsubnet(t);
+                       netunlock(c);
+                       continue;
+               case ADD_EDGE:
+                       if(n != 8 || (h = gethost(f[2], 1)) == nil || h == myhost
+                       || (r = gethost(f[3], 1)) == nil)
+                               break;
+                       netlock(c);
+                       if((e = getedge(h, r, 1)) != nil){
+                               if(parseip(e->ip, f[4]) == -1)
+                                       memmove(e->ip, r->ip, IPaddrlen);
+                               e->port = atoi(f[5]);
+                               e->weight = atoi(f[7]);
+                               e->options = strtol(f[6], nil, 16);
+                       }
+                       netunlock(c);
+                       continue;
+               case DEL_EDGE:
+                       if(n != 4 || (h = gethost(f[2], 0)) == nil || h == myhost
+                       || (r = gethost(f[3], 1)) == nil)
+                               break;
+                       netlock(c);
+                       if((e = getedge(h, r, 0)) != nil)
+                               deledge(e);
+                       netunlock(c);
+                       continue;
+               case KEY_CHANGED:
+                       if(n != 3 || (h = gethost(f[2], 0)) == nil || h == myhost)
+                               break;
+                       netlock(c);
+                       clearkey(h);
+                       for(e = myhost->link; e != nil; e = e->next)
+                               if(e->dst->conn != c && e->dst->from == myhost)
+                                       consend(e->dst->conn, "%s %s %s", f[0], f[1], f[2]);
+                       netunlock(c);
+                       continue;
+               case REQ_KEY:
+                       if(n != 3 || (h = gethost(f[1], 0)) == nil || h == myhost
+                       || (r = gethost(f[2], 0)) == nil)
+                               break;
+                       netlock(nil);
+                       if(r != myhost)
+                               consend(nearcon(r), "%s %s %s", f[0], f[1], f[2]);
+                       else
+                               sendkey(h);
+                       netunlock(nil);
+                       continue;
+               case ANS_KEY:
+                       if(n != 8 || (h = gethost(f[1], 0)) == nil || h == myhost
+                       || (r = gethost(f[2], 0)) == nil)
+                               break;
+                       netlock(nil);
+                       if(r != myhost)
+                               consend(nearcon(r), "%s %s %s %s %s %s %s %s",
+                                       f[0], f[1], f[2], f[3],
+                                       f[4], f[5], f[6], f[7]);
+                       else
+                               recvkey(h, &f[3]);
+                       netunlock(nil);
+                       continue;
+               case TCP_PACKET:
+                       if(n != 2)
+                               return;
+                       n = atoi(f[1]);
+                       if(n < 0 || n > MaxPacket)
+                               return;
+                       while((c->wp - c->rp) < n && conread(c) > 0)
+                               ;
+                       if(c->wp - c->rp < n)
+                               return;
+                       routepkt(c->host, (uchar*)c->rp, n);
+                       c->rp += n;
+                       continue;
+               }
+       }
+}
+
+void
+tcpclient(int fd)
+{
+       Conn *c;
+       char dir[128];
+
+       c = emalloc(sizeof(Conn));
+       c->host = nil;
+       c->fd = fd;
+       c->rp = c->wp = c->buf;
+       c->port = dir2ipport(fd2dir(fd, dir, sizeof(dir)), c->ip);
+       procsetname("tcpclient %s %s %I!%d", myhost->name,
+               dir, c->ip, c->port);
+       if(metaauth(c) == 0){
+               procsetname("tcpclient %s %s %I!%d %s", myhost->name,
+                       dir, c->ip, c->port, c->host->name);
+               metapeer(c);
+       }
+       netlock(c);
+       if(c->host != nil && c->host->conn == c){
+               c->host->conn = nil;
+               if(c->edge != nil && c->edge->dst == c->host){
+                       deledge(c->edge->rev);
+                       deledge(c->edge);
+               }
+               hangupfd(c->host->udpfd);
+       }
+       netunlock(c);
+       memset(c, 0, sizeof(*c));
+       free(c);
+       close(fd);
+}
+
+void
+udpclient(int fd)
+{
+       uchar ip[IPaddrlen];
+       char dir[128];
+       Host *h;
+
+       if((h = findhost(ip, dir2ipport(fd2dir(fd, dir, sizeof(dir)), ip))) != nil
+       && h != myhost){
+               procsetname("udpclient %s %s %I!%d %s", myhost->name,
+                       dir, h->ip, h->port, h->name);
+               h->udpfd = fd;
+               do {
+                       alarm(15*1000);
+               } while(recvudp(h) == 0);
+               if(h->udpfd == fd)
+                       h->udpfd = -1;
+       }
+       close(fd);
+}
+
+int
+dialer(char *proto, char *host, int rport, int lport)
+{
+       char addr[40], local[16];
+       int dfd;
+
+       snprint(local, sizeof(local), "%d", lport);
+       snprint(addr, sizeof(addr), "%s/%s!%s!%d", outside, proto, host, rport);
+       procsetname("dialer %s %s", myhost->name, addr);
+
+       for(;;){
+               if((dfd = dial(addr, lport ? local : nil, nil, nil)) >= 0){
+                       switch(rfork(RFPROC|RFMEM)){
+                       case 0:
+                               return dfd;
+                       case -1:
+                               close(dfd);
+                               continue;
+                       }
+                       if(waitpid() < 0)
+                               return -1;
+               }
+               sleep(10000);
+       }
+}
+
+int
+listener(char *proto, int port, int nprocs)
+{
+       char addr[40], adir[40], ldir[40];
+       int acfd, lcfd, dfd;
+
+       snprint(addr, sizeof(addr), "%s/%s!*!%d", outside, proto, port);
+       procsetname("listener %s %s", myhost->name, addr);
+
+       if((acfd = announce(addr, adir)) < 0)
+               return -1;
+       while((lcfd = listen(adir, ldir)) >= 0){
+               if((dfd = accept(lcfd, ldir)) >= 0)
+                       switch(rfork(RFPROC|RFMEM)){
+                       default:
+                               if(nprocs > 1 || waitpid() < 0) nprocs--;
+                               break;
+                       case 0:
+                               return dfd;
+                       case -1:
+                               close(dfd);
+                       }
+               close(lcfd);
+       }
+       close(acfd);
+       return -1;
+}
+
+void
+pingpong(void)
+{
+       Edge *e;
+       Conn *c;
+
+       procsetname("pingpong %s", myhost->name);
+       for(;;){
+               sleep(15*1000 + (rand() % 3000));
+               netlock(nil);
+               for(e = myhost->link; e != nil; e = e->next){
+                       if((c = e->dst->conn) != nil){
+                               if(c->pingtime != 0){
+                                       hangupfd(c->fd);
+                                       continue;
+                               }
+                               c->pingtime = nsec();
+                               consend(c, "%d %x", PING, rand());
+                       }
+               }
+               netunlock(nil);
+       }
+}
+
+void
+ipifcsetup(void)
+{
+       char buf[128];
+       int n;
+
+       snprint(buf, sizeof buf, "%s/ipifc/clone", inside);
+       if((ipcfd = open(buf, ORDWR)) < 0)
+               sysfatal("can't open ip interface: %r");
+       if((n = read(ipcfd, buf, sizeof buf - 1)) <= 0)
+               sysfatal("can't read interface number: %r");
+       buf[n] = 0;
+       ipifn = atoi(buf);
+       snprint(buf, sizeof buf, "%s/ipifc/%d/data", inside, ipifn);
+       if((ipdfd = open(buf, ORDWR)) < 0)
+               sysfatal("can't open ip data: %r");
+       fprint(ipcfd, "bind pkt");
+       fprint(ipcfd, "mtu %d", myhost->pmtu-EtherHdr);
+       fprint(ipcfd, "add %I %M", localip, localmask);
+}
+
+void
+ip2tunnel(void)
+{
+       uchar buf[MaxPacket];
+       int n;
+
+       procsetname("ip2tunnel %s %s %I %M", myhost->name,
+               fd2dir(ipdfd, (char*)buf, sizeof(buf)),
+               localip, localmask);
+       while((n = read(ipdfd, buf+EtherHdr, sizeof buf-EtherHdr)) > 0){
+               memset(buf, 0, 2*Eaddrlen);
+               buf[EtherType+0] = 0x08;
+               buf[EtherType+1] = 0x00;
+               routepkt(myhost, buf, n+EtherHdr);
+       }
+}
+
+void
+catch(void*, char *msg)
+{
+       if(strcmp(msg, "alarm") == 0 || strcmp(msg, "interrupt") == 0)
+               noted(NCONT);
+       noted(NDFLT);
+}
+void
+shutdown(void)
+{
+       postnote(PNGROUP, getpid(), "shutdown");
+}
+
+void
+usage(void)
+{
+       fprint(2, "%s [-d] [-p maxprocs] [-x inside] [-o outside] [-c confdir] [-n myname] "
+               "localip localmask [host...]\n", argv0);
+       exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+       Host *h;
+       Snet *t;
+       AuthRpc *rpc;
+       int i;
+
+       quotefmtinstall();
+       fmtinstall('I', eipfmt);
+       fmtinstall('M', eipfmt);
+       fmtinstall('H', encodefmt);
+
+       ARGBEGIN {
+       case 'd':
+               debug++;
+               break;
+       case 'p':
+               if((maxprocs = atoi(EARGF(usage()))) < 1)
+                       sysfatal("bad number of procs");
+               break;
+       case 'c':
+               confdir = EARGF(usage());
+               break;
+       case 'n':
+               myname = EARGF(usage());
+               break;
+       case 'x':
+               outside = inside = EARGF(usage());
+               break;
+       case 'o':
+               outside = EARGF(usage());
+               break;
+       default:
+               usage();
+       } ARGEND;
+
+       if(argc < 2)
+               usage();
+       if(parseip(localip, argv[0]) == -1)
+               sysfatal("bad local ip: %s", argv[0]);
+       if(parseipmask(localmask, argv[1]) == -1)
+               sysfatal("bad local mask: %s", argv[1]);
+       argv += 2, argc -= 2;
+
+       srand(fastrand());
+       if(myname == nil)
+               myname = sysname();
+       if((myhost = gethost(myname, 0)) == nil)
+               sysfatal("can't get my host: %r");
+       if((rpc = getrsarpc()) == nil)
+               sysfatal("can't find my key in factotum: %r");
+       putrsarpc(rpc);
+
+       for(i = 0; i < argc; i++){
+               if((h = gethost(argv[i], 0)) == nil)
+                       sysfatal("unknown host: %s", *argv);
+               if(h == myhost)
+                       sysfatal("will not connect to myself");
+               if(h->rsapub == nil)
+                       sysfatal("no RSA public key for: %s", h->name);
+       }
+
+       if((t = lookupnet(localip)) == nil)
+               sysfatal("no subnet found for local ip %I", localip);
+       if(t->owner != myhost)
+               sysfatal("local ip %I belongs to host %s subnet %I/%d",
+                       localip, t->owner->name, t->ip, t->prefixlen);
+
+       ipifcsetup();
+       notify(catch);
+       switch(rfork(RFPROC|RFFDG|RFREND|RFNOTEG)){
+       case -1:
+               sysfatal("can't fork: %r");
+       case 0:
+               break;
+       default:
+               if(debug){
+                       waitpid();
+                       fprint(ipcfd, "unbind");
+               }
+               exits(nil);
+       }
+       atexit(shutdown);
+       if(rfork(RFPROC|RFMEM) == 0){
+               tcpclient(listener("tcp", myhost->port, maxprocs));
+               exits(nil);
+       }
+       if(rfork(RFPROC|RFMEM) == 0){
+               udpclient(listener("udp", myhost->port, maxprocs));
+               exits(nil);
+       }
+       for(i = 0; i < argc; i++){
+               if((h = gethost(argv[i], 0)) == nil)
+                       continue;
+               if(rfork(RFPROC|RFMEM) == 0){
+                       tcpclient(dialer("tcp", h->addr, h->port, myhost->port));
+                       exits(nil);
+               }
+               if(rfork(RFPROC|RFMEM) == 0){
+                       udpclient(dialer("udp", h->addr, h->port, myhost->port));
+                       exits(nil);
+               }
+       }
+       if(rfork(RFPROC|RFMEM) == 0){
+               pingpong();
+               exits(nil);
+       }
+       ip2tunnel();
+}