chiark / gitweb /
site: Deal with losing our MSG6 - retransmit MSG6 when we see MSG5 in RUN
[secnet.git] / site.c
diff --git a/site.c b/site.c
index ed9e803b308211a01a10930fcfadfcc33dcc3be7..9f3ee856b8adb2cb015b477b2ba5e64afc852eef 100644 (file)
--- a/site.c
+++ b/site.c
@@ -226,7 +226,8 @@ struct site {
     string_t address; /* DNS name for bootstrapping, optional */
     int remoteport; /* Port for bootstrapping, optional */
     struct netlink_if *netlink;
-    struct comm_if *comm;
+    struct comm_if **comms;
+    int ncomms;
     struct resolver_if *resolver;
     struct log_if *log;
     struct random_if *random;
@@ -276,6 +277,7 @@ struct site {
     uint8_t localN[NONCELEN]; /* Nonces for key exchange */
     uint8_t remoteN[NONCELEN];
     struct buffer_if buffer; /* Current outgoing key exchange packet */
+    struct buffer_if scratch;
     int32_t retries; /* Number of retries remaining */
     uint64_t timeout; /* Timeout for current state */
     uint8_t *dhsecret;
@@ -320,6 +322,7 @@ static void enter_state_run(struct site *st);
 static bool_t enter_state_resolve(struct site *st);
 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);
 
 #define CHECK_AVAIL(b,l) do { if ((b)->size<(l)) return False; } while(0)
 #define CHECK_EMPTY(b) do { if ((b)->size!=0) return False; } while(0)
@@ -627,7 +630,6 @@ static bool_t generate_msg5(struct site *st)
     buffer_init(&st->buffer,st->transform->max_start_pad+(4*4));
     /* Give the netlink code an opportunity to put its own stuff in the
        message (configuration information, etc.) */
-    st->netlink->output_config(st->netlink->st,&st->buffer);
     buf_prepend_uint32(&st->buffer,LABEL_MSG5);
     st->new_transform->forwards(st->new_transform->st,&st->buffer,
                                &transform_err);
@@ -640,15 +642,15 @@ static bool_t generate_msg5(struct site *st)
 }
 
 static bool_t process_msg5(struct site *st, struct buffer_if *msg5,
-                          const struct comm_addr *src)
+                          const struct comm_addr *src,
+                          struct transform_inst_if *transform)
 {
     struct msg0 m;
     cstring_t transform_err;
 
     if (!unpick_msg0(st,msg5,&m)) return False;
 
-    if (st->new_transform->reverse(st->new_transform->st,
-                                  msg5,&transform_err)) {
+    if (transform->reverse(transform->st,msg5,&transform_err)) {
        /* There's a problem */
        slog(st,LOG_SEC,"process_msg5: transform: %s",transform_err);
        return False;
@@ -659,15 +661,13 @@ static bool_t process_msg5(struct site *st, struct buffer_if *msg5,
        slog(st,LOG_SEC,"MSG5/PING packet contained wrong label");
        return False;
     }
-    if (!st->netlink->check_config(st->netlink->st,msg5)) {
-       slog(st,LOG_SEC,"MSG5/PING packet contained bad netlink config");
-       return False;
-    }
-    CHECK_EMPTY(msg5);
+    /* Older versions of secnet used to write some config data here
+     * which we ignore.  So we don't CHECK_EMPTY */
     return True;
 }
 
-static bool_t generate_msg6(struct site *st)
+static void create_msg6(struct site *st, struct transform_inst_if *transform,
+                       uint32_t session_id)
 {
     cstring_t transform_err;
 
@@ -676,14 +676,16 @@ static bool_t generate_msg6(struct site *st)
     buffer_init(&st->buffer,st->transform->max_start_pad+(4*4));
     /* Give the netlink code an opportunity to put its own stuff in the
        message (configuration information, etc.) */
-    st->netlink->output_config(st->netlink->st,&st->buffer);
     buf_prepend_uint32(&st->buffer,LABEL_MSG6);
-    st->new_transform->forwards(st->new_transform->st,&st->buffer,
-                               &transform_err);
+    transform->forwards(transform->st,&st->buffer,&transform_err);
     buf_prepend_uint32(&st->buffer,LABEL_MSG6);
     buf_prepend_uint32(&st->buffer,st->index);
-    buf_prepend_uint32(&st->buffer,st->setup_session_id);
+    buf_prepend_uint32(&st->buffer,session_id);
+}
 
+static bool_t generate_msg6(struct site *st)
+{
+    create_msg6(st,st->new_transform,st->setup_session_id);
     st->retries=1; /* Peer will retransmit MSG5 if this packet gets lost */
     return True;
 }
@@ -708,34 +710,58 @@ static bool_t process_msg6(struct site *st, struct buffer_if *msg6,
        slog(st,LOG_SEC,"MSG6/PONG packet contained invalid data");
        return False;
     }
-    if (!st->netlink->check_config(st->netlink->st,msg6)) {
-       slog(st,LOG_SEC,"MSG6/PONG packet contained bad netlink config");
+    /* Older versions of secnet used to write some config data here
+     * which we ignore.  So we don't CHECK_EMPTY */
+    return True;
+}
+
+static bool_t decrypt_msg0(struct site *st, struct buffer_if *msg0)
+{
+    cstring_t transform_err, newkey_err="n/a";
+    struct msg0 m;
+    uint32_t problem;
+
+    if (!unpick_msg0(st,msg0,&m)) return False;
+
+    /* Keep a copy so we can try decrypting it with multiple keys */
+    buffer_copy(&st->scratch, msg0);
+
+    problem = st->current_transform->reverse(st->current_transform->st,
+                                            msg0,&transform_err);
+    if (!problem) return True;
+
+    if (problem==2) {
+       slog(st,LOG_DROP,"transform: %s (merely skew)",transform_err);
        return False;
     }
-    CHECK_EMPTY(msg6);
-    return True;
+
+    if (st->state==SITE_SENTMSG5) {
+       buffer_copy(msg0, &st->scratch);
+       if (!st->new_transform->reverse(st->new_transform->st,
+                                       msg0,&newkey_err)) {
+           /* It looks like we didn't get the peer's MSG6 */
+           /* This is like a cut-down enter_new_state(SITE_RUN) */
+           slog(st,LOG_STATE,"will enter state RUN (MSG0 with new key)");
+           BUF_FREE(&st->buffer);
+           st->timeout=0;
+           activate_new_key(st);
+           return True; /* do process the data in this packet */
+       }
+    }
+
+    slog(st,LOG_SEC,"transform: %s (new: %s)",transform_err,newkey_err);
+    initiate_key_setup(st,"incoming message would not decrypt");
+    return False;
 }
 
 static bool_t process_msg0(struct site *st, struct buffer_if *msg0,
                           const struct comm_addr *src)
 {
-    struct msg0 m;
-    cstring_t transform_err;
     uint32_t type;
 
-    if (!st->current_valid) {
-       slog(st,LOG_DROP,"incoming message but no current key -> dropping");
-       return initiate_key_setup(st,"incoming message but no current key");
-    }
-
-    if (!unpick_msg0(st,msg0,&m)) return False;
+    if (!decrypt_msg0(st,msg0))
+       return False;
 
-    if (st->current_transform->reverse(st->current_transform->st,
-                                      msg0,&transform_err)) {
-       /* There's a problem */
-       slog(st,LOG_SEC,"transform: %s",transform_err);
-       return initiate_key_setup(st,"incoming message would not decrypt");
-    }
     CHECK_AVAIL(msg0,4);
     type=buf_unprepend_uint32(msg0);
     switch(type) {
@@ -803,7 +829,7 @@ static void site_resolve_callback(void *sst, struct in_addr *address)
     }
     if (address) {
        FILLZERO(ca_buf);
-       ca_buf.comm=st->comm;
+       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;
@@ -1120,6 +1146,9 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
                            const struct comm_addr *source)
 {
     struct site *st=sst;
+
+    if (buf->size < 12) return False;
+
     uint32_t dest=ntohl(*(uint32_t *)buf->start);
 
     if (dest==0) {
@@ -1230,16 +1259,28 @@ static bool_t site_incoming(void *sst, struct buffer_if *buf,
            /* Setup packet: expected only in state SENTMSG4 */
            /* (may turn up in state RUN if our return MSG6 was lost
               and the new key has already been activated. In that
-              case we should treat it as an ordinary PING packet. We
-              can't pass it to process_msg5() because the
-              new_transform will now be unkeyed. XXX) */
-           if (st->state!=SITE_SENTMSG4) {
-               slog(st,LOG_UNEXPECTED,"unexpected MSG5");
-           } else if (process_msg5(st,buf,source)) {
-               transport_setup_msgok(st,source);
-               enter_new_state(st,SITE_RUN);
+              case we discard it. The peer will realise that we
+              are using the new key when they see our data packets.
+              Until then the peer's data packets to us get discarded. */
+           if (st->state==SITE_SENTMSG4) {
+               if (process_msg5(st,buf,source,st->new_transform)) {
+                   transport_setup_msgok(st,source);
+                   enter_new_state(st,SITE_RUN);
+               } else {
+                   slog(st,LOG_SEC,"invalid MSG5");
+               }
+           } else if (st->state==SITE_RUN) {
+               if (process_msg5(st,buf,source,st->current_transform)) {
+                   slog(st,LOG_DROP,"got MSG5, retransmitting MSG6");
+                   transport_setup_msgok(st,source);
+                   create_msg6(st,st->current_transform,st->remote_session_id);
+                   transport_xmit(st,&st->peers,&st->buffer,True);
+                   BUF_FREE(&st->buffer);
+               } else {
+                   slog(st,LOG_SEC,"invalid MSG5 (in state RUN)");
+               }
            } else {
-               slog(st,LOG_SEC,"invalid MSG5");
+               slog(st,LOG_UNEXPECTED,"unexpected MSG5");
            }
            break;
        case LABEL_MSG6:
@@ -1288,6 +1329,7 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     struct site *st;
     item_t *item;
     dict_t *dict;
+    int i;
 
     st=safe_malloc(sizeof(*st),"site_apply");
 
@@ -1335,7 +1377,21 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     assert(index_sequence < 0xffffffffUL);
     st->index = ++index_sequence;
     st->netlink=find_cl_if(dict,"link",CL_NETLINK,True,"site",loc);
-    st->comm=find_cl_if(dict,"comm",CL_COMM,True,"site",loc);
+
+    list_t *comms_cfg=dict_lookup(dict,"comm");
+    if (!comms_cfg) cfgfatal(loc,"site","closure list \"comm\" not found\n");
+    st->ncomms=list_length(comms_cfg);
+    st->comms=safe_malloc_ary(sizeof(*st->comms),st->ncomms,"comms");
+    assert(st->ncomms);
+    for (i=0; i<st->ncomms; i++) {
+       item_t *item=list_elem(comms_cfg,i);
+       if (item->type!=t_closure)
+           cfgfatal(loc,"site","comm is not a closure\n");
+       closure_t *cl=item->data.closure;
+       if (cl->type!=CL_COMM) cfgfatal(loc,"site","comm closure wrong type\n");
+       st->comms[i]=cl->interface;
+    }
+
     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);
@@ -1406,6 +1462,9 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
 
     buffer_new(&st->buffer,SETUP_BUFFER_LEN);
 
+    buffer_new(&st->scratch,0);
+    BUF_ALLOC(&st->scratch,"site:scratch");
+
     /* 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");
@@ -1419,13 +1478,25 @@ static list_t *site_apply(closure_t *self, struct cloc loc, dict_t *context,
     st->dhsecret=safe_malloc(st->dh->len,"site:dhsecret");
     st->sharedsecret=safe_malloc(st->transform->keylen,"site:sharedsecret");
 
+    /* We need to compute some properties of our comms */
+#define COMPUTE_WORST(pad)                     \
+    int worst_##pad=0;                         \
+    for (i=0; i<st->ncomms; i++) {             \
+       int thispad=st->comms[i]->pad;          \
+       if (thispad > worst_##pad)              \
+           worst_##pad=thispad;                \
+    }
+    COMPUTE_WORST(min_start_pad)
+    COMPUTE_WORST(min_end_pad)
+
     /* We need to register the remote networks with the netlink device */
     st->netlink->reg(st->netlink->st, site_outgoing, st,
                     st->transform->max_start_pad+(4*4)+
-                    st->comm->min_start_pad,
-                    st->transform->max_end_pad+st->comm->min_end_pad);
+                    worst_min_start_pad,
+                    st->transform->max_end_pad+worst_min_end_pad);
     
-    st->comm->request_notify(st->comm->st, st, site_incoming);
+    for (i=0; i<st->ncomms; i++)
+       st->comms[i]->request_notify(st->comms[i]->st, st, site_incoming);
 
     st->current_transform=st->transform->create(st->transform->st);
     st->new_transform=st->transform->create(st->transform->st);
@@ -1572,7 +1643,7 @@ static void transport_peers_copy(struct site *st, transport_peers *dst,
     dst->npeers=src->npeers;
     memcpy(dst->peers, src->peers, sizeof(*dst->peers) * dst->npeers);
     transport_peers_debug(st,dst,"copy",
-                         src->npeers, &src->peers->addr, sizeof(src->peers));
+                         src->npeers, &src->peers->addr, sizeof(*src->peers));
 }
 
 void transport_xmit(struct site *st, transport_peers *peers,