/* This file is in the public domain. */ static const char *RND_DEV = "/dev/urandom"; #define REKEY_N_BASE 0x3fff0000 #define QLIMIT 65536 #define UPLINK_EXPIRE (60*15) #define REACH_SECS_PER_DIST 64 #define REACH_KILL (60*10) #define TRY_CONNECT_MIN 55 #define TRY_CONNECT_MAX 60 #define UPLINK_EXPIRE_MIN 55 #define UPLINK_EXPIRE_MAX 60 #define REACH_EXPIRE_MIN 15 #define REACH_EXPIRE_MAX 30 #define REACH_GEN_MIN 55 #define REACH_GEN_MAX 60 #define PUBLIC_GEN_MIN 270 #define PUBLIC_GEN_MAX 300 #define IPMAP_GEN_MIN 80000 #define IPMAP_GEN_MAX 86400 #define IPMAP_EXPIRE (7*86400) #define ROUTE_GEN_MIN 80000 #define ROUTE_GEN_MAX 86400 #define ROUTE_EXPIRE (7*86400) #define LISTEN_RETRY_MIN 20 #define LISTEN_RETRY_MAX 30 #include #include #include #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 route ROUTE; typedef struct periodic PERIODIC; typedef struct pendlisten PENDLISTEN; typedef struct aia AIA; typedef struct admin ADMIN; typedef struct verbosity VERBOSITY; typedef struct vfp VFP; typedef struct vbit VBIT; typedef struct vops VOPS; typedef struct vpriv_file VPRIV_FILE; typedef struct ir IR; /* * Private data for file-output verbosity. */ struct vpriv_file { VPRIV_FILE *flink; VPRIV_FILE *blink; VERBOSITY *v; char *fn; FILE *f; } ; /* * Private data for a FILE * returned by verb_fopen(). */ struct vfp { unsigned int mask; char *stamp; int stamplen; char *s; int l; } ; /* * A consumer of verbosity. There is one of these for each * verbose-output output stream. */ struct verbosity { VERBOSITY *flink; VERBOSITY *blink; unsigned int mask; const VOPS *ops; void *priv; } ; /* * Method dispatch vector for VERBOSITYs. First arg to most of these * is the priv member of the VERBOSITY in question; the exception is * the find member, which is called to find or create a relevant * VERBOSITY, so of course it doesn't have a generic private-data * input. */ struct vops { VERBOSITY *(*find)(void (*)(const char *, ...), ...); void (*line)(VERBOSITY *, const char *, int); void (*maskchanged)(VERBOSITY *); } ; #define VOPS_INIT(name) { \ &vop_find_##name, \ &vop_line_##name, \ &vop_maskchanged_##name } #define VOP_FIND_ARGS void (*err)(const char *, ...) __attribute__((__unused__)), ... #define VOP_VA_START_ARG err /* * Verbosity bits. There's one of these per type of verbosity. One * also exists for each of certain cases where a single name in the * interface corresponds to multiple bits. */ struct vbit { int alias; unsigned int bit; const char *name; const char *help; int namelen; } ; /* * An administrative control point. This corresponds to a connection * accepted on an accept point resulting from a control line. f is * set only during command processing; it is the FILE * for output in * response to the command. */ struct admin { int fd; OQ oq; char *txt; int id; char *ilb; int ila; int iln; char ilprev; FILE *f; VERBOSITY *verb; } ; /* * This is an address returned by getaddrinfo(). We have our own * structure for these because we want to keep a list of them which we * gradually winnow, and struct addrinfo has no fields available for * us to scribble on. (With the current implementation, we could get * away with using ai_flags for this, but the API does not promise * this.) */ struct aia { AIA *link; struct addrinfo *ai; char *(*gettext)(AIA *); void (*postbind)(AIA *); union { struct { int mode; } local; } ; const char *errmsg; } ; /* * This represents a pending listen, one that was declared with `retry' * but which has not yet succeeded in bind()ing and thus turning into * an ordinary LISTEN. ai is the list of addresses returned by * getaddrinfo (normally just one); flags is the flags on the listen * line, in their LLF_* form. */ struct pendlisten { struct addrinfo *ai0; AIA *addrs; unsigned int flags; void (*onaccept)(LISTEN *, int); PERIODIC *pd; } ; /* * 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, with argument arg. * minsec and maxsec are the minmium and maximum times between events, * in seconds; intervals are randomly distributed between these * limits. ramp is used to start of with frequent calls, with the * interval ramping up to the ones specified; see periodic_rearm(). */ struct periodic { int fd; int id; void (*cb)(void *); void *arg; int minsec; int maxsec; int ramp; } ; /* * 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 the bind() arguments used. * LF_WILDCARD means the *, *4, or *6 syntax was used, and we thus * should never advertise it; LF_PUBLIC means that it was declared, or * defaulted, to be advertised. */ struct listen { LISTEN *link; struct sockaddr *sa; int salen; unsigned int flags; #define LF_WILDCARD 0x00000001 #define LF_PUBLIC 0x00000002 char *txt; int fd; int id; void (*onaccept)(LISTEN *, int); } ; /* * Flags used when parsing listen-on lines in the config file. */ #define LLF_RETRY 0x00000001 #define LLF_PUBLIC 0x00000002 #define LLF_PRIVATE 0x00000004 #define LLF_WILDCARD 0x00000008 /* * 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 { char *txt; char *tag; 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 #define WANT_ROUTE 0x00000008 int peerid; PQ *pq; PQ **pqt; PERIODIC *gen_reach; PERIODIC *gen_public; PERIODIC *gen_ipmap; PERIODIC *gen_route; unsigned char *lastreach_data; int lastreach_alloc; int lastreach_len; time_t lastreach_stamp; } ; /* * 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. Our own entries are distinguished by id * being set to cid; stamp is irrelevant for such entries. */ struct ipmap { IPMAP *link; IPADDR a; unsigned char id; time_t stamp; } ; /* * A "route this netblock via this ID" entry. Our own entries are * distinguished by id being set to cid; stamp is irrelevant for such * entries. An ID with no netblocks is indicated by an entry with a * width of 255; block is meaningless for such entries. */ struct route { ROUTE *link; IPADDR block; int width; unsigned char id; time_t stamp; } ; /* * An installable route. We compute a list of these things based on * the database of route-via-ID entries (ROUTEs) and ID reachability * info; the resulting list drives creation and deletion of kernel * routes. */ struct ir { IR *link; IPADDR block; int width; unsigned char via; } ; /* * A boolean indicating whether we should auto-background on startup. */ static int autobg = 0; /* * 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; /* * Count of PEERs generated. Used for tag string creation. */ static unsigned int peerserial = 0; /* * PERIODICs for attempts to (re)connect to uplinks and for expiration * of learned uplinks. */ static PERIODIC *pd_reconnect; static PERIODIC *pd_expire_uplink; /* * 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 info. This is indexed by the member ID * we want to reach. reachvia is the member ID we can reach it via, * or -1 if we think it's unreachable; reachdist is the hop count to * the target (meaningless for unreachable targets). * * Note that reachabilities that have timed out are left marked * reachable but with infinite distance for a little while, to help * reduce counting to infinity. (Split horizon with poisoned reverse * deals with most but not all cases; broadcasting unreachables helps * with most of the rest.) This is done by having recompute_reach set * reachdist to infinity rather than reachvia to -1 when an ID goes * unreachable. When it does this, it also sets reachdrop to the time * at which it should be switched to reachvia being set to -1; * recompute_reach does this when appropriate. */ static int reachvia[256]; static int reachdist[256]; static time_t reachdrop[256]; /* * State for expiring reachability info. This is somewhat like a * PERIODIC, but the time isn't chosen randomly from a range; rather, * recompute_reach sets it, on each call, to the next time which could * be useful. Thus, provided we get reachability data often enough, * the only overhead is resetting the timer each time we do. */ static int reach_expire_fd; static int reach_expire_id; /* * 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; /* * The route-via table. This is the data maintained based on any route * lines of our own and any ROUTE packets received; the routes * installed in the kernel are not these, though they are derived * fromk these. */ static ROUTE *routes; /* * Flags applying to handling the route-via table. */ static unsigned int routeflags; #define RF_LISTEN 0x00000001 #define RF_IGNORE 0x00000002 #define RF_INSTALL 0x00000004 #define RF_NOINSTALL 0x00000008 /* * When a change occurs to routes or the ID reachability data, we run * do_reroute as a block function. This is the ID for that. (When * there is no do_reroute run pending, this is PL_NOID.) */ static int reroute_id; /* * The routes installed in the kernel. */ static IR *inkernel; /* * Variables in which we accumulate output packets for PKTDATA * verbosity. These are not used unless PKTDATA verbosity is enabled. */ static unsigned char *opktbuf; static int opktalloc; static int opktlen; /* * Variables for verbose output. * * verbose is bits indicating what, if any, verbosity we're doing. * This is the joint OR of the mask fields of all VERBOSITYs. * verbosities is the list of VERBOSITYs themselves. */ static unsigned int vmask; static VERBOSITY *verbosities; static VBIT vbits[] = { #define VERB_TIMER 0x00000001 { 0, VERB_TIMER, "timer", "AF_TIMER sockets", 0 }, #define VERB_LISTEN 0x00000002 { 0, VERB_LISTEN, "listen", "Listening point setup" }, #define VERB_IO 0x00000004 { 0, VERB_IO, "io", "reading/writing data" }, #define VERB_CRYPTO 0x00000008 { 0, VERB_CRYPTO, "crypto", "initial crypto exchange" }, #define VERB_REKEY 0x00000010 { 0, VERB_REKEY, "rekey", "crypto rekeying" }, #define VERB_PUBLIC 0x00000020 { 0, VERB_PUBLIC, "public", "PUBLIC packet processing" }, #define VERB_IP 0x00000040 { 0, VERB_IP, "ip", "IP packet processing" }, #define VERB_REACH 0x00000080 { 0, VERB_REACH, "reach", "REACH packet processing" }, #define VERB_REACHCHG 0x00000100 { 0, VERB_REACHCHG, "reachchg", "reachability changes" }, #define VERB_IPMAP 0x00000200 { 0, VERB_IPMAP, "ipmap", "IPMAP packet processing" }, #define VERB_IAM 0x00000400 { 0, VERB_IAM, "iam", "IAM packet processing" }, #define VERB_CONN 0x00000800 { 0, VERB_CONN, "conn", "connection setup/teardown" }, #define VERB_ROUTING 0x00001000 { 0, VERB_ROUTING, "routing", "IP packet routing" }, #define VERB_CONFIG 0x00002000 { 0, VERB_CONFIG, "config", "configuration processing" }, #define VERB_DUP 0x00004000 { 0, VERB_DUP, "dup", "DUP packet processing" }, #define VERB_ROUTE 0x00008000 { 0, VERB_ROUTE, "route", "ROUTE packet processing" }, #define VERB_KROUTE 0x00008000 { 0, VERB_KROUTE, "kroute", "kernel route manipulation" }, #define VERB_PKTDATA 0x00010000 { 0, VERB_PKTDATA, "pktdata", "cleartext packet data" }, { 1, 0x0001ffff, "all", "all verbosity" }, { 1, VERB_PUBLIC|VERB_REACH|VERB_IPMAP|VERB_IAM|VERB_DUP|VERB_ROUTE, "overhead", "overhead packet processing" }, { 0, 0, 0 } }; /* * The stdout VERBOSITY, or nil if none has been set up yet. */ static VERBOSITY *v_stdout = 0; /* * The list of VPRIV_FILEs. */ static VPRIV_FILE *v_files = 0; /* * Data for a simple PRNG, used for data that does not need to be * cryptographically strong (such as randomizing intervals between * events for which we want to avoid traffic spikes). The fd and id * are used to set wrnudge, to give it a small nudge every few hours * in an attempt to break any possible fluke synchronization. */ #define WRPOOLSIZE 64 static unsigned int wrpool[WRPOOLSIZE]; static unsigned int wrhand; static unsigned int wrfd; static unsigned int wrid; static unsigned int wrnudge; #define Cisspace(c) isspace((unsigned char)(c)) #define Cisalpha(c) isalpha((unsigned char)(c)) #define V(kind,fmt,args...) do { if (vmask & VERB_##kind) verb(VERB_##kind, fmt , ## args); } while (0) #define VS(kinds,fmt,args...) do { if (vmask & (kinds)) verb((kinds), fmt , ## args); } while (0) static void *dequal(const volatile void *arg) { return( ((char *)0) + (((const volatile char *)arg) - ((const volatile char *)0)) ); } static void init_verbose(void) { vmask = 0; verbosities = 0; } static void verb_line(unsigned int mask, const char *s, int l) { VERBOSITY *v; for (v=verbosities;v;v=v->flink) { if (v->mask & mask) (*v->ops->line)(v,s,l); } } static void verb_stamp_prefix(FILE *f, const struct tm *tm, unsigned int sf) { fprintf( f, "%04d-%02d-%02d %02d:%02d:%02d.%02u ", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, sf ); } static void verb(unsigned int, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void verb(unsigned int mask, const char *fmt, ...) { va_list ap; struct timeval nowtv; time_t nowtt; struct tm nowtm_; struct tm *nowtm; char *s; int l; FILE *f; if (! (vmask & mask)) return; gettimeofday(&nowtv,0); nowtt = nowtv.tv_sec; nowtm = localtime_r(&nowtt,&nowtm_); f = open_accum(&s,&l); verb_stamp_prefix(f,nowtm,nowtv.tv_usec/10000); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); verb_line(mask,s,l); free(s); } static int vfp_w(void *pv, const char *buf, int len) { VFP *p; int i; int n; char *sp; int nl; p = pv; nl = (p->l < 1) || (p->s[p->l-1] == '\n'); n = nl ? 1 : 0; for (i=len-2;i>=0;i--) if (buf[i] == '\n') n ++; n = len + (n * p->stamplen); p->s = realloc(p->s,p->l+n+1); sp = p->s + p->l; for (i=0;istamp,sp,p->stamplen); sp += p->stamplen; } nl = ((*sp++ = buf[i]) == '\n'); } if (sp != p->s+p->l+n) abort(); p->l += n; return(len); } static int vfp_c(void *pv) { VFP *p; int i; int i0; p = pv; i0 = -1; for (i=0;il;i++) { if (i0 < 0) i0 = i; if (p->s[i] == '\n') { verb_line(p->mask,p->s+i0,i-i0); i0 = -1; } } if (i0 >= 0) verb_line(p->mask,p->s+i0,i-i0); free(p->s); free(p->stamp); free(p); return(0); } static FILE *verb_fopen(unsigned int mask) { VFP *p; FILE *f; struct timeval nowtv; time_t nowtt; struct tm nowtm_; struct tm *nowtm; if (! (vmask & mask)) return(0); gettimeofday(&nowtv,0); nowtt = nowtv.tv_sec; nowtm = localtime_r(&nowtt,&nowtm_); p = malloc(sizeof(VFP)); p->mask = mask; f = open_accum(&p->stamp,&p->stamplen); verb_stamp_prefix(f,nowtm,nowtv.tv_usec/10000); fclose(f); p->s = malloc(1); p->l = 0; return(funopen(p,0,&vfp_w,0,&vfp_c)); } static void crack_verbosity( const char *arg, unsigned int *setp, unsigned int *clrp, const char **trailp, void (*err)(const char *, ...) ) { unsigned int set; unsigned int clr; int neg; int i; const char *ap; int l; if (! vbits[0].namelen) { for (i=0;vbits[i].bit;i++) vbits[i].namelen = strlen(vbits[i].name); } set = 0; clr = 0; ap = arg; while (1) { neg = 0; switch (*ap) { case '-': neg = 1; ap ++; break; case '+': ap ++; break; } for (l=0;ap[l]&&Cisalpha(ap[l]);l++) ; if (l == 0) break; do <"found"> { for (i=0;vbits[i].bit;i++) { if ( (vbits[i].namelen == l) && !strncasecmp(vbits[i].name,ap,l) ) { if (neg) { clr |= vbits[i].bit; set &= ~vbits[i].bit; } else { set |= vbits[i].bit; clr &= ~vbits[i].bit; } break <"found">; } } (*err)("unrecognized verbosity keyword `%.*s'",l,ap); abort(); } while (0); ap += l; switch (*ap) { case ',': case ' ': ap ++; break; } } *setp = set; *clrp = clr; *trailp = ap; } static VERBOSITY *new_verbosity(const VOPS *ops) { VERBOSITY *v; v = malloc(sizeof(VERBOSITY)); v->flink = verbosities; v->blink = 0; verbosities = v; if (v->flink) v->flink->blink = v; v->mask = 0; v->ops = ops; v->priv = 0; return(v); } static void drop_verbosity(VERBOSITY *v) { if (v->flink) v->flink->blink = v->blink; if (v->blink) v->blink->flink = v->flink; else verbosities = v->flink; free(v); } static void recompute_vmask(void) { VERBOSITY *v; vmask = 0; for (v=verbosities;v;v=v->flink) vmask |= v->mask; } static VOPS vops_stdout; /* forward */ static VERBOSITY *vop_find_stdout(VOP_FIND_ARGS) { if (! v_stdout) v_stdout = new_verbosity(&vops_stdout); return(v_stdout); } static void vop_line_stdout(VERBOSITY *v __attribute__((__unused__)), const char *buf, int len) { fwrite(buf,1,len,stdout); putchar('\n'); fflush(stdout); } static void vop_maskchanged_stdout(VERBOSITY *v) { if (v->mask == 0) drop_verbosity(v); } static VOPS vops_stdout = VOPS_INIT(stdout); static VOPS vops_file; /* forward */ static VERBOSITY *vop_find_file(VOP_FIND_ARGS) { va_list ap; const char *fn; VPRIV_FILE *p; VERBOSITY *v; FILE *f; va_start(ap,VOP_VA_START_ARG); fn = va_arg(ap,const char *); va_end(ap); for (p=v_files;p;p=p->flink) { if (! strcmp(p->fn,fn)) { return(p->v); } } f = fopen(fn,"w"); if (f == 0) (*err)("%s: open: %s",fn,strerror(errno)); p = malloc(sizeof(VPRIV_FILE)); p->flink = v_files; p->blink = 0; v_files = p; if (p->flink) p->flink->blink = p; v = new_verbosity(&vops_file); p->v = v; p->fn = strdup(fn); p->f = f; v->priv = p; return(v); } static void vop_line_file(VERBOSITY *v, const char *buf, int len) { VPRIV_FILE *p; p = v->priv; fwrite(buf,1,len,p->f); putc('\n',p->f); fflush(p->f); } static void vop_maskchanged_file(VERBOSITY *v) { VPRIV_FILE *p; if (v->mask) return; p = v->priv; fclose(p->f); free(p->fn); drop_verbosity(v); if (p->flink) p->flink->blink = p->blink; if (p->blink) p->blink->flink = p->flink; else v_files = p->flink; } static VOPS vops_file = VOPS_INIT(file); static VOPS vops_admin; /* forward */ static VERBOSITY *vop_find_admin(VOP_FIND_ARGS) { va_list ap; VERBOSITY *v; ADMIN *a; va_start(ap,VOP_VA_START_ARG); a = va_arg(ap,ADMIN *); va_end(ap); if (a->verb) return(a->verb); v = new_verbosity(&vops_admin); a->verb = v; v->priv = a; return(v); } static void vop_line_admin(VERBOSITY *v, const char *buf, int len) { ADMIN *a; a = v->priv; oq_queue_copy(&a->oq,buf,len); oq_queue_point(&a->oq,"\r\n",2); } static void vop_maskchanged_admin(VERBOSITY *v) { ADMIN *a; if (v->mask) return; a = v->priv; a->verb = 0; drop_verbosity(v); } static VOPS vops_admin = VOPS_INIT(admin); static void set_verbose(const char *arg) { unsigned int set; unsigned int clr; const char *trail; VERBOSITY *v; auto void err(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto void err(const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: ",__progname); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); exit(1); } if (! strcmp(arg,"?")) { int i; for (i=0;vbits[i].bit;i++) { fprintf(stderr,"%-12s%s\n",vbits[i].name,vbits[i].help); } exit(0); } crack_verbosity(arg,&set,&clr,&trail,&err); if (! *trail) { v = (*vops_stdout.find)(&err); } else if (*trail == '=') { v = (*vops_file.find)(&err,trail+1); } else { err("trailing junk `%s' on verbosity spec",trail); } v->mask = (v->mask & ~clr) | set; (*v->ops->maskchanged)(v); recompute_vmask(); } 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) return; 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 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 nonblocking_fd(int fd) { fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); } static void random_data(void *buf, int len) { unsigned char *bp; int n; bp = buf; while (len > 0) { n = read(rndfd,bp,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); } bp += n; len -= n; } } static unsigned int weak_random(void) { if (wrnudge) { unsigned char c; wrnudge = 0; random_data(&c,1); wrpool[wrhand] += c; } if (wrhand == 0) { wrpool[WRPOOLSIZE-1] += wrpool[0]; wrhand = WRPOOLSIZE - 1; } else { wrpool[wrhand-1] += wrpool[wrhand]; wrhand --; } return(wrpool[wrhand]>>1); } static void periodic_rearm(PERIODIC *pd) { struct itimerval itv; unsigned int us; if (pd->ramp) { itv.it_value.tv_sec = pd->ramp; itv.it_value.tv_usec = weak_random() % 1000000; pd->ramp <<= 1; if (pd->ramp >= pd->maxsec) pd->ramp = 0; } else { us = weak_random() % ((pd->maxsec - pd->minsec) * 1000000); itv.it_value.tv_sec = pd->minsec + (us / 1000000); itv.it_value.tv_usec = us % 1000000; } itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(pd->fd,&itv,sizeof(itv)); } static void periodic_tick(void *pdv) { PERIODIC *pd; struct timersock_event ev; pd = pdv; while (read(pd->fd,&ev,sizeof(ev)) > 0) ; periodic_rearm(pd); (*pd->cb)(pd->arg); } static void kill_periodic(PERIODIC *pd) { remove_poll_id(pd->id); close(pd->fd); free(pd); } static PERIODIC *start_periodic( unsigned int sec_min, unsigned int sec_max, void (*cb)(void *), void *cbarg ) { PERIODIC *pd; pd = malloc(sizeof(PERIODIC)); pd->fd = socket(AF_TIMER,SOCK_STREAM,0); nonblocking_fd(pd->fd); pd->id = add_poll_fd(pd->fd,&rwtest_always,&rwtest_never,&periodic_tick,0,pd); pd->minsec = sec_min; pd->maxsec = sec_max; pd->cb = cb; pd->arg = cbarg; pd->ramp = 1; V(TIMER,"AF_TIMER %d start_periodic %d..%d %p(%p)",pd->fd,sec_min,sec_max,(void *)cb,cbarg); periodic_rearm(pd); return(pd); } static int do_bind(int s, const struct sockaddr *sa, socklen_t salen) { int e; const struct sockaddr_un *sun; struct stat stb; char *t; int l; if (bind(s,sa,salen) >= 0) return(0); e = errno; if ((errno != EADDRINUSE) || (sa->sa_family != AF_LOCAL)) return(-1); sun = (const void *) sa; l = salen - (sizeof(*sun) - sizeof(sun->sun_path)); t = malloc(l+1); bcopy(&sun->sun_path[0],t,l); t[l] = '\0'; if ( (stat(t,&stb) < 0) || ((stb.st_mode & S_IFMT) != S_IFSOCK) || (unlink(t) < 0) ) { free(t); errno = e; return(-1); } free(t); return(bind(s,sa,salen)); } static LISTEN *try_setup_listen( AIA **addrp, unsigned int flags, const char *textarg, void (*onaccept)(LISTEN *, int) ) { AIA *a; AIA **ap; int fd; char *txt; LISTEN *l; #define SNIPOUT() do { *ap = a->link; free(a); } while (0) #define NEXTADDR() do { ap = &a->link; } while (0) V(LISTEN,"try_setup_listen starting"); ap = addrp; while ((a = *ap)) { fd = -1; txt = (*a->gettext)(a); if (! txt) { if (textarg) { V(LISTEN,"try_setup_listen failing: can't get text"); config_err("can't get text [for %s]: %s",textarg,a->errmsg); } V(LISTEN,"try_setup_listen: can't get text form (af %d - %s), dropping address",a->ai->ai_addr->sa_family,a->errmsg); SNIPOUT(); continue; } fd = socket(a->ai->ai_family,a->ai->ai_socktype,a->ai->ai_protocol); if (fd < 0) continue; if (do_bind(fd,a->ai->ai_addr,a->ai->ai_addrlen) < 0) { if (textarg) { V(LISTEN,"try_setup_listen failing: bind failed"); config_err("bind %s [for %s]: %s",txt,textarg,strerror(errno)); } free(txt); close(fd); NEXTADDR(); continue; } (*a->postbind)(a); if (listen(fd,10) < 0) { if (textarg) { V(LISTEN,"try_setup_listen failing: listen failed"); config_err("listen %s [for %s]: %s",txt,textarg,strerror(errno)); } free(txt); close(fd); NEXTADDR(); continue; } nonblocking_fd(fd); l = malloc(sizeof(LISTEN)); l->sa = malloc(a->ai->ai_addrlen); bcopy(a->ai->ai_addr,l->sa,a->ai->ai_addrlen); l->salen = a->ai->ai_addrlen; if (flags & LLF_WILDCARD) flags = (flags & ~LLF_PUBLIC) | LLF_PRIVATE; if ((flags & (LLF_PRIVATE|LLF_PUBLIC)) == 0) { switch (l->sa->sa_family) { case AF_INET: { unsigned int a; a = ntohl(((const struct sockaddr_in *)l->sa)->sin_addr.s_addr); flags |= ( ((a & 0xff000000) == 0x0a000000) || ((a & 0xfff00000) == 0xac100000) || ((a & 0xffff0000) == 0xc0a80000) ) ? LLF_PRIVATE : LLF_PUBLIC; } break; case AF_INET6: { const struct in6_addr *a; a = &((const struct sockaddr_in6 *)l->sa)->sin6_addr; flags |= ( IN6_IS_ADDR_LINKLOCAL(a) || IN6_IS_ADDR_SITELOCAL(a) ) ? LLF_PRIVATE : LLF_PUBLIC; } break; default: flags |= LLF_PRIVATE; break; } } l->flags = ((flags & LLF_WILDCARD) ? LF_WILDCARD : 0) | ((flags & LLF_PUBLIC) ? LF_PUBLIC : 0); l->txt = txt; l->fd = fd; l->onaccept = onaccept; l->link = listens; listens = l; V(LISTEN,"try_setup_listen worked (%s)",txt); SNIPOUT(); return(l); } V(LISTEN,"try_setup_listen failing: out of addresses"); return(0); #undef SNIPOUT #undef NEXTADDR } static void print_sa_txt(const struct sockaddr *sa, int salen, FILE *f) { switch (sa->sa_family) { default: abort(); break; case AF_LOCAL: fprintf( f, "%.*s", (int) (sa->sa_len - (sizeof(struct sockaddr_un) - sizeof(((struct sockaddr_un *)0)->sun_path))), &((const struct sockaddr_un *)sa)->sun_path[0] ); break; case AF_INET: case AF_INET6: { 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) { fprintf(f,"[getnameinfo (af %d): ",sa->sa_family); if (e == EAI_SYSTEM) { fprintf(f," %s]",strerror(errno)); } else { fprintf(f," %s]",gai_strerror(e)); } } else { fprintf(f,"%s/%s",&hstr[0],&sstr[0]); } } break; } } static UPLINK *new_uplink(const struct sockaddr *sa, int salen) { FILE *f; UPLINK *u; u = malloc(sizeof(UPLINK)); u->sa = malloc(salen); bcopy(sa,u->sa,salen); u->salen = salen; f = open_accum(&u->txt,0); print_sa_txt(sa,salen,f); fclose(f); u->flags = 0; u->stamp = 0; u->peer = 0; u->link = uplinks; uplinks = u; return(u); } static void raise_fdlimit(void) { struct rlimit rl; getrlimit(RLIMIT_NOFILE,&rl); rl.rlim_cur = rl.rlim_max; setrlimit(RLIMIT_NOFILE,&rl); } static void init(void) { init_polling(); init_verbose(); ck = 0; ncipaddrs = 0; cipaddrs = 0; ignore_public = -1; listens = 0; uplinks = 0; npeers = 0; apeers = 0; peers = 0; tunpath = 0; raise_fdlimit(); routes = 0; reroute_id = 0; routeflags = RF_LISTEN | RF_INSTALL; inkernel = 0; opktbuf = 0; opktalloc = 0; opktlen = 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 void gen_tag(PEER *p) { int i; peerserial ++; asprintf(&p->tag,"%d",peerserial); for (i=0;p->tag[i];i++) { switch (p->tag[i]) { case '0': p->tag[i] = 'f'; break; case '1': p->tag[i] = 's'; break; case '2': p->tag[i] = 'm'; break; case '3': p->tag[i] = 'k'; break; case '4': p->tag[i] = 'g'; break; case '5': p->tag[i] = 't'; break; case '6': p->tag[i] = 'v'; break; case '7': p->tag[i] = 'j'; break; case '8': p->tag[i] = 'b'; break; case '9': p->tag[i] = 'r'; break; } } } static PEER *new_peer(void) { PEER *p; if (npeers >= apeers) peers = realloc(peers,(apeers=npeers+1)*sizeof(PEER *)); p = malloc(sizeof(PEER)); gen_tag(p); 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->gen_reach = 0; p->gen_public = 0; p->gen_ipmap = 0; p->gen_route = 0; p->lastreach_data = 0; p->lastreach_alloc = 0; p->lastreach_len = 0; peers[npeers++] = p; return(p); } static int ir_cmp(IR *a, IR *b) { int i; if (a->width < b->width) return(-1); if (a->width > b->width) return(1); if (a->block.af < b->block.af) return(-1); if (a->block.af > b->block.af) return(1); switch (a->block.af) { default: abort(); break; case AF_INET: if (a->block.v4.s_addr < b->block.v4.s_addr) return(-1); if (a->block.v4.s_addr > b->block.v4.s_addr) return(1); return(0); break; case AF_INET6: for (i=16-1;i>=0;i--) { if (a->block.v6.s6_addr[i] < b->block.v6.s6_addr[i]) return(-1); if (a->block.v6.s6_addr[i] > b->block.v6.s6_addr[i]) return(1); } return(0); break; } } static IR *sort_ir(IR *list) { IR *a; IR *b; IR *t; IR **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_ir(a); b = sort_ir(b); lt = &list; while (a || b) { if (!b || (a && (ir_cmp(a,b) < 0))) { t = a; a = a->link; } else { t = b; b = b ->link; } *lt = t; lt = &t->link; } *lt = 0; return(list); } 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 const char *a_ntop(const IPADDR *a, char *ob, int oblen) { switch (a->af) { default: snprintf(ob,oblen,"AF %d",a->af); return(ob); break; case AF_INET: return(inet_ntop(AF_INET,&a->v4,ob,oblen)); break; case AF_INET6: return(inet_ntop(AF_INET6,&a->v6,ob,oblen)); break; } } static void gain_route(IR *r) { char obuf[256]; if (r->width == 255) return; V(KROUTE,"installing kernel route to %s/%d via %d",a_ntop(&r->block,&obuf[0],sizeof(obuf)),r->width,r->via); } static void lose_route(IR *r) { char obuf[256]; if (r->width == 255) return; V(KROUTE,"removing kernel route to %s/%d via %d",a_ntop(&r->block,&obuf[0],sizeof(obuf)),r->width,r->via); } static void free_ir_list(IR *r) { IR *t; while (r) { t = r; r = r->link; free(t); } } static int do_reroute(void *arg __attribute__((__unused__))) { IR *ir; IR *list; ROUTE *r; IR *nir; IR *oir; remove_block_id(reroute_id); reroute_id = PL_NOID; if (routeflags & RF_NOINSTALL) abort(); list = 0; for <"route"> (r=routes;r;r=r->link) { if (r->id == cid) continue; if (r->width == 255) continue; if (reachvia[r->id] < 0) continue; if (reachdist[r->id] >= REACH_INFINITY) continue; for (ir=list;ir;ir=ir->link) { if ((ir->width == r->width) && ipaddr_match(&ir->block,&r->block)) { if (r->id < ir->via) ir->via = r->id; } continue <"route">; } ir = malloc(sizeof(IR)); ir->block = r->block; ir->width = r->width; ir->via = r->id; ir->link = list; list = ir; } list = sort_ir(list); inkernel = sort_ir(inkernel); nir = list; oir = inkernel; while (1) { if (nir) { if (oir) { int c; c = ir_cmp(nir,oir); if (c < 0) gain_route(nir); if (c > 0) lose_route(oir); if (c <= 0) nir = nir->link; if (c >= 0) oir = oir->link; } else { gain_route(nir); nir = nir->link; } } else { if (oir) { lose_route(oir); oir = oir->link; } else { break; } } } free_ir_list(inkernel); inkernel = list; return(BLOCK_LOOP); } static void set_reroute(void) { if ((reroute_id == PL_NOID) && (routeflags & RF_INSTALL)) reroute_id = add_block_fn(&do_reroute,0); } static int recompute_reach(void) { int newvia[256]; int newdist[256]; int i; int px; PEER *p; time_t now; int age; unsigned char tgt; int dist; unsigned char via; int rv; time_t next; time_t minnext; struct itimerval itv; time(&now); minnext = REACH_SECS_PER_DIST + 1; for (i=0;i<256;i++) newvia[i] = -1; newvia[cid] = cid; newdist[cid] = 0; for (px=npeers-1;px>=0;px--) { p = peers[px]; if (p->peerid < 0) continue; via = p->peerid; if (p->lastreach_len & 1) abort(); age = (now - p->lastreach_stamp) / REACH_SECS_PER_DIST; if (age < 0) age = 0; for (i=p->lastreach_len-2;i>=0;i-=2) { tgt = p->lastreach_data[i]; dist = age + p->lastreach_data[i+1] + 1; if (dist > REACH_INFINITY) dist = REACH_INFINITY; if ( (newvia[tgt] < 0) || (dist < newdist[tgt]) || ( (dist == newdist[tgt]) && (reachvia[tgt] == (int)via) ) ) { newvia[tgt] = via; newdist[tgt] = dist; if (dist < REACH_INFINITY) { next = (( (now - p->lastreach_stamp) / REACH_SECS_PER_DIST ) + 1) * REACH_SECS_PER_DIST; if (next < minnext) minnext = next; } } } } rv = 0; for (i=0;i<256;i++) { if ((newvia[i] < 0) || (newdist[i] >= REACH_INFINITY)) { if ( (reachvia[i] >= 0) && (reachdist[i] < REACH_INFINITY) ) { reachdist[i] = REACH_INFINITY; V(REACHCHG,"%d unreachable (entering holddown)",i); reachdrop[i] = now + REACH_KILL; rv = 1; } else if ( (reachvia[i] >= 0) && (reachdist[i] >= REACH_INFINITY) && (now > reachdrop[i]) ) { reachvia[i] = -1; V(REACHCHG,"%d unreachable (holddown expired)",i); rv = 1; } } else { if ( (newvia[i] != reachvia[i]) || (newdist[i] != reachdist[i]) ) { reachvia[i] = newvia[i]; reachdist[i] = newdist[i]; V(REACHCHG,"%d reachable, dist %d via %d",i,newdist[i],newvia[i]); rv = 1; } } } itv.it_value.tv_sec = minnext + 1; itv.it_value.tv_usec = weak_random() % 1000000; itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; write(reach_expire_fd,&itv,sizeof(itv)); if (rv) set_reroute(); return(rv); } static void set_flood(unsigned int bit) { int i; for (i=npeers-1;i>=0;i--) peers[i]->want |= bit; want_send = 1; } 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->tag); free(p->txt); oq_flush(&p->oq); free(p->ibuf); while (p->pq) { PQ *q; q = p->pq; p->pq = q->link; free(q); } if (p->gen_reach) kill_periodic(p->gen_reach); if (p->gen_public) kill_periodic(p->gen_public); if (p->gen_ipmap) kill_periodic(p->gen_ipmap); if (p->gen_route) kill_periodic(p->gen_route); free(p->lastreach_data); free(p); npeers --; if (i < npeers) peers[i] = peers[npeers]; } if (recompute_reach()) set_flood(WANT_REACH); return(BLOCK_LOOP); } static void close_peer(PEER *p) { UPLINK *u; V(CONN,"closing peer %s",p->tag); if ((p->peerid >= 0) && (peertbl[p->peerid] == p)) peertbl[p->peerid] = 0; if (p->id != PL_NOID) remove_poll_id(p->id); close(p->fd); p->fd = -1; for (u=uplinks;u;u=u->link) if (u->peer == p) u->peer = 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); } V(IO,"wrote %d from oq",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 wr_rd(void *arg __attribute__((__unused__))) { struct timersock_event ev; while (read(wrfd,&ev,sizeof(ev)) > 0) ; wrnudge = 1; } static void init_wrandom(void) { unsigned int r; int i; struct itimerval itv; random_data(&r,sizeof(r)); for (i=64-1;i>=0;i--) { wrpool[i] = r; r = (r * 1103515245U) + 12345U; } wrhand = 0; wrnudge = 0; for (i=1000;i>0;i--) weak_random(); wrfd = socket(AF_TIMER,SOCK_STREAM,0); nonblocking_fd(wrfd); itv.it_interval.tv_sec = 3600 * 6; itv.it_interval.tv_usec = 0; itv.it_value = itv.it_interval; write(wrfd,&itv,sizeof(itv)); wrid = add_poll_fd(wrfd,&rwtest_always,&rwtest_never,&wr_rd,0,0); V(TIMER,"AF_TIMER %d for weak random nudging",wrfd); } 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)); V(TIMER,"AF_TIMER %d for nap %u.%06u %p(%p)",s,sec,usec,(void *)done,arg); } 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 (vmask & VERB_CRYPTO) { int i; FILE *f; f = verb_fopen(VERB_CRYPTO); fprintf(f,"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]); fprintf(f,"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); V(CRYPTO,"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", 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; V(REKEY,"sending rekey for %s",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; if (vmask & VERB_PKTDATA) { if (opktlen+len > opktalloc) { opktbuf = realloc(opktbuf,opktalloc=opktlen+len); } bcopy(data,opktbuf+opktlen,len); opktlen += len; } 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 (vmask & VERB_PKTDATA) { FILE *f; int i; f = verb_fopen(VERB_PKTDATA); fprintf(f,"output packet [%d]:",opktlen); for (i=0;ibytes_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 void learn_public(const struct sockaddr *sa, int salen, time_t stamp) { UPLINK *u; if (vmask & VERB_PUBLIC) { char obuf[256]; FILE *f; f = verb_fopen(VERB_PUBLIC); fprintf(f,"learn_public: sa="); switch (sa->sa_family) { default: fprintf(f,"AF %d",sa->sa_family); break; case AF_INET: #define sin ((const struct sockaddr_in *)sa) fprintf(f,"%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) fprintf(f,"%s/%d",inet_ntop(AF_INET6,&sin6->sin6_addr,&obuf[0],sizeof(obuf)),ntohs(sin6->sin6_port)); #undef sin6 break; } fprintf(f," stamp=%lu\n",(unsigned long int)stamp); fclose(f); } for (u=uplinks;u;u=u->link) { if (addr_port_match(u->sa,sa)) { u->stamp = stamp; V(PUBLIC,"updating existing"); return; } } V(PUBLIC,"new"); new_uplink(sa,salen)->stamp = stamp; } static void my_ip_pkt(PEER *p, int addrtype, const unsigned char *data, int len) { struct sockaddr sa; struct iovec iov[2]; if (len <= 3) abort(); V(IP,"my IP packet: addrtype %d, data %02x%02x%02x%02x%02x%02x...%02x%02x", addrtype, data[0],data[1],data[2],data[3],data[4],data[5], data[len-2],data[len-1]); switch (addrtype) { default: fprintf(stderr,"%s: ignoring AF-%d packet from %s\n",__progname,addrtype,p?p->txt:"myself"); 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); iov[1].iov_len = len; 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; } V(IP,"peer_pkt_ip: hopcount=%d id=%d dsttype=%d len=%d",data[0],data[1],data[2],len-3); if (data[1] == cid) { my_ip_pkt(p,data[2],data+3,len-3); return; } else { if (data[0] == 0) return; if ( (reachvia[data[1]] < 0) || (reachdist[data[1]] >= REACH_INFINITY) ) return; nexthop = peertbl[reachvia[data[1]]]; if (! nexthop) return; if (oq_qlen(&nexthop->oq) > QLIMIT) return; if (len > 65535) abort(); V(IP,"forwarding IP from %s to %s",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 void dump_reach_body(const unsigned char *data, int len, FILE *f) { int i; for (i=1;i 1) putc(' ',f); fprintf(f,"%d:%d",data[i-1],data[i]); } if (len & 1) { if (len > 1) putc(' ',f); fprintf(f,"%d:?",data[len-1]); } } static void peer_pkt_reach(PEER *p, const unsigned char *data, int len) { if (p->peerid < 0) { V(REACH,"peer_pkt_reach: from %s, ignored (peerid < 0)",p->txt); return; } if (len % 2) { V(REACH,"peer_pkt_reach: from %s, ignored (odd length)",p->txt); return; } if (vmask & VERB_REACH) { FILE *f; f = verb_fopen(VERB_REACH); fprintf(f,"peer_pkt_reach from %s: ",p->txt); dump_reach_body(data,len,f); fclose(f); } time(&p->lastreach_stamp); if (len > p->lastreach_alloc) { free(p->lastreach_data); p->lastreach_alloc = len; p->lastreach_data = malloc(len); } bcopy(data,p->lastreach_data,len); p->lastreach_len = len; if (recompute_reach()) { V(REACH,"flooding REACH"); 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) { V(PUBLIC,"peer_pkt_public: from %s, ignored (ignore_public)",p->txt); return; } if (p->peerid < 0) { V(PUBLIC,"peer_pkt_public: from %s, ignored (peerid < 0)",p->txt); return; } V(PUBLIC,"peer_pkt_public: from %s",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 void free_ipmap_list(IPMAP *m) { IPMAP *t; while (m) { t = m; m = m->link; free(t); } } static IPMAP *replace_ipmap(unsigned char id, const IPADDR *a, IPMAP *list, time_t stamp) { IPMAP *m; for (m=list;m;m=m->link) { if (ipaddr_match(&m->a,a)) { m->id = id; m->stamp = stamp; return(list); } } m = new_ipmap(id,a); m->stamp = stamp; 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; time_t stamp; char obuf[256]; 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) { V(IPMAP,"peer_pkt_ipmap: from %s, ignored (peerid < 0)",p->txt); return; } V(IPMAP,"peer_pkt_ipmap: from %s",p->txt); old = 0; new = 0; if (0) { throwto:; close_peer(p); free_ipmap_list(new); 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; } overrun(o+4); stamp = (data[o ] * 0x01000000) + (data[o+1] * 0x00010000) + (data[o+2] * 0x00000100) + (data[o+3] * 0x00000001); o += 4; if (id == cid) { V(IPMAP,"ignoring mapping for our ID"); continue; } if (! (sawid[id>>3] & (1 << (id & 7)))) { sawid[id>>3] |= 1 << (id & 7); V(IPMAP,"saving maps for %d",id); old = save_ipmaps(id,old); } V(IPMAP,"mapping %s to %d, stamp %lu",a_ntop(&a,&obuf[0],sizeof(obuf)),id,(unsigned long int)stamp); new = replace_ipmap(id,&a,new,stamp); } old = sort_ipmaps(old); new = sort_ipmaps(new); if (vmask & VERB_IPMAP) { IPMAP *tm; FILE *f; f = verb_fopen(VERB_IPMAP); fprintf(f,"old:"); for (tm=old;tm;tm=tm->link) { fprintf(f," %s->%d",a_ntop(&tm->a,&obuf[0],sizeof(obuf)),tm->id); } fprintf(f,"\nnew:"); for (tm=new;tm;tm=tm->link) { fprintf(f," %s->%d",a_ntop(&tm->a,&obuf[0],sizeof(obuf)),tm->id); } fclose(f); } 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); V(IPMAP,"mismatch, flooding IPMAP"); set_flood(WANT_IPMAP); } while (0); V(IPMAP,"done, saving new"); free_ipmap_list(old); ipmaps = prepend_ipmaps(new,ipmaps); } static ROUTE *new_route(unsigned char id, int width, const IPADDR *a) { ROUTE *r; r = malloc(sizeof(ROUTE)); r->width = width; if (width != 255) r->block = *a; r->id = id; return(r); } static void free_route_list(ROUTE *r) { ROUTE *t; while (r) { t = r; r = r->link; free(t); } } static ROUTE *prepend_routes(ROUTE *r, ROUTE *list) { ROUTE *t; while (r) { t = r; r = r->link; t->link = list; list = t; } return(list); } static ROUTE *save_routes(unsigned char id, ROUTE *list) { ROUTE *r; ROUTE **rp; rp = &routes; while ((r = *rp)) { if (r->id == id) { *rp = r->link; r->link = list; list = r; } else { rp = &r->link; } } return(list); } static ROUTE *replace_route(unsigned char id, int width, const IPADDR *a, ROUTE *list, time_t stamp) { ROUTE *r; if (width != 255) { for (r=list;r;r=r->link) { if ((r->width == width) && ipaddr_match(&r->block,a)) { r->id = id; r->stamp = stamp; return(list); } } } r = new_route(id,width,a); r->stamp = stamp; r->link = list; return(r); } /* * It is important that the ID be the most significant comparison key * and that width be the next most significant (with width 255 coming * after other widths); there is code elsewhere that depends on these. */ static int route_lt(ROUTE *a, ROUTE *b) { int i; if (a->id != b->id) return(a->id < b->id); if (a->width != b->width) return(a->width < b->width); if (a->width == 255) return(0); if (a->block.af != b->block.af) return(a->block.af < b->block.af); switch (a->block.af) { default: abort(); break; case AF_INET: return(a->block.v4.s_addr < b->block.v4.s_addr); break; case AF_INET6: for (i=16-1;i>=0;i--) { if (a->block.v6.s6_addr[i] != b->block.v6.s6_addr[i]) { return(a->block.v6.s6_addr[i] < b->block.v6.s6_addr[i]); } } return(0); break; } } static ROUTE *sort_routes(ROUTE *list) { ROUTE *a; ROUTE *b; ROUTE *t; ROUTE **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_routes(a); b = sort_routes(b); lt = &list; while (a || b) { if (!b || (a && route_lt(a,b))) { t = a; a = a->link; } else { t = b; b = b ->link; } *lt = t; lt = &t->link; } *lt = 0; return(list); } static ROUTE *route_prune_255s(ROUTE *list) { ROUTE *r; ROUTE *i; ROUTE **it; ROUTE *l; ROUTE **lt; if (!list || !list->link) return(list); lt = &l; i = list; it = &i->link; for (r=list->link;r;r=r->link) { if (r->id != i->id) { *lt = i; lt = it; i = r; it = &i->link; } else { if (r->width == 255) { ROUTE *t; char obuf[256]; *it = 0; fprintf(stderr,"warning: dropping 255-bogused routes via %d:",r->id); for (t=i;t;t=t->link) fprintf(stderr," %s/%d",a_ntop(&t->block,&obuf[0],sizeof(obuf)),t->width); fprintf(stderr,"\n"); free_route_list(i); it = &i; } *it = r; it = &r->link; } } if (i) { *lt = i; *it = 0; } return(l); } static void peer_pkt_route(PEER *p, const unsigned char *data, int len) { __label__ throwto; int o; int w; IPADDR a; unsigned char sawid[256/8]; unsigned char id; ROUTE *old; ROUTE *new; ROUTE *or; ROUTE *nr; time_t stamp; char obuf[256]; void fail(void) { goto throwto; } void overrun(int o) { if (o > len) { fprintf(stderr,"%s: packet overrun in ROUTE from %s (%d > %d)\n",__progname,p->txt,o,len); fail(); } } if (p->peerid < 0) { V(ROUTE,"peer_pkt_route: from %s, ignored (peerid < 0)",p->txt); return; } if (routeflags & RF_IGNORE) { V(ROUTE,"peer_pkt_route: from %s, ignored (route ignore)",p->txt); return; } V(ROUTE,"peer_pkt_route: from %s",p->txt); old = 0; new = 0; if (0) { throwto:; close_peer(p); free_route_list(new); routes = prepend_routes(old,routes); return; } bzero(&sawid[0],sizeof(sawid)); o = 0; while (1) { if (o > len) abort(); if (o == len) break; overrun(o+2); id = data[o]; w = data[o+1]; if (w == 255) { } else { overrun(o+3); switch (data[o+2]) { default: fprintf(stderr,"%s: unknown IP type %d in ROUTE from %s\n",__progname,data[o+1],p->txt); fail(); break; case IPTYPE_v4: if (w > 32) { fprintf(stderr,"%s: invalid IPv4 width %d in ROUTE from %s\n",__progname,w,p->txt); fail(); } overrun(o+7); a.af = AF_INET; bcopy(data+o+3,&a.v4,4); o += 7; break; case IPTYPE_v6: if (w > 128) { fprintf(stderr,"%s: invalid IPv6 width %d in ROUTE from %s\n",__progname,w,p->txt); fail(); } overrun(o+19); a.af = AF_INET6; bcopy(data+o+3,&a.v6,16); o += 19; break; } } overrun(o+4); stamp = (data[o ] * 0x01000000) + (data[o+1] * 0x00010000) + (data[o+2] * 0x00000100) + (data[o+3] * 0x00000001); o += 4; if (id == cid) { V(ROUTE,"ignoring routing for our ID"); continue; } if (! (sawid[id>>3] & (1 << (id & 7)))) { sawid[id>>3] |= 1 << (id & 7); V(ROUTE,"saving routing for %d",id); old = save_routes(id,old); } V(ROUTE,"routing %s via %d, stamp %lu",a_ntop(&a,&obuf[0],sizeof(obuf)),id,(unsigned long int)stamp); new = replace_route(id,w,&a,new,stamp); } old = sort_routes(old); new = route_prune_255s(sort_routes(new)); if (vmask & VERB_ROUTE) { ROUTE *tr; FILE *f; f = verb_fopen(VERB_ROUTE); fprintf(f,"old:"); for (tr=old;tr;tr=tr->link) { fprintf(f," %s->%d",a_ntop(&tr->block,&obuf[0],sizeof(obuf)),tr->id); } fprintf(f,"\nnew:"); for (tr=new;tr;tr=tr->link) { fprintf(f," %s->%d",a_ntop(&tr->block,&obuf[0],sizeof(obuf)),tr->id); } fclose(f); } do <"match"> { do <"mismatch"> { for (nr=new,or=old;nr;nr=nr->link,or=or->link) { if (! or) break <"mismatch">; if (or->id != nr->id) break <"mismatch">; if (or->width != nr->width) break <"mismatch">; if (or->width != 255) { if (or->block.af != nr->block.af) break <"mismatch">; switch (or->block.af) { default: abort(); break; case AF_INET: if (or->block.v4.s_addr != nr->block.v4.s_addr) break <"mismatch">; break; case AF_INET6: if (! IN6_ARE_ADDR_EQUAL(&or->block.v6,&nr->block.v6)) break <"mismatch">; break; } } } if (or) break <"mismatch">; break <"match">; } while (0); V(ROUTE,"mismatch, flooding ROUTE"); set_flood(WANT_ROUTE); set_reroute(); } while (0); V(ROUTE,"done, saving new"); free_route_list(old); routes = prepend_routes(new,routes); } static void gen_reach(void *pv) { ((PEER *)pv)->want |= WANT_REACH; want_send = 1; } static void gen_public(void *pv) { ((PEER *)pv)->want |= WANT_PUBLIC; want_send = 1; } static void gen_ipmap(void *pv) { ((PEER *)pv)->want |= WANT_IPMAP; want_send = 1; } static void gen_route(void *pv) { ((PEER *)pv)->want |= WANT_ROUTE; want_send = 1; } static void peer_pkt_iam(PEER *p, const unsigned char *data, int len) { if (len != 1) { V(IAM,"peer %s sent IAM with length %d",p->txt,len); fprintf(stderr,"%s: peer %s sent IAM with length %d\n",__progname,p->txt,len); close_peer(p); } if (p->peerid >= 0) { V(IAM,"peer %s sent another IAM",p->txt); fprintf(stderr,"%s: peer %s sent another IAM\n",__progname,p->txt); close_peer(p); } if (data[0] == cid) { V(IAM,"peer %s claims to be us",p->txt); fprintf(stderr,"%s: peer %s claims to be us\n",__progname,p->txt); set_quietdrop(p->fd); close_peer(p); } V(IAM,"peer_pkt_iam: from %s, id %d",p->txt,data[0]); if (peertbl[data[0]] && (cid < data[0])) { unsigned char opkt[4]; UPLINK *u; PEER *p2; p2 = peertbl[data[0]]; V(DUP,"%d reachable via %s (%s) and %s (%s), DUP-killing %s", data[0],p->tag,p->txt,p2->tag,p2->txt,p2->tag); opkt[0] = PKT_DUP; opkt[1] = 0; opkt[2] = 1; opkt[3] = DUP_DUPLICATE; send_encrypted(p2,&opkt[0],4); send_postpacket(p2); p2->peerid = -1; for (u=uplinks;u;u=u->link) { if (u->peer == p2) u->peer = p; } } p->peerid = data[0]; peertbl[data[0]] = p; p->gen_reach = start_periodic(REACH_GEN_MIN,REACH_GEN_MAX,&gen_reach,p); p->gen_public = start_periodic(PUBLIC_GEN_MIN,PUBLIC_GEN_MAX,&gen_public,p); p->gen_ipmap = start_periodic(IPMAP_GEN_MIN,IPMAP_GEN_MAX,&gen_ipmap,p); p->gen_route = start_periodic(ROUTE_GEN_MIN,ROUTE_GEN_MAX,&gen_route,p); p->want |= WANT_REACH | WANT_PUBLIC | WANT_IPMAP | WANT_ROUTE; want_send = 1; } static void peer_pkt_dup(PEER *p, const unsigned char *data, int len) { int i; PEER *p2; UPLINK *u; if (len != 1) { V(DUP,"peer %s sent DUP with length %d",p->txt,len); fprintf(stderr,"%s: peer %s sent DUP with length %d\n",__progname,p->txt,len); close_peer(p); } if (p->peerid < 0) { V(DUP,"peer %s sent DUP before IAM",p->txt); fprintf(stderr,"%s: peer %s sent DUP before IAM\n",__progname,p->txt); set_quietdrop(p->fd); close_peer(p); } switch (data[0]) { default: V(DUP,"peer %s sent bad DUP sub-opcode %02x",p->txt,data[0]); fprintf(stderr,"%s: peer %s sent bad DUP sub-opcode %02x",__progname,p->txt,data[0]); set_quietdrop(p->fd); close_peer(p); break; case DUP_DUPLICATE: V(DUP,"DUPLICATE sent by %s (%s)",p->tag,p->txt); for (i=npeers-1;i>=0;i--) { p2 = peers[i]; if (p2 == p) continue; if (p2->peerid != p->peerid) continue; V(DUP,"peer_pkt_dup: closing %s, switching to %s",p->txt,p2->txt); for (u=uplinks;u;u=u->link) { if (u->peer == p) u->peer = p2; } if (peertbl[p->peerid] == p) peertbl[p->peerid] = p2; close_peer(p); return; } V(DUP,"peer_pkt_dup: couldn't find a replacement"); close_peer(p); break; } } static void peer_packet(PEER *p, unsigned char type, const unsigned char *data, int len) { if (vmask & VERB_PKTDATA) { FILE *f; int i; f = verb_fopen(VERB_PKTDATA); fprintf(f,"input packet [%d]:",p->iwant); for (i=0;iiwant;i++) fprintf(f," %02x",p->ibuf[i]); fclose(f); } 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_ROUTE: peer_pkt_route(p,data,len); break; case PKT_IAM: peer_pkt_iam(p,data,len); break; case PKT_DUP: peer_pkt_dup(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]; p->iwant = len + 3; if (len == 0) { peer_packet(p,p->ibuf[0],0,0); p->ifill = 0; } else { 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; } V(IO,"read from %s: %s",p->txt,strerror(errno)); close_peer(p); return; } if (n == 0) { V(IO,"read EOF from %s",p->txt); close_peer(p); return; } V(IO,"read %d from %s",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]; V(IAM,"sending IAM to %s",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) { V(CRYPTO,"crypto done for %s",p->txt); V(CONN,"peer %s (%s) up, fd %d",p->tag,p->txt,p->fd); 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) { V(CRYPTO,"starting crypto on %s",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 char *get_sockaddrs_txt(int fd, const char *what) { struct sockaddr_storage lss; socklen_t ll; struct sockaddr_storage rss; socklen_t rl; FILE *f; char *rv; ll = sizeof(lss); if (getsockname(fd,(struct sockaddr *)&lss,&ll) < 0) { fprintf(stderr,"%s: getsockname for %s: %s\n",__progname,what,strerror(errno)); return(0); } rl = sizeof(rss); if (getpeername(fd,(struct sockaddr *)&rss,&rl) < 0) { fprintf(stderr,"%s: getpeername for %s: %s\n",__progname,what,strerror(errno)); return(0); } f = open_accum(&rv,0); print_sa_txt((const struct sockaddr *)&lss,ll,f); fprintf(f," <-> "); print_sa_txt((const struct sockaddr *)&rss,rl,f); fclose(f); return(rv); } static int peer_get_sockaddrs(PEER *p) { char *t; t = get_sockaddrs_txt(p->fd,p->txt); if (t) { free(p->txt); p->txt = t; return(0); } else { return(-1); } } static void peer_connect_ok(PEER *p) { CRYPTINIT *ci; if (peer_get_sockaddrs(p) < 0) { VS(VERB_CONN|VERB_IO,"peer_get_sockaddrs failed"); close_peer(p); return; } VS(VERB_CONN|VERB_IO,"peer_get_sockaddrs worked: %s",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 ) { V(CONN,"%s failed",p->txt); close_peer(p); return; } V(CONN,"%s worked",p->txt); peer_connect_ok(p); } static void try_reconnect(void *arg __attribute__((__unused__))) { UPLINK *ul; PEER *p; int s; FILE *vf; 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; asprintf(&p->txt,"connect to %s",ul->txt); vf = verb_fopen(VERB_CONN); if (vf) fprintf(vf,"trying to connect to %s...",ul->txt); if (connect(s,ul->sa,ul->salen) < 0) { if (errno == EINPROGRESS) { p->id = add_poll_fd(s,&rwtest_never,&rwtest_always,0,&peer_connect_done,p); if (vf) fprintf(vf,"pending"); } else { if (vf) fprintf(vf,"failed (%s)",strerror(errno)); fclose(vf); close_peer(p); continue; } } else { if (vf) fprintf(vf,"worked"); peer_connect_ok(p); } ul->peer = p; if (vf) fclose(vf); } } 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); } V(IO,"tun_rd got len %d",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) { if (vmask & VERB_REACH) { FILE *f; f = verb_fopen(VERB_REACH); fprintf(f,"sending REACH to %s: ",p->txt); dump_reach_body(&pkt[3],pp-3,f); fclose(f); } 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) { V(REACH,"not sending REACH to %s (peerid < 0)",p->txt); return; } pp = 0; maxpp = sizeof(pkt); for (i=256-1;i>=0;i--) { if (i == cid) { add_target(i,0); } else if (reachvia[i] == p->peerid) { add_target(i,REACH_INFINITY); } else if (reachvia[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) { V(PUBLIC,"not sending PUBLIC to %s (peerid < 0)",p->txt); return; } V(PUBLIC,"sending PUBLIC to %s",p->txt); pp = 0; maxpp = sizeof(pkt); now = 0; for (l=listens;l;l=l->link) { if ((l->flags & (LF_PUBLIC|LF_WILDCARD)) != LF_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; IPMAP **mp; time_t now; void add_mapping(unsigned char id, const IPADDR *a, time_t stamp) { 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; } pkt[pp++] = (stamp >> 24) & 0xff; pkt[pp++] = (stamp >> 16) & 0xff; pkt[pp++] = (stamp >> 8) & 0xff; pkt[pp++] = stamp & 0xff; } if (p->peerid < 0) { V(IPMAP,"not sending IPMAP to %s (peerid < 0)",p->txt); return; } V(IPMAP,"sending IPMAP to %s",p->txt); time(&now); ipmaps = sort_ipmaps(ipmaps); lastid = -1; pp = 0; maxpp = sizeof(pkt); mp = &ipmaps; while ((m = *mp)) { if (m->id == cid) { add_mapping(cid,&m->a,now); } else if (m->stamp+IPMAP_EXPIRE < now) { char obuf[256]; V(IPMAP,"expiring mapping %s <-> %d (age %d)\n", a_ntop(&m->a,&obuf[0],sizeof(obuf)),m->id,(int)(now-m->stamp)); *mp = m->link; m->link = 0; free_ipmap_list(m); continue; } else { add_mapping(m->id,&m->a,m->stamp); } mp = &m->link; } if (pp > 0) { set_pkt_length(&pkt[0],pp); send_encrypted(p,&pkt[0],pp); send_postpacket(p); } } static void send_route(PEER *p) { unsigned char pkt[512]; int pp; int maxpp; int lastid; int lastidswitch; ROUTE *r; ROUTE **rp; time_t now; int need_255; void add_mapping(unsigned char id, int width, const IPADDR *a, time_t stamp) { if (id != lastid) { lastid = id; lastidswitch = pp; } if (pp == 0) { pkt[pp++] = PKT_ROUTE; pp += 2; } if (pp+19 > maxpp) { if (lastidswitch == 0) { fprintf(stderr,"%s: too many routes 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; pkt[pp++] = width; if (width != 255) { 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; } } pkt[pp++] = (stamp >> 24) & 0xff; pkt[pp++] = (stamp >> 16) & 0xff; pkt[pp++] = (stamp >> 8) & 0xff; pkt[pp++] = stamp & 0xff; } if (p->peerid < 0) { V(ROUTE,"not sending ROUTE to %s (peerid < 0)",p->txt); return; } V(ROUTE,"sending ROUTE to %s",p->txt); time(&now); routes = sort_routes(routes); lastid = -1; pp = 0; maxpp = sizeof(pkt); need_255 = 1; rp = &routes; while ((r = *rp)) { if (r->id == cid) { add_mapping(cid,r->width,&r->block,now); need_255 = 0; } else if (r->stamp+IPMAP_EXPIRE < now) { char obuf[256]; if (r->width == 255) { V(IPMAP,"expiring route /255 via %d (age %d)\n", r->id,(int)(now-r->stamp)); } else { V(IPMAP,"expiring route %s/%d via %d (age %d)\n", a_ntop(&r->block,&obuf[0],sizeof(obuf)),r->width,r->id,(int)(now-r->stamp)); } *rp = r->link; r->link = 0; free_route_list(r); continue; } else { add_mapping(r->id,r->width,&r->block,r->stamp); } rp = &r->link; } if (need_255) add_mapping(cid,255,0,now); if (pp > 0) { set_pkt_length(&pkt[0],pp); send_encrypted(p,&pkt[0],pp); send_postpacket(p); } } static int send_packet(PEER *p) { PQ *q; unsigned char hdr[6]; int l; if (p->peerid < 0) { V(IP,"not sending IP to %s (peerid < 0)",p->txt); return(-1); } V(IP,"sending IP to %s",p->txt); q = p->pq; p->pq = q->link; if (! p->pq) p->pqt = &p->pq; l = q->bodylen + 3; if (l > 65535) { fprintf(stderr,"%s: dropping huge packet (length %d)\n",__progname,q->bodylen); free(q); return(0); } 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; } V(IP,"IP packet: hopcount=%d id=%d dsttype=%d len=%d",hdr[3],hdr[4],hdr[5],q->bodylen); V(IP,"IP packet: addrtype %d, data %02x%02x%02x%02x%02x%02x...%02x%02x",hdr[5], ((unsigned char *)q->body)[0], ((unsigned char *)q->body)[1], ((unsigned char *)q->body)[2], ((unsigned char *)q->body)[3], ((unsigned char *)q->body)[4], ((unsigned char *)q->body)[5], ((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); return(0); } static void route_pq(PQ *q) { IPMAP *m; PEER *p; char astr[256]; VS(VERB_IP|VERB_ROUTING,"route_pq, dst %s",a_ntop(&q->dst,&astr[0],sizeof(astr))); do <"fail"> { for (m=ipmaps;m;m=m->link) { if (ipaddr_match(&q->dst,&m->a)) { VS(VERB_IP|VERB_ROUTING,"match, id %d",m->id); if (m->id == cid) { int atype; switch (q->dst.af) { default: abort(); break; case AF_INET: atype = IPTYPE_v4; break; case AF_INET6: atype = IPTYPE_v6; break; } my_ip_pkt(0,atype,q->body,q->bodylen); free(q); return; } if ( (reachvia[m->id] < 0) || (reachdist[m->id] >= REACH_INFINITY) ) break <"fail">; p = peertbl[reachvia[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); if (p->want & WANT_ROUTE) send_route(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) >= 0)) ; } return(BLOCK_LOOP); } static void listen_accept(void *lv) { LISTEN *l; int new; struct sockaddr_storage ss; socklen_t sslen; 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); V(CONN,"accepted on %s",l->txt); (*l->onaccept)(l,new); } static void start_listener(LISTEN *l) { l->id = add_poll_fd(l->fd,&rwtest_always,&rwtest_never,&listen_accept,0,l); new_uplink(l->sa,l->salen)->flags |= UF_MYSELF; } static void start_accepts(void) { LISTEN *l; for (l=listens;l;l=l->link) start_listener(l); } static void expire_uplinks(void *arg __attribute__((__unused__))) { 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); free(u); changed = 1; continue; } } up = &u->link; } if (changed) set_flood(WANT_PUBLIC); } static void expire_reaches(void *arg __attribute__((__unused__))) { if (recompute_reach()) 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 init_route(void) { ROUTE *r; for (r=routes;r;r=r->link) r->id = cid; } 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 retry_listen(void *plv) { PENDLISTEN *pl; LISTEN *l; pl = plv; l = try_setup_listen(&pl->addrs,pl->flags,0,pl->onaccept); if (l) { start_listener(l); if (pl->addrs == 0) { kill_periodic(pl->pd); freeaddrinfo(pl->ai0); free(pl); } } } static int capcustom_stars(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(0); } free(s->addr); s->addr = 0; return(1); } static void onaccept_listen(LISTEN *l, int fd) { PEER *p; CRYPTINIT *ci; p = new_peer(); p->fd = fd; p->id = PL_NOID; asprintf(&p->txt,"(accept from %s)",l->txt); if (peer_get_sockaddrs(p) < 0) { V(CONN,"peer_get_sockaddrs failed"); close_peer(p); return; } V(CONN,"peer_get_sockaddrs worked: %s",p->txt); ci = malloc(sizeof(CRYPTINIT)); ci->myrole = CR_PASSIVE; ci->p = p; peer_start_crypto(ci); } static char *gettext_inet(AIA *a) { int e; char *txt; char hstr[NI_MAXHOST]; char sstr[NI_MAXSERV]; e = getnameinfo(a->ai->ai_addr,a->ai->ai_addrlen,&hstr[0],NI_MAXHOST,&sstr[0],NI_MAXSERV,NI_NUMERICHOST|NI_NUMERICSERV); if (e) { a->errmsg = gai_strerror(e); return(0); } asprintf(&txt,"%s/%s",&hstr[0],&sstr[0]); return(txt); } static void postbind_nil(AIA *a __attribute__((__unused__))) { } static AIA *aia_for_addrinfo(struct addrinfo *ai) { AIA *list; AIA *a; list = 0; for (;ai;ai=ai->ai_next) { a = malloc(sizeof(AIA)); a->ai = ai; a->gettext = &gettext_inet; a->postbind = &postbind_nil; a->link = list; list = a; } return(list); } static void confline_listen(const char *s) { int i; int i0; char *aps; unsigned int flags; struct addrinfo *ai0; AIA *ailist; void capcustom_listen(APSTATE *s) { if (capcustom_stars(s)) flags |= LLF_WILDCARD; } for (i=0;s[i]&&Cisspace(s[i]);i++) ; if (! s[i]) config_err("no argument given on listen line"); for (i0=i;s[i]&&!Cisspace(s[i]);i++) ; aps = malloc((i-i0)+1); bcopy(s+i0,aps,i-i0); aps[i-i0] = '\0'; V(CONFIG,"confline_listen: aps %s",aps); flags = 0; while (1) { for (;s[i]&&Cisspace(s[i]);i++) ; if (! s[i]) break; for (i0=i;s[i]&&!Cisspace(s[i]);i++) ; V(CONFIG,"confline_listen: option %.*s",i-i0,s+i0); if (((i-i0) == 5) && !strncasecmp(s+i0,"retry",5)) { flags |= LLF_RETRY; } else if (((i-i0) == 6) && !strncasecmp(s+i0,"public",6)) { flags = (flags & ~LLF_PRIVATE) | LLF_PUBLIC; } else if (((i-i0) == 7) && !strncasecmp(s+i0,"private",7)) { flags = (flags & ~LLF_PUBLIC) | LLF_PRIVATE; } else { free(aps); config_err("unrecognized listen option `%.*s'",i-i0,s+i0); } } if (vmask & VERB_CONFIG) { FILE *f; f = verb_fopen(VERB_CONFIG); fprintf(f,"confline_listen: cracking %s, flags",aps); if (flags == 0) fprintf(f," none"); if (flags & LLF_RETRY) fprintf(f," RETRY"); if (flags & LLF_PUBLIC) fprintf(f," PUBLIC"); if (flags & LLF_PRIVATE) fprintf(f," PRIVATE"); if (flags & LLF_WILDCARD) fprintf(f," WILDCARD"); fclose(f); } ai0 = crack_addr_port(aps,AI_PASSIVE,&capcustom_listen); ailist = aia_for_addrinfo(ai0); if (flags & LLF_RETRY) { PENDLISTEN *pl; V(CONFIG,"confline_listen starting retry"); pl = malloc(sizeof(PENDLISTEN)); pl->ai0 = ai0; pl->addrs = ailist; pl->flags = flags; pl->onaccept = &onaccept_listen; pl->pd = start_periodic(LISTEN_RETRY_MIN,LISTEN_RETRY_MAX,&retry_listen,pl); } else { V(CONFIG,"confline_listen doing setup now"); while (ailist) try_setup_listen(&ailist,flags,s,&onaccept_listen); freeaddrinfo(ai0); } } static void confline_peer(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 dump_status(FILE *f) { int i; time_t now; time(&now); fprintf(f,"ID %d, %s PUBLIC packets\n",cid,ignore_public?"ignores":"listens to"); if (listens == 0) { fprintf(f,"No listening points\n"); } else { LISTEN *l; for (l=listens;l;l=l->link) { fprintf(f,"Listening: fd %d, %s",l->fd,l->txt); switch (l->flags & (LF_WILDCARD|LF_PUBLIC)) { case 0: break; case LF_WILDCARD: fprintf(f," (wildcard)"); break; case LF_PUBLIC: fprintf(f," (advertised)"); break; case LF_WILDCARD|LF_PUBLIC: fprintf(f," (IMPOSSIBLE advertised wildcard)"); break; } fprintf(f,"\n"); } } if (uplinks == 0) { fprintf(f,"No uplinks\n"); } else { UPLINK *u; for (u=uplinks;u;u=u->link) { fprintf(f,"Uplink: %s",u->txt); switch (u->flags & (UF_LOCALCONF|UF_MYSELF)) { case 0: fprintf(f," (learned)"); break; case UF_LOCALCONF: fprintf(f," (locally configured)"); break; case UF_MYSELF: fprintf(f," (myself)"); break; case UF_LOCALCONF|UF_MYSELF: fprintf(f," (configured to talk to myself?)"); break; } fprintf(f,"\n"); } } fprintf(f,"Active peer count: %d\n",npeers); for (i=0;itag,p->txt,p->fd,p->peerid); if (p->lastreach_len > 0) { fprintf(f," Last REACH (age %lu): ", (unsigned long int)(now-p->lastreach_stamp)); dump_reach_body(p->lastreach_data,p->lastreach_len,f); fprintf(f,"\n"); } } fprintf(f,"tun path %s, fd %d\n",tunpath,tunfd); for (i=0;i<256;i++) if (peertbl[i]) fprintf(f,"ID %d -> peer %s\n",i,peertbl[i]->tag); for (i=0;i<256;i++) { if (reachvia[i] >= 0) { fprintf(f,"Reach %d via %d distance %d\n",i,reachvia[i],reachdist[i]); } } if (ipmaps == 0) { fprintf(f,"No IP mapping information\n"); } else { IPMAP *m; char astr[256]; ipmaps = sort_ipmaps(ipmaps); for (m=ipmaps;m;m=m->link) { fprintf(f,"IP map: %d : %s\n",m->id,a_ntop(&m->a,&astr[0],sizeof(astr))); } } if (routes == 0) { fprintf(f,"No route-via information\n"); } else { ROUTE *r; char astr[256]; routes = sort_routes(routes); for (r=routes;r;r=r->link) { if (r->width == 255) { fprintf(f,"Route nothing via %d\n",r->id); } else { fprintf(f,"Route %s/%d via %d\n",a_ntop(&r->block,&astr[0],sizeof(astr)),r->width,r->id); } } } } static int cleanup_admin(void *av) { ADMIN *a; a = av; remove_block_id(a->id); oq_flush(&a->oq); free(a->txt); free(a); return(BLOCK_NIL); } static void close_admin(ADMIN *a) { if (a->fd < 0) return; close(a->fd); if (a->f) { fclose(a->f); a->f = 0; } if (a->verb) drop_verbosity(a->verb); a->fd = -1; remove_poll_id(a->id); a->id = add_block_fn(&cleanup_admin,a); } static int wtest_admin(void *av) { return(oq_nonempty(&((ADMIN *)av)->oq)); } static void wr_admin(void *av) { ADMIN *a; a = av; if (wr_oq(&a->oq,a->fd,a->txt) < 0) close_admin(a); } static void admin_prompt(ADMIN *a) { if (a->verb) return; oq_queue_point(&a->oq,"cloud> ",7); } static int admin_out_w(void *av, const char *buf, int len) { ADMIN *a; int i0; int i; void flush(void) { if (i0 < 0) return; oq_queue_copy(&a->oq,buf+i0,i-i0); i0 = -1; } a = av; if (a->fd >= 0) { i0 = -1; for (i=0;ioq,"\r\n",2); } else { if (i0 < 0) i0 = i; } } flush(); } return(len); } static void admin_verbosity(ADMIN *a, const char *arg) { __label__ err_throw; unsigned int set; unsigned int clr; const char *trail; unsigned int m; int i; const char *sep; char *errmsg; VERBOSITY *v; void err(const char *fmt, ...) { va_list ap; char *s; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); errmsg = s; goto err_throw; } if (0) { err_throw:; fprintf(a->f,"%s\n",errmsg); free(errmsg); return; } while (*arg && Cisspace(*arg)) arg ++; if (! *arg) { if (! a->verb) { fprintf(a->f,"No verbosity.\n"); return; } m = a->verb->mask; sep = ""; for (i=0;vbits[i].bit;i++) { if (vbits[i].alias) continue; if (m & vbits[i].bit) { m &= ~vbits[i].bit; fprintf(a->f,"%s%s",sep,vbits[i].name); sep = " "; } } if (m) fprintf(a->f,"%s0x%x",sep,m); fprintf(a->f,"\n"); return; } if ((arg[0] == '?') && !arg[1]) { for (i=0;vbits[i].bit;i++) { fprintf(a->f,"%-12s%s\n",vbits[i].name,vbits[i].help); } return; } crack_verbosity(arg,&set,&clr,&trail,&err); v = (*vops_admin.find)(&err,a); v->mask = (v->mask & ~clr) | set; (*v->ops->maskchanged)(v); recompute_vmask(); } static void admin_drop(ADMIN *a, const char *arg) { int i; PEER *p; while (*arg && Cisspace(*arg)) arg ++; for (i=0;itag)) { close_peer(p); return; } } fprintf(a->f,"Not found.\n"); } static void admin_line(ADMIN *a) { char *p; char *p0; int l; for (p=a->ilb;*p&&Cisspace(*p);p++) ; p0 = p; for (p=a->ilb;*p&&!Cisspace(*p);p++) ; l = p - p0; a->f = funopen(a,0,&admin_out_w,0,0); if ((l == 1) && (*p0 == '?')) { fprintf(a->f, "? This help\n" "V [-]arg[,[-]arg...]\n" " Turn verbosity on/off for this connection.\n" "drop name\n" " Forcibly drop a PEER, identified by the name printed by `stat'.\n" "quit Disconnect\n" "exit Disconnect\n" "stat Show status\n"); } else if ((l == 1) && (*p0 == 'V')) { admin_verbosity(a,p0+1); } else if ((l == 4) && !bcmp(p0,"drop",4)) { admin_drop(a,p0+4); } else if ((l == 4) && (!bcmp(p0,"quit",4) || !bcmp(p0,"exit",4))) { close_admin(a); } else if ((l == 4) && !bcmp(p0,"stat",4)) { dump_status(a->f); } else { fprintf(a->f,"Unrecognized command `%.*s'\n",l,p0); } fclose(a->f); a->f = 0; } static void admin_input(ADMIN *a, char ch) { void savec(char c) { if (a->iln >= a->ila) a->ilb = realloc(a->ilb,a->ila=a->iln+8); a->ilb[a->iln++] = c; } switch (ch) { case '\r': if (a->ilprev == '\n') { a->ilprev = '\0'; return; } if (0) { case '\n': if (a->ilprev == '\r') { a->ilprev = '\0'; return; } } savec('\0'); a->iln --; admin_line(a); if (a->fd < 0) return; a->iln = 0; admin_prompt(a); break; default: savec(ch); break; } a->ilprev = ch; } static void rd_admin(void *av) { ADMIN *a; char rbuf[512]; int n; int i; a = av; n = read(a->fd,&rbuf[0],sizeof(rbuf)); if (n <= 0) { close_admin(a); return; } for (i=0;(ifd>=0);i++) admin_input(a,rbuf[i]); } static void onaccept_control(LISTEN *l, int fd) { ADMIN *a; a = malloc(sizeof(ADMIN)); a->fd = fd; oq_init(&a->oq); if (l->sa->sa_family == AF_LOCAL) { asprintf( &a->txt, "local %.*s", (int)(l->sa->sa_len-offsetof(struct sockaddr_un,sun_path)), &((struct sockaddr_un *)l->sa)->sun_path[0] ); } else { a->txt = get_sockaddrs_txt(fd,l->txt); } a->id = add_poll_fd(fd,&rwtest_always,&wtest_admin,&rd_admin,&wr_admin,a); a->ilb = 0; a->ila = 0; a->iln = 0; a->ilprev = '\0'; a->f = 0; a->verb = 0; admin_prompt(a); } static char *gettext_local(AIA *a) { const struct sockaddr_un *sun; int l; char *t; sun = (const struct sockaddr_un *)a->ai->ai_addr; l = sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path)); t = malloc(l+1); bcopy(&sun->sun_path[0],t,l); t[l] = '\0'; return(t); } static void postbind_local(AIA *a) { if (a->local.mode >= 0) chmod(&((const struct sockaddr_un *)a->ai->ai_addr)->sun_path[0],a->local.mode); } static void confline_control(const char *s) { int i; unsigned int flags; struct addrinfo *ai0; AIA *ailist; void capcustom_control(APSTATE *s) { if (capcustom_stars(s)) flags |= LLF_WILDCARD; } for (i=0;s[i]&&Cisspace(s[i]);i++) ; if (! s[i]) config_err("no argument given on control line"); if (s[i] == '/') { struct addrinfo ai; struct sockaddr_un *sun; int l; int j; for (j=i;s[j]&&!Cisspace(s[j]);j++) ; l = sizeof(struct sockaddr_un) - sizeof(sun->sun_path) + (j-i); sun = malloc(l+1); sun->sun_family = AF_LOCAL; sun->sun_len = l; bcopy(s+i,&sun->sun_path[0],j-i); sun->sun_path[j-i] = '\0'; for (;s[j]&&Cisspace(s[j]);j++) ; /* just the fields try_setup_listen uses */ ai.ai_family = AF_LOCAL; ai.ai_socktype = SOCK_STREAM; ai.ai_protocol = 0; ai.ai_addr = (struct sockaddr *)sun; ai.ai_addrlen = l; ailist = malloc(sizeof(AIA)); ailist->link = 0; ailist->ai = &ai; ailist->gettext = &gettext_local; ailist->postbind = &postbind_local; ailist->local.mode = -1; if (s[j]) { unsigned long int mode; char *ep; mode = strtoul(s+j,&ep,8); if (*ep) { config_err("junk after mode on local control line"); } else if (mode & ~0777UL) { config_err("invalid mode on local control line"); } else { ailist->local.mode = mode; } } try_setup_listen(&ailist,LLF_PRIVATE,s,&onaccept_control); free(sun); } else { flags = LLF_PRIVATE; ai0 = crack_addr_port(s+i,AI_PASSIVE,&capcustom_control); ailist = aia_for_addrinfo(ai0); while (ailist) try_setup_listen(&ailist,flags,s,&onaccept_control); } } static int trim_addr_to_width(IPADDR *a, int w) { switch (a->af) { default: abort(); break; case AF_INET: { unsigned int m; m = htonl( (w < 32) ? 0xffffffffU >> w : 0 ); if (a->v4.s_addr & m) { a->v4.s_addr &= ~m; return(1); } return(0); } break; case AF_INET6: { unsigned char m[16]; int i; int bad; memset(&m[0],0xff,16); if (w >= 8) memset(&m[0],0,w>>3); if (w & 7) m[w>>3] = 0xff >> (w & 7); bad = 0; for (i=0;i<16;i++) { if (a->v6.s6_addr[i] & m[i]) { bad = 1; a->v6.s6_addr[i] &= ~m[i]; } } return(bad); } break; } } static void confline_route(const char *s) { int sx; int a0; char *arg; struct addrinfo hints; struct addrinfo *ai0; int e; ROUTE *r; int maxw; unsigned long int w; char *ep; char *slash; sx = 0; while (1) { while (s[sx] && Cisspace(s[sx])) sx ++; if (! s[sx]) break; a0 = sx; while (s[sx] && !Cisspace(s[sx])) sx ++; arg = malloc((sx-a0)+1); bcopy(s+sx,arg,sx-a0); arg[sx-a0] = '\0'; slash = index(arg,'/'); if (slash) { *slash++ = '\0'; hints.ai_flags = AI_NUMERICHOST; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = 0; hints.ai_canonname = 0; hints.ai_next = 0; e = getaddrinfo(arg,0,&hints,&ai0); if (e) config_err("lookup %s: %s",arg,gai_strerror(e)); if (! ai0) config_err("lookup %s: succeeded but returned zero answers",arg); if (ai0->ai_next) config_err("lookup %s: returned multiple answers",arg); r = new_route(0,255,0); switch (ai0->ai_addr->sa_family) { default: config_err("lookup %s: returned unknown AF %d",arg,ai0->ai_addr->sa_family); break; case AF_INET: r->block.af = AF_INET; r->block.v4 = ((const struct sockaddr_in *)ai0->ai_addr)->sin_addr; maxw = 32; break; case AF_INET6: r->block.af = AF_INET6; r->block.v6 = ((const struct sockaddr_in6 *)ai0->ai_addr)->sin6_addr; maxw = 128; break; } w = strtoul(slash,&ep,0); if ((ep == slash) || *ep) { config_err("bad width on route line: %s",slash); } if (w > maxw) { config_err("out-of-range width on route line: %s",slash); } r->width = w; if (trim_addr_to_width(&r->block,w)) { config_err("route address doesn't have all-0 host part: %s/%s",arg,slash); } r->link = routes; routes = r; } else { if (!strcmp(arg,"listen")) { routeflags = (routeflags & ~RF_IGNORE) | RF_LISTEN; } else if (!strcmp(arg,"ignore")) { routeflags = (routeflags & ~RF_LISTEN) | RF_IGNORE; } else if (!strcmp(arg,"install")) { routeflags = (routeflags & ~RF_INSTALL) | RF_NOINSTALL; } else if (!strcmp(arg,"noinstall")) { routeflags = (routeflags & ~RF_NOINSTALL) | RF_INSTALL; } else { config_err("unknown route keyword `%s'",arg); } } } } 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 }, { "peer", &confline_peer }, { "tun", &confline_tun }, { "control", &confline_control }, { "route", &confline_route }, { 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")) { WANTARG(); set_verbose(av[skip]); continue; } if (!strcmp(*av,"-bg")) { autobg = 1; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void startup(void) { int i; open_random(); init_wrandom(); pd_reconnect = start_periodic(TRY_CONNECT_MIN,TRY_CONNECT_MAX,&try_reconnect,0); peercleanup_id = add_block_fn(&peercleanup,0); peercleanup_needed = 0; 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; reachvia[i] = -1; } pd_expire_uplink = ignore_public ? 0 : start_periodic(UPLINK_EXPIRE_MIN,UPLINK_EXPIRE_MAX,&expire_uplinks,0); init_ipmap(); init_route(); reach_expire_fd = socket(AF_TIMER,SOCK_STREAM,0); nonblocking_fd(reach_expire_fd); reach_expire_id = add_poll_fd(reach_expire_fd,&rwtest_always,&rwtest_never,&expire_reaches,0,0); V(TIMER,"AF_TIMER %d reach expiration",reach_expire_fd); } static void background(void) { pid_t p; p = fork(); if (p < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (p > 0) exit(0); } int main(int, char **); int main(int ac, char **av) { init(); handleargs(ac,av); set_defaults(); startup(); if (autobg) background(); 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); }