/***** TRANSPORT PEERS declarations *****/
/* 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.
- - 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).
- - Outgoing packets are sent to every recorded peer in the
- applicable list.
+ - 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.
- - 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
+ - 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.)
+
+ - 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.
+
+ If neither end is mobile:
+
+ - When peer initiated the key exchange, we use the incoming packet
+ address.
- - Key setup peers are slightly more complicated.
+ - When we initiate the key exchange, we try configured addresses
+ until we get one which isn't unsupported then fixate on that.
- Whenever we receive and successfully process a key exchange
- packet, we record the peer.
+ - When we complete a key setup, we replace the data transport peers
+ with those from the key setup.
- 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).
+ If we are mobile:
- (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.)
+ - 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.
- "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.
+ - 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.
+
+ - When we complete a key setup, we replace the data transport peers
+ with those from the key setup.
*/
* 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
* 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);
static void site_resolve_callback(void *sst, struct in_addr *address)
{
struct site *st=sst;
- struct comm_addr ca_buf, *ca_use;
+ struct comm_addr ca_buf;
+ const struct comm_addr *addrs;
+ int naddrs;
st->resolving=False;
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;
+ addrs=&ca_buf;
+ naddrs=1;
slog(st,LOG_STATE,"resolution of %s completed: %s",
- st->address, comm_addr_to_string(ca_use));;
+ st->address, comm_addr_to_string(&addrs[0]));;
} else {
slog(st,LOG_ERROR,"resolution of %s failed",st->address);
- ca_use=0;
+ addrs=0;
+ naddrs=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 */
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. */
}
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. */
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");
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);
}
}
-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) {
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; slot<peers->npeers; 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; search<peers->npeers; search++)
+ if (transport_addrs_equal(&peers->peers[search].addr, ca))
+ return 1;
+
+ peers->peers[peers->npeers].addr = *ca;
+ peers->peers[peers->npeers].last = *tv;
+ peers->npeers++;
+ return 1;
+}
+
+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 (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;
+ }
- found:
- peers->peers[slot].addr=*addr;
- peers->peers[slot].last=*tv_now;
+ int old_npeers=peers->npeers;
+ transport_peer old_peers[old_npeers];
+ COPY_ARRAY(old_peers,peers->peers,old_npeers);
- if (peers->npeers>1)
- qsort(peers->peers, peers->npeers,
- sizeof(*peers->peers), transport_peer_compar);
+ peers->npeers=0;
+ int i;
+ for (i=0; i<naddrs; i++) {
+ if (!transport_peer_record_one(st,peers, &addrs[i], tv_now))
+ break;
+ }
+ for (i=0; i<old_npeers; i++) {
+ const transport_peer *old=&old_peers[i];
+ if (!transport_peer_record_one(st,peers, &old->addr, &old->last))
+ break;
+ }
- if (changed || peers->npeers!=1)
- transport_peers_debug(st,peers,m, 1,addr,0);
- transport_peers_expire(st, peers);
+ transport_peers_debug(st,peers,m, naddrs,addrs,0);
+}
+
+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);
}
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);
* 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");
+ 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) {
}
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_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 *ca_use) {
- transport_record_peer(st,&st->peers,ca_use,"resolved tardily");
+ 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; slot<inp->npeers; 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_MOBILE_PEERS_MAX < sizeof(unsigned)*CHAR_BIT);
+
+ int nfailed=0;
for (slot=0; slot<peers->npeers; 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;
+ }
}
}