diff options
author | cinap_lenrek <cinap_lenrek@felloff.net> | 2017-10-31 22:44:25 +0100 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@felloff.net> | 2017-10-31 22:44:25 +0100 |
commit | daf292ac9d03f58aa9c788f4ec4838f887df28d3 (patch) | |
tree | 44960126aa000977fdeb988ea1205995999352aa | |
parent | 5c1afc882cb8cf533048dde508bcf8117244f361 (diff) | |
download | plan9front-daf292ac9d03f58aa9c788f4ec4838f887df28d3.tar.xz |
tinc: implement experimental mash peer to peer VPN from http://www.tinc-vpn.org/
-rw-r--r-- | sys/man/8/tinc | 119 | ||||
-rw-r--r-- | sys/src/cmd/ip/mkfile | 1 | ||||
-rw-r--r-- | sys/src/cmd/ip/tinc.c | 1662 |
3 files changed, 1782 insertions, 0 deletions
diff --git a/sys/man/8/tinc b/sys/man/8/tinc new file mode 100644 index 000000000..18163e88a --- /dev/null +++ b/sys/man/8/tinc @@ -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 diff --git a/sys/src/cmd/ip/mkfile b/sys/src/cmd/ip/mkfile index 900bb17f7..702a2e01e 100644 --- a/sys/src/cmd/ip/mkfile +++ b/sys/src/cmd/ip/mkfile @@ -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 index 000000000..31ac6e380 --- /dev/null +++ b/sys/src/cmd/ip/tinc.c @@ -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(); +} |