chiark / gitweb /
string_item_to_iaddr: Actually set port if !CONFIG_IPV6
[secnet.git] / site.c
diff --git a/site.c b/site.c
index b19388dd7c61a10eb794c3d20c4d8bd658dfa712..756ad357775fbab3f359cad38cf4649657523921 100644 (file)
--- 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. */
 
@@ -144,9 +143,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 +216,6 @@ static struct flagstr log_event_table[]={
 
    */
 
-#define MAX_MOBILE_PEERS_MAX MAX_PEER_ADDRS /* send at most this many copies */
-
 typedef struct {
     struct timeval last;
     struct comm_addr addr;
@@ -231,7 +225,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 */
@@ -286,7 +280,7 @@ struct site {
     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;
@@ -321,7 +315,10 @@ struct site {
     uint32_t state;
     uint64_t now; /* Most recently seen time */
     bool_t allow_send_prod;
-    bool_t resolving;
+    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;
@@ -436,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);
@@ -522,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,
@@ -1197,22 +1196,51 @@ static bool_t send_msg(struct site *st)
 }
 
 static void site_resolve_callback(void *sst, const struct comm_addr *addrs,
-                                 int naddrs, int was_naddrs)
+                                 int stored_naddrs, int all_naddrs,
+                                 const char *address, const char *failwhy)
 {
     struct site *st=sst;
 
-    st->resolving=False;
+    if (!stored_naddrs) {
+       slog(st,LOG_ERROR,"resolution of %s failed: %s",address,failwhy);
+    } else {
+       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;
+    }
+
+    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) {
-       slog(st,LOG_STATE,"resolution of %s completed, %d addrs, eg: %s",
-            st->address, was_naddrs, comm_addr_to_string(&addrs[0]));;
-       if (naddrs != was_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",
-                was_naddrs, naddrs);
+                st->resolving_n_results_all, naddrs);
        }
-    } else {
-       slog(st,LOG_ERROR,"resolution of %s failed",st->address);
+       slog(st,LOG_STATE,"resolution completed, %d addrs, eg: %s",
+            naddrs, comm_addr_to_string(&addrs[0]));;
     }
 
     switch (st->state) {
@@ -1238,29 +1266,27 @@ static void site_resolve_callback(void *sst, const struct comm_addr *addrs,
        } 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 of %s failed: "
-                "abandoning key exchange",st->address);
+           slog(st,LOG_SETUP_INIT,"resolution failed: "
+                "abandoning key exchange");
            enter_state_wait(st);
        } else {
-           slog(st,LOG_SETUP_INIT,"resolution of %s failed: "
+           slog(st,LOG_SETUP_INIT,"resolution failed: "
                 " continuing to use source address of peer's packets"
-                " for key exchange and ultimately data",
-                st->address);
+                " for key exchange and ultimately data");
        }
        break;
     case SITE_RUN:
        if (naddrs) {
-           slog(st,LOG_SETUP_INIT,"resolution of %s completed tardily,"
-                " updating peer address(es)",st->address);
+           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 of %s failed: "
-                " continuing to use source address of peer's packets",
-                st->address);
+           slog(st,LOG_SETUP_INIT,"resolution failed: "
+                " continuing to use source address of peer's packets");
        }
        break;
     case SITE_WAIT:
@@ -1276,8 +1302,8 @@ static bool_t initiate_key_setup(struct site *st, cstring_t reason,
     /* 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,0,prod_hint)) {
        return enter_new_state(st,SITE_SENTMSG1);
@@ -1358,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;
@@ -1388,23 +1414,37 @@ 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)
+    if (st->resolving_count)
         return True;
 
-    assert(st->address);
+    assert(st->addresses);
 
     /* resolver->request might reentrantly call site_resolve_callback
-     * which will clear st->resolving, so we need to set it beforehand
-     * rather than afterwards; also, it might return False, in which
-     * 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;
-
-    return ok;
+     * 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)
@@ -1561,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
@@ -1683,7 +1723,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
            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);
-               if (entered && st->address && st->local_mobile)
+               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);
@@ -1861,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)
 {
@@ -1870,7 +1927,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     dict_t *dict;
     int i;
 
-    st=safe_malloc(sizeof(*st),"site_apply");
+    NEW(st);
 
     st->cl.description="site";
     st->cl.type=CL_SITE;
@@ -1923,7 +1980,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     if (!things##_cfg)                                                 \
        cfgfatal(loc,"site","closure list \"%s\" not found\n",dictkey); \
     st->nthings=list_length(things##_cfg);                             \
-    st->things=safe_malloc_ary(sizeof(*st->things),st->nthings,dictkey "s"); \
+    NEW_ARY(st->things,st->nthings);                                   \
     assert(st->nthings);                                               \
     for (i=0; i<st->nthings; i++) {                                    \
        item_t *item=list_elem(things##_cfg,i);                         \
@@ -1943,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);
@@ -1967,12 +2024,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, 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)
@@ -1989,7 +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=False;
+    st->resolving_count=0;
     st->allow_send_prod=0;
 
     st->tunname=safe_malloc(strlen(st->localname)+strlen(st->remotename)+5,
@@ -2008,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;
@@ -2048,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);
 }
@@ -2092,11 +2152,6 @@ static void transport_peers_debug(struct site *st, transport_peers *dst,
     }
 }
 
-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) {
     /* peers must be sorted first */
     int previous_peers=peers->npeers;
@@ -2120,7 +2175,7 @@ static bool_t transport_peer_record_one(struct site *st, transport_peers *peers,
        return 0;
 
     for (search=0; search<peers->npeers; search++)
-       if (transport_addrs_equal(&peers->peers[search].addr, ca))
+       if (comm_addr_equal(&peers->peers[search].addr, ca))
            return 1;
 
     peers->peers[peers->npeers].addr = *ca;
@@ -2139,7 +2194,7 @@ static void transport_record_peers(struct site *st, transport_peers *peers,
      * Caller must first call transport_peers_expire. */
 
     if (naddrs==1 && peers->npeers>=1 &&
-       transport_addrs_equal(&addrs[0], &peers->peers[0].addr)) {
+       comm_addr_equal(&addrs[0], &peers->peers[0].addr)) {
        /* optimisation, also avoids debug for trivial updates */
        peers->peers[0].last = *tv_now;
        return;
@@ -2187,11 +2242,11 @@ static bool_t transport_compute_setupinit_peers(struct site *st,
         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);
@@ -2268,7 +2323,7 @@ void transport_xmit(struct site *st, transport_peers *peers,
     int slot;
     transport_peers_expire(st, peers);
     unsigned failed=0; /* bitmask */
-    assert(MAX_MOBILE_PEERS_MAX < sizeof(unsigned)*CHAR_BIT);
+    assert(MAX_PEER_ADDRS < sizeof(unsigned)*CHAR_BIT);
 
     int nfailed=0;
     for (slot=0; slot<peers->npeers; slot++) {