chiark / gitweb /
sig: Move hashing into algorithm
[secnet.git] / site.c
diff --git a/site.c b/site.c
index f0548f9072cb3866237a8dca248bf123b656d5c3..df0ab3a46433f41f3f506a00bae751f61cbd5dae 100644 (file)
--- a/site.c
+++ b/site.c
@@ -1,5 +1,24 @@
 /* site.c - manage communication with a remote network site */
 
+/*
+ * This file is part of secnet.
+ * See README for full list of copyright holders.
+ *
+ * secnet is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * secnet is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * version 3 along with secnet; if not, see
+ * https://www.gnu.org/licenses/gpl.html.
+ */
+
 /* The 'site' code doesn't know anything about the structure of the
    packets it's transmitting.  In fact, under the new netlink
    configuration scheme it doesn't need to know anything at all about
 #define SITE_SENTMSG5 7
 #define SITE_WAIT     8
 
+#define CASES_MSG3_KNOWN LABEL_MSG3: case LABEL_MSG3BIS
+
 int32_t site_max_start_pad = 4*4;
 
 static cstring_t state_name(uint32_t state)
@@ -277,6 +298,7 @@ struct site {
 /* configuration information */
     string_t localname;
     string_t remotename;
+    bool_t keepalive;
     bool_t local_mobile, peer_mobile; /* Mobile client support */
     int32_t transport_peers_max;
     string_t tunname; /* localname<->remotename by default, used in logs */
@@ -285,36 +307,37 @@ struct site {
     uint32_t mtu_target;
     struct netlink_if *netlink;
     struct comm_if **comms;
+    struct comm_clientinfo **commclientinfos;
     int ncomms;
     struct resolver_if *resolver;
     struct log_if *log;
     struct random_if *random;
-    struct rsaprivkey_if *privkey;
-    struct rsapubkey_if *pubkey;
+    struct sigprivkey_if *privkey;
+    struct sigpubkey_if *pubkey;
     struct transform_if **transforms;
     int ntransforms;
     struct dh_if *dh;
-    struct hash_if *hash;
 
     uint32_t index; /* Index of this site */
+    uint32_t early_capabilities;
     uint32_t local_capabilities;
     int32_t setup_retries; /* How many times to send setup packets */
     int32_t setup_retry_interval; /* Initial timeout for setup packets */
-    int32_t wait_timeout; /* How long to wait if setup unsuccessful */
+    int32_t wait_timeout_mean; /* How long to wait if setup unsuccessful */
     int32_t mobile_peer_expiry; /* How long to remember 2ary addresses */
     int32_t key_lifetime; /* How long a key lasts once set up */
     int32_t key_renegotiate_time; /* If we see traffic (or a keepalive)
                                      after this time, initiate a new
                                      key exchange */
 
-    bool_t setup_priority; /* Do we have precedence if both sites emit
-                             message 1 simultaneously? */
+    bool_t our_name_later; /* our name > peer name */
     uint32_t log_events;
 
 /* runtime information */
     uint32_t state;
     uint64_t now; /* Most recently seen time */
     bool_t allow_send_prod;
+    bool_t msg1_crossed_logged;
     int resolving_count;
     int resolving_n_results_all;
     int resolving_n_results_stored;
@@ -449,14 +472,15 @@ static bool_t current_valid(struct site *st)
 }
 
 #define DEFINE_CALL_TRANSFORM(fwdrev)                                  \
-static int call_transform_##fwdrev(struct site *st,                    \
+static transform_apply_return                                           \
+call_transform_##fwdrev(struct site *st,                               \
                                   struct transform_inst_if *transform, \
                                   struct buffer_if *buf,               \
                                   const char **errmsg)                 \
 {                                                                      \
     if (!is_transform_valid(transform)) {                              \
        *errmsg="transform not set up";                                 \
-       return 1;                                                       \
+       return transform_apply_err;                                     \
     }                                                                  \
     return transform->fwdrev(transform->st,buf,errmsg);                        \
 }
@@ -483,10 +507,10 @@ static void dispose_transform(struct transform_inst_if **transform_var)
 
 static _Bool type_is_msg34(uint32_t type)
 {
-    return
-       type == LABEL_MSG3 ||
-       type == LABEL_MSG3BIS ||
-       type == LABEL_MSG4;
+    switch (type) {
+       case CASES_MSG3_KNOWN: case LABEL_MSG4: return True;
+       default: return False;
+    }
 }
 
 struct parsedname {
@@ -509,12 +533,23 @@ struct msg {
     int32_t pklen;
     char *pk;
     int32_t hashlen;
-    int32_t siglen;
-    char *sig;
+    struct alg_msg_data sig;
 };
 
-static void set_new_transform(struct site *st, char *pk)
+static int32_t wait_timeout(struct site *st) {
+    int32_t t = st->wait_timeout_mean;
+    int8_t factor;
+    if (t < INT_MAX/2) {
+       st->random->generate(st->random->st,sizeof(factor),&factor);
+       t += (t / 256) * factor;
+    }
+    return t;
+}
+
+static _Bool set_new_transform(struct site *st, char *pk)
 {
+    _Bool ok;
+
     /* Make room for the shared key */
     st->sharedsecretlen=st->chosen_transform->keylen?:st->dh->ceil_len;
     assert(st->sharedsecretlen);
@@ -532,15 +567,18 @@ static void set_new_transform(struct site *st, char *pk)
     /* Set up the transform */
     struct transform_if *generator=st->chosen_transform;
     struct transform_inst_if *generated=generator->create(generator->st);
-    generated->setkey(generated->st,st->sharedsecret,
-                     st->sharedsecretlen,st->setup_priority);
+    ok = generated->setkey(generated->st,st->sharedsecret,
+                          st->sharedsecretlen,st->our_name_later);
+
     dispose_transform(&st->new_transform);
+    if (!ok) return False;
     st->new_transform=generated;
 
     slog(st,LOG_SETUP_INIT,"key exchange negotiated transform"
         " %d (capabilities ours=%#"PRIx32" theirs=%#"PRIx32")",
-        st->chosen_transform->capab_transformnum,
+        st->chosen_transform->capab_bit,
         st->local_capabilities, st->remote_capabilities);
+    return True;
 }
 
 struct xinfoadd {
@@ -575,9 +613,8 @@ static void append_string_xinfo_done(struct buffer_if *buf,
    out using a transform of config data supplied by netlink */
 static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what)
 {
-    void *hst;
-    uint8_t *hash;
-    string_t dhpub, sig;
+    string_t dhpub;
+    unsigned minor;
 
     st->retries=st->setup_retries;
     BUF_ALLOC(&st->buffer,what);
@@ -589,7 +626,8 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what)
 
     struct xinfoadd xia;
     append_string_xinfo_start(&st->buffer,&xia,st->localname);
-    if ((st->local_capabilities & CAPAB_EARLY) || (type != LABEL_MSG1)) {
+    if ((st->local_capabilities & st->early_capabilities) ||
+       (type != LABEL_MSG1)) {
        buf_append_uint32(&st->buffer,st->local_capabilities);
     }
     if (type_is_msg34(type)) {
@@ -605,21 +643,25 @@ static bool_t generate_msg(struct site *st, uint32_t type, cstring_t what)
 
     if (hacky_par_mid_failnow()) return False;
 
-    if (type==LABEL_MSG3BIS)
-       buf_append_uint8(&st->buffer,st->chosen_transform->capab_transformnum);
+    if (MSGMAJOR(type) == 3) do {
+       minor = MSGMINOR(type);
+       if (minor < 1) break;
+       buf_append_uint8(&st->buffer,st->chosen_transform->capab_bit);
+    } while (0);
 
     dhpub=st->dh->makepublic(st->dh->st,st->dhsecret,st->dh->len);
     buf_append_string(&st->buffer,dhpub);
     free(dhpub);
-    hash=safe_malloc(st->hash->len, "generate_msg");
-    hst=st->hash->init();
-    st->hash->update(hst,st->buffer.start,st->buffer.size);
-    st->hash->final(hst,hash);
-    sig=st->privkey->sign(st->privkey->st,hash,st->hash->len);
-    buf_append_string(&st->buffer,sig);
-    free(sig);
-    free(hash);
+
+    bool_t ok=st->privkey->sign(st->privkey->st,
+                               st->buffer.start,
+                               st->buffer.size,
+                               &st->buffer);
+    if (!ok) goto fail;
     return True;
+
+ fail:
+    return False;
 }
 
 static bool_t unpick_name(struct buffer_if *msg, struct parsedname *nm)
@@ -641,6 +683,8 @@ static bool_t unpick_name(struct buffer_if *msg, struct parsedname *nm)
 static bool_t unpick_msg(struct site *st, uint32_t type,
                         struct buffer_if *msg, struct msg *m)
 {
+    unsigned minor;
+
     m->capab_transformnum=-1;
     m->hashstart=msg->start;
     CHECK_AVAIL(msg,4);
@@ -676,22 +720,31 @@ static bool_t unpick_msg(struct site *st, uint32_t type,
        CHECK_EMPTY(msg);
        return True;
     }
-    if (type==LABEL_MSG3BIS) {
-       CHECK_AVAIL(msg,1);
-       m->capab_transformnum = buf_unprepend_uint8(msg);
-    } else {
-       m->capab_transformnum = CAPAB_TRANSFORMNUM_ANCIENT;
-    }
+    if (MSGMAJOR(type) == 3) do {
+       minor = MSGMINOR(type);
+#define MAYBE_READ_CAP(minminor, kind, dflt) do {                      \
+    if (minor < (minminor))                                            \
+       m->capab_##kind##num = (dflt);                                  \
+    else {                                                             \
+       CHECK_AVAIL(msg, 1);                                            \
+       m->capab_##kind##num = buf_unprepend_uint8(msg);                \
+    }                                                                  \
+} while (0)
+       MAYBE_READ_CAP(1, transform, CAPAB_BIT_ANCIENTTRANSFORM);
+#undef MAYBE_READ_CAP
+    } while (0);
     CHECK_AVAIL(msg,2);
     m->pklen=buf_unprepend_uint16(msg);
     CHECK_AVAIL(msg,m->pklen);
     m->pk=buf_unprepend(msg,m->pklen);
     m->hashlen=msg->start-m->hashstart;
-    CHECK_AVAIL(msg,2);
-    m->siglen=buf_unprepend_uint16(msg);
-    CHECK_AVAIL(msg,m->siglen);
-    m->sig=buf_unprepend(msg,m->siglen);
+
+    if (!st->pubkey->unpick(st->pubkey->st,msg,&m->sig)) {
+       return False;
+    }
+
     CHECK_EMPTY(msg);
+
     return True;
 }
 
@@ -723,13 +776,13 @@ static bool_t check_msg(struct site *st, uint32_t type, struct msg *m,
        return False;
     }
     if (type==LABEL_MSG2) return True;
-    if (!consttime_memeq(m->nR,st->remoteN,NONCELEN)!=0) {
+    if (!consttime_memeq(m->nR,st->remoteN,NONCELEN)) {
        *error="wrong remotely-generated nonce";
        return False;
     }
     /* MSG3 has complicated rules about capabilities, which are
      * handled in process_msg3. */
-    if (type==LABEL_MSG3 || type==LABEL_MSG3BIS) return True;
+    if (MSGMAJOR(type) == 3) return True;
     if (m->remote_capabilities!=st->remote_capabilities) {
        *error="remote capabilities changed";
        return False;
@@ -780,25 +833,32 @@ static bool_t process_msg2(struct site *st, struct buffer_if *msg2,
 
     /* Select the transform to use */
 
-    uint32_t remote_transforms = st->remote_capabilities & CAPAB_TRANSFORM_MASK;
-    if (!remote_transforms)
+    uint32_t remote_crypto_caps = st->remote_capabilities & CAPAB_TRANSFORM_MASK;
+    if (!remote_crypto_caps)
        /* old secnets only had this one transform */
-       remote_transforms = 1UL << CAPAB_TRANSFORMNUM_ANCIENT;
+       remote_crypto_caps = 1UL << CAPAB_BIT_ANCIENTTRANSFORM;
+
+#define CHOOSE_CRYPTO(kind, whats) do {                                        \
+    struct kind##_if *iface;                                           \
+    uint32_t bit, ours = 0;                                            \
+    int i;                                                             \
+    for (i= 0; i < st->n##kind##s; i++) {                              \
+       iface=st->kind##s[i];                                           \
+       bit = 1UL << iface->capab_bit;                                  \
+       if (bit & remote_crypto_caps) goto kind##_found;                \
+       ours |= bit;                                                    \
+    }                                                                  \
+    slog(st,LOG_ERROR,"no " whats " in common"                         \
+        " (us %#"PRIx32"; them: %#"PRIx32")",                          \
+        st->local_capabilities & ours, remote_crypto_caps);            \
+    return False;                                                      \
+kind##_found:                                                          \
+    st->chosen_##kind = iface;                                         \
+} while (0)
 
-    struct transform_if *ti;
-    int i;
-    for (i=0; i<st->ntransforms; i++) {
-       ti=st->transforms[i];
-       if ((1UL << ti->capab_transformnum) & remote_transforms)
-           goto transform_found;
-    }
-    slog(st,LOG_ERROR,"no transforms in common"
-        " (us %#"PRIx32"; them: %#"PRIx32")",
-        st->local_capabilities & CAPAB_TRANSFORM_MASK,
-        remote_transforms);
-    return False;
- transform_found:
-    st->chosen_transform=ti;
+    CHOOSE_CRYPTO(transform, "transforms");
+
+#undef CHOOSE_CRYPTO
 
     memcpy(st->remoteN,m.nR,NONCELEN);
     return True;
@@ -810,29 +870,21 @@ static bool_t generate_msg3(struct site *st)
        and create message number 3. */
     st->random->generate(st->random->st,st->dh->len,st->dhsecret);
     return generate_msg(st,
-                       (st->remote_capabilities & CAPAB_TRANSFORM_MASK
-                        ? LABEL_MSG3BIS : LABEL_MSG3),
+                       (st->remote_capabilities & CAPAB_TRANSFORM_MASK)
+                       ? LABEL_MSG3BIS
+                       : LABEL_MSG3,
                        "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)) {
+    if (!st->pubkey->check(st->pubkey->st,
+                          m->hashstart,m->hashlen,
+                          &m->sig)) {
        slog(st,LOG_SEC,"msg3/msg4 signature failed check!");
-       free(hash);
        return False;
     }
-    free(hash);
 
     st->remote_adv_mtu=m->remote_mtu;
 
@@ -845,7 +897,10 @@ static bool_t process_msg3(struct site *st, struct buffer_if *msg3,
     struct msg m;
     cstring_t err;
 
-    assert(msgtype==LABEL_MSG3 || msgtype==LABEL_MSG3BIS);
+    switch (msgtype) {
+       case CASES_MSG3_KNOWN: break;
+       default: assert(0);
+    }
 
     if (!unpick_msg(st,msgtype,msg3,&m)) return False;
     if (!check_msg(st,msgtype,&m,&err)) {
@@ -853,38 +908,53 @@ static bool_t process_msg3(struct site *st, struct buffer_if *msg3,
        return False;
     }
     uint32_t capab_adv_late = m.remote_capabilities
-       & ~st->remote_capabilities & CAPAB_EARLY;
+       & ~st->remote_capabilities & st->early_capabilities;
     if (capab_adv_late) {
        slog(st,LOG_SEC,"msg3 impermissibly adds early capability flag(s)"
             " %#"PRIx32" (was %#"PRIx32", now %#"PRIx32")",
             capab_adv_late, st->remote_capabilities, m.remote_capabilities);
        return False;
     }
-    st->remote_capabilities|=m.remote_capabilities;
 
-    struct transform_if *ti;
-    int i;
-    for (i=0; i<st->ntransforms; i++) {
-       ti=st->transforms[i];
-       if (ti->capab_transformnum == m.capab_transformnum)
-           goto transform_found;
-    }
-    slog(st,LOG_SEC,"peer chose unknown-to-us transform %d!",
-        m.capab_transformnum);
-    return False;
- transform_found:
-    st->chosen_transform=ti;
+#define CHOSE_CRYPTO(kind, what) do {                                  \
+    struct kind##_if *iface;                                           \
+    int i;                                                             \
+    for (i=0; i<st->n##kind##s; i++) {                                 \
+       iface=st->kind##s[i];                                           \
+       if (iface->capab_bit == m.capab_##kind##num)                    \
+           goto kind##_found;                                          \
+    }                                                                  \
+    slog(st,LOG_SEC,"peer chose unknown-to-us " what " %d!",           \
+        m.capab_##kind##num);                                                  \
+    return False;                                                      \
+kind##_found:                                                          \
+    st->chosen_##kind=iface;                                           \
+} while (0)
+
+    CHOSE_CRYPTO(transform, "transform");
+
+#undef CHOSE_CRYPTO
 
     if (!process_msg3_msg4(st,&m))
        return False;
 
+    /* Update our idea of the remote site's capabilities, now that we've
+     * verified that its message was authentic.
+     *
+     * Our previous idea of the remote site's capabilities came from the
+     * unauthenticated MSG1.  We've already checked that this new message
+     * doesn't change any of the bits we relied upon in the past, but it may
+     * also have set additional capability bits.  We simply throw those away
+     * now, and use the authentic capabilities from this MSG3. */
+    st->remote_capabilities=m.remote_capabilities;
+
     /* Terminate their DH public key with a '0' */
     m.pk[m.pklen]=0;
     /* Invent our DH secret key */
     st->random->generate(st->random->st,st->dh->len,st->dhsecret);
 
     /* Generate the shared key and set up the transform */
-    set_new_transform(st,m.pk);
+    if (!set_new_transform(st,m.pk)) return False;
 
     return True;
 }
@@ -915,7 +985,7 @@ static bool_t process_msg4(struct site *st, struct buffer_if *msg4,
     m.pk[m.pklen]=0;
 
     /* Generate the shared key and set up the transform */
-    set_new_transform(st,m.pk);
+    if (!set_new_transform(st,m.pk)) return False;
 
     return True;
 }
@@ -996,8 +1066,9 @@ static void create_msg6(struct site *st, struct transform_inst_if *transform,
     /* Give the netlink code an opportunity to put its own stuff in the
        message (configuration information, etc.) */
     buf_prepend_uint32(&st->buffer,LABEL_MSG6);
-    int problem = call_transform_forwards(st,transform,
-                                         &st->buffer,&transform_err);
+    transform_apply_return problem =
+       call_transform_forwards(st,transform,
+                               &st->buffer,&transform_err);
     assert(!problem);
     buf_prepend_uint32(&st->buffer,LABEL_MSG6);
     buf_prepend_uint32(&st->buffer,st->index);
@@ -1037,12 +1108,13 @@ static bool_t process_msg6(struct site *st, struct buffer_if *msg6,
     return True;
 }
 
-static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
+static transform_apply_return
+decrypt_msg0(struct site *st, struct buffer_if *msg0,
                           const struct comm_addr *src)
 {
     cstring_t transform_err, auxkey_err, newkey_err="n/a";
     struct msg0 m;
-    uint32_t problem;
+    transform_apply_return problem;
 
     if (!unpick_msg0(st,msg0,&m)) return False;
 
@@ -1055,15 +1127,15 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
        if (!st->auxiliary_is_new)
            delete_one_key(st,&st->auxiliary_key,
                           "peer has used new key","auxiliary key",LOG_SEC);
-       return True;
+       return 0;
     }
-    if (problem==2)
-       goto skew;
+    if (transform_apply_return_badseq(problem))
+       goto badseq;
 
     buffer_copy(msg0, &st->scratch);
     problem = call_transform_reverse(st,st->auxiliary_key.transform,
                                     msg0,&auxkey_err);
-    if (problem==0) {
+    if (!problem) {
        slog(st,LOG_DROP,"processing packet which uses auxiliary key");
        if (st->auxiliary_is_new) {
            /* We previously timed out in state SENTMSG5 but it turns
@@ -1080,10 +1152,10 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
            st->auxiliary_is_new=0;
            st->renegotiate_key_time=st->auxiliary_renegotiate_key_time;
        }
-       return True;
+       return 0;
     }
-    if (problem==2)
-       goto skew;
+    if (transform_apply_return_badseq(problem))
+       goto badseq;
 
     if (st->state==SITE_SENTMSG5) {
        buffer_copy(msg0, &st->scratch);
@@ -1096,29 +1168,41 @@ static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0,
            BUF_FREE(&st->buffer);
            st->timeout=0;
            activate_new_key(st);
-           return True; /* do process the data in this packet */
+           return 0; /* do process the data in this packet */
        }
-       if (problem==2)
-           goto skew;
+       if (transform_apply_return_badseq(problem))
+           goto badseq;
     }
 
     slog(st,LOG_SEC,"transform: %s (aux: %s, new: %s)",
         transform_err,auxkey_err,newkey_err);
     initiate_key_setup(st,"incoming message would not decrypt",0);
     send_nak(src,m.dest,m.source,m.type,msg0,"message would not decrypt");
-    return False;
+    assert(problem);
+    return problem;
 
- skew:
-    slog(st,LOG_DROP,"transform: %s (merely skew)",transform_err);
-    return False;
+ badseq:
+    slog(st,LOG_DROP,"transform: %s (bad seq.)",transform_err);
+    assert(problem);
+    return problem;
 }
 
 static bool_t process_msg0(struct site *st, struct buffer_if *msg0,
                           const struct comm_addr *src)
 {
     uint32_t type;
-
-    if (!decrypt_msg0(st,msg0,src))
+    transform_apply_return problem;
+
+    problem = decrypt_msg0(st,msg0,src);
+    if (problem==transform_apply_seqdupe) {
+       /* We recently received another copy of this packet, maybe due
+        * to polypath.  That's not a problem; indeed, for the
+        * purposes of transport address management it is a success.
+        * But we don't want to process the packet. */
+       transport_data_msgok(st,src);
+       return False;
+    }
+    if (problem)
        return False;
 
     CHECK_AVAIL(msg0,4);
@@ -1127,6 +1211,10 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0,
     case LABEL_MSG7:
        /* We must forget about the current session. */
        delete_keys(st,"request from peer",LOG_SEC);
+       /* probably, the peer is shutting down, and this is going to fail,
+        * but we need to be trying to bring the link up again */
+       if (st->keepalive)
+           initiate_key_setup(st,"peer requested key teardown",0);
        return True;
     case LABEL_MSG9:
        /* Deliver to netlink layer */
@@ -1145,16 +1233,34 @@ static bool_t process_msg0(struct site *st, struct buffer_if *msg0,
 }
 
 static void dump_packet(struct site *st, struct buffer_if *buf,
-                       const struct comm_addr *addr, bool_t incoming)
+                       const struct comm_addr *addr, bool_t incoming,
+                       bool_t ok)
 {
     uint32_t dest=get_uint32(buf->start);
     uint32_t source=get_uint32(buf->start+4);
     uint32_t msgtype=get_uint32(buf->start+8);
 
     if (st->log_events & LOG_DUMP)
-       slilog(st->log,M_DEBUG,"%s: %s: %08x<-%08x: %08x:",
+       slilog(st->log,M_DEBUG,"%s: %s: %08x<-%08x: %08x: %s%s",
               st->tunname,incoming?"incoming":"outgoing",
-              dest,source,msgtype);
+              dest,source,msgtype,comm_addr_to_string(addr),
+              ok?"":" - fail");
+}
+
+static bool_t comm_addr_sendmsg(struct site *st,
+                               const struct comm_addr *dest,
+                               struct buffer_if *buf)
+{
+    int i;
+    struct comm_clientinfo *commclientinfo = 0;
+
+    for (i=0; i < st->ncomms; i++) {
+       if (st->comms[i] == dest->comm) {
+           commclientinfo = st->commclientinfos[i];
+           break;
+       }
+    }
+    return dest->comm->sendmsg(dest->comm->st, buf, dest, commclientinfo);
 }
 
 static uint32_t site_status(void *st)
@@ -1283,7 +1389,7 @@ static void decrement_resolving_count(struct site *st, int by)
        } 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;
+           st->key_renegotiate_time=st->now+wait_timeout(st);
        } else {
            slog(st,LOG_SETUP_INIT,"resolution failed: "
                 " continuing to use source address of peer's packets");
@@ -1396,7 +1502,8 @@ static void set_link_quality(struct site *st)
 
 static void enter_state_run(struct site *st)
 {
-    slog(st,LOG_STATE,"entering state RUN");
+    slog(st,LOG_STATE,"entering state RUN%s",
+        current_valid(st) ? " (keyed)" : " (unkeyed)");
     st->state=SITE_RUN;
     st->timeout=0;
 
@@ -1406,8 +1513,11 @@ static void enter_state_run(struct site *st)
     FILLZERO(st->remoteN);
     dispose_transform(&st->new_transform);
     memset(st->dhsecret,0,st->dh->len);
-    memset(st->sharedsecret,0,st->sharedsecretlen);
+    if (st->sharedsecret) memset(st->sharedsecret,0,st->sharedsecretlen);
     set_link_quality(st);
+
+    if (st->keepalive && !current_valid(st))
+       initiate_key_setup(st, "keepalive", 0);
 }
 
 static bool_t ensure_resolving(struct site *st)
@@ -1466,6 +1576,7 @@ static bool_t enter_new_state(struct site *st, uint32_t next)
     case SITE_SENTMSG1:
        state_assert(st,st->state==SITE_RUN || st->state==SITE_RESOLVE);
        gen=generate_msg1;
+       st->msg1_crossed_logged = False;
        break;
     case SITE_SENTMSG2:
        state_assert(st,st->state==SITE_RUN || st->state==SITE_RESOLVE ||
@@ -1554,7 +1665,7 @@ static bool_t send_msg7(struct site *st, cstring_t reason)
 static void enter_state_wait(struct site *st)
 {
     slog(st,LOG_STATE,"entering state WAIT");
-    st->timeout=st->now+st->wait_timeout;
+    st->timeout=st->now+wait_timeout(st);
     st->state=SITE_WAIT;
     set_link_quality(st);
     BUF_FREE(&st->buffer); /* will have had an outgoing packet in it */
@@ -1581,8 +1692,8 @@ static void generate_send_prod(struct site *st,
     slog(st,LOG_SETUP_INIT,"prodding peer for key exchange");
     st->allow_send_prod=0;
     generate_prod(st,&st->scratch);
-    dump_packet(st,&st->scratch,source,False);
-    source->comm->sendmsg(source->comm->st, &st->scratch, source);
+    bool_t ok = comm_addr_sendmsg(st, source, &st->scratch);
+    dump_packet(st,&st->scratch,source,False,ok);
 }
 
 static inline void site_settimeout(uint64_t timeout, int *timeout_io)
@@ -1695,6 +1806,35 @@ static bool_t named_for_us(struct site *st, const struct buffer_if *buf_in,
        && name_matches(&m->local,st->localname);
 }
 
+static bool_t we_have_priority(struct site *st, const struct msg *m) {
+    if (st->local_capabilities & m->remote_capabilities &
+       CAPAB_PRIORITY_MOBILE) {
+       if (st->local_mobile) return True;
+       if (st-> peer_mobile) return False;
+    }
+    return st->our_name_later;
+}
+
+static bool_t setup_late_msg_ok(struct site *st, 
+                               const struct buffer_if *buf_in,
+                               uint32_t msgtype,
+                               const struct comm_addr *source) {
+    /* For setup packets which seem from their type like they are
+     * late.  Maybe they came via a different path.  All we do is make
+     * a note of the sending address, iff they look like they are part
+     * of the current key setup attempt. */
+    struct msg m;
+    if (!named_for_us(st,buf_in,msgtype,&m))
+       /* named_for_us calls unpick_msg which gets the nonces */
+       return False;
+    if (!consttime_memeq(m.nR,st->remoteN,NONCELEN) ||
+       !consttime_memeq(m.nL,st->localN, NONCELEN))
+       /* spoof ?  from stale run ?  who knows */
+       return False;
+    transport_setup_msgok(st,source);
+    return True;
+}
+
 /* This function is called by the communication device to deliver
    packets from our peers.
    It should return True if the packet is recognised as being for
@@ -1715,7 +1855,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
        if (!named_for_us(st,buf,msgtype,&named_msg))
            return False;
        /* It's a MSG1 addressed to us. Decide what to do about it. */
-       dump_packet(st,buf,source,True);
+       dump_packet(st,buf,source,True,True);
        if (st->state==SITE_RUN || st->state==SITE_RESOLVE ||
            st->state==SITE_WAIT) {
            /* We should definitely process it */
@@ -1736,13 +1876,14 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
            /* We've just sent a message 1! They may have crossed on
               the wire. If we have priority then we ignore the
               incoming one, otherwise we process it as usual. */
-           if (st->setup_priority) {
+           if (we_have_priority(st,&named_msg)) {
                BUF_FREE(buf);
-               slog(st,LOG_DUMP,"crossed msg1s; we are higher "
-                    "priority => ignore incoming msg1");
+               if (!st->msg1_crossed_logged++)
+                   slog(st,LOG_SETUP_INIT,"crossed msg1s; we are higher "
+                        "priority => ignore incoming msg1");
                return True;
            } else {
-               slog(st,LOG_DUMP,"crossed msg1s; we are lower "
+               slog(st,LOG_SETUP_INIT,"crossed msg1s; we are lower "
                     "priority => use incoming msg1");
                if (process_msg1(st,buf,source,&named_msg)) {
                    BUF_FREE(&st->buffer); /* Free our old message 1 */
@@ -1755,9 +1896,21 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
                BUF_FREE(buf);
                return True;
            }
+       } else if (st->state==SITE_SENTMSG2 ||
+                  st->state==SITE_SENTMSG4) {
+           if (consttime_memeq(named_msg.nR,st->remoteN,NONCELEN)) {
+               /* We are ahead in the protocol, but that msg1 had the
+                * peer's nonce so presumably it is from this key
+                * exchange run, via a slower route */
+               transport_setup_msgok(st,source);
+           } else {
+               slog(st,LOG_UNEXPECTED,"competing incoming message 1");
+           }
+           BUF_FREE(buf);
+           return True;
        }
        /* The message 1 was received at an unexpected stage of the
-          key setup. XXX POLICY - what do we do? */
+          key setup.  Well, they lost the race. */
        slog(st,LOG_UNEXPECTED,"unexpected incoming message 1");
        BUF_FREE(buf);
        return True;
@@ -1765,7 +1918,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
     if (msgtype==LABEL_PROD) {
        if (!named_for_us(st,buf,msgtype,&named_msg))
            return False;
-       dump_packet(st,buf,source,True);
+       dump_packet(st,buf,source,True,True);
        if (st->state!=SITE_RUN) {
            slog(st,LOG_DROP,"ignoring PROD when not in state RUN");
        } else if (current_valid(st)) {
@@ -1778,7 +1931,7 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
     }
     if (dest==st->index) {
        /* Explicitly addressed to us */
-       if (msgtype!=LABEL_MSG0) dump_packet(st,buf,source,True);
+       if (msgtype!=LABEL_MSG0) dump_packet(st,buf,source,True,True);
        switch (msgtype) {
        case LABEL_NAK:
            /* If the source is our current peer then initiate a key setup,
@@ -1802,6 +1955,10 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
        case LABEL_MSG2:
            /* Setup packet: expected only in state SENTMSG1 */
            if (st->state!=SITE_SENTMSG1) {
+               if ((st->state==SITE_SENTMSG3 ||
+                    st->state==SITE_SENTMSG5) &&
+                   setup_late_msg_ok(st,buf,msgtype,source))
+                   break;
                slog(st,LOG_UNEXPECTED,"unexpected MSG2");
            } else if (process_msg2(st,buf,source)) {
                transport_setup_msgok(st,source);
@@ -1810,10 +1967,12 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
                slog(st,LOG_SEC,"invalid MSG2");
            }
            break;
-       case LABEL_MSG3:
-       case LABEL_MSG3BIS:
+       case CASES_MSG3_KNOWN:
            /* Setup packet: expected only in state SENTMSG2 */
            if (st->state!=SITE_SENTMSG2) {
+               if ((st->state==SITE_SENTMSG4) &&
+                   setup_late_msg_ok(st,buf,msgtype,source))
+                   break;
                slog(st,LOG_UNEXPECTED,"unexpected MSG3");
            } else if (process_msg3(st,buf,source,msgtype)) {
                transport_setup_msgok(st,source);
@@ -1825,6 +1984,9 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
        case LABEL_MSG4:
            /* Setup packet: expected only in state SENTMSG3 */
            if (st->state!=SITE_SENTMSG3) {
+               if ((st->state==SITE_SENTMSG5) &&
+                   setup_late_msg_ok(st,buf,msgtype,source))
+                   break;
                slog(st,LOG_UNEXPECTED,"unexpected MSG4");
            } else if (process_msg4(st,buf,source)) {
                transport_setup_msgok(st,source);
@@ -1946,6 +2108,8 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->localname=dict_read_string(dict, "local-name", True, "site", loc);
     st->remotename=dict_read_string(dict, "name", True, "site", loc);
 
+    st->keepalive=dict_read_bool(dict,"keepalive",False,"site",loc,False);
+
     st->peer_mobile=dict_read_bool(dict,"mobile",False,"site",loc,False);
     st->local_mobile=
        dict_read_bool(dict,"local-mobile",False,"site",loc,False);
@@ -1973,6 +2137,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     assert(index_sequence < 0xffffffffUL);
     st->index = ++index_sequence;
     st->local_capabilities = 0;
+    st->early_capabilities = CAPAB_PRIORITY_MOBILE;
     st->netlink=find_cl_if(dict,"link",CL_NETLINK,True,"site",loc);
 
 #define GET_CLOSURE_LIST(dictkey,things,nthings,CL_TYPE) do{           \
@@ -1995,21 +2160,34 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
 
     GET_CLOSURE_LIST("comm",comms,ncomms,CL_COMM);
 
+    NEW_ARY(st->commclientinfos, st->ncomms);
+    dict_t *comminfo = dict_read_dict(dict,"comm-info",False,"site",loc);
+    for (i=0; i<st->ncomms; i++) {
+       st->commclientinfos[i] =
+           !comminfo ? 0 :
+           st->comms[i]->clientinfo(st->comms[i],comminfo,loc);
+    }
+
     st->resolver=find_cl_if(dict,"resolver",CL_RESOLVER,True,"site",loc);
     st->log=find_cl_if(dict,"log",CL_LOG,True,"site",loc);
     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->privkey=find_cl_if(dict,"local-key",CL_SIGPRIVKEY,True,"site",loc);
     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);
+    st->pubkey=find_cl_if(dict,"key",CL_SIGPUBKEY,True,"site",loc);
 
     GET_CLOSURE_LIST("transform",transforms,ntransforms,CL_TRANSFORM);
 
     st->dh=find_cl_if(dict,"dh",CL_DH,True,"site",loc);
-    st->hash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc);
+
+    if (st->privkey->sethash || st->pubkey->sethash) {
+       struct hash_if *hash=find_cl_if(dict,"hash",CL_HASH,True,"site",loc);
+       if (st->privkey->sethash) st->privkey->sethash(st->privkey->st,hash);
+       if (st->pubkey->sethash) st->pubkey->sethash(st->pubkey->st,hash);
+    }
 
 #define DEFAULT(D) (st->peer_mobile || st->local_mobile        \
                     ? DEFAULT_MOBILE_##D : DEFAULT_##D)
@@ -2018,7 +2196,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->key_lifetime=         CFG_NUMBER("key-lifetime",  KEY_LIFETIME);
     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->wait_timeout_mean=    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(
@@ -2058,7 +2236,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     /* The information we expect to see in incoming messages of type 1 */
     /* fixme: lots of unchecked overflows here, but the results are only
        corrupted packets rather than undefined behaviour */
-    st->setup_priority=(strcmp(st->localname,st->remotename)>0);
+    st->our_name_later=(strcmp(st->localname,st->remotename)>0);
 
     buffer_new(&st->buffer,SETUP_BUFFER_LEN);
 
@@ -2081,14 +2259,21 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->sharedsecretlen=st->sharedsecretallocd=0;
     st->sharedsecret=0;
 
-    for (i=0; i<st->ntransforms; i++) {
-       struct transform_if *ti=st->transforms[i];
-       uint32_t capbit = 1UL << ti->capab_transformnum;
-       if (st->local_capabilities & capbit)
-           slog(st,LOG_ERROR,"transformnum capability bit"
-                " %d (%#"PRIx32") reused", ti->capab_transformnum, capbit);
-       st->local_capabilities |= capbit;
-    }
+#define SET_CAPBIT(bit) do {                                           \
+    uint32_t capflag = 1UL << (bit);                                   \
+    if (st->local_capabilities & capflag)                              \
+       slog(st,LOG_ERROR,"capability bit"                              \
+            " %d (%#"PRIx32") reused", (bit), capflag);                \
+    st->local_capabilities |= capflag;                                 \
+} while (0)
+
+    for (i=0; i<st->ntransforms; i++)
+       SET_CAPBIT(st->transforms[i]->capab_bit);
+
+#undef SET_CAPBIT
+
+    if (st->local_mobile || st->peer_mobile)
+       st->local_capabilities |= CAPAB_PRIORITY_MOBILE;
 
     /* We need to register the remote networks with the netlink device */
     uint32_t netlink_mtu; /* local virtual interface mtu */
@@ -2193,11 +2378,18 @@ static void transport_record_peers(struct site *st, transport_peers *peers,
      *
      * Caller must first call transport_peers_expire. */
 
-    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;
+    if (naddrs==1) {
+       /* avoids debug for uninteresting updates */
+       int i;
+       for (i=0; i<peers->npeers; i++) {
+           if (comm_addr_equal(&addrs[0], &peers->peers[i].addr)) {
+               memmove(peers->peers+1, peers->peers,
+                       sizeof(peers->peers[0]) * i);
+               peers->peers[0].addr = addrs[0];
+               peers->peers[0].last = *tv_now;
+               return;
+           }
+       }
     }
 
     int old_npeers=peers->npeers;
@@ -2328,10 +2520,9 @@ void transport_xmit(struct site *st, transport_peers *peers,
     int nfailed=0;
     for (slot=0; slot<peers->npeers; slot++) {
        transport_peer *peer=&peers->peers[slot];
+       bool_t ok = comm_addr_sendmsg(st, &peer->addr, buf);
        if (candebug)
-           dump_packet(st, buf, &peer->addr, False);
-       bool_t ok =
-           peer->addr.comm->sendmsg(peer->addr.comm->st, buf, &peer->addr);
+           dump_packet(st, buf, &peer->addr, False, ok);
        if (!ok) {
            failed |= 1U << slot;
            nfailed++;
@@ -2353,12 +2544,14 @@ void transport_xmit(struct site *st, transport_peers *peers,
            transport_peers__copy_by_mask(peers->peers,&wslot,~failed,peers);
            assert(wslot+nfailed == peers->npeers);
            COPY_ARRAY(peers->peers+wslot, failedpeers, nfailed);
+           transport_peers_debug(st,peers,"mobile failure reorder",0,0,0);
        }
     } else {
        if (failed && peers->npeers > 1) {
            int wslot=0;
            transport_peers__copy_by_mask(peers->peers,&wslot,~failed,peers);
            peers->npeers=wslot;
+           transport_peers_debug(st,peers,"non-mobile failure cleanup",0,0,0);
        }
     }
 }