chiark / gitweb /
resolver: construct comm_addr; honour multiple addresses from the resolver
[secnet.git] / site.c
diff --git a/site.c b/site.c
index 297bb74..cea331e 100644 (file)
--- a/site.c
+++ b/site.c
@@ -143,48 +143,84 @@ static struct flagstr log_event_table[]={
 /***** 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).
+
+   - 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.
+
+   If neither end is mobile:
+
+   - When peer initiated the key exchange, we use the incoming packet
+     address.
+
+   - When we initiate the key exchange, we try configured addresses
+     until we get one which isn't unsupported then fixate on that.
 
-   - Key setup peers are slightly more complicated.
+   - When we complete a key setup, we replace the data transport peers
+     with those from the key setup.
 
-     Whenever we receive and successfully process a key exchange
-     packet, we record the peer.
+   If we are mobile:
 
-     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).
+   - 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.
 
-     (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.)
+   - Use only configured addresses.  (Except, that if our peer
+     initiated a key exchange we use the incoming packet address until
+     our name resolution completes.)
 
-   "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.
+   - 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.
 
    */
 
-#define MAX_MOBILE_PEERS_MAX 5 /* send at most this many copies, compiled max */
+#define MAX_MOBILE_PEERS_MAX MAX_PEER_ADDRS /* send at most this many copies */
 
 typedef struct {
     struct timeval last;
@@ -213,7 +249,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
@@ -224,9 +261,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);
@@ -1159,30 +1196,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 */
@@ -1194,12 +1224,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. */
@@ -1214,10 +1244,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. */
@@ -1244,7 +1274,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");
@@ -1364,6 +1394,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;
@@ -1643,7 +1674,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);
@@ -2056,13 +2087,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) {
@@ -2078,44 +2105,70 @@ 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;
+}
 
- found:
-    peers->peers[slot].addr=*addr;
-    peers->peers[slot].last=*tv_now;
+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 (peers->npeers>1)
-       qsort(peers->peers, peers->npeers,
-             sizeof(*peers->peers), transport_peer_compar);
+    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;
+    }
 
-    if (changed || peers->npeers!=1)
-       transport_peers_debug(st,peers,m, 1,addr,0);
-    transport_peers_expire(st, peers);
+    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; 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;
+    }
+
+    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);
 
@@ -2126,25 +2179,31 @@ static bool_t transport_compute_setupinit_peers(struct site *st,
      * 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) {
@@ -2157,31 +2216,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; 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;
+       }
     }
 }