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;
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;
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)
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);
}
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;
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;
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;
}
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) {
}
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;
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) {
/* 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:
struct site *st;
item_t *item;
dict_t *dict;
+ int i;
st=safe_malloc(sizeof(*st),"site_apply");
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);
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");
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);
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,