X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=secnet.git;a=blobdiff_plain;f=site.c;h=31900cf44420d44bc2fa8bb5398bf107bd44da79;hp=f035e79e08ea1c11890a4776e5deebd210884fdb;hb=16f21d687de8d4f071df36aa7c2fd4b57ed53f88;hpb=ac570433618f9324d99d0fbb400cd6b3cc95aecc diff --git a/site.c b/site.c index f035e79..31900cf 100644 --- a/site.c +++ b/site.c @@ -41,6 +41,7 @@ #define DEFAULT_MOBILE_PEER_EXPIRY (2*60) /* [s] */ #define DEFAULT_MOBILE_PEERS_MAX 3 /* send at most this many copies (default) */ +#define DEFAULT_STATIC_PEERS_MAX 3 /* retain at most this many peer addresses */ /* Each site can be in one of several possible states. */ @@ -144,9 +145,6 @@ static struct flagstr log_event_table[]={ /* Details of "mobile peer" semantics: - | Note: this comment is wishful thinking right now. It will be - | implemented in subsequent commits. - - We use the same data structure for the different configurations, but manage it with different algorithms. @@ -220,8 +218,6 @@ static struct flagstr log_event_table[]={ */ -#define MAX_MOBILE_PEERS_MAX 5 /* send at most this many copies, compiled max */ - typedef struct { struct timeval last; struct comm_addr addr; @@ -231,7 +227,7 @@ 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 */ @@ -249,7 +245,8 @@ static void transport_data_msgok(struct site *st, const struct comm_addr *a); * 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 *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 @@ -260,9 +257,9 @@ static bool_t transport_compute_setupinit_peers(struct site *st, * 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 *a); + const struct comm_addr *addrs, int naddrs); static void transport_resolve_complete_tardy(struct site *st, - const struct comm_addr *ca_use); + const struct comm_addr *addrs, int naddrs); static void transport_xmit(struct site *st, transport_peers *peers, struct buffer_if *buf, bool_t candebug); @@ -1195,30 +1192,23 @@ static bool_t send_msg(struct site *st) } } -static void site_resolve_callback(void *sst, struct in_addr *address) +static void site_resolve_callback(void *sst, const struct comm_addr *addrs, + int naddrs) { struct site *st=sst; - struct comm_addr ca_buf, *ca_use; st->resolving=False; - 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; - slog(st,LOG_STATE,"resolution of %s completed: %s", - st->address, comm_addr_to_string(ca_use));; + if (naddrs) { + slog(st,LOG_STATE,"resolution of %s completed, %d addrs, eg: %s", + st->address, naddrs, comm_addr_to_string(&addrs[0]));; } else { slog(st,LOG_ERROR,"resolution of %s failed",st->address); - ca_use=0; } switch (st->state) { case SITE_RESOLVE: - if (transport_compute_setupinit_peers(st,ca_use,0)) { + 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 */ @@ -1230,12 +1220,12 @@ static void site_resolve_callback(void *sst, struct in_addr *address) case SITE_SENTMSG1: case SITE_SENTMSG2: case SITE_SENTMSG3: case SITE_SENTMSG4: case SITE_SENTMSG5: - if (ca_use) { + 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,ca_use); + 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. */ @@ -1250,10 +1240,10 @@ static void site_resolve_callback(void *sst, struct in_addr *address) } break; case SITE_RUN: - if (ca_use) { + if (naddrs) { slog(st,LOG_SETUP_INIT,"resolution of %s completed tardily," " updating peer address(es)",st->address); - transport_resolve_complete_tardy(st,ca_use); + 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. */ @@ -1280,7 +1270,7 @@ static bool_t initiate_key_setup(struct site *st, cstring_t reason, if (st->address) { slog(st,LOG_SETUP_INIT,"resolving peer address"); 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"); @@ -1400,6 +1390,7 @@ static bool_t ensure_resolving(struct site *st) * case we have to clear ->resolving again. */ st->resolving=True; bool_t ok = st->resolver->request(st->resolver->st,st->address, + st->remoteport,st->comms[0], site_resolve_callback,st); if (!ok) st->resolving=False; @@ -1679,7 +1670,7 @@ 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,source); + 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"); bool_t entered=enter_new_state(st,SITE_SENTMSG2); @@ -1967,12 +1958,14 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context, 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,DEFAULT_MOBILE_PEERS_MAX); 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) @@ -2092,13 +2085,9 @@ static void transport_peers_debug(struct site *st, transport_peers *dst, } } -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 bool_t transport_addrs_equal(const struct comm_addr *a, + const struct comm_addr *b) { + return !memcmp(a,b,sizeof(*a)); } static void transport_peers_expire(struct site *st, transport_peers *peers) { @@ -2114,73 +2103,105 @@ 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-1; - else - slot=peers->npeers++; + for (search=0; searchnpeers; search++) + if (transport_addrs_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 && + transport_addrs_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 *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 (!configured_addr && !incoming_packet_addr && + 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;" : "", + "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 have 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 (incoming_packet_addr) - transport_record_peer(st,&st->setup_peers,incoming_packet_addr, - "incoming"); + 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; } 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"); + if (st->peer_mobile) { + transport_peers_expire(st,&st->setup_peers); + transport_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"); + if (st->peer_mobile) { + transport_peers_expire(st,&st->setup_peers); + transport_record_peers(st,&st->peers,a,1,"datamsg"); + } } static int transport_peers_valid(transport_peers *peers) { @@ -2193,31 +2214,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 *ca_use) { - transport_record_peer(st,&st->peers,ca_use,"resolved data"); - transport_record_peer(st,&st->setup_peers,ca_use,"resolved setup"); + const struct comm_addr *addrs, + int naddrs) { + transport_peers_expire(st,&st->peers); + transport_record_peers(st,&st->peers,addrs,naddrs,"resolved data"); + transport_peers_expire(st,&st->setup_peers); + transport_record_peers(st,&st->setup_peers,addrs,naddrs,"resolved setup"); } static void transport_resolve_complete_tardy(struct site *st, - const struct comm_addr *ca_use) { - transport_record_peer(st,&st->peers,ca_use,"resolved tardily"); + const struct comm_addr *addrs, + int naddrs) { + transport_peers_expire(st,&st->peers); + transport_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; + } } }