X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=secnet.git;a=blobdiff_plain;f=site.c;h=1c57a8a66b6296ca305b7efab2d4cf9035838c56;hp=324a2061682e69188605c04650d6e012d4993624;hb=b85fe3f90b025a3742bf1c0ea91bc213a421f74d;hpb=8aaaa63461faf2cd29b3e012710fcdc8717b2ea9 diff --git a/site.c b/site.c index 324a206..1c57a8a 100644 --- a/site.c +++ b/site.c @@ -40,7 +40,6 @@ #define DEFAULT_MOBILE_WAIT_TIME (10*1000) /* [ms] */ #define DEFAULT_MOBILE_PEER_EXPIRY (2*60) /* [s] */ -#define DEFAULT_MOBILE_PEERS_MAX 3 /* send at most this many copies (default) */ /* Each site can be in one of several possible states. */ @@ -143,48 +142,79 @@ static struct flagstr log_event_table[]={ /***** TRANSPORT PEERS declarations *****/ /* Details of "mobile peer" semantics: + + - We use the same data structure for the different configurations, + but manage it with different algorithms. - - We record mobile_peers_max peer address/port numbers ("peers") - for key setup, and separately mobile_peers_max for data - transfer. If these lists fill up, we retain the newest peers. - (For non-mobile peers we only record one of each.) + - We record up to mobile_peers_max peer address/port numbers + ("peers") for key setup, and separately up to mobile_peers_max + for data transfer. + + - In general, we make a new set of addrs (see below) when we start + a new key exchange; the key setup addrs become the data transport + addrs when key setup complets. + + If our peer is mobile: + + - We send to all recent addresses of incoming packets, plus + initially all configured addresses (which we also expire). + + - So, we record addrs of good incoming packets, as follows: + 1. expire any peers last seen >120s ("mobile-peer-expiry") ago + 2. add the peer of the just received packet to the applicable list + (possibly evicting the oldest entries to make room) + NB that we do not expire peers until an incoming packet arrives. - - Outgoing packets are sent to every recorded peer in the - applicable list. + - If the peer has a configured address or name, we record them the + same way, but only as a result of our own initiation of key + setup. (We might evict some incoming packet addrs to make room.) - - Data transfer peers are straightforward: whenever we successfully - process a data packet, we record the peer. Also, whenever we - successfully complete a key setup, we merge the key setup + - The default number of addrs to keep is 3, or 4 if we have a + configured name or address. That's space for two configured + addresses (one IPv6 and one IPv4), plus two received addresses. + + - Outgoing packets are sent to every recorded address in the + applicable list. Any unsupported[1] addresses are deleted from + the list right away. (This should only happen to configured + addresses, of course, but there is no need to check that.) + + - When we successfully complete a key setup, we merge the key setup peers into the data transfer peers. - (For "non-mobile" peers we simply copy the peer used for - successful key setup, and don't change the peer otherwise.) + [1] An unsupported address is one for whose AF we don't have a + socket (perhaps because we got EAFNOSUPPORT or some such) or for + which sendto gives ENETUNREACH. - - Key setup peers are slightly more complicated. + If neither end is mobile: - Whenever we receive and successfully process a key exchange - packet, we record the peer. + - When peer initiated the key exchange, we use the incoming packet + address. - Whenever we try to initiate a key setup, we copy the list of data - transfer peers and use it for key setup. But we also look to see - if the config supplies an address and port number and if so we - add that as a key setup peer (possibly evicting one of the data - transfer peers we just copied). + - When we initiate the key exchange, we try configured addresses + until we get one which isn't unsupported then fixate on that. - (For "non-mobile" peers, if we if we have a configured peer - address and port, we always use that; otherwise if we have a - current data peer address we use that; otherwise we do not - attempt to initiate a key setup for lack of a peer address.) + - When we complete a key setup, we replace the data transport peers + with those from the key setup. - "Record the peer" means - 1. expire any peers last seen >120s ("mobile-peer-expiry") ago - 2. add the peer of the just received packet to the applicable list - (possibly evicting older entries) - NB that we do not expire peers until an incoming packet arrives. + If we are mobile: - */ + - We can't tell when local network setup changes so we can't cache + the unsupported addrs and completely remove the spurious calls to + sendto, but we can optimise things a bit by deprioritising addrs + which seem to be unsupported. + + - Use only configured addresses. (Except, that if our peer + initiated a key exchange we use the incoming packet address until + our name resolution completes.) + + - When we send a packet, try each address in turn; if addr + supported, put that address to the end of the list for future + packets, and go onto the next address. -#define MAX_MOBILE_PEERS_MAX 5 /* send at most this many copies, compiled max */ + - When we complete a key setup, we replace the data transport peers + with those from the key setup. + + */ typedef struct { struct timeval last; @@ -195,21 +225,39 @@ typedef struct { /* configuration information */ /* runtime information */ int npeers; - transport_peer peers[MAX_MOBILE_PEERS_MAX]; + transport_peer peers[MAX_PEER_ADDRS]; } transport_peers; +/* Basic operations on transport peer address sets */ static void transport_peers_clear(struct site *st, transport_peers *peers); static int transport_peers_valid(transport_peers *peers); static void transport_peers_copy(struct site *st, transport_peers *dst, const transport_peers *src); +/* Record address of incoming setup packet; resp. data packet. */ static void transport_setup_msgok(struct site *st, const struct comm_addr *a); static void transport_data_msgok(struct site *st, const struct comm_addr *a); + +/* Initialise the setup addresses. Called before we send the first + * packet in a key exchange. If we are the initiator, as a result of + * resolve completing (or being determined not to be relevant) or an + * incoming PROD; if we are the responder, as a result of the MSG1. */ static bool_t transport_compute_setupinit_peers(struct site *st, - const struct comm_addr *configured_addr /* 0 if none or not found */, - const struct comm_addr *prod_hint_addr /* 0 if none */); -static void transport_record_peer(struct site *st, transport_peers *peers, - const struct comm_addr *addr, const char *m); + const struct comm_addr *configured_addrs /* 0 if none or not found */, + int n_configured_addrs /* 0 if none or not found */, + const struct comm_addr *incoming_packet_addr /* 0 if none */); + +/* Called if we are the responder in a key setup, when the resolve + * completes. transport_compute_setupinit_peers will hvae been called + * earlier. If _complete is called, we are still doing the key setup + * (and we should use the new values for both the rest of the key + * setup and the ongoing data exchange); if _tardy is called, the key + * setup is done (either completed or not) and only the data peers are + * relevant */ +static void transport_resolve_complete(struct site *st, + const struct comm_addr *addrs, int naddrs); +static void transport_resolve_complete_tardy(struct site *st, + const struct comm_addr *addrs, int naddrs); static void transport_xmit(struct site *st, transport_peers *peers, struct buffer_if *buf, bool_t candebug); @@ -229,11 +277,12 @@ struct site { /* configuration information */ string_t localname; string_t remotename; - bool_t peer_mobile; /* Mobile client support */ + bool_t local_mobile, peer_mobile; /* Mobile client support */ int32_t transport_peers_max; string_t tunname; /* localname<->remotename by default, used in logs */ - string_t address; /* DNS name for bootstrapping, optional */ + cstring_t *addresses; /* DNS name or address(es) for bootstrapping, optional */ int remoteport; /* Port for bootstrapping, optional */ + uint32_t mtu_target; struct netlink_if *netlink; struct comm_if **comms; int ncomms; @@ -266,6 +315,10 @@ struct site { uint32_t state; uint64_t now; /* Most recently seen time */ bool_t allow_send_prod; + int resolving_count; + int resolving_n_results_all; + int resolving_n_results_stored; + struct comm_addr resolving_results[MAX_PEER_ADDRS]; /* The currently established session */ struct data_key current; @@ -282,6 +335,7 @@ struct site { timeout before we can listen for another setup packet); perhaps we should keep a list of 'bad' sources for setup packets. */ uint32_t remote_capabilities; + uint16_t remote_adv_mtu; struct transform_if *chosen_transform; uint32_t setup_session_id; transport_peers setup_peers; @@ -297,35 +351,75 @@ struct site { struct transform_inst_if *new_transform; /* For key setup/verify */ }; +static uint32_t event_log_priority(struct site *st, uint32_t event) +{ + if (!(event&st->log_events)) + return 0; + switch(event) { + case LOG_UNEXPECTED: return M_INFO; + case LOG_SETUP_INIT: return M_INFO; + case LOG_SETUP_TIMEOUT: return M_NOTICE; + case LOG_ACTIVATE_KEY: return M_INFO; + case LOG_TIMEOUT_KEY: return M_INFO; + case LOG_SEC: return M_SECURITY; + case LOG_STATE: return M_DEBUG; + case LOG_DROP: return M_DEBUG; + case LOG_DUMP: return M_DEBUG; + case LOG_ERROR: return M_ERR; + case LOG_PEER_ADDRS: return M_DEBUG; + default: return M_ERR; + } +} + +static void vslog(struct site *st, uint32_t event, cstring_t msg, va_list ap) +FORMAT(printf,3,0); +static void vslog(struct site *st, uint32_t event, cstring_t msg, va_list ap) +{ + uint32_t class; + + class=event_log_priority(st, event); + if (class) { + slilog_part(st->log,class,"%s: ",st->tunname); + vslilog_part(st->log,class,msg,ap); + slilog_part(st->log,class,"\n"); + } +} + static void slog(struct site *st, uint32_t event, cstring_t msg, ...) FORMAT(printf,3,4); static void slog(struct site *st, uint32_t event, cstring_t msg, ...) { va_list ap; - char buf[240]; - uint32_t class; - va_start(ap,msg); + vslog(st,event,msg,ap); + va_end(ap); +} - if (event&st->log_events) { - switch(event) { - case LOG_UNEXPECTED: class=M_INFO; break; - case LOG_SETUP_INIT: class=M_INFO; break; - case LOG_SETUP_TIMEOUT: class=M_NOTICE; break; - case LOG_ACTIVATE_KEY: class=M_INFO; break; - case LOG_TIMEOUT_KEY: class=M_INFO; break; - case LOG_SEC: class=M_SECURITY; break; - case LOG_STATE: class=M_DEBUG; break; - case LOG_DROP: class=M_DEBUG; break; - case LOG_DUMP: class=M_DEBUG; break; - case LOG_ERROR: class=M_ERR; break; - case LOG_PEER_ADDRS: class=M_DEBUG; break; - default: class=M_ERR; break; - } +static void logtimeout(struct site *st, const char *fmt, ...) +FORMAT(printf,2,3); +static void logtimeout(struct site *st, const char *fmt, ...) +{ + uint32_t class=event_log_priority(st,LOG_SETUP_TIMEOUT); + if (!class) + return; - vsnprintf(buf,sizeof(buf),msg,ap); - slilog(st->log,class,"%s: %s",st->tunname,buf); + va_list ap; + va_start(ap,fmt); + + slilog_part(st->log,class,"%s: ",st->tunname); + vslilog_part(st->log,class,fmt,ap); + + const char *delim; + int i; + for (i=0, delim=" (tried "; + isetup_peers.npeers; + i++, delim=", ") { + transport_peer *peer=&st->setup_peers.peers[i]; + const char *s=comm_addr_to_string(&peer->addr); + slilog_part(st->log,class,"%s%s",delim,s); } + + slilog_part(st->log,class,")\n"); va_end(ap); } @@ -339,6 +433,7 @@ static bool_t initiate_key_setup(struct site *st, cstring_t reason, const struct comm_addr *prod_hint); static void enter_state_run(struct site *st); static bool_t enter_state_resolve(struct site *st); +static void decrement_resolving_count(struct site *st, int by); static bool_t enter_new_state(struct site *st,uint32_t next); static void enter_state_wait(struct site *st); static void activate_new_key(struct site *st); @@ -386,6 +481,14 @@ static void dispose_transform(struct transform_inst_if **transform_var) type=buf_unprepend_uint32((b)); \ if (type!=(t)) return False; } while(0) +static _Bool type_is_msg34(uint32_t type) +{ + return + type == LABEL_MSG3 || + type == LABEL_MSG3BIS || + type == LABEL_MSG4; +} + struct parsedname { int32_t len; uint8_t *name; @@ -399,6 +502,7 @@ struct msg { struct parsedname remote; struct parsedname local; uint32_t remote_capabilities; + uint16_t remote_mtu; int capab_transformnum; uint8_t *nR; uint8_t *nL; @@ -416,9 +520,10 @@ static void set_new_transform(struct site *st, char *pk) assert(st->sharedsecretlen); if (st->sharedsecretlen > st->sharedsecretallocd) { st->sharedsecretallocd=st->sharedsecretlen; - st->sharedsecret=realloc(st->sharedsecret,st->sharedsecretallocd); + st->sharedsecret=safe_realloc_ary(st->sharedsecret,1, + st->sharedsecretallocd, + "site:sharedsecret"); } - if (!st->sharedsecret) fatal_perror("site:sharedsecret"); /* Generate the shared key */ st->dh->makeshared(st->dh->st,st->dhsecret,st->dh->len,pk, @@ -487,12 +592,15 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what) if ((st->local_capabilities & CAPAB_EARLY) || (type != LABEL_MSG1)) { buf_append_uint32(&st->buffer,st->local_capabilities); } + if (type_is_msg34(type)) { + buf_append_uint16(&st->buffer,st->mtu_target); + } append_string_xinfo_done(&st->buffer,&xia); buf_append_string(&st->buffer,st->remotename); - memcpy(buf_append(&st->buffer,NONCELEN),st->localN,NONCELEN); + BUF_ADD_OBJ(append,&st->buffer,st->localN); if (type==LABEL_MSG1) return True; - memcpy(buf_append(&st->buffer,NONCELEN),st->remoteN,NONCELEN); + BUF_ADD_OBJ(append,&st->buffer,st->remoteN); if (type==LABEL_MSG2) return True; if (hacky_par_mid_failnow()) return False; @@ -542,10 +650,15 @@ static bool_t unpick_msg(struct site *st, uint32_t type, CHECK_TYPE(msg,type); if (!unpick_name(msg,&m->remote)) return False; m->remote_capabilities=0; + m->remote_mtu=0; if (m->remote.extrainfo.size) { CHECK_AVAIL(&m->remote.extrainfo,4); m->remote_capabilities=buf_unprepend_uint32(&m->remote.extrainfo); } + if (type_is_msg34(type) && m->remote.extrainfo.size) { + CHECK_AVAIL(&m->remote.extrainfo,2); + m->remote_mtu=buf_unprepend_uint16(&m->remote.extrainfo); + } if (!unpick_name(msg,&m->local)) return False; if (type==LABEL_PROD) { CHECK_EMPTY(msg); @@ -639,7 +752,6 @@ static bool_t process_msg1(struct site *st, struct buffer_if *msg1, process an incoming MSG1, and that the MSG1 has correct values of A and B. */ - transport_record_peer(st,&st->setup_peers,src,"msg1"); st->setup_session_id=m->source; st->remote_capabilities=m->remote_capabilities; memcpy(st->remoteN,m->nR,NONCELEN); @@ -703,12 +815,34 @@ static bool_t generate_msg3(struct site *st) "site:MSG3"); } +static bool_t process_msg3_msg4(struct site *st, struct msg *m) +{ + uint8_t *hash; + void *hst; + + /* Check signature and store g^x mod m */ + hash=safe_malloc(st->hash->len, "process_msg3_msg4"); + hst=st->hash->init(); + st->hash->update(hst,m->hashstart,m->hashlen); + st->hash->final(hst,hash); + /* Terminate signature with a '0' - cheating, but should be ok */ + m->sig[m->siglen]=0; + if (!st->pubkey->check(st->pubkey->st,hash,st->hash->len,m->sig)) { + slog(st,LOG_SEC,"msg3/msg4 signature failed check!"); + free(hash); + return False; + } + free(hash); + + st->remote_adv_mtu=m->remote_mtu; + + return True; +} + static bool_t process_msg3(struct site *st, struct buffer_if *msg3, const struct comm_addr *src, uint32_t msgtype) { struct msg m; - uint8_t *hash; - void *hst; cstring_t err; assert(msgtype==LABEL_MSG3 || msgtype==LABEL_MSG3BIS); @@ -741,19 +875,8 @@ static bool_t process_msg3(struct site *st, struct buffer_if *msg3, transform_found: st->chosen_transform=ti; - /* Check signature and store g^x mod m */ - hash=safe_malloc(st->hash->len, "process_msg3"); - hst=st->hash->init(); - st->hash->update(hst,m.hashstart,m.hashlen); - st->hash->final(hst,hash); - /* Terminate signature with a '0' - cheating, but should be ok */ - m.sig[m.siglen]=0; - if (!st->pubkey->check(st->pubkey->st,hash,st->hash->len,m.sig)) { - slog(st,LOG_SEC,"msg3 signature failed check!"); - free(hash); + if (!process_msg3_msg4(st,&m)) return False; - } - free(hash); /* Terminate their DH public key with a '0' */ m.pk[m.pklen]=0; @@ -777,8 +900,6 @@ static bool_t process_msg4(struct site *st, struct buffer_if *msg4, const struct comm_addr *src) { struct msg m; - uint8_t *hash; - void *hst; cstring_t err; if (!unpick_msg(st,LABEL_MSG4,msg4,&m)) return False; @@ -787,19 +908,8 @@ static bool_t process_msg4(struct site *st, struct buffer_if *msg4, return False; } - /* Check signature and store g^x mod m */ - hash=safe_malloc(st->hash->len, "process_msg4"); - hst=st->hash->init(); - st->hash->update(hst,m.hashstart,m.hashlen); - st->hash->final(hst,hash); - /* Terminate signature with a '0' - cheating, but should be ok */ - m.sig[m.siglen]=0; - if (!st->pubkey->check(st->pubkey->st,hash,st->hash->len,m.sig)) { - slog(st,LOG_SEC,"msg4 signature failed check!"); - free(hash); + if (!process_msg3_msg4(st,&m)) return False; - } - free(hash); /* Terminate their DH public key with a '0' */ m.pk[m.pklen]=0; @@ -1060,7 +1170,7 @@ static bool_t send_msg(struct site *st) st->retries--; return True; } else if (st->state==SITE_SENTMSG5) { - slog(st,LOG_SETUP_TIMEOUT,"timed out sending MSG5, stashing new key"); + logtimeout(st,"timed out sending MSG5, stashing new key"); /* We stash the key we have produced, in case it turns out that * our peer did see our MSG5 after all and starts using it. */ /* This is a bit like some of activate_new_key */ @@ -1078,51 +1188,124 @@ static bool_t send_msg(struct site *st) enter_state_wait(st); return False; } else { - slog(st,LOG_SETUP_TIMEOUT,"timed out sending key setup packet " + logtimeout(st,"timed out sending key setup packet " "(in state %s)",state_name(st->state)); enter_state_wait(st); return False; } } -static void site_resolve_callback(void *sst, struct in_addr *address) +static void site_resolve_callback(void *sst, const struct comm_addr *addrs, + int stored_naddrs, int all_naddrs, + const char *address, const char *failwhy) { struct site *st=sst; - struct comm_addr ca_buf, *ca_use; - if (st->state!=SITE_RESOLVE) { - slog(st,LOG_UNEXPECTED,"site_resolve_callback called unexpectedly"); - return; - } - if (address) { - FILLZERO(ca_buf); - ca_buf.comm=st->comms[0]; - ca_buf.sin.sin_family=AF_INET; - ca_buf.sin.sin_port=htons(st->remoteport); - ca_buf.sin.sin_addr=*address; - ca_use=&ca_buf; + if (!stored_naddrs) { + slog(st,LOG_ERROR,"resolution of %s failed: %s",address,failwhy); } else { - slog(st,LOG_ERROR,"resolution of %s failed",st->address); - ca_use=0; + slog(st,LOG_PEER_ADDRS,"resolution of %s completed, %d addrs, eg: %s", + address, all_naddrs, comm_addr_to_string(&addrs[0]));; + + int space=st->transport_peers_max-st->resolving_n_results_stored; + int n_tocopy=MIN(stored_naddrs,space); + COPY_ARRAY(st->resolving_results + st->resolving_n_results_stored, + addrs, + n_tocopy); + st->resolving_n_results_stored += n_tocopy; + st->resolving_n_results_all += all_naddrs; } - if (transport_compute_setupinit_peers(st,ca_use,0)) { - enter_new_state(st,SITE_SENTMSG1); - } else { - /* Can't figure out who to try to to talk to */ - slog(st,LOG_SETUP_INIT,"key exchange failed: cannot find peer address"); - enter_state_run(st); + + decrement_resolving_count(st,1); +} + +static void decrement_resolving_count(struct site *st, int by) +{ + assert(st->resolving_count>0); + st->resolving_count-=by; + + if (st->resolving_count) + return; + + /* OK, we are done with them all. Handle combined results. */ + + const struct comm_addr *addrs=st->resolving_results; + int naddrs=st->resolving_n_results_stored; + assert(naddrs<=st->transport_peers_max); + + if (naddrs) { + if (naddrs != st->resolving_n_results_all) { + slog(st,LOG_SETUP_INIT,"resolution of supplied addresses/names" + " yielded too many results (%d > %d), some ignored", + st->resolving_n_results_all, naddrs); + } + slog(st,LOG_STATE,"resolution completed, %d addrs, eg: %s", + naddrs, comm_addr_to_string(&addrs[0]));; + } + + switch (st->state) { + case SITE_RESOLVE: + if (transport_compute_setupinit_peers(st,addrs,naddrs,0)) { + enter_new_state(st,SITE_SENTMSG1); + } else { + /* Can't figure out who to try to to talk to */ + slog(st,LOG_SETUP_INIT, + "key exchange failed: cannot find peer address"); + enter_state_run(st); + } + break; + case SITE_SENTMSG1: case SITE_SENTMSG2: + case SITE_SENTMSG3: case SITE_SENTMSG4: + case SITE_SENTMSG5: + if (naddrs) { + /* We start using the address immediately for data too. + * It's best to store it in st->peers now because we might + * go via SENTMSG5, WAIT, and a MSG0, straight into using + * the new key (without updating the data peer addrs). */ + transport_resolve_complete(st,addrs,naddrs); + } else if (st->local_mobile) { + /* We can't let this rest because we may have a peer + * address which will break in the future. */ + slog(st,LOG_SETUP_INIT,"resolution failed: " + "abandoning key exchange"); + enter_state_wait(st); + } else { + slog(st,LOG_SETUP_INIT,"resolution failed: " + " continuing to use source address of peer's packets" + " for key exchange and ultimately data"); + } + break; + case SITE_RUN: + if (naddrs) { + slog(st,LOG_SETUP_INIT,"resolution completed tardily," + " updating peer address(es)"); + transport_resolve_complete_tardy(st,addrs,naddrs); + } else if (st->local_mobile) { + /* Not very good. We should queue (another) renegotiation + * so that we can update the peer address. */ + st->key_renegotiate_time=st->now+st->wait_timeout; + } else { + slog(st,LOG_SETUP_INIT,"resolution failed: " + " continuing to use source address of peer's packets"); + } + break; + case SITE_WAIT: + case SITE_STOP: + /* oh well */ + break; } } static bool_t initiate_key_setup(struct site *st, cstring_t reason, const struct comm_addr *prod_hint) { + /* Reentrancy hazard: can call enter_new_state/enter_state_* */ if (st->state!=SITE_RUN) return False; slog(st,LOG_SETUP_INIT,"initiating key exchange (%s)",reason); - if (st->address) { - slog(st,LOG_SETUP_INIT,"resolving peer address"); + if (st->addresses) { + slog(st,LOG_SETUP_INIT,"resolving peer address(es)"); return enter_state_resolve(st); - } else if (transport_compute_setupinit_peers(st,0,prod_hint)) { + } else if (transport_compute_setupinit_peers(st,0,0,prod_hint)) { return enter_new_state(st,SITE_SENTMSG1); } slog(st,LOG_SETUP_INIT,"key exchange failed: no address for peer"); @@ -1149,7 +1332,15 @@ static void activate_new_key(struct site *st) transport_peers_copy(st,&st->peers,&st->setup_peers); st->current.remote_session_id=st->setup_session_id; - slog(st,LOG_ACTIVATE_KEY,"new key activated"); + /* Compute the inter-site MTU. This is min( our_mtu, their_mtu ). + * But their mtu be unspecified, in which case we just use ours. */ + uint32_t intersite_mtu= + MIN(st->mtu_target, st->remote_adv_mtu ?: ~(uint32_t)0); + st->netlink->set_mtu(st->netlink->st,intersite_mtu); + + slog(st,LOG_ACTIVATE_KEY,"new key activated" + " (mtu ours=%"PRId32" theirs=%"PRId32" intersite=%"PRId32")", + st->mtu_target, st->remote_adv_mtu, intersite_mtu); enter_state_run(st); } @@ -1193,7 +1384,7 @@ static void set_link_quality(struct site *st) quality=LINK_QUALITY_UP; else if (st->state==SITE_WAIT || st->state==SITE_STOP) quality=LINK_QUALITY_DOWN; - else if (st->address) + else if (st->addresses) quality=LINK_QUALITY_DOWN_CURRENT_ADDRESS; else if (transport_peers_valid(&st->peers)) quality=LINK_QUALITY_DOWN_STALE_ADDRESS; @@ -1211,22 +1402,58 @@ static void enter_state_run(struct site *st) st->setup_session_id=0; transport_peers_clear(st,&st->setup_peers); - memset(st->localN,0,NONCELEN); - memset(st->remoteN,0,NONCELEN); + FILLZERO(st->localN); + FILLZERO(st->remoteN); dispose_transform(&st->new_transform); memset(st->dhsecret,0,st->dh->len); memset(st->sharedsecret,0,st->sharedsecretlen); set_link_quality(st); } +static bool_t ensure_resolving(struct site *st) +{ + /* Reentrancy hazard: may call site_resolve_callback and hence + * enter_new_state, enter_state_* and generate_msg*. */ + if (st->resolving_count) + return True; + + assert(st->addresses); + + /* resolver->request might reentrantly call site_resolve_callback + * which will decrement st->resolving, so we need to increment it + * twice beforehand to prevent decrement from thinking we're + * finished, and decrement it ourselves. Alternatively if + * everything fails then there are no callbacks due and we simply + * set it to 0 and return false.. */ + st->resolving_n_results_stored=0; + st->resolving_n_results_all=0; + st->resolving_count+=2; + const char **addrp=st->addresses; + const char *address; + bool_t anyok=False; + for (; (address=*addrp++); ) { + bool_t ok = st->resolver->request(st->resolver->st,address, + st->remoteport,st->comms[0], + site_resolve_callback,st); + if (ok) + st->resolving_count++; + anyok|=ok; + } + if (!anyok) { + st->resolving_count=0; + return False; + } + decrement_resolving_count(st,2); + return True; +} + static bool_t enter_state_resolve(struct site *st) { + /* Reentrancy hazard! See ensure_resolving. */ state_assert(st,st->state==SITE_RUN); slog(st,LOG_STATE,"entering state RESOLVE"); st->state=SITE_RESOLVE; - st->resolver->request(st->resolver->st,st->address, - site_resolve_callback,st); - return True; + return ensure_resolving(st); } static bool_t enter_new_state(struct site *st, uint32_t next) @@ -1374,7 +1601,7 @@ static int site_beforepoll(void *sst, struct pollfd *fds, int *nfds_io, { struct site *st=sst; - *nfds_io=0; /* We don't use any file descriptors */ + BEFOREPOLL_WANT_FDS(0); /* We don't use any file descriptors */ st->now=*now; /* Work out when our next timeout is. The earlier of 'timeout' or @@ -1492,9 +1719,14 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, if (st->state==SITE_RUN || st->state==SITE_RESOLVE || st->state==SITE_WAIT) { /* We should definitely process it */ + transport_compute_setupinit_peers(st,0,0,source); if (process_msg1(st,buf,source,&named_msg)) { slog(st,LOG_SETUP_INIT,"key setup initiated by peer"); - enter_new_state(st,SITE_SENTMSG2); + bool_t entered=enter_new_state(st,SITE_SENTMSG2); + if (entered && st->addresses && st->local_mobile) + /* We must do this as the very last thing, because + the resolver callback might reenter us. */ + ensure_resolving(st); } else { slog(st,LOG_ERROR,"failed to process incoming msg1"); } @@ -1514,6 +1746,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, "priority => use incoming msg1"); if (process_msg1(st,buf,source,&named_msg)) { BUF_FREE(&st->buffer); /* Free our old message 1 */ + transport_setup_msgok(st,source); enter_new_state(st,SITE_SENTMSG2); } else { slog(st,LOG_ERROR,"failed to process an incoming " @@ -1552,7 +1785,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf, because our peer's forgotten the key */ if (get_uint32(buf->start+4)==st->current.remote_session_id) { bool_t initiated; - initiated = initiate_key_setup(st,"received a NAK",0); + initiated = initiate_key_setup(st,"received a NAK",source); if (!initiated) generate_send_prod(st,source); } else { slog(st,LOG_SEC,"bad incoming NAK"); @@ -1668,6 +1901,23 @@ static void site_phase_hook(void *sst, uint32_t newphase) send_msg7(st,"shutting down"); } +static void site_childpersist_clearkeys(void *sst, uint32_t newphase) +{ + struct site *st=sst; + dispose_transform(&st->current.transform); + dispose_transform(&st->auxiliary_key.transform); + dispose_transform(&st->new_transform); + /* Not much point overwiting the signing key, since we loaded it + from disk, and it is only valid prospectively if at all, + anyway. */ + /* XXX it would be best to overwrite the DH state, because that + _is_ relevant to forward secrecy. However we have no + convenient interface for doing that and in practice gmp has + probably dribbled droppings all over the malloc arena. A good + way to fix this would be to have a privsep child for asymmetric + crypto operations, but that's a task for another day. */ +} + static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, list_t *args) { @@ -1697,7 +1947,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->remotename=dict_read_string(dict, "name", True, "site", loc); st->peer_mobile=dict_read_bool(dict,"mobile",False,"site",loc,False); - bool_t local_mobile= + st->local_mobile= dict_read_bool(dict,"local-mobile",False,"site",loc,False); /* Sanity check (which also allows the 'sites' file to include @@ -1706,14 +1956,14 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, if (strcmp(st->localname,st->remotename)==0) { Message(M_DEBUG,"site %s: local-name==name -> ignoring this site\n", st->localname); - if (st->peer_mobile != local_mobile) + if (st->peer_mobile != st->local_mobile) cfgfatal(loc,"site","site %s's peer-mobile=%d" " but our local-mobile=%d\n", - st->localname, st->peer_mobile, local_mobile); + st->localname, st->peer_mobile, st->local_mobile); free(st); return NULL; } - if (st->peer_mobile && local_mobile) { + if (st->peer_mobile && st->local_mobile) { Message(M_WARNING,"site %s: site is mobile but so are we" " -> ignoring this site\n", st->remotename); free(st); @@ -1750,8 +2000,8 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->random=find_cl_if(dict,"random",CL_RANDOMSRC,True,"site",loc); st->privkey=find_cl_if(dict,"local-key",CL_RSAPRIVKEY,True,"site",loc); - st->address=dict_read_string(dict, "address", False, "site", loc); - if (st->address) + st->addresses=dict_read_string_array(dict,"address",False,"site",loc,0); + if (st->addresses) st->remoteport=dict_read_number(dict,"port",True,"site",loc,0); else st->remoteport=0; st->pubkey=find_cl_if(dict,"key",CL_RSAPUBKEY,True,"site",loc); @@ -1761,7 +2011,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->dh=find_cl_if(dict,"dh",CL_DH,True,"site",loc); st->hash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc); -#define DEFAULT(D) (st->peer_mobile || local_mobile \ +#define DEFAULT(D) (st->peer_mobile || st->local_mobile \ ? DEFAULT_MOBILE_##D : DEFAULT_##D) #define CFG_NUMBER(k,D) dict_read_number(dict,(k),False,"site",loc,DEFAULT(D)); @@ -1769,16 +2019,19 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->setup_retries= CFG_NUMBER("setup-retries", SETUP_RETRIES); st->setup_retry_interval= CFG_NUMBER("setup-timeout", SETUP_RETRY_INTERVAL); st->wait_timeout= CFG_NUMBER("wait-time", WAIT_TIME); + st->mtu_target= dict_read_number(dict,"mtu-target",False,"site",loc,0); st->mobile_peer_expiry= dict_read_number( dict,"mobile-peer-expiry",False,"site",loc,DEFAULT_MOBILE_PEER_EXPIRY); - st->transport_peers_max= !st->peer_mobile ? 1 : dict_read_number( - dict,"mobile-peers-max",False,"site",loc,DEFAULT_MOBILE_PEERS_MAX); + const char *peerskey= st->peer_mobile + ? "mobile-peers-max" : "static-peers-max"; + st->transport_peers_max= dict_read_number( + dict,peerskey,False,"site",loc, st->addresses ? 4 : 3); if (st->transport_peers_max<1 || - st->transport_peers_max>=MAX_MOBILE_PEERS_MAX) { - cfgfatal(loc,"site","mobile-peers-max must be in range 1.." - STRING(MAX_MOBILE_PEERS_MAX) "\n"); + st->transport_peers_max>MAX_PEER_ADDRS) { + cfgfatal(loc,"site", "%s must be in range 1.." + STRING(MAX_PEER_ADDRS) "\n", peerskey); } if (st->key_lifetime < DEFAULT(KEY_RENEGOTIATE_GAP)*2) @@ -1795,6 +2048,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, st->log_events=string_list_to_word(dict_lookup(dict,"log-events"), log_event_table,"site"); + st->resolving_count=0; st->allow_send_prod=0; st->tunname=safe_malloc(strlen(st->localname)+strlen(st->remotename)+5, @@ -1813,7 +2067,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, /* We are interested in poll(), but only for timeouts. We don't have any fds of our own. */ - register_for_poll(st, site_beforepoll, site_afterpoll, 0, "site"); + register_for_poll(st, site_beforepoll, site_afterpoll, "site"); st->timeout=0; st->remote_capabilities=0; @@ -1837,7 +2091,10 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, } /* We need to register the remote networks with the netlink device */ - st->netlink->reg(st->netlink->st, site_outgoing, st); + uint32_t netlink_mtu; /* local virtual interface mtu */ + st->netlink->reg(st->netlink->st, site_outgoing, st, &netlink_mtu); + if (!st->mtu_target) + st->mtu_target=netlink_mtu; for (i=0; incomms; i++) st->comms[i]->request_notify(st->comms[i]->st, st, site_incoming); @@ -1850,6 +2107,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, enter_state_stop(st); add_hook(PHASE_SHUTDOWN,site_phase_hook,st); + add_hook(PHASE_CHILDPERSIST,site_childpersist_clearkeys,st); return new_closure(&st->cl); } @@ -1882,27 +2140,18 @@ static void transport_peers_debug(struct site *st, transport_peers *dst, i++, (argp+=stride?stride:sizeof(*args))) { const struct comm_addr *ca=(void*)argp; slog(st, LOG_PEER_ADDRS, " args: addrs[%d]=%s", - i, ca->comm->addr_to_string(ca->comm->st,ca)); + i, comm_addr_to_string(ca)); } for (i=0; inpeers; i++) { struct timeval diff; timersub(tv_now,&dst->peers[i].last,&diff); const struct comm_addr *ca=&dst->peers[i].addr; slog(st, LOG_PEER_ADDRS, " peers: addrs[%d]=%s T-%ld.%06ld", - i, ca->comm->addr_to_string(ca->comm->st,ca), + i, comm_addr_to_string(ca), (unsigned long)diff.tv_sec, (unsigned long)diff.tv_usec); } } -static int transport_peer_compar(const void *av, const void *bv) { - const transport_peer *a=av; - const transport_peer *b=bv; - /* put most recent first in the array */ - if (timercmp(&a->last, &b->last, <)) return +1; - if (timercmp(&a->last, &b->last, >)) return -11; - return 0; -} - static void transport_peers_expire(struct site *st, transport_peers *peers) { /* peers must be sorted first */ int previous_peers=peers->npeers; @@ -1916,60 +2165,99 @@ static void transport_peers_expire(struct site *st, transport_peers *peers) { transport_peers_debug(st,peers,"expire", 0,0,0); } -static void transport_record_peer(struct site *st, transport_peers *peers, - const struct comm_addr *addr, const char *m) { - int slot, changed=0; +static bool_t transport_peer_record_one(struct site *st, transport_peers *peers, + const struct comm_addr *ca, + const struct timeval *tv) { + /* returns false if output is full */ + int search; - for (slot=0; slotnpeers; slot++) - if (!memcmp(&peers->peers[slot].addr, addr, sizeof(*addr))) - goto found; + if (peers->npeers >= st->transport_peers_max) + return 0; - changed=1; - if (peers->npeers==st->transport_peers_max) - slot=st->transport_peers_max; - else - slot=peers->npeers++; + for (search=0; searchnpeers; search++) + if (comm_addr_equal(&peers->peers[search].addr, ca)) + return 1; - found: - peers->peers[slot].addr=*addr; - peers->peers[slot].last=*tv_now; + peers->peers[peers->npeers].addr = *ca; + peers->peers[peers->npeers].last = *tv; + peers->npeers++; + return 1; +} - if (peers->npeers>1) - qsort(peers->peers, peers->npeers, - sizeof(*peers->peers), transport_peer_compar); +static void transport_record_peers(struct site *st, transport_peers *peers, + const struct comm_addr *addrs, int naddrs, + const char *m) { + /* We add addrs into peers. The new entries end up at the front + * and displace entries towards the end (perhaps even off the + * end). Any existing matching entries are moved up to the front. + * + * Caller must first call transport_peers_expire. */ - if (changed || peers->npeers!=1) - transport_peers_debug(st,peers,m, 1,addr,0); - transport_peers_expire(st, peers); + if (naddrs==1 && peers->npeers>=1 && + comm_addr_equal(&addrs[0], &peers->peers[0].addr)) { + /* optimisation, also avoids debug for trivial updates */ + peers->peers[0].last = *tv_now; + return; + } + + int old_npeers=peers->npeers; + transport_peer old_peers[old_npeers]; + COPY_ARRAY(old_peers,peers->peers,old_npeers); + + peers->npeers=0; + int i; + for (i=0; iaddr, &old->last)) + break; + } + + transport_peers_debug(st,peers,m, naddrs,addrs,0); } -static bool_t transport_compute_setupinit_peers(struct site *st, - const struct comm_addr *configured_addr /* 0 if none or not found */, - const struct comm_addr *prod_hint_addr /* 0 if none */) { +static void transport_expire_record_peers(struct site *st, + transport_peers *peers, + const struct comm_addr *addrs, + int naddrs, const char *m) { + /* Convenience function */ + transport_peers_expire(st,peers); + transport_record_peers(st,peers,addrs,naddrs,m); +} - if (!configured_addr && !prod_hint_addr && +static bool_t transport_compute_setupinit_peers(struct site *st, + const struct comm_addr *configured_addrs /* 0 if none or not found */, + int n_configured_addrs /* 0 if none or not found */, + const struct comm_addr *incoming_packet_addr /* 0 if none */) { + if (!n_configured_addrs && !incoming_packet_addr && !transport_peers_valid(&st->peers)) return False; slog(st,LOG_SETUP_INIT, - "using:%s%s %d old peer address(es)", - configured_addr ? " configured address;" : "", - prod_hint_addr ? " PROD hint address;" : "", + "using: %d configured addr(s);%s %d old peer addrs(es)", + n_configured_addrs, + incoming_packet_addr ? " incoming packet address;" : "", st->peers.npeers); - /* Non-mobile peers havve st->peers.npeers==0 or ==1, since they - * have transport_peers_max==1. The effect is that this code - * always uses the configured address if supplied, or otherwise - * the address of the incoming PROD, or the existing data peer if - * one exists; this is as desired. */ + /* Non-mobile peers try addresses until one is plausible. The + * effect is that this code always tries first the configured + * address if supplied, or otherwise the address of the incoming + * PROD, or finally the existing data peer if one exists; this is + * as desired. */ transport_peers_copy(st,&st->setup_peers,&st->peers); + transport_peers_expire(st,&st->setup_peers); - if (prod_hint_addr) - transport_record_peer(st,&st->setup_peers,prod_hint_addr,"prod"); + if (incoming_packet_addr) + transport_record_peers(st,&st->setup_peers, + incoming_packet_addr,1, "incoming"); - if (configured_addr) - transport_record_peer(st,&st->setup_peers,configured_addr,"setupinit"); + if (n_configured_addrs) + transport_record_peers(st,&st->setup_peers, + configured_addrs,n_configured_addrs, "setupinit"); assert(transport_peers_valid(&st->setup_peers)); return True; @@ -1977,11 +2265,11 @@ static bool_t transport_compute_setupinit_peers(struct site *st, static void transport_setup_msgok(struct site *st, const struct comm_addr *a) { if (st->peer_mobile) - transport_record_peer(st,&st->setup_peers,a,"setupmsg"); + transport_expire_record_peers(st,&st->setup_peers,a,1,"setupmsg"); } static void transport_data_msgok(struct site *st, const struct comm_addr *a) { if (st->peer_mobile) - transport_record_peer(st,&st->peers,a,"datamsg"); + transport_expire_record_peers(st,&st->peers,a,1,"datamsg"); } static int transport_peers_valid(transport_peers *peers) { @@ -1994,20 +2282,84 @@ static void transport_peers_clear(struct site *st, transport_peers *peers) { static void transport_peers_copy(struct site *st, transport_peers *dst, const transport_peers *src) { dst->npeers=src->npeers; - memcpy(dst->peers, src->peers, sizeof(*dst->peers) * dst->npeers); + COPY_ARRAY(dst->peers, src->peers, dst->npeers); transport_peers_debug(st,dst,"copy", src->npeers, &src->peers->addr, sizeof(*src->peers)); } +static void transport_resolve_complete(struct site *st, + const struct comm_addr *addrs, + int naddrs) { + transport_expire_record_peers(st,&st->peers,addrs,naddrs, + "resolved data"); + transport_expire_record_peers(st,&st->setup_peers,addrs,naddrs, + "resolved setup"); +} + +static void transport_resolve_complete_tardy(struct site *st, + const struct comm_addr *addrs, + int naddrs) { + transport_expire_record_peers(st,&st->peers,addrs,naddrs, + "resolved tardily"); +} + +static void transport_peers__copy_by_mask(transport_peer *out, int *nout_io, + unsigned mask, + const transport_peers *inp) { + /* out and in->peers may be the same region, or nonoverlapping */ + const transport_peer *in=inp->peers; + int slot; + for (slot=0; slotnpeers; slot++) { + if (!(mask & (1U << slot))) + continue; + if (!(out==in && slot==*nout_io)) + COPY_OBJ(out[*nout_io], in[slot]); + (*nout_io)++; + } +} + void transport_xmit(struct site *st, transport_peers *peers, struct buffer_if *buf, bool_t candebug) { int slot; transport_peers_expire(st, peers); + unsigned failed=0; /* bitmask */ + assert(MAX_PEER_ADDRS < sizeof(unsigned)*CHAR_BIT); + + int nfailed=0; for (slot=0; slotnpeers; slot++) { transport_peer *peer=&peers->peers[slot]; if (candebug) dump_packet(st, buf, &peer->addr, False); - peer->addr.comm->sendmsg(peer->addr.comm->st, buf, &peer->addr); + bool_t ok = + peer->addr.comm->sendmsg(peer->addr.comm->st, buf, &peer->addr); + if (!ok) { + failed |= 1U << slot; + nfailed++; + } + if (ok && !st->peer_mobile) + break; + } + /* Now we need to demote/delete failing addrs: if we are mobile we + * merely demote them; otherwise we delete them. */ + if (st->local_mobile) { + unsigned expected = ((1U << nfailed)-1) << (peers->npeers-nfailed); + /* `expected' has all the failures at the end already */ + if (failed != expected) { + int fslot=0; + transport_peer failedpeers[nfailed]; + transport_peers__copy_by_mask(failedpeers, &fslot, failed,peers); + assert(fslot == nfailed); + int wslot=0; + transport_peers__copy_by_mask(peers->peers,&wslot,~failed,peers); + assert(wslot+nfailed == peers->npeers); + COPY_ARRAY(peers->peers+wslot, failedpeers, nfailed); + } + } else { + if (failed && peers->npeers > 1) { + int wslot=0; + transport_peers__copy_by_mask(peers->peers,&wslot,~failed,peers); + peers->npeers=wslot; + } } }