#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. */
/* Details of "mobile peer" semantics:
- | Note: this comment is wishful thinking right now. It will be
- | implemented in subsequent commits.
-
- We use the same data structure for the different configurations,
but manage it with different algorithms.
*/
-#define MAX_MOBILE_PEERS_MAX 5 /* send at most this many copies, compiled max */
-
typedef struct {
struct timeval last;
struct comm_addr addr;
/* 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 */
* 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
* 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);
+ const struct comm_addr *addrs, int naddrs);
static void transport_xmit(struct site *st, transport_peers *peers,
struct buffer_if *buf, bool_t candebug);
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;
uint32_t state;
uint64_t now; /* Most recently seen time */
bool_t allow_send_prod;
- bool_t resolving;
+ int resolving_count;
+ int resolving_n_results_all;
+ int resolving_n_results_stored;
+ struct comm_addr resolving_results[MAX_PEER_ADDRS];
/* The currently established session */
struct data_key current;
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);
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,
}
}
-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, comm_addr_to_string(&addrs[0]));;
}
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;
{
/* 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)
{
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 (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);
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;
}
}
-static bool_t transport_addrs_equal(const struct comm_addr *a,
- const struct comm_addr *b) {
- return !memcmp(a,b,sizeof(*a));
-}
-
static void transport_peers_expire(struct site *st, transport_peers *peers) {
/* peers must be sorted first */
int previous_peers=peers->npeers;
return 0;
for (search=0; search<peers->npeers; search++)
- if (transport_addrs_equal(&peers->peers[search].addr, ca))
+ if (comm_addr_equal(&peers->peers[search].addr, ca))
return 1;
peers->peers[peers->npeers].addr = *ca;
* Caller must first call transport_peers_expire. */
if (naddrs==1 && peers->npeers>=1 &&
- transport_addrs_equal(&addrs[0], &peers->peers[0].addr)) {
+ comm_addr_equal(&addrs[0], &peers->peers[0].addr)) {
/* optimisation, also avoids debug for trivial updates */
peers->peers[0].last = *tv_now;
return;
}
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);
transport_record_peers(st,&st->setup_peers,
incoming_packet_addr,1, "incoming");
- if (configured_addr)
+ if (n_configured_addrs)
transport_record_peers(st,&st->setup_peers,
- configured_addr,1, "setupinit");
+ configured_addrs,n_configured_addrs, "setupinit");
assert(transport_peers_valid(&st->setup_peers));
return True;
}
static void transport_resolve_complete(struct site *st,
- const struct comm_addr *ca) {
- transport_expire_record_peers(st,&st->peers,ca,1,"resolved data");
- transport_expire_record_peers(st,&st->setup_peers,ca,1,"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) {
- transport_expire_record_peers(st,&st->peers,ca,1,"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,
int slot;
transport_peers_expire(st, peers);
unsigned failed=0; /* bitmask */
- assert(MAX_MOBILE_PEERS_MAX < sizeof(unsigned)*CHAR_BIT);
+ assert(MAX_PEER_ADDRS < sizeof(unsigned)*CHAR_BIT);
int nfailed=0;
for (slot=0; slot<peers->npeers; slot++) {