/* This file is in the public domain. */ static const char *RND_DEV = "/dev/urandom"; #define REKEY_N_BASE 0x10000 /* XXX 0x3fff0000 */ #define QLIMIT 65536 #define UPLINK_EXPIRE (60*15) #define REACH_EXPIRE (60*10) #define REACH_KILL (60*15) #define TRY_CONNECT_SEC 60 #define TRY_CONNECT_USEC 123456 #define UPLINK_EXPIRE_SEC 59 #define UPLINK_EXPIRE_USEC 987654 #define REACH_EXPIRE_SEC 59 #define REACH_EXPIRE_USEC 876543 #define REACH_GEN_SEC 59 #define REACH_GEN_USEC 765432 #define PUBLIC_GEN_SEC 299 #define PUBLIC_GEN_USEC 654321 #define IPMAP_GEN_SEC 3579 #define IPMAP_GEN_USEC 543210 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "oq.h" #include "protocol.h" #include "pollloop.h" #include "stdio-util.h" typedef enum { CR_ACTIVE = 1, CR_PASSIVE } CONNROLE; typedef struct ipaddr IPADDR; typedef struct peer PEER; typedef struct uplink UPLINK; typedef struct listen LISTEN; typedef struct isource ISOURCE; typedef struct apstate APSTATE; typedef struct cryptinit CRYPTINIT; typedef struct nap NAP; typedef struct pq PQ; typedef struct ipmap IPMAP; typedef struct periodic PERIODIC; /* * No, chemists, this has nothing to do with iodine. :) This * represents a recurring event. fd is the AF_TIMER socket, id is the * poll ID, and cb is the function to be called. */ struct periodic { int fd; int id; void (*cb)(void); } ; /* * State for "delay briefly" operations. s is an AF_TIMER socket. id * is the poll ID. done is a function to be called when the delay is * over, with arg its argument. */ struct nap { int s; int id; void (*done)(void *); void *arg; } ; /* * State for the initial crypto exchange. This is a separate struct so * it can be discarded easily when the exchange is over. * * Rc is actually only 16 bytes long. The array here is dimensioned * [49] to simplify the code in rd_crypto_passive_1 - this way we can * read the whole reply from the connecter into Rc, rather than * creating another buffer for it. */ struct cryptinit { CONNROLE myrole; PEER *p; unsigned char Rc[49]; unsigned char Rl[16]; unsigned char b; unsigned char s[32]; int n; } ; /* * State for parsing ADDR/PORT stuff. This exists so that specific * uses of the parser can tweak it in a failure callback. */ struct apstate { char *addr; char *port; struct addrinfo hints; } ; /* * An item on the stack of includes for config files. */ struct isource { ISOURCE *link; char *file; FILE *f; int lno; } ; /* * A listen-at point. sa/salen describe where we bind() to. public * is true or false according as the listen point should or shouldn't * be advertised in PUBLIC messages. */ struct listen { LISTEN *link; struct sockaddr *sa; int salen; int public; char *txt; int fd; int id; } ; /* * An uplink point. Each of these is somewhere we want to maintain a * connection to. * * LOCALCONF is true if this uplink was configured in the config file * and thus should never be thrown away. stamp is the timestamp of * last time we heard this uplink advertised. An uplink which we * haven't heard advertised, or which has timed out, but which we * don't want to throw away because it's configured in is represented * with stamp set to zero. Such uplinks are nonexistent for purposes * of generating PUBLIC messages, but still get connection attempts. * * MYSELF indicates that this uplink is an address we ourselves are * listening on and thus should be ignored. * * peer is the PEER we created to connect to this UPLINK. */ struct uplink { UPLINK *link; struct sockaddr *sa; int salen; char *txt; unsigned int flags; #define UF_LOCALCONF 0x00000001 #define UF_MYSELF 0x00000002 time_t stamp; PEER *peer; } ; /* * A connection to a peer. * * These include connections which we have initiated but which have not * yet completed at the socket layer and connections which have come * up at the socket layer but have not passed crypto verification; * they are just like other PEERs except that the callbacks for their * poll IDs are unusual. * * Kd is for the direction in which we write. Kd_ is what the protocol * document calls Kd', ie, Kd in the other direction. up is the * UPLINK for which we created this PEER, or nil if it wasn't created * to connect to an uplink. */ struct peer { struct sockaddr *lclsa; int lclsalen; struct sockaddr *remsa; int remsalen; char *txt; int fd; int id; OQ oq; unsigned char Kd[32]; unsigned char Kd_[32]; int bytes_to_rekey_out; int bytes_to_rekey_in; unsigned int n_rekeys_out; unsigned int n_rekeys_in; ARC4_STATE a4_out; ARC4_STATE a4_in; unsigned char *ibuf; int ifill; int iwant; void (*readdone)(PEER *); unsigned int want; #define WANT_REACH 0x00000001 #define WANT_PUBLIC 0x00000002 #define WANT_IPMAP 0x00000004 int peerid; PQ *pq; PQ **pqt; UPLINK *up; } ; /* * An IP address, without a port number. */ struct ipaddr { int af; union { struct in_addr v4; struct in6_addr v6; } ; } ; /* * An IP packet, queued for output. dst is the address we want to send * it to; dstid is the same information as a cloud member ID. Once a * packet is moved from the shared output queue to a PEER-specific * output queue, dst no longer matters, but dstid still does. */ struct pq { PQ *link; IPADDR dst; unsigned char dstid; void *body; int bodylen; } ; /* * An IP<->ID mapping entry. */ struct ipmap { IPMAP *link; IPADDR a; unsigned char id; } ; /* * The shared-cloudwide secret key, pointer and length. */ static unsigned char *ck; static int cklen; /* * This host's cloud ID. */ static unsigned char cid; /* * This host's list of cloud IP addresses. */ static int ncipaddrs; static IPADDR **cipaddrs; /* * Boolean saying whether this host ignores PUBLIC packets. */ static int ignore_public; /* * Stack of input sources. Used when reading the config. */ static ISOURCE *isrc; /* * Listening points. The difference between types 1 and 2, and between * types 4 and 3, is whether this list is empty or not. */ static LISTEN *listens; /* * Uplink points. Initially, these come from uplink declarations in * the configuration; if ignore_public is false, the list is mutated * as PUBLIC packets come in and timers expire. */ static UPLINK *uplinks; /* * The list of PEERs. */ static int npeers; static int apeers; static PEER **peers; /* * PERIODICs for attempts to (re)connect to uplinks, for expiration of * learned uplinks, for expiration of reachability data, and for * periodic generation of REACH, PUBLIC, and IPMAP packets. */ static PERIODIC *pd_reconnect; static PERIODIC *pd_expire_uplink; static PERIODIC *pd_expire_reach; static PERIODIC *pd_gen_reach; static PERIODIC *pd_gen_public; static PERIODIC *pd_gen_ipmap; /* * The block ID for cleaning up dead PEERs, and a boolean indicating * whether it might have work to do. */ static int peercleanup_id; static int peercleanup_needed; /* * Descriptor open onto RND_DEV. */ static int rndfd; /* * The queue of packets waiting to be sent. */ static PQ *pktq; static PQ **pktqt; /* * Pathname of the tun instance, descriptor open on it, and poll ID for * reading from it. tuntype is the interface type, IFF_POINTOPOINT or * IFF_BROADCAST, or 0 if no type is specified. */ static char *tunpath; static int tunfd; static int tunid; static int tuntype; /* * Block ID for generating output traffic, and boolean indicating it * might have something to do. */ static int sendid; static int want_send; /* * Table of PEERs, indexed by cloud member ID. This allows us to * quickly find the PEER, if any, for a given ID. */ static PEER *peertbl[256]; /* * Cloud member ID reachability data. This is indexed by the member ID * we want to reach. reachtbl is the member ID we can reach it via, * or -1 if we think it's unreachable. reachtime is the time_t at * which reachtbl was last updated. reachdist is the hop count to the * target. (reachtime and reachdist are meaningless for unreachable * targets.) This is updated by REACH packets. * * Note that reachabilities that have timed out are left marked * reachable but with infinite distance for a little while, to help * reduce the need to count to infinity. (Split horizon with poisoned * reverse deals with most but not all cases; broadcasting * unreachables helps with most of the rest.) */ static int reachtbl[256]; static int reachdist[256]; static time_t reachtime[256]; /* * IP <-> ID mapping. This is not 1-to-1; there may be any number of * IPs (zero, one, or more) for a given ID - but at most one ID for an * IP. This is updated by IPMAP packets. */ static IPMAP *ipmaps; /* * Flag indicating whether we're doing verbosity, and, if so, variables * for the log (a FILE * we write log lines to and a state variable * indicating when we want to prepend a timestamp). */ static int verbose = 0; static FILE *verbf; static int verbnewline; /* * Optional file to log verbose logs to and the ID of a flusher block * function for it. */ static FILE *logf = 0; static int logflushid = PL_NOID; #define Cisspace(c) isspace((unsigned char)(c)) #define VERB if (verbose) plog static void *dequal(const volatile void *arg) { return( ((char *)0) + (((const volatile char *)arg) - ((const volatile char *)0)) ); } static void plog(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void plog(const char *fmt, ...) { va_list ap; va_start(ap,fmt); vfprintf(verbf,fmt,ap); va_end(ap); } static void openlog(const char *fn) { FILE *f; f = fopen(fn,"w"); if (f == 0) { fprintf(stderr,"%s: %s: %s\n",__progname,fn,strerror(errno)); exit(1); } if (logf) fclose(logf); logf = f; } static int verbf_w(void *cookie __attribute__((__unused__)), const char *buf, int len) { int i; int havenow; struct timeval nowtv; time_t nowtt; struct tm *nowtm; struct tm nowtmbuf; havenow = 0; for (i=0;itm_year+1900, nowtm->tm_mon+1, nowtm->tm_mday, nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec, (int)(nowtv.tv_usec/10000) ); printf("%s",pref); if (logf) fprintf(logf,"%s",pref); } putchar(buf[i]); if (logf) putc(buf[i],logf); verbnewline = (buf[i] == '\n'); } return(len); } static void setuplog(void) { if (! verbose) return; verbf = funopen(0,0,&verbf_w,0,0); setlinebuf(verbf); verbnewline = 1; } static void config_err(const char *, ...) __attribute__((__format__(__printf__,1,2),__noreturn__)); static void config_err(const char *fmt, ...) { va_list ap; char *s; void dump_isrc_chain(ISOURCE *i) { if (i->link) dump_isrc_chain(i->link); fprintf(stderr," \"%s\", line %d:",i->file,i->lno); } va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s:",__progname); dump_isrc_chain(isrc); fprintf(stderr," %s\n",s); free(s); exit(1); } static void config_err_cleanup(void (*)(void), const char *, ...) __attribute__((__format__(__printf__,2,3),__noreturn__)); static void config_err_cleanup(void (*cleanup)(void), const char *fmt, ...) { va_list ap; char *s; void dump_isrc_chain(ISOURCE *i) { if (i->link) dump_isrc_chain(i->link); fprintf(stderr," \"%s\", line %d:",i->file,i->lno); } va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s:",__progname); dump_isrc_chain(isrc); fprintf(stderr," %s\n",s); free(s); (*cleanup)(); exit(1); } static void push_isrc(FILE *f, const char *name) { ISOURCE *i; i = malloc(sizeof(ISOURCE)); i->file = strdup(name); i->f = f; i->lno = 0; i->link = isrc; isrc = i; } static void pop_isrc(void) { ISOURCE *i; i = isrc; isrc = i->link; free(i->file); fclose(i->f); free(i); } static struct addrinfo *crack_addr_port(const char *s, int flags, void (*custom)(APSTATE *)) { APSTATE st; const char *slash; int i; struct addrinfo *rv; slash = index(s,'/'); if (! slash) config_err("no slash in ADDR/PORT"); i = slash - s; st.addr = malloc(i+1); bcopy(s,st.addr,i); st.addr[i] = '\0'; st.port = strdup(slash+1); st.hints.ai_flags = flags; st.hints.ai_family = PF_UNSPEC; st.hints.ai_socktype = SOCK_STREAM; st.hints.ai_protocol = 0; st.hints.ai_addrlen = 0; st.hints.ai_addr = 0; st.hints.ai_canonname = 0; st.hints.ai_next = 0; if (custom) (*custom)(&st); i = getaddrinfo(st.addr,st.port,&st.hints,&rv); if (i == 0) return(rv); config_err("lookup %s: %s",s,gai_strerror(i)); } static void confline__at(const char *s) { FILE *f; f = fopen(s,"r"); if (f == 0) config_err("can't open %s: %s",s,strerror(errno)); push_isrc(f,s); } static void confline_key(const char *s) { free(ck); cklen = strlen(s); ck = malloc(cklen); bcopy(s,ck,cklen); } static void confline_keyfile(const char *s) { int fd; struct stat stb; int r; int l; unsigned char *b; fd = open(s,O_RDONLY,0); if (fd < 0) config_err("open %s: %s",s,strerror(errno)); if (fstat(fd,&stb) < 0) config_err("fstat %s: %s",s,strerror(errno)); if (stb.st_size < 1) config_err("%s: empty",s); else if (stb.st_size > 65536) { fprintf(stderr,"%s: %s: too large, using only first 64K\n",__progname,s); l = 65536; } else { l = stb.st_size; } b = malloc(l); r = read(fd,b,l); if (r < 0) config_err("read from %s: %s",s,strerror(errno)); if (r != l) config_err("read from %s: wanted %d, got %d",s,l,r); close(fd); free(ck); ck = b; cklen = l; } static void confline_id(const char *s) { long int id; char *ep; id = strtol(s,&ep,0); if (ep != s) while (*ep && Cisspace(*ep)) ep ++; if (ep == s) config_err("bad/missing ID number"); if (* ep) config_err("junk after ID number"); if ((id < 0) || (id > 255)) config_err("ID %ld is out of range",id); cid = id; } static void confline_ip(const char *s) { IPADDR *a; a = malloc(sizeof(IPADDR)); if (inet_pton(AF_INET,s,&a->v4) > 0) { a->af = AF_INET; } else if (inet_pton(AF_INET6,s,&a->v6) > 0) { a->af = AF_INET6; } else { config_err("unparseable IP `%s'",s); } cipaddrs = realloc(cipaddrs,(ncipaddrs+1)*sizeof(IPADDR *)); cipaddrs[ncipaddrs++] = a; } static void confline_type(const char *s) { if (! strcmp(s,"public")) { ignore_public = 0; } else if (! strcmp(s,"private")) { ignore_public = 1; } else { config_err("bad `type' value `%s'",s); } } static void capcustom_listen(APSTATE *s) { if (! strcmp(s->addr,"*")) { } else if (! strcmp(s->addr,"*4")) { s->hints.ai_family = AF_INET; } else if (! strcmp(s->addr,"*6")) { s->hints.ai_family = AF_INET6; } else { return; } free(s->addr); s->addr = 0; } static void nonblocking_fd(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void confline_listen(const char *s) { struct addrinfo *ai0; struct addrinfo *ai; LISTEN *l; char *txt; int fd; int e; const char *se; const char *k; int publicity; char *sfree; void errcleanup(void) { if (fd >= 0) close(fd); if (txt) free(txt); freeaddrinfo(ai0); } for (se=s;*se&&!Cisspace(*se);se++) ; for (k=se;*k&&Cisspace(*k);k++) ; if (* k) { char *t; if (! strcasecmp(k,"public")) { publicity = 1; } else if (! strcasecmp(k,"private")) { publicity = 0; } else { config_err("unrecognized publication keyword `%s'",k); } t = malloc((se-s)+1); bcopy(s,t,se-s); t[se-s] = '\0'; sfree = t; se = t; } else { se = s; publicity = -1; sfree = 0; } ai0 = crack_addr_port(se,AI_PASSIVE,&capcustom_listen); for (ai=ai0;ai;ai=ai->ai_next) { char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; fd = -1; txt = 0; e = getnameinfo(ai->ai_addr,ai->ai_addrlen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) config_err_cleanup(&errcleanup,"getnameinfo [for %s]: %s",se,gai_strerror(e)); asprintf(&txt,"%s/%s",&hstr[0],&sstr[0]); fd = socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol); if (fd < 0) continue; if (bind(fd,ai->ai_addr,ai->ai_addrlen) < 0) { config_err_cleanup(&errcleanup,"bind %s [for %s]: %s",txt,s,strerror(errno)); } if (listen(fd,10) < 0) { config_err_cleanup(&errcleanup,"bind %s [for %s]: %s",txt,s,strerror(errno)); } nonblocking_fd(fd); l = malloc(sizeof(LISTEN)); l->sa = malloc(ai->ai_addrlen); bcopy(ai->ai_addr,l->sa,ai->ai_addrlen); l->salen = ai->ai_addrlen; if (publicity < 0) { switch (l->sa->sa_family) { case AF_INET: { unsigned int a; a = ntohl(((const struct sockaddr_in *)l->sa)->sin_addr.s_addr); publicity = ! ( ((a & 0xff000000) == 0x0a000000) || ((a & 0xfff00000) == 0xac100000) || ((a & 0xffff0000) == 0xc0a80000) ); } break; case AF_INET6: { const struct in6_addr *a; a = &((const struct sockaddr_in6 *)l->sa)->sin6_addr; publicity = !IN6_IS_ADDR_LINKLOCAL(a) && !IN6_IS_ADDR_SITELOCAL(a); } break; default: publicity = 0; break; } } l->public = publicity; l->txt = txt; l->fd = fd; l->link = listens; listens = l; } } static UPLINK *new_uplink(const struct sockaddr *sa, int salen) { char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; UPLINK *u; char *txt; int e; txt = 0; e = getnameinfo(sa,salen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) { asprintf(&txt,"getnameinfo [af %d]: %s",sa->sa_family,gai_strerror(e)); } else { asprintf(&txt,"%s/%s",&hstr[0],&sstr[0]); } u = malloc(sizeof(UPLINK)); u->sa = malloc(salen); bcopy(sa,u->sa,salen); u->salen = salen; u->txt = txt; u->flags = 0; u->stamp = 0; u->peer = 0; u->link = uplinks; uplinks = u; return(u); } static void confline_uplink(const char *s) { struct addrinfo *ai0; struct addrinfo *ai; ai0 = crack_addr_port(s,0,0); for (ai=ai0;ai;ai=ai->ai_next) { new_uplink(ai->ai_addr,ai->ai_addrlen)->flags |= UF_LOCALCONF; } freeaddrinfo(ai0); } static void confline_tun(const char *s) { long int n; int i; int k0; int kl; int t0; char *t; char *ep; for (i=0;s[i]&&Cisspace(s[i]);i++) ; k0 = i; for (;s[i]&&!Cisspace(s[i]);i++) ; kl = i - k0; for (;s[i]&&Cisspace(s[i]);i++) ; t0 = i; t = malloc(kl+1); bcopy(s+k0,t,kl); t[kl] = '\0'; free(tunpath); n = strtol(t,&ep,0); if (ep == t) { tunpath = strdup(t); } else { if (* ep) { tunpath = strdup(t); } else { asprintf(&tunpath,"/dev/tun%ld",n); } } free(t); if (! s[t0]) { tuntype = 0; } else if (! strcasecmp(s+t0,"broadcast")) { tuntype = IFF_BROADCAST; } else if (! strcasecmp(s+t0,"pointopoint")) { tuntype = IFF_POINTOPOINT; } else { config_err("bad tun type `%s'",s+t0); } } static void config_line(const char *s) { const char *k0; int kl; static struct { const char *key; void (*handler)(const char *); int keylen; } keys[] = { { "@", &confline__at }, { "key", &confline_key }, { "keyfile", &confline_keyfile }, { "id", &confline_id }, { "ip", &confline_ip }, { "type", &confline_type }, { "listen", &confline_listen }, { "uplink", &confline_uplink }, { "tun", &confline_tun }, { 0 } }; int i; if (keys[0].keylen == 0) for (i=0;keys[i].key;i++) keys[i].keylen = strlen(keys[i].key); while (*s && Cisspace(*s)) s ++; if (! *s) return; if (*s == '#') return; k0 = s; while (*s && !Cisspace(*s)) s ++; kl = s - k0; while (*s && Cisspace(*s)) s ++; for (i=0;keys[i].key;i++) { if ((kl == keys[i].keylen) && !strncasecmp(k0,keys[i].key,kl)) { (*keys[i].handler)(s); return; } } config_err("unrecognized config key `%.*s'",kl,k0); } static void config_read(void) { char *lb; int la; int ln; int c; void savec(char ch) { if (ln >= la) lb = realloc(lb,la=ln+16); lb[ln++] = ch; } void cl(void) { savec('\0'); config_line(lb); } lb = 0; la = 0; ln = 0; while <"read"> (1) { c = getc(isrc->f); switch (c) { case EOF: if (ln > 0) cl(); ln = 0; pop_isrc(); if (! isrc) break <"read">; break; case '\n': cl(); ln = 0; break; default: if (ln == 0) isrc->lno ++; savec(c); break; } } free(lb); } static void readconf(const char *fn) { FILE *f; isrc = 0; f = fopen(fn,"r"); if (f == 0) config_err("can't open config file %s: %s",fn,strerror(errno)); push_isrc(f,fn); config_read(); } static void readconfline(const char *line) { FILE *f; isrc = 0; f = open_strings_r(line,OSR_STRLEN,"\n",1,OSR_END); push_isrc(f,""); config_read(); } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: stray argument `%s'\n",__progname,*av); errs ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-config")) { WANTARG(); readconf(av[skip]); continue; } if (!strcmp(*av,"-C")) { WANTARG(); readconfline(av[skip]); continue; } if (!strcmp(*av,"-v")) { verbose = 1; continue; } if (!strcmp(*av,"-log")) { WANTARG(); openlog(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void init(void) { ck = 0; ncipaddrs = 0; cipaddrs = 0; ignore_public = -1; listens = 0; uplinks = 0; npeers = 0; apeers = 0; peers = 0; tunpath = 0; } static void set_defaults(void) { if (ignore_public < 0) { fprintf(stderr,"%s: no `type' line configured\n",__progname); exit(1); } if (tunpath == 0) { fprintf(stderr,"%s: no tun device configured\n",__progname); exit(1); } } static PEER *new_peer(void) { PEER *p; if (npeers >= apeers) peers = realloc(peers,(apeers=npeers+1)*sizeof(PEER *)); p = malloc(sizeof(PEER)); p->lclsa = 0; p->remsa = 0; p->txt = 0; p->fd = -1; p->id = PL_NOID; oq_init(&p->oq); arc4_init(&p->a4_out); arc4_init(&p->a4_in); p->ibuf = malloc(1+2+65535); p->ifill = 0; p->peerid = -1; p->pq = 0; p->pqt = &p->pq; p->up = 0; peers[npeers++] = p; return(p); } static int peercleanup(void *arg __attribute__((__unused__))) { int i; PEER *p; if (! peercleanup_needed) return(BLOCK_NIL); peercleanup_needed = 0; for (i=npeers-1;i>=0;i--) { p = peers[i]; if (p->fd >= 0) continue; free(p->lclsa); free(p->remsa); free(p->txt); oq_flush(&p->oq); free(p->ibuf); while (p->pq) { PQ *q; q = p->pq; p->pq = q->link; free(q); } free(p); npeers --; if (i < npeers) peers[i] = peers[npeers]; } return(BLOCK_LOOP); } static void close_peer(PEER *p) { close(p->fd); p->fd = -1; if (p->up) { if (p->up->peer != p) abort(); p->up->peer = 0; p->up = 0; } peercleanup_needed = 1; } static void close_cryptinit(CRYPTINIT *ci) { remove_poll_id(ci->p->id); close_peer(ci->p); free(ci); } static int wr_oq(OQ *q, int fd, const char *txt) { int n; n = oq_writev(q,fd); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(0); } fprintf(stderr,"%s: write to %s: %s\n",__progname,txt,strerror(errno)); return(-1); } VERB("wrote %d from oq\n",n); oq_dropdata(q,n); return(0); } static void wr_cryptinit(void *civ) { CRYPTINIT *ci; ci = civ; if (wr_oq(&ci->p->oq,ci->p->fd,ci->p->txt) < 0) close_cryptinit(ci); } static int wtest_cryptinit(void *civ) { return(oq_nonempty(&((CRYPTINIT *)civ)->p->oq)); } static int rd_crypto_1(CRYPTINIT *ci, unsigned char *Rv, int len) { int n; n = read(ci->p->fd,&Rv[ci->n],len-ci->n); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(1); break; } close_cryptinit(ci); return(1); } if (n == 0) { close_cryptinit(ci); return(1); } ci->n += n; if (ci->n < len) return(1); return(0); } static void random_data(unsigned char *buf, int len) { int n; while (len > 0) { n = read(rndfd,buf,len); if (n < 0) { fprintf(stderr,"%s: read from %s: %s\n",__progname,RND_DEV,strerror(errno)); exit(1); } if (n == 0) { fprintf(stderr,"%s: read EOF from %s\n",__progname,RND_DEV); exit(1); } buf += n; len -= n; } } static unsigned int crc32(const void *data, ...) #define CRC32TERM ((const void *)0) { unsigned int h; static const unsigned int crctbl[256] = { #include "crctable" }; va_list ap; int len; const unsigned char *dp; h = 0x31bb29b0; va_start(ap,data); while (data) { len = va_arg(ap,int); for (dp=data;len>0;len--) { h = (h >> 8) ^ crctbl[(h^*dp++)&0xff]; } data = va_arg(ap,const void *); } va_end(ap); return(h); } static void hash(unsigned char *rv, const void *data, ...) #define HASHTERM ((const void *)0) { void *hh; va_list ap; int len; hh = sha256_init(); va_start(ap,data); while (data) { len = va_arg(ap,int); sha256_process_bytes(hh,data,len); data = va_arg(ap,const void *); } va_end(ap); sha256_result(hh,rv); } static void nap_done(void *nv) { NAP *n; n = nv; remove_poll_id(n->id); (*n->done)(n->arg); close(n->s); free(n); } static void start_nap(unsigned int sec, unsigned int usec, void (*done)(void *), void *arg) { int s; NAP *n; struct itimerval itv; s = socket(AF_TIMER,SOCK_STREAM,0); n = malloc(sizeof(NAP)); n->s = s; n->done = done; n->arg = arg; n->id = add_poll_fd(s,&rwtest_always,&rwtest_never,&nap_done,0,n); itv.it_value.tv_sec = sec; itv.it_value.tv_usec = usec; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(s,&itv,sizeof(itv)); } static void cryptinit_gen_b_s(CRYPTINIT *ci) { unsigned int t; unsigned char b; unsigned char xs[32]; unsigned char s[36]; unsigned char hk[32]; int sl; int i; int j; void *rj1; void *rj2; unsigned char key1[32]; unsigned char B; static const unsigned char zero[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; unsigned char data2[32]; if (verbose) { int i; plog("Rl = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x, " "Rc = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", ci->Rl[ 0], ci->Rl[ 1], ci->Rl[ 2], ci->Rl[ 3], ci->Rl[ 4], ci->Rl[ 5], ci->Rl[ 6], ci->Rl[ 7], ci->Rl[ 8], ci->Rl[ 9], ci->Rl[10], ci->Rl[11], ci->Rl[12], ci->Rl[13], ci->Rl[14], ci->Rl[15], ci->Rc[ 0], ci->Rc[ 1], ci->Rc[ 2], ci->Rc[ 3], ci->Rc[ 4], ci->Rc[ 5], ci->Rc[ 6], ci->Rc[ 7], ci->Rc[ 8], ci->Rc[ 9], ci->Rc[10], ci->Rc[11], ci->Rc[12], ci->Rc[13], ci->Rc[14], ci->Rc[15]); plog("K = "); for (i=0;iRl[0],16,ck,cklen,&ci->Rc[0],16,ck,cklen,CRC32TERM); b = ((t >> 24) ^ (t >> 16) ^ (t >> 8) ^ t) & 0xff; for (i=0;i<32;i++) xs[i] = (b + i) & 0xff; sl = 36; bcopy(&ci->Rl[0],&s[0],16); s[16] = (t >> 24) & 0xff; s[17] = (t >> 16) & 0xff; s[18] = (t >> 8) & 0xff; s[19] = t & 0xff; bcopy(&ci->Rc[0],&s[20],16); hash(&hk[0],ck,cklen,HASHTERM); rj1 = rijndael_init(8,8); rj2 = rijndael_init(8,8); rijndael_set_key(rj2,&hk[0]); for (i=0;i<64;i++) { B = i; hash(&key1[0],ck,cklen,&B,1,&s[0],sl,&B,1,ck,cklen,HASHTERM); rijndael_set_key(rj1,&key1[0]); rijndael_encrypt(rj1,&zero[0],&data2[0]); for (j=32-1;j>=0;j--) data2[j] ^= xs[j]; rijndael_encrypt(rj2,&data2[0],&s[0]); sl = 32; } ci->b = b; bcopy(&s[0],&ci->s[0],32); rijndael_done(rj1); rijndael_done(rj2); if (verbose) { plog("b = %02x, s = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", ci->b, ci->s[ 0], ci->s[ 1], ci->s[ 2], ci->s[ 3], ci->s[ 4], ci->s[ 5], ci->s[ 6], ci->s[ 7], ci->s[ 8], ci->s[ 9], ci->s[10], ci->s[11], ci->s[12], ci->s[13], ci->s[14], ci->s[15], ci->s[16], ci->s[17], ci->s[18], ci->s[19], ci->s[20], ci->s[21], ci->s[22], ci->s[23], ci->s[24], ci->s[25], ci->s[26], ci->s[27], ci->s[28], ci->s[29], ci->s[30], ci->s[31]); } } static void cryptinit_gen_Kd(CRYPTINIT *ci, unsigned char Kl2c[32], unsigned char Kc2l[32]) { hash(&Kl2c[0],"->",2,ck,cklen,&ci->Rl[0],16,"->",2,&ci->Rc[0],16,ck,cklen,"->",2,HASHTERM); hash(&Kc2l[0],"<-",2,ck,cklen,&ci->Rl[0],16,"<-",2,&ci->Rc[0],16,ck,cklen,"<-",2,HASHTERM); } static unsigned int min_bits(unsigned int v) { int n; if (v == 0) return(0); n = 1; if (v & 0xffff0000) { v >>= 16; n += 16; } if (v & 0xff00) { v >>= 8; n += 8; } if (v & 0xf0) { v >>= 4; n += 4; } if (v & 0xc) { v >>= 2; n += 2; } if (v & 0x2) { /* v >>= 1; unnecessary */ n += 1; } return(n); } static void gen_a4_key( ARC4_STATE *a4, const unsigned char *Kd, const unsigned char *Kd_, const unsigned char *Rk, unsigned int n_rekeys ) { unsigned char t0[96]; unsigned char *t; int tlen; unsigned char k[256]; int klen; void *rj; int i; int B; unsigned char SERbuf[8]; unsigned char *SER; int SERlen; unsigned char h[32]; SER = &SERbuf[8]; SERlen = 0; while (n_rekeys) { *--SER = n_rekeys & 0xff; n_rekeys >>= 8; SERlen ++; } rj = rijndael_init(8,8); rijndael_set_key(rj,Kd); bcopy(Kd,&t0[0],32); bcopy(Rk,&t0[32],32); bcopy(Kd_,&t0[64],32); t = &t0[0]; tlen = 96; klen = 0; for (i=0;i<8;i++) { B = i; hash(&h[0],t,tlen,Rk,32,&B,1,SER,SERlen,t,tlen,HASHTERM); rijndael_encrypt(rj,&h[0],&k[klen]); t = &k[klen]; tlen = 32; klen += 32; } rijndael_done(rj); arc4_setkey(a4,&k[0],klen,65536); } static void send_rekey(PEER *p) { unsigned char Rk[32]; int L; unsigned char *P1; unsigned char P2[32]; int N; unsigned char enc[32]; void *rj; unsigned int Lmask; VERB("sending rekey for %s\n",p->txt); random_data(&Rk[0],32); L = 8; // XXX select intelligently P1 = malloc(L); random_data(P1,L); random_data(&P2[0],32); N = min_bits(L); if ((N < 0) || (N > 15)) abort(); P2[0] = (P2[0] & 0x0f) | (N << 4); Lmask = (~0U) << N; P2[31] = ((P2[31] & Lmask) | (L & ~Lmask)) & 0xff; P2[30] = ((P2[30] & (Lmask >> 8)) | ((L & ~Lmask) >> 8)) & 0xff; rj = rijndael_init(8,8); rijndael_set_key(rj,&p->Kd[0]); rijndael_encrypt(rj,&Rk[0],&enc[0]); oq_queue_copy(&p->oq,&enc[0],32); rijndael_encrypt(rj,&P2[0],&enc[0]); oq_queue_copy(&p->oq,&enc[0],32); rijndael_done(rj); oq_queue_free(&p->oq,P1,L); gen_a4_key(&p->a4_out,&p->Kd[0],&p->Kd_[0],&Rk[0],p->n_rekeys_out); p->bytes_to_rekey_out = REKEY_N_BASE + (P2[1] << 8) + P2[2]; p->n_rekeys_out ++; } static int wtest_peer(void *pv) { return(oq_nonempty(&((PEER *)pv)->oq)); } static void rd_peer_rekey_2(PEER *p) { p->ifill = 0; } static void rd_peer_rekey_1(PEER *p) { void *rj; unsigned char Rk[32]; unsigned char P2[32]; int L; int N; rj = rijndael_init(8,8); rijndael_set_key(rj,&p->Kd_[0]); rijndael_decrypt(rj,p->ibuf,&Rk[0]); rijndael_decrypt(rj,p->ibuf+32,&P2[0]); N = P2[0] >> 4; L = ((P2[30] * 256U) | P2[31]) & ~((~0U) << N); gen_a4_key(&p->a4_in,&p->Kd_[0],&p->Kd[0],&Rk[0],p->n_rekeys_in); p->bytes_to_rekey_in = REKEY_N_BASE + (P2[1] << 8) + P2[2]; p->n_rekeys_in ++; if (L == 0) { rd_peer_rekey_2(p); } else { p->ifill = 1; p->iwant = L + 1; p->readdone = &rd_peer_rekey_2; } } static void set_quietdrop(int fd) { int on; on = 1; setsockopt(fd,IPPROTO_TCP,TCP_QUIETDROP,&on,sizeof(on)); } static void send_encrypted(PEER *p, const void *data, int len) { unsigned char *obuf; if (len < 1) return; obuf = malloc(len); arc4_crypt(&p->a4_out,data,len,obuf); oq_queue_free(&p->oq,obuf,len); p->bytes_to_rekey_out -= len; } static void send_postpacket(PEER *p) { if (p->bytes_to_rekey_out < 1) send_rekey(p); } static int addr_port_match(const struct sockaddr *a, const struct sockaddr *b) { if (a->sa_family != b->sa_family) return(0); switch (a->sa_family) { default: abort(); break; case AF_INET: return( ( ((const struct sockaddr_in *)a)->sin_addr.s_addr == ((const struct sockaddr_in *)b)->sin_addr.s_addr ) && ( ((const struct sockaddr_in *)a)->sin_port == ((const struct sockaddr_in *)b)->sin_port ) ); break; case AF_INET6: return( ( ((const struct sockaddr_in6 *)a)->sin6_port == ((const struct sockaddr_in6 *)b)->sin6_port ) && IN6_ARE_ADDR_EQUAL( &((const struct sockaddr_in6 *)a)->sin6_addr, &((const struct sockaddr_in6 *)b)->sin6_addr ) ); break; } } static int ipaddr_match(const IPADDR *a, const IPADDR *b) { if (a->af != b->af) return(0); switch (a->af) { default: abort(); break; case AF_INET: return(a->v4.s_addr==b->v4.s_addr); break; case AF_INET6: return(IN6_ARE_ADDR_EQUAL(&a->v6,&b->v6)); break; } } static void learn_public(const struct sockaddr *sa, int salen, time_t stamp) { UPLINK *u; if (verbose) { char obuf[256]; plog("learn_public: sa="); switch (sa->sa_family) { default: plog("AF %d",sa->sa_family); break; case AF_INET: #define sin ((const struct sockaddr_in *)sa) plog("%s/%d",inet_ntop(AF_INET,&sin->sin_addr,&obuf[0],sizeof(obuf)),ntohs(sin->sin_port)); #undef sin break; case AF_INET6: #define sin6 ((const struct sockaddr_in6 *)sa) plog("%s/%d",inet_ntop(AF_INET6,&sin6->sin6_addr,&obuf[0],sizeof(obuf)),ntohs(sin6->sin6_port)); #undef sin6 break; } plog(" stamp=%lu\n",(unsigned long int)stamp); } for (u=uplinks;u;u=u->link) { if (addr_port_match(u->sa,sa)) { u->stamp = stamp; VERB("updating existing\n"); return; } } VERB("new\n"); new_uplink(sa,salen)->stamp = stamp; } static void my_ip_pkt(PEER *p, const unsigned char *data, int len) { struct sockaddr sa; struct iovec iov[2]; if (len <= 3) abort(); VERB("my IP packet: addrtype %d, data %02x%02x...%02x%02x\n",data[2],data[3],data[4],data[len-2],data[len-1]); switch (data[2]) { default: fprintf(stderr,"%s: ignoring AF-%d packet from %s\n",__progname,data[2],p->txt); return; break; case IPTYPE_v4: sa.sa_family = AF_INET; break; case IPTYPE_v6: sa.sa_family = AF_INET6; break; } #define L (offsetof(struct sockaddr,sa_len) + sizeof(sa.sa_len)) #define F (offsetof(struct sockaddr,sa_family) + sizeof(sa.sa_family)) sa.sa_len = (L > F) ? L : F; #undef L #undef F iov[0].iov_base = (void *) &sa; iov[0].iov_len = sa.sa_len; iov[1].iov_base = dequal(data+3); iov[1].iov_len = len - 3; writev(tunfd,&iov[0],2); } static void peer_pkt_ip(PEER *p, const unsigned char *data, int len) { PEER *nexthop; unsigned char hdr[6]; if (p->peerid < 0) return; if (len <= 3) { fprintf(stderr,"%s: ignoring runt IP from peer %s (len %d)\n",__progname,p->txt,len); return; } VERB("peer_pkt_ip: hopcount=%d id=%d dsttype=%d len=%d\n",data[0],data[1],data[2],len-3); if (data[1] == cid) { my_ip_pkt(p,data,len); return; } else { if (data[0] == 0) return; if ( (reachtbl[data[1]] < 0) || (reachdist[data[1]] >= REACH_INFINITY) ) return; nexthop = peertbl[reachtbl[data[1]]]; if (! nexthop) return; if (oq_qlen(&nexthop->oq) > QLIMIT) return; if (len > 65535) abort(); VERB("forwarding IP from %s to %s\n",p->txt,nexthop->txt); hdr[0] = PKT_IP; hdr[1] = len >> 8; hdr[2] = len & 0xff; hdr[3] = data[0] - 1; hdr[4] = data[1]; hdr[5] = data[2]; send_encrypted(nexthop,&hdr[0],6); send_encrypted(nexthop,data+3,len-3); send_postpacket(nexthop); } } static int canreach( unsigned char tgt, unsigned char dist, unsigned char via, time_t stamp ) { VERB("canreach tgt=%d dist=%d via=%d stamp=%lu " "reachtbl[]=%d reachdist[]=%d reachtime[]=%lu\n", tgt, dist, via, (unsigned long int)stamp, reachtbl[tgt], reachdist[tgt], (unsigned long int)reachtime[tgt]); if ( (reachtbl[tgt] < 0) || (dist < reachdist[tgt]) || (via == reachtbl[tgt]) || ( (dist == reachdist[tgt]) && (stamp-reachtime[tgt] > 180) ) ) { if (dist >= REACH_INFINITY) { if (reachtbl[tgt] != -1) { reachtbl[tgt] = -1; VERB("returning 1 (unreachable)\n"); return(1); } } else { reachtime[tgt] = stamp; if ( (reachtbl[tgt] != via) || (reachdist[tgt] != dist) ) { reachtbl[tgt] = via; reachdist[tgt] = dist; VERB("returning 1 (reachable)\n"); return(1); } } } VERB("returning 0\n"); return(0); } static void set_flood(unsigned int bit) { int i; for (i=npeers-1;i>=0;i--) peers[i]->want |= bit; want_send = 1; } static void peer_pkt_reach(PEER *p, const unsigned char *data, int len) { int o; time_t now; int wantflood; if (p->peerid < 0) { VERB("peer_pkt_reach: from %s, ignored (peerid < 0)\n",p->txt); return; } VERB("peer_pkt_reach: from %s\n",p->txt); time(&now); o = 0; wantflood = 0; while (1) { if (o > len) abort(); if (o == len) break; if (o+2 > len) { fprintf(stderr,"%s: peer %s sent REACH that overruns packet (%d > %d)\n",__progname,p->txt,o+2,len); close_peer(p); return; } if (canreach(data[o],data[o+1]+1,p->peerid,now)) wantflood = 1; o += 2; } if (wantflood) { VERB("flooding REACH\n"); set_flood(WANT_REACH); } } static void peer_pkt_public(PEER *p, const unsigned char *data, int len) { __label__ throwto; int o; void overrun(int o) { if (o <= len) return; fprintf(stderr,"%s: peer %s sent PUBLIC that overruns packet (%d > %d)\n",__progname,p->txt,o,len); goto throwto; } if (0) { throwto:; close_peer(p); return; } if (ignore_public) { VERB("peer_pkt_public: from %s, ignored (ignore_public)\n",p->txt); return; } if (p->peerid < 0) { VERB("peer_pkt_public: from %s, ignored (peerid < 0)\n",p->txt); return; } VERB("peer_pkt_public: from %s\n",p->txt); o = 0; while (1) { if (o > len) abort(); if (o == len) break; switch (data[o]) { default: fprintf(stderr,"%s: unknown IP type %d in PUBLIC from %s\n",__progname,data[o],p->txt); close_peer(p); return; break; case IPTYPE_v4: { struct sockaddr_in sin; overrun(o+11); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; bcopy(data+o+1,&sin.sin_addr,4); sin.sin_port = htons((data[o+5]*256)+data[o+6]); learn_public( (void *)&sin, sizeof(sin), (data[o+7] * 0x01000000) + (data[o+8] * 0x00010000) + (data[o+9] * 0x00000100) + (data[o+10] * 0x00000001) ); o += 11; } break; case IPTYPE_v6: { struct sockaddr_in6 sin6; overrun(o+23); sin6.sin6_len = sizeof(sin6); sin6.sin6_family = AF_INET6; bcopy(data+o+1,&sin6.sin6_addr,16); sin6.sin6_port = htons((data[o+17]*256)+data[o+18]); learn_public( (void *)&sin6, sizeof(sin6), (data[o+19] * 0x01000000) + (data[o+20] * 0x00010000) + (data[o+21] * 0x00000100) + (data[o+22] * 0x00000001) ); o += 23; } break; } } } static IPMAP *new_ipmap(unsigned char id, const IPADDR *a) { IPMAP *m; m = malloc(sizeof(IPMAP)); m->a = *a; m->id = id; return(m); } static IPMAP *replace_ipmap(unsigned char id, const IPADDR *a, IPMAP *list) { IPMAP *m; for (m=list;m;m=m->link) { if (ipaddr_match(&m->a,a)) { m->id = id; return(list); } } m = new_ipmap(id,a); m->link = list; return(m); } static IPMAP *save_ipmaps(unsigned char id, IPMAP *list) { IPMAP *m; IPMAP **mp; mp = &ipmaps; while ((m = *mp)) { if (m->id == id) { *mp = m->link; m->link = list; list = m; } else { mp = &m->link; } } return(list); } /* * It is important that the ID be the most significant comparison key * here, because there is code that depends on sort_ipmaps to group * all mappings with the same ID together. */ static int ipmap_lt(IPMAP *a, IPMAP *b) { int i; if (a->id != b->id) return(a->id < b->id); if (a->a.af != b->a.af) return(a->a.af < b->a.af); switch (a->a.af) { default: abort(); break; case AF_INET: return(a->a.v4.s_addr < b->a.v4.s_addr); break; case AF_INET6: for (i=16-1;i>=0;i--) { if (a->a.v6.s6_addr[i] != b->a.v6.s6_addr[i]) { return(a->a.v6.s6_addr[i] < b->a.v6.s6_addr[i]); } } return(0); break; } } static IPMAP *sort_ipmaps(IPMAP *list) { IPMAP *a; IPMAP *b; IPMAP *t; IPMAP **lt; if (!list || !list->link) return(list); a = 0; b = 0; while (list) { t = list; list = t->link; t->link = a; a = b; b = t; } a = sort_ipmaps(a); b = sort_ipmaps(b); lt = &list; while (a || b) { if (!b || (a && ipmap_lt(a,b))) { t = a; a = a->link; } else { t = b; b = b ->link; } *lt = t; lt = &t->link; } *lt = 0; return(list); } static IPMAP *prepend_ipmaps(IPMAP *m, IPMAP *list) { IPMAP *t; while (m) { t = m; m = m->link; t->link = list; list = t; } return(list); } static void peer_pkt_ipmap(PEER *p, const unsigned char *data, int len) { __label__ throwto; int o; IPADDR a; unsigned char sawid[256/8]; unsigned char id; IPMAP *old; IPMAP *new; IPMAP *om; IPMAP *nm; void fail(void) { goto throwto; } void overrun(int o) { if (o > len) { fprintf(stderr,"%s: packet overrun in IPMAP from %s (%d > %d)\n",__progname,p->txt,o,len); fail(); } } if (p->peerid < 0) { VERB("peer_pkt_ipmap: from %s, ignored (peerid < 0)\n",p->txt); return; } VERB("peer_pkt_ipmap: from %s\n",p->txt); old = 0; new = 0; if (0) { throwto:; close_peer(p); ipmaps = prepend_ipmaps(old,ipmaps); return; } bzero(&sawid[0],sizeof(sawid)); o = 0; while (1) { if (o > len) abort(); if (o == len) break; id = data[o]; overrun(o+2); switch (data[o+1]) { default: fprintf(stderr,"%s: unknown IP type %d in IPMAP from %s\n",__progname,data[o+1],p->txt); fail(); break; case IPTYPE_v4: overrun(o+6); a.af = AF_INET; bcopy(data+o+2,&a.v4,4); o += 6; break; case IPTYPE_v6: overrun(o+18); a.af = AF_INET6; bcopy(data+o+2,&a.v6,16); o += 18; break; } if (id == cid) continue; if (! (sawid[id>>3] & (1 << (id & 7)))) { sawid[id>>3] |= 1 << (id & 7); VERB("saving maps for %d\n",id); old = save_ipmaps(id,old); } if (verbose) { char obuf[256]; plog("mapping %s to %d\n",inet_ntop(a.af,&a.v4,&obuf[0],sizeof(obuf)),id); } new = replace_ipmap(id,&a,new); } old = sort_ipmaps(old); new = sort_ipmaps(new); if (verbose) { IPMAP *tm; char obuf[256]; plog("old:"); for (tm=old;tm;tm=tm->link) { plog(" %s->%d",inet_ntop(tm->a.af,&tm->a.v4,&obuf[0],sizeof(obuf)),tm->id); } plog("\nnew:"); for (tm=new;tm;tm=tm->link) { plog(" %s->%d",inet_ntop(tm->a.af,&tm->a.v4,&obuf[0],sizeof(obuf)),tm->id); } plog("\n"); } do <"match"> { do <"mismatch"> { for (nm=new,om=old;nm;nm=nm->link,om=om->link) { if (! om) break <"mismatch">; if (om->id != nm->id) break <"mismatch">; if (om->a.af != nm->a.af) break <"mismatch">; switch (om->a.af) { default: abort(); break; case AF_INET: if (om->a.v4.s_addr != nm->a.v4.s_addr) break <"mismatch">; break; case AF_INET6: if (! IN6_ARE_ADDR_EQUAL(&om->a.v6,&nm->a.v6)) break <"mismatch">; break; } } if (om) break <"mismatch">; break <"match">; } while (0); VERB("mismatch, flooding IPMAP\n"); set_flood(WANT_IPMAP); } while (0); VERB("done, saving new\n"); ipmaps = prepend_ipmaps(new,ipmaps); } static void peer_pkt_iam(PEER *p, const unsigned char *data, int len) { if (len != 1) { fprintf(stderr,"%s: peer %s sent IAM with length %d\n",__progname,p->txt,len); close_peer(p); } if (p->peerid >= 0) { fprintf(stderr,"%s: peer %s sent another IAM\n",__progname,p->txt); close_peer(p); } if (data[0] == cid) { fprintf(stderr,"%s: peer %s claims to be us\n",__progname,p->txt); set_quietdrop(p->fd); close_peer(p); } VERB("peer_pkt_iam: from %s, id %d\n",p->txt,data[0]); // if (peertbl[data[0]] && (cid < data[0])) close_peer(peertbl[data[0]]); p->peerid = data[0]; peertbl[data[0]] = p; p->want |= WANT_REACH | WANT_PUBLIC | WANT_IPMAP; } static void peer_packet(PEER *p, unsigned char type, const unsigned char *data, int len) { switch (type) { case PKT_IGNORE: break; case PKT_IP: peer_pkt_ip(p,data,len); break; case PKT_REACH: peer_pkt_reach(p,data,len); break; case PKT_PUBLIC: peer_pkt_public(p,data,len); break; case PKT_IPMAP: peer_pkt_ipmap(p,data,len); break; case PKT_IAM: peer_pkt_iam(p,data,len); break; default: fprintf(stderr,"%s: received bad packet type 0x%02x from %s\n",__progname,type,p->txt); close_peer(p); break; } } static void rd_peer_packet_2(PEER *p) { arc4_crypt(&p->a4_in,&p->ibuf[3],p->iwant-3,&p->ibuf[3]); p->bytes_to_rekey_in -= p->iwant; peer_packet(p,p->ibuf[0],&p->ibuf[3],p->iwant-3); p->ifill = 0; } static void rd_peer_packet_1(PEER *p) { int len; arc4_crypt(&p->a4_in,&p->ibuf[0],3,&p->ibuf[0]); len = (p->ibuf[1] * 256) + p->ibuf[2]; if (len == 0) { peer_packet(p,p->ibuf[0],0,0); } else { p->iwant = len + 3; p->readdone = &rd_peer_packet_2; } } static void rd_peer(void *pv) { PEER *p; int n; p = pv; while (1) { if (p->fd < 0) return; if (p->ifill == 0) { if (p->bytes_to_rekey_in < 1) { p->iwant = 64; p->readdone = &rd_peer_rekey_1; } else { p->iwant = 3; p->readdone = &rd_peer_packet_1; } } if (p->ifill >= p->iwant) abort(); n = read(p->fd,p->ibuf+p->ifill,p->iwant-p->ifill); if (n < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } fprintf(stderr,"%s: read from %s: %s\n",__progname,p->txt,strerror(errno)); close_peer(p); return; } if (n == 0) { fprintf(stderr,"%s: read EOF from %s\n",__progname,p->txt); close_peer(p); return; } VERB("read %d from %s\n",n,p->txt); p->ifill += n; if (p->ifill >= p->iwant) (*p->readdone)(p); } } static void wr_peer(void *pv) { PEER *p; p = pv; if (p->fd < 0) return; if (wr_oq(&p->oq,p->fd,p->txt) < 0) close_peer(p); } static void send_iam(PEER *p) { unsigned char opkt[4]; VERB("sending IAM to %s\n",p->txt); opkt[0] = PKT_IAM; opkt[1] = 0; opkt[2] = 1; opkt[3] = cid; send_encrypted(p,&opkt[0],4); send_postpacket(p); } static void start_peer(PEER *p) { VERB("crypto done for %s\n",p->txt); p->n_rekeys_out = 0; p->n_rekeys_in = 0; p->bytes_to_rekey_in = 0; p->want = 0; want_send = 1; send_rekey(p); send_iam(p); p->id = add_poll_fd(p->fd,&rwtest_always,&wtest_peer,&rd_peer,&wr_peer,p); } static void start_peer_v(void *pv) { start_peer(pv); } static void rd_crypto_active_1(void *civ) { CRYPTINIT *ci; PEER *p; ci = civ; if (rd_crypto_1(ci,&ci->Rl[0],16)) return; p = ci->p; random_data(&ci->Rc[0],16); cryptinit_gen_b_s(ci); oq_queue_copy(&p->oq,&ci->Rc[0],16); oq_queue_copy(&p->oq,&ci->b,1); oq_queue_copy(&p->oq,&ci->s[0],32); cryptinit_gen_Kd(ci,&p->Kd_[0],&p->Kd[0]); remove_poll_id(p->id); free(ci); start_peer(p); } static void rd_crypto_passive_1(void *civ) { CRYPTINIT *ci; PEER *p; ci = civ; if (rd_crypto_1(ci,&ci->Rc[0],49)) return; p = ci->p; cryptinit_gen_b_s(ci); if ( (ci->b != ci->Rc[16]) || bcmp(&ci->s[0],&ci->Rc[17],32) ) { set_quietdrop(p->fd); close_cryptinit(ci); return; } cryptinit_gen_Kd(ci,&p->Kd[0],&p->Kd_[0]); remove_poll_id(p->id); free(ci); start_nap(5,0,&start_peer_v,p); } static void peer_start_crypto(CRYPTINIT *ci) { VERB("starting crypto on %s\n",ci->p->txt); switch (ci->myrole) { case CR_ACTIVE: ci->p->id = add_poll_fd(ci->p->fd,&rwtest_always,&wtest_cryptinit,&rd_crypto_active_1,&wr_cryptinit,ci); break; case CR_PASSIVE: ci->p->id = add_poll_fd(ci->p->fd,&rwtest_always,&wtest_cryptinit,&rd_crypto_passive_1,&wr_cryptinit,ci); random_data(&ci->Rl[0],16); oq_queue_copy(&ci->p->oq,&ci->Rl[0],16); break; default: abort(); break; } ci->n = 0; } static int peer_get_sockaddrs(PEER *p) { struct sockaddr_storage ss; socklen_t l; FILE *f; void build_txt(struct sockaddr *sa, int salen, FILE *f) { char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; int e; e = getnameinfo(sa,salen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) { if (e == EAI_SYSTEM) { fprintf(f,"[getnameinfo: %s]",strerror(errno)); } else { fprintf(f,"[getnameinfo: %s]",gai_strerror(e)); } } else { fprintf(f,"%s/%s",&hstr[0],&sstr[0]); } } if (! p->lclsa) { l = sizeof(ss); if (getsockname(p->fd,(struct sockaddr *)&ss,&l) < 0) { fprintf(stderr,"%s: getsockname for %s: %s\n",__progname,p->txt,strerror(errno)); return(-1); } p->lclsa = malloc(l); bcopy(&ss,p->lclsa,l); p->lclsalen = l; } if (! p->remsa) { l = sizeof(ss); if (getpeername(p->fd,(struct sockaddr *)&ss,&l) < 0) { fprintf(stderr,"%s: getpeername for %s: %s\n",__progname,p->txt,strerror(errno)); return(-1); } p->remsa = malloc(l); bcopy(&ss,p->remsa,l); p->remsalen = l; } free(p->txt); f = open_accum(&p->txt,0); build_txt(p->lclsa,p->lclsalen,f); fprintf(f," <-> "); build_txt(p->remsa,p->remsalen,f); fclose(f); return(0); } static void peer_connect_ok(PEER *p) { CRYPTINIT *ci; if (peer_get_sockaddrs(p) < 0) { VERB("peer_get_sockaddrs failed\n"); close_peer(p); return; } VERB("peer_get_sockaddrs worked: %s\n",p->txt); ci = malloc(sizeof(CRYPTINIT)); ci->myrole = CR_ACTIVE; ci->p = p; peer_start_crypto(ci); } static void peer_connect_done(void *pv) { PEER *p; int e; socklen_t el; p = pv; remove_poll_id(p->id); p->id = PL_NOID; el = sizeof(e); if ( (getsockopt(p->fd,SOL_SOCKET,SO_ERROR,&e,&el) < 0) || (el != sizeof(e)) || e ) { VERB("connect to %s failed\n",p->up?p->up->txt:"(destroyed)"); close_peer(p); return; } VERB("connect to %s worked\n",p->up?p->up->txt:"(destroyed)"); peer_connect_ok(p); } static void try_reconnect(void) { UPLINK *ul; PEER *p; int s; for (ul=uplinks;ul;ul=ul->link) { if (ul->peer) continue; if (ul->flags & UF_MYSELF) continue; s = socket(ul->sa->sa_family,SOCK_STREAM,0); if (s < 0) continue; nonblocking_fd(s); p = new_peer(); p->fd = s; p->id = PL_NOID; p->remsa = malloc(ul->salen); bcopy(ul->sa,p->remsa,ul->salen); p->remsalen = ul->salen; asprintf(&p->txt,"(connect to %s)",ul->txt); VERB("trying to connect to %s...",ul->txt); if (connect(s,p->remsa,p->remsalen) < 0) { if (errno == EINPROGRESS) { p->id = add_poll_fd(s,&rwtest_never,&rwtest_always,0,&peer_connect_done,p); VERB("pending\n"); } else { VERB("failed (%s)\n",strerror(errno)); close_peer(p); continue; } } else { VERB("worked\n"); peer_connect_ok(p); } ul->peer = p; p->up = ul; } } static void open_random(void) { rndfd = open(RND_DEV,O_RDONLY,0); if (rndfd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,RND_DEV,strerror(errno)); exit(1); } } static void open_tun(void) { int v; tunfd = open(tunpath,O_RDWR,0); if (tunfd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,tunpath,strerror(errno)); exit(1); } if (tuntype) { v = tuntype; if (ioctl(tunfd,TUNSIFMODE,&v) < 0) { fprintf(stderr,"%s: TUNSIFMODE on %s: %s\n",__progname,tunpath,strerror(errno)); exit(1); } } v = 1; if (ioctl(tunfd,TUNSLMODE,&v) < 0) { fprintf(stderr,"%s: TUNSLMODE on %s: %s\n",__progname,tunpath,strerror(errno)); exit(1); } nonblocking_fd(tunfd); } static void tun_rd(void *arg __attribute__((__unused__))) { unsigned char pkt[2048]; int len; int n; struct sockaddr_storage ss; PQ *pq; while (1) { len = read(tunfd,&pkt[0],sizeof(pkt)); if (len < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } fprintf(stderr,"%s: %s read: %s\n",__progname,tunpath,strerror(errno)); exit(1); } if (len == 0) { fprintf(stderr,"%s: %s read: %s\n",__progname,tunpath,strerror(errno)); exit(1); } VERB("tun_rd got len %d\n",len); n = offsetof(struct sockaddr_storage,ss_len) + sizeof(ss.ss_len); bcopy(&pkt[0],&ss,n); if ((ss.ss_len < n) || (ss.ss_len > sizeof(ss))) { fprintf(stderr,"%s: %s packet has impossible dst len %d\n",__progname,tunpath,ss.ss_len); continue; } if (ss.ss_len > len) { fprintf(stderr,"%s: %s: sockaddr overruns packet (%d > %d)\n",__progname,tunpath,ss.ss_len,len); continue; } bcopy(&pkt[0],&ss,ss.ss_len); pq = malloc(sizeof(PQ)+len-ss.ss_len); switch (ss.ss_family) { default: fprintf(stderr,"%s: %s packet has unknown AF %d\n",__progname,tunpath,ss.ss_family); continue; break; case AF_INET: pq->dst.af = AF_INET; pq->dst.v4 = ((const struct sockaddr_in *)&ss)->sin_addr; break; case AF_INET6: pq->dst.af = AF_INET6; pq->dst.v6 = ((const struct sockaddr_in6 *)&ss)->sin6_addr; break; } pq->body = (void *)(pq+1); pq->bodylen = len - ss.ss_len; bcopy(&pkt[ss.ss_len],pq->body,pq->bodylen); pq->link = 0; *pktqt = pq; pktqt = &pq->link; want_send = 1; } } static void set_pkt_length(unsigned char *pktbase, int pktoff) { int l; l = pktoff - 3; if (l < 1) abort(); pktbase[1] = l >> 8; pktbase[2] = l & 0xff; } static void send_reach(PEER *p) { unsigned char pkt[512]; int pp; int maxpp; int i; void flush(void) { set_pkt_length(&pkt[0],pp); send_encrypted(p,&pkt[0],pp); send_postpacket(p); pp = 0; } void add_target(unsigned char target, unsigned char distance) { if (maxpp-pp < 2) flush(); if (pp == 0) { pkt[pp++] = PKT_REACH; pp += 2; } pkt[pp++] = target; pkt[pp++] = distance; } if (p->peerid < 0) { VERB("not sending REACH to %s (peerid < 0)\n",p->txt); return; } VERB("sending REACH to %s\n",p->txt); pp = 0; maxpp = sizeof(pkt); for (i=256-1;i>=0;i--) { if (i == cid) { add_target(i,0); } else if (reachtbl[i] == p->peerid) { add_target(i,REACH_INFINITY); } else if (reachtbl[i] >= 0) { add_target(i,reachdist[i]); } } if (pp > 0) flush(); } static void send_public(PEER *p) { unsigned char pkt[512]; int pp; int maxpp; time_t now; LISTEN *l; UPLINK *u; void flush(void) { set_pkt_length(&pkt[0],pp); send_encrypted(p,&pkt[0],pp); send_postpacket(p); pp = 0; } void add_target(const struct sockaddr *a, time_t stamp) { int port; if (maxpp-pp < 1+16+4+4) flush(); if (pp == 0) { pkt[pp++] = PKT_PUBLIC; pp += 2; } switch (a->sa_family) { default: abort(); break; case AF_INET: pkt[pp++] = IPTYPE_v4; bcopy(&((const struct sockaddr_in *)a)->sin_addr,pkt+pp,4); pp += 4; port = ntohs(((const struct sockaddr_in *)a)->sin_port); pkt[pp++] = port >> 8; pkt[pp++] = port & 0xff; break; case AF_INET6: pkt[pp++] = IPTYPE_v6; bcopy(&((const struct sockaddr_in6 *)a)->sin6_addr,pkt+pp,16); pp += 16; port = ntohs(((const struct sockaddr_in6 *)a)->sin6_port); pkt[pp++] = port >> 8; pkt[pp++] = port & 0xff; break; } pkt[pp++] = (stamp >> 24) & 0xff; pkt[pp++] = (stamp >> 16) & 0xff; pkt[pp++] = (stamp >> 8) & 0xff; pkt[pp++] = stamp & 0xff; } if (p->peerid < 0) { VERB("not sending PUBLIC to %s (peerid < 0)\n",p->txt); return; } VERB("sending PUBLIC to %s\n",p->txt); pp = 0; maxpp = sizeof(pkt); now = 0; for (l=listens;l;l=l->link) { if (! l->public) continue; if (now == 0) time(&now); add_target(l->sa,now); } for (u=uplinks;u;u=u->link) { if (u->stamp && !(u->flags & UF_MYSELF)) { add_target(u->sa,u->stamp); } } if (pp > 0) flush(); } static void send_ipmap(PEER *p) { unsigned char pkt[512]; int pp; int maxpp; int lastid; int lastidswitch; IPMAP *m; void add_mapping(unsigned char id, const IPADDR *a) { if (id != lastid) { lastid = id; lastidswitch = pp; } if (pp == 0) { pkt[pp++] = PKT_IPMAP; pp += 2; } if (pp+18 > maxpp) { if (lastidswitch == 0) { fprintf(stderr,"%s: too many IPs for ID %d\n",__progname,lastid); exit(1); } set_pkt_length(&pkt[0],lastidswitch); send_encrypted(p,&pkt[0],lastidswitch); send_postpacket(p); if (lastidswitch < pp) bcopy(&pkt[lastidswitch],&pkt[1],pp-lastidswitch); pp -= lastidswitch - 1; lastidswitch = 0; } pkt[pp++] = id; switch (a->af) { default: abort(); break; case AF_INET: pkt[pp++] = IPTYPE_v4; bcopy(&a->v4,pkt+pp,4); pp += 4; break; case AF_INET6: pkt[pp++] = IPTYPE_v6; bcopy(&a->v6,pkt+pp,16); pp += 16; break; } } if (p->peerid < 0) { VERB("not sending IPMAP to %s (peerid < 0)\n",p->txt); return; } VERB("sending IPMAP to %s\n",p->txt); ipmaps = sort_ipmaps(ipmaps); lastid = -1; pp = 0; maxpp = sizeof(pkt); for (m=ipmaps;m;m=m->link) { add_mapping(m->id,&m->a); } if (pp > 0) { set_pkt_length(&pkt[0],pp); send_encrypted(p,&pkt[0],pp); send_postpacket(p); } } static void send_packet(PEER *p) { PQ *q; unsigned char hdr[6]; int l; if (p->peerid < 0) { VERB("not sending IP to %s (peerid < 0)\n",p->txt); return; } VERB("sending IP to %s\n",p->txt); q = p->pq; p->pq = q->link; if (! p->pq) p->pqt = &p->pq; if (p->peerid < 0) { free(q); return; } l = q->bodylen + 3; if (l > 65535) { fprintf(stderr,"%s: dropping huge packet (length %d)\n",__progname,q->bodylen); free(q); return; } hdr[0] = PKT_IP; hdr[1] = l >> 8; hdr[2] = l & 0xff; hdr[3] = REACH_INFINITY; hdr[4] = q->dstid; switch (q->dst.af) { default: abort(); break; case AF_INET: hdr[5] = IPTYPE_v4; break; case AF_INET6: hdr[5] = IPTYPE_v6; break; } VERB("IP packet: hopcount=%d id=%d dsttype=%d len=%d\n",hdr[3],hdr[4],hdr[5],q->bodylen); VERB("IP packet: addrtype %d, data %02x%02x...%02x%02x\n",hdr[5], ((unsigned char *)q->body)[0], ((unsigned char *)q->body)[1], ((unsigned char *)q->body)[q->bodylen-2], ((unsigned char *)q->body)[q->bodylen-1]); send_encrypted(p,&hdr[0],6); send_encrypted(p,q->body,q->bodylen); send_postpacket(p); free(q); } static void route_pq(PQ *q) { IPMAP *m; PEER *p; do <"fail"> { for (m=ipmaps;m;m=m->link) { if (ipaddr_match(&q->dst,&m->a)) { if ( (reachtbl[m->id] < 0) || (reachdist[m->id] >= REACH_INFINITY) ) break <"fail">; p = peertbl[reachtbl[m->id]]; if (! p) break <"fail">; q->dstid = m->id; q->link = 0; *p->pqt = q; p->pqt = &q->link; } } return; } while (0); free(q); } static int send_output(void *arg __attribute__((__unused__))) { PEER *p; int i; if (! want_send) return(BLOCK_NIL); want_send = 0; for (i=npeers-1;i>=0;i--) { p = peers[i]; if (oq_qlen(&p->oq) > QLIMIT) continue; if (p->want & WANT_REACH) send_reach(p); if (p->want & WANT_PUBLIC) send_public(p); if (p->want & WANT_IPMAP) send_ipmap(p); p->want = 0; } while (pktq) { PQ *q; q = pktq; pktq = q->link; route_pq(q); } pktqt = &pktq; for (i=npeers-1;i>=0;i--) { p = peers[i]; while ((oq_qlen(&p->oq) < QLIMIT) && p->pq) send_packet(p); } return(BLOCK_LOOP); } static void listen_accept(void *lv) { LISTEN *l; int new; struct sockaddr_storage ss; socklen_t sslen; PEER *p; CRYPTINIT *ci; l = lv; sslen = sizeof(ss); new = accept(l->fd,(void *)&ss,&sslen); if (new < 0) { fprintf(stderr,"%s: accept on %s: %s\n",__progname,l->txt,strerror(errno)); return; } nonblocking_fd(new); VERB("accepted on %s\n",l->txt); p = new_peer(); p->fd = new; p->id = PL_NOID; p->remsa = malloc(sslen); bcopy(&ss,p->remsa,sslen); p->remsalen = sslen; asprintf(&p->txt,"(accept from %s)",l->txt); if (peer_get_sockaddrs(p) < 0) { VERB("peer_get_sockaddrs failed\n"); close_peer(p); return; } VERB("peer_get_sockaddrs worked: %s\n",p->txt); ci = malloc(sizeof(CRYPTINIT)); ci->myrole = CR_PASSIVE; ci->p = p; peer_start_crypto(ci); } static void start_accepts(void) { LISTEN *l; for (l=listens;l;l=l->link) { l->id = add_poll_fd(l->fd,&rwtest_always,&rwtest_never,&listen_accept,0,l); } } static void expire_uplinks(void) { time_t now; UPLINK *u; UPLINK **up; int changed; time(&now); changed = 0; up = &uplinks; while ((u = *up)) { if (u->stamp && (u->stamp+UPLINK_EXPIRE < now)) { if (u->flags & (UF_LOCALCONF|UF_MYSELF)) { u->stamp = 0; } else { *up = u->link; free(u->sa); free(u->txt); if (u->peer) { if (u->peer->up != u) abort(); u->peer->up = 0; u->peer = 0; } free(u); changed = 1; continue; } } up = &u->link; } if (changed) set_flood(WANT_PUBLIC); } static void expire_reaches(void) { time_t now; int changed; int i; time(&now); changed = 0; for (i=256-1;i>=0;i--) { if (reachtbl[i] < 0) continue; if (reachdist[i] >= REACH_INFINITY) { if (reachtime[i] + REACH_KILL < now) { reachtbl[i] = -1; changed = 1; } continue; } if (reachtime[i] + REACH_EXPIRE < now) { reachdist[i] = REACH_INFINITY; changed = 1; } } if (changed) set_flood(WANT_REACH); } static void init_ipmap(void) { int i; IPMAP *m; ipmaps = 0; for (i=ncipaddrs-1;i>=0;i--) { m = new_ipmap(cid,cipaddrs[i]); m->link = ipmaps; ipmaps = m; } } static void periodic_tick(void *pdv) { PERIODIC *pd; struct timersock_event ev; pd = pdv; while (read(pd->fd,&ev,sizeof(ev)) > 0) ; (*pd->cb)(); } static PERIODIC *start_periodic( unsigned int sec_first, unsigned int usec_first, unsigned int sec_recurring, unsigned int usec_recurring, void (*callback)(void) ) { PERIODIC *pd; struct itimerval itv; pd = malloc(sizeof(PERIODIC)); pd->fd = socket(AF_TIMER,SOCK_STREAM,0); itv.it_value.tv_sec = sec_first; itv.it_value.tv_usec = usec_first; itv.it_interval.tv_sec = sec_recurring; itv.it_interval.tv_usec = usec_recurring; write(pd->fd,&itv,sizeof(itv)); nonblocking_fd(pd->fd); pd->id = add_poll_fd(pd->fd,&rwtest_always,&rwtest_never,&periodic_tick,0,pd); pd->cb = callback; return(pd); } static void gen_reach(void) { set_flood(WANT_REACH); } static void gen_public(void) { set_flood(WANT_PUBLIC); } static void gen_ipmap(void) { set_flood(WANT_IPMAP); } static int logflush(void *arg __attribute__((__unused__))) { fflush(logf); return(BLOCK_NIL); } static void init_uplinks(void) { LISTEN *l; for (l=listens;l;l=l->link) new_uplink(l->sa,l->salen)->flags |= UF_MYSELF; } static void startup(void) { int i; init_polling(); if (logf) logflushid = add_block_fn(logflush,0); setuplog(); pd_reconnect = start_periodic(1,0,TRY_CONNECT_SEC,TRY_CONNECT_USEC,&try_reconnect); peercleanup_id = add_block_fn(&peercleanup,0); peercleanup_needed = 0; open_random(); open_tun(); start_accepts(); pktq = 0; pktqt = &pktq; tunid = add_poll_fd(tunfd,&rwtest_always,&rwtest_never,&tun_rd,0,0); want_send = 0; sendid = add_block_fn(&send_output,0); for (i=256-1;i>=0;i--) { peertbl[i] = 0; reachtbl[i] = -1; } pd_expire_reach = start_periodic(1,0,REACH_EXPIRE_SEC,REACH_EXPIRE_USEC,&expire_reaches); pd_expire_uplink = ignore_public ? 0 : start_periodic(1,0,UPLINK_EXPIRE_SEC,UPLINK_EXPIRE_USEC,&expire_uplinks); pd_gen_reach = start_periodic(1,0,REACH_GEN_SEC,REACH_GEN_USEC,&gen_reach); pd_gen_public = start_periodic(1,0,PUBLIC_GEN_SEC,PUBLIC_GEN_USEC,&gen_public); pd_gen_ipmap = start_periodic(1,0,IPMAP_GEN_SEC,IPMAP_GEN_USEC,&gen_ipmap); init_ipmap(); init_uplinks(); } int main(int, char **); int main(int ac, char **av) { init(); handleargs(ac,av); set_defaults(); startup(); while (1) { pre_poll(); if (do_poll() < 0) { if (errno == EINTR) continue; fprintf(stderr,"%s: poll: %s",__progname,strerror(errno)); exit(1); } post_poll(); } return(0); }