/* 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 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. */
/***** TRANSPORT PEERS declarations *****/
/* Details of "mobile peer" semantics:
+
+ - 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.
- - Key setup peers are slightly more complicated.
+ If neither end is mobile:
- Whenever we receive and successfully process a key exchange
- packet, we record the peer.
+ - When peer initiated the key exchange, we use the incoming packet
+ address.
- 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).
+ - When we initiate the key exchange, we try configured addresses
+ until we get one which isn't unsupported then fixate on that.
- (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.)
+ - When we complete a key setup, we replace the data transport peers
+ with those from the key setup.
- "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.
+ If we are mobile:
- */
+ - 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.
+
+ - Use only configured addresses. (Except, that if our peer
+ initiated a key exchange we use the incoming packet address until
+ our name resolution completes.)
+
+ - 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.
-#define MAX_MOBILE_PEERS_MAX 5 /* send at most this many copies, compiled max */
+ - When we complete a key setup, we replace the data transport peers
+ with those from the key setup.
+
+ */
typedef struct {
struct timeval last;
/* 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 */
static void transport_peers_clear(struct site *st, transport_peers *peers);
static int transport_peers_valid(transport_peers *peers);
static void transport_peers_copy(struct site *st, transport_peers *dst,
const transport_peers *src);
+/* Record address of incoming setup packet; resp. data packet. */
static void transport_setup_msgok(struct site *st, const struct comm_addr *a);
static void transport_data_msgok(struct site *st, const struct comm_addr *a);
+
+/* Initialise the setup addresses. Called before we send the first
+ * packet in a key exchange. If we are the initiator, as a result of
+ * 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
+ * completes. transport_compute_setupinit_peers will hvae been called
+ * earlier. If _complete is called, we are still doing the key setup
+ * (and we should use the new values for both the rest of the key
+ * setup and the ongoing data exchange); if _tardy is called, the key
+ * 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);
-static void transport_record_peer(struct site *st, transport_peers *peers,
- const struct comm_addr *addr, const char *m);
+ const struct comm_addr *addrs, int naddrs);
static void transport_xmit(struct site *st, transport_peers *peers,
struct buffer_if *buf, bool_t candebug);
/* 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 */
- 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;
struct comm_if **comms;
+ struct comm_clientinfo **commclientinfos;
int ncomms;
struct resolver_if *resolver;
struct log_if *log;
uint32_t state;
uint64_t now; /* Most recently seen time */
bool_t allow_send_prod;
- bool_t resolving;
+ bool_t msg1_crossed_logged;
+ 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;
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);
char *sig;
};
-static void set_new_transform(struct site *st, char *pk)
+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);
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,
/* 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->setup_priority);
+
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->local_capabilities, st->remote_capabilities);
+ return True;
}
struct xinfoadd {
append_string_xinfo_done(&st->buffer,&xia);
buf_append_string(&st->buffer,st->remotename);
- memcpy(buf_append(&st->buffer,NONCELEN),st->localN,NONCELEN);
+ BUF_ADD_OBJ(append,&st->buffer,st->localN);
if (type==LABEL_MSG1) return True;
- memcpy(buf_append(&st->buffer,NONCELEN),st->remoteN,NONCELEN);
+ BUF_ADD_OBJ(append,&st->buffer,st->remoteN);
if (type==LABEL_MSG2) return True;
if (hacky_par_mid_failnow()) return False;
CHECK_AVAIL(msg,m->siglen);
m->sig=buf_unprepend(msg,m->siglen);
CHECK_EMPTY(msg);
+
+ /* In `process_msg3_msg4' below, we assume that we can write a nul
+ * terminator following the signature. Make sure there's enough space.
+ */
+ if (msg->start >= msg->base + msg->alloclen)
+ return False;
+
return True;
}
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 */
+ /* Terminate signature with a '0' - already checked that this will fit */
m->sig[m->siglen]=0;
if (!st->pubkey->check(st->pubkey->st,hash,st->hash->len,m->sig)) {
slog(st,LOG_SEC,"msg3/msg4 signature failed check!");
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;
}
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;
}
skew:
slog(st,LOG_DROP,"transform: %s (merely skew)",transform_err);
- return False;
+ assert(problem);
+ return problem;
}
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 */
}
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)
}
}
-static void site_resolve_callback(void *sst, struct in_addr *address)
+static void site_resolve_callback(void *sst, const struct comm_addr *addrs,
+ int stored_naddrs, int all_naddrs,
+ const char *address, const char *failwhy)
{
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 (!stored_naddrs) {
+ slog(st,LOG_ERROR,"resolution of %s failed: %s",address,failwhy);
} else {
- slog(st,LOG_ERROR,"resolution of %s failed",st->address);
- ca_use=0;
+ 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) {
+ 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",
+ st->resolving_n_results_all, naddrs);
+ }
+ slog(st,LOG_STATE,"resolution completed, %d addrs, eg: %s",
+ naddrs, iaddr_to_string(&addrs[0].ia));;
}
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 */
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. */
- 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 (ca_use) {
- slog(st,LOG_SETUP_INIT,"resolution of %s completed tardily,"
- " updating peer address(es)",st->address);
- transport_resolve_complete_tardy(st,ca_use);
+ if (naddrs) {
+ 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:
/* 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,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");
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;
st->setup_session_id=0;
transport_peers_clear(st,&st->setup_peers);
- memset(st->localN,0,NONCELEN);
- memset(st->remoteN,0,NONCELEN);
+ FILLZERO(st->localN);
+ 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)
{
/* 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,
- 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)
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 ||
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)
{
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
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 */
- 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);
- 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);
incoming one, otherwise we process it as usual. */
if (st->setup_priority) {
BUF_FREE(buf);
- slog(st,LOG_DUMP,"crossed msg1s; we are higher "
- "priority => ignore incoming msg1");
+ if (!st->msg1_crossed_logged++)
+ slog(st,LOG_DUMP,"crossed msg1s; we are higher "
+ "priority => ignore incoming msg1");
return True;
} else {
slog(st,LOG_DUMP,"crossed msg1s; we are lower "
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)) {
}
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,
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)
{
dict_t *dict;
int i;
- st=safe_malloc(sizeof(*st),"site_apply");
+ NEW(st);
st->cl.description="site";
st->cl.type=CL_SITE;
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);
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); \
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->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);
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)
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,
/* 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;
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);
}
}
}
-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 void transport_peers_expire(struct site *st, transport_peers *peers) {
/* peers must be sorted first */
int previous_peers=peers->npeers;
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 (comm_addr_equal(&peers->peers[search].addr, ca))
+ return 1;
+
+ peers->peers[peers->npeers].addr = *ca;
+ peers->peers[peers->npeers].last = *tv;
+ peers->npeers++;
+ return 1;
+}
+
+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 (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;
+ }
+ }
+ }
- found:
- peers->peers[slot].addr=*addr;
- peers->peers[slot].last=*tv_now;
+ int old_npeers=peers->npeers;
+ transport_peer old_peers[old_npeers];
+ COPY_ARRAY(old_peers,peers->peers,old_npeers);
- if (peers->npeers>1)
- qsort(peers->peers, peers->npeers,
- sizeof(*peers->peers), transport_peer_compar);
+ 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;
+ }
- if (changed || peers->npeers!=1)
- transport_peers_debug(st,peers,m, 1,addr,0);
- transport_peers_expire(st, peers);
+ transport_peers_debug(st,peers,m, naddrs,addrs,0);
+}
+
+static void transport_expire_record_peers(struct site *st,
+ transport_peers *peers,
+ const struct comm_addr *addrs,
+ int naddrs, const char *m) {
+ /* Convenience function */
+ transport_peers_expire(st,peers);
+ transport_record_peers(st,peers,addrs,naddrs,m);
}
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);
- /* 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);
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");
+ transport_expire_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");
+ transport_expire_record_peers(st,&st->peers,a,1,"datamsg");
}
static int transport_peers_valid(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_expire_record_peers(st,&st->peers,addrs,naddrs,
+ "resolved data");
+ transport_expire_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_expire_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_PEER_ADDRS < sizeof(unsigned)*CHAR_BIT);
+
+ 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);
- 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++;
+ }
+ 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);
+ 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);
+ }
}
}