From 6e1880a370e900a61835ebbc2c8987bf2809accd Mon Sep 17 00:00:00 2001 From: markus Date: Tue, 6 May 2014 10:24:22 +0000 Subject: [PATCH] initiate ike sa rekeying (ikesalifetime keyword), re-queue pfkey events while we are busy initiating child-SAs; ok mikeb@ --- sbin/iked/config.c | 3 +- sbin/iked/iked.conf.5 | 12 +- sbin/iked/iked.h | 16 +- sbin/iked/ikev2.c | 410 ++++++++++++++++++++++++++++++++++-------- sbin/iked/ikev2.h | 5 +- sbin/iked/ikev2_msg.c | 18 +- sbin/iked/ikev2_pld.c | 10 +- sbin/iked/parse.y | 30 +++- sbin/iked/pfkey.c | 81 ++++++--- sbin/iked/policy.c | 18 +- 10 files changed, 469 insertions(+), 134 deletions(-) diff --git a/sbin/iked/config.c b/sbin/iked/config.c index 1c6ee7c4756..00f0b9de7d7 100644 --- a/sbin/iked/config.c +++ b/sbin/iked/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.29 2014/05/06 09:48:40 markus Exp $ */ +/* $OpenBSD: config.c,v 1.30 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -79,6 +79,7 @@ void config_free_sa(struct iked *env, struct iked_sa *sa) { timer_del(env, &sa->sa_timer); + timer_del(env, &sa->sa_rekey); config_free_proposals(&sa->sa_proposals, 0); config_free_childsas(env, &sa->sa_childsas, NULL, NULL); diff --git a/sbin/iked/iked.conf.5 b/sbin/iked/iked.conf.5 index 0428f89a672..efaa51896ab 100644 --- a/sbin/iked/iked.conf.5 +++ b/sbin/iked/iked.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: iked.conf.5,v 1.30 2014/04/28 16:23:19 jmc Exp $ +.\" $OpenBSD: iked.conf.5,v 1.31 2014/05/06 10:24:22 markus Exp $ .\" .\" Copyright (c) 2010 - 2014 Reyk Floeter .\" Copyright (c) 2004 Mathieu Sauve-Frankel All rights reserved. @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: April 28 2014 $ +.Dd $Mdocdate: May 6 2014 $ .Dt IKED.CONF 5 .Os .Sh NAME @@ -435,6 +435,14 @@ is similar to .Ic srcid , but instead specifies the ID to be used by the remote peer. +.It Ic ikelifetime Ar time +The optional +.Ic ikelifetime +parameter defines the IKE SA expiration timeout by the +.Ar time +SA was in created. +A zero value disables active IKE SA rekeying. +This is the default. .It Ic lifetime Ar time Op Ic bytes Ar bytes The optional .Ic lifetime diff --git a/sbin/iked/iked.h b/sbin/iked/iked.h index 59321aedc6a..a386ded6873 100644 --- a/sbin/iked/iked.h +++ b/sbin/iked/iked.h @@ -1,4 +1,4 @@ -/* $OpenBSD: iked.h,v 1.75 2014/05/06 07:24:37 markus Exp $ */ +/* $OpenBSD: iked.h,v 1.76 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -274,7 +274,8 @@ struct iked_policy { struct iked_cfg pol_cfg[IKED_CFG_MAX]; u_int pol_ncfg; - struct iked_lifetime pol_lifetime; + u_int32_t pol_rekey; /* ike SA lifetime */ + struct iked_lifetime pol_lifetime; /* child SA lifetime */ struct iked_sapeers pol_sapeers; @@ -411,6 +412,7 @@ struct iked_sa { struct iked_childsas sa_childsas; /* IPSec Child SAs */ struct iked_saflows sa_flows; /* IPSec flows */ + struct iked_sa *sa_next; /* IKE SA rekeying */ u_int64_t sa_rekeyspi; /* peerspi for rekey*/ u_int8_t sa_ipcomp; /* IPcomp transform */ @@ -418,9 +420,11 @@ struct iked_sa { u_int16_t sa_cpi_in; /* IPcomp incoming*/ struct iked_timer sa_timer; /* SA timeouts */ -#define IKED_IKE_SA_REKEY_TIMEOUT 300 /* 5 minutes */ +#define IKED_IKE_SA_DELETE_TIMEOUT 300 /* 5 minutes */ #define IKED_IKE_SA_ALIVE_TIMEOUT 60 /* 1 minute */ + struct iked_timer sa_rekey; /* rekey timeout */ + struct iked_msgqueue sa_requests; /* request queue */ #define IKED_RETRANSMIT_TIMEOUT 2 /* 2 seconds */ @@ -751,10 +755,10 @@ struct ikev2_payload * ikev2_add_payload(struct ibuf *); int ikev2_next_payload(struct ikev2_payload *, size_t, u_int8_t); -void ikev2_acquire_sa(struct iked *, struct iked_flow *); +int ikev2_acquire_sa(struct iked *, struct iked_flow *); void ikev2_disable_rekeying(struct iked *, struct iked_sa *); -void ikev2_rekey_sa(struct iked *, struct iked_spi *); -void ikev2_drop_sa(struct iked *, struct iked_spi *); +int ikev2_rekey_sa(struct iked *, struct iked_spi *); +int ikev2_drop_sa(struct iked *, struct iked_spi *); int ikev2_print_id(struct iked_id *, char *, size_t); /* ikev2_msg.c */ diff --git a/sbin/iked/ikev2.c b/sbin/iked/ikev2.c index 02fdf026d09..c4fce4d69bc 100644 --- a/sbin/iked/ikev2.c +++ b/sbin/iked/ikev2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ikev2.c,v 1.106 2014/05/06 09:48:40 markus Exp $ */ +/* $OpenBSD: ikev2.c,v 1.107 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -73,13 +73,16 @@ int ikev2_resp_ike_eap(struct iked *, struct iked_sa *, struct ibuf *); int ikev2_send_create_child_sa(struct iked *, struct iked_sa *, struct iked_spi *, u_int8_t); +int ikev2_ikesa_enable(struct iked *, struct iked_sa *, struct iked_sa *); +void ikev2_ikesa_delete(struct iked *, struct iked_sa *, int); int ikev2_init_create_child_sa(struct iked *, struct iked_message *); int ikev2_resp_create_child_sa(struct iked *, struct iked_message *); +void ikev2_ike_sa_rekey(struct iked *, void *); void ikev2_ike_sa_timeout(struct iked *env, void *); void ikev2_ike_sa_alive(struct iked *, void *); int ikev2_sa_initiator(struct iked *, struct iked_sa *, - struct iked_message *); + struct iked_sa *, struct iked_message *); int ikev2_sa_responder(struct iked *, struct iked_sa *, struct iked_sa *, struct iked_message *); int ikev2_sa_initiator_dh(struct iked_sa *, struct iked_message *, u_int); @@ -787,7 +790,7 @@ ikev2_init_ike_sa_peer(struct iked *env, struct iked_policy *pol, /* XXX free old sa_dhgroup ? */ sa->sa_dhgroup = pol->pol_peerdh; - if (ikev2_sa_initiator(env, sa, NULL) == -1) + if (ikev2_sa_initiator(env, sa, NULL, NULL) == -1) goto done; if (pol->pol_local.addr.ss_family == AF_UNSPEC) { @@ -932,7 +935,7 @@ ikev2_init_auth(struct iked *env, struct iked_message *msg) if (sa == NULL) return (-1); - if (ikev2_sa_initiator(env, sa, msg) == -1) { + if (ikev2_sa_initiator(env, sa, NULL, msg) == -1) { log_debug("%s: failed to get IKE keys", __func__); return (-1); } @@ -1082,6 +1085,9 @@ ikev2_init_done(struct iked *env, struct iked_sa *sa) sa_state(env, sa, IKEV2_STATE_ESTABLISHED); timer_set(env, &sa->sa_timer, ikev2_ike_sa_alive, sa); timer_add(env, &sa->sa_timer, IKED_IKE_SA_ALIVE_TIMEOUT); + timer_set(env, &sa->sa_rekey, ikev2_ike_sa_rekey, sa); + if (sa->sa_policy->pol_rekey) + timer_add(env, &sa->sa_rekey, sa->sa_policy->pol_rekey); } if (ret) @@ -2212,6 +2218,9 @@ ikev2_resp_ike_auth(struct iked *env, struct iked_sa *sa) sa_state(env, sa, IKEV2_STATE_ESTABLISHED); timer_set(env, &sa->sa_timer, ikev2_ike_sa_alive, sa); timer_add(env, &sa->sa_timer, IKED_IKE_SA_ALIVE_TIMEOUT); + timer_set(env, &sa->sa_rekey, ikev2_ike_sa_rekey, sa); + if (sa->sa_policy->pol_rekey) + timer_add(env, &sa->sa_rekey, sa->sa_policy->pol_rekey); } done: @@ -2389,6 +2398,13 @@ ikev2_send_create_child_sa(struct iked *env, struct iked_sa *sa, else log_debug("%s: creating new CHILD SAs", __func__); + /* XXX cannot initiate multiple concurrent CREATE_CHILD_SA exchanges */ + if (sa->sa_stateflags & IKED_REQ_CHILDSA) { + log_debug("%s: another CREATE_CHILD_SA exchange already active", + __func__); + return (-1); + } + sa->sa_rekeyspi = 0; /* clear rekey spi */ initiator = sa->sa_hdr.sh_initiator ? 1 : 0; @@ -2524,12 +2540,110 @@ done: return (ret); } +void +ikev2_ike_sa_rekey(struct iked *env, void *arg) +{ + struct iked_sa *sa = arg; + struct iked_sa *nsa = NULL; + struct ikev2_payload *pld = NULL; + struct ikev2_keyexchange *ke; + struct group *group; + struct ibuf *e = NULL, *nonce = NULL; + ssize_t len = 0; + int ret = -1; + + log_debug("%s: called for IKE SA %p", __func__, sa); + + if (sa->sa_stateflags & IKED_REQ_CHILDSA) { + /* + * We cannot initiate multiple concurrent CREATE_CHILD_SA + * exchanges, so retry in one minute. + */ + timer_add(env, &sa->sa_rekey, 60); + return; + } + + if ((nsa = sa_new(env, 0, 0, 1, sa->sa_policy)) == NULL) { + log_debug("%s: failed to get new SA", __func__); + goto done; + } + + if (ikev2_sa_initiator(env, nsa, sa, NULL)) { + log_debug("%s: failed to setup DH", __func__); + goto done; + } + sa_state(env, nsa, IKEV2_STATE_AUTH_SUCCESS); + nonce = nsa->sa_inonce; + + if ((e = ibuf_static()) == NULL) + goto done; + + /* SA payload */ + if ((pld = ikev2_add_payload(e)) == NULL) + goto done; + + /* just reuse the old IKE SA proposals */ + if ((len = ikev2_add_proposals(env, nsa, e, &sa->sa_proposals, + IKEV2_SAPROTO_IKE, 1, 1)) == -1) + goto done; + + if (ikev2_next_payload(pld, len, IKEV2_PAYLOAD_NONCE) == -1) + goto done; + + /* NONCE payload */ + if ((pld = ikev2_add_payload(e)) == NULL) + goto done; + if (ikev2_add_buf(e, nonce) == -1) + goto done; + len = ibuf_size(nonce); + + if (ikev2_next_payload(pld, len, IKEV2_PAYLOAD_KE) == -1) + goto done; + + /* KE payload */ + if ((pld = ikev2_add_payload(e)) == NULL) + goto done; + if ((ke = ibuf_advance(e, sizeof(*ke))) == NULL) + goto done; + if ((group = nsa->sa_dhgroup) == NULL) { + log_debug("%s: invalid dh", __func__); + goto done; + } + ke->kex_dhgroup = htobe16(group->id); + if (ikev2_add_buf(e, nsa->sa_dhiexchange) == -1) + goto done; + len = sizeof(*ke) + dh_getlen(group); + + if (ikev2_next_payload(pld, len, IKEV2_PAYLOAD_NONE) == -1) + goto done; + + ret = ikev2_msg_send_encrypt(env, sa, &e, + IKEV2_EXCHANGE_CREATE_CHILD_SA, IKEV2_PAYLOAD_SA, 0); + if (ret == 0) { + sa->sa_stateflags |= IKED_REQ_CHILDSA; + sa->sa_next = nsa; + nsa = NULL; + } +done: + if (nsa) + sa_free(env, nsa); + ibuf_release(e); + + if (ret == 0) + log_debug("%s: create child SA sent", __func__); + else + log_debug("%s: could not send create child SA", __func__); + /* XXX should we try again in case of ret != 0 ? */ +} + int ikev2_init_create_child_sa(struct iked *env, struct iked_message *msg) { struct iked_childsa *csa = NULL; struct iked_proposal *prop; struct iked_sa *sa = msg->msg_sa; + struct iked_sa *nsa; + struct iked_spi *spi; struct ikev2_delete *del; struct ibuf *buf = NULL; u_int32_t spi32; @@ -2561,6 +2675,33 @@ ikev2_init_create_child_sa(struct iked *env, struct iked_message *msg) return (-1); } + /* IKE SA rekeying */ + if (prop->prop_protoid == IKEV2_SAPROTO_IKE) { + if (sa->sa_next == NULL) { + log_debug("%s: missing IKE SA for rekeying", __func__); + return (-1); + } + /* Update the responder SPI */ + spi = &msg->msg_prop->prop_peerspi; + if ((nsa = sa_new(env, sa->sa_next->sa_hdr.sh_ispi, + spi->spi, 1, NULL)) == NULL || nsa != sa->sa_next) { + log_debug("%s: invalid rekey SA", __func__); + if (nsa) + sa_free(env, nsa); + sa_free(env, sa->sa_next); + sa->sa_next = NULL; + return (-1); + } + if (ikev2_sa_initiator(env, nsa, sa, msg) == -1) { + log_debug("%s: failed to get IKE keys", __func__); + return (-1); + } + sa->sa_stateflags &= ~IKED_REQ_CHILDSA; + sa->sa_next = NULL; + return (ikev2_ikesa_enable(env, sa, nsa)); + } + + /* Child SA rekeying */ if (sa->sa_rekeyspi && (csa = childsa_lookup(sa, sa->sa_rekeyspi, prop->prop_protoid)) != NULL) { @@ -2635,11 +2776,163 @@ done: } int -ikev2_resp_create_child_sa(struct iked *env, struct iked_message *msg) +ikev2_ikesa_enable(struct iked *env, struct iked_sa *sa, struct iked_sa *nsa) { struct iked_childsa *csa, *nextcsa; struct iked_flow *flow, *nextflow; struct iked_proposal *prop, *nextprop; + int initiator; + + log_debug("%s: IKE SA %p ispi %s rspi %s replaced" + " by SA %p ispi %s rspi %s ", + __func__, sa, + print_spi(sa->sa_hdr.sh_ispi, 8), + print_spi(sa->sa_hdr.sh_rspi, 8), + nsa, + print_spi(nsa->sa_hdr.sh_ispi, 8), + print_spi(nsa->sa_hdr.sh_rspi, 8)); + + /* + * Transfer policy and address: + * - Remember if we initiated the original IKE-SA because of our policy. + * - Note that sa_address() will insert the new SA when we set sa_peer. + */ + initiator = !memcmp(&sa->sa_polpeer, &sa->sa_policy->pol_peer, + sizeof(sa->sa_polpeer)); + nsa->sa_policy = sa->sa_policy; + RB_REMOVE(iked_sapeers, &sa->sa_policy->pol_sapeers, sa); + sa->sa_policy = NULL; + if (sa_address(nsa, &nsa->sa_peer, &sa->sa_peer.addr, + initiator) == -1 || + sa_address(nsa, &nsa->sa_local, &sa->sa_local.addr, + initiator) == -1) { + /* reinsert old SA :/ */ + sa->sa_policy = nsa->sa_policy; + if (RB_FIND(iked_sapeers, &nsa->sa_policy->pol_sapeers, nsa)) + RB_REMOVE(iked_sapeers, &nsa->sa_policy->pol_sapeers, nsa); + RB_INSERT(iked_sapeers, &sa->sa_policy->pol_sapeers, sa); + nsa->sa_policy = NULL; + return (-1); + } + + /* Transfer socket and NAT information */ + nsa->sa_fd = sa->sa_fd; + nsa->sa_natt = sa->sa_natt; + nsa->sa_udpencap = sa->sa_udpencap; + + /* Transfer all Child SAs and flows from the old IKE SA */ + for (flow = TAILQ_FIRST(&sa->sa_flows); flow != NULL; + flow = nextflow) { + nextflow = TAILQ_NEXT(flow, flow_entry); + TAILQ_REMOVE(&sa->sa_flows, flow, flow_entry); + TAILQ_INSERT_TAIL(&nsa->sa_flows, flow, + flow_entry); + flow->flow_ikesa = nsa; + flow->flow_local = &nsa->sa_local; + flow->flow_peer = &nsa->sa_peer; + } + for (csa = TAILQ_FIRST(&sa->sa_childsas); csa != NULL; + csa = nextcsa) { + nextcsa = TAILQ_NEXT(csa, csa_entry); + TAILQ_REMOVE(&sa->sa_childsas, csa, csa_entry); + TAILQ_INSERT_TAIL(&nsa->sa_childsas, csa, + csa_entry); + csa->csa_ikesa = nsa; + if (csa->csa_dir == IPSP_DIRECTION_IN) { + csa->csa_local = &nsa->sa_peer; + csa->csa_peer = &nsa->sa_local; + } else { + csa->csa_local = &nsa->sa_local; + csa->csa_peer = &nsa->sa_peer; + } + } + /* Transfer all non-IKE proposals */ + for (prop = TAILQ_FIRST(&sa->sa_proposals); prop != NULL; + prop = nextprop) { + nextprop = TAILQ_NEXT(prop, prop_entry); + if (prop->prop_protoid == IKEV2_SAPROTO_IKE) + continue; + TAILQ_REMOVE(&sa->sa_proposals, prop, prop_entry); + TAILQ_INSERT_TAIL(&nsa->sa_proposals, prop, + prop_entry); + } + + /* Preserve ID information */ + if (sa->sa_hdr.sh_initiator == nsa->sa_hdr.sh_initiator) { + nsa->sa_iid = sa->sa_iid; + nsa->sa_rid = sa->sa_rid; + } else { + /* initiator and responder role swapped */ + nsa->sa_iid = sa->sa_rid; + nsa->sa_rid = sa->sa_iid; + } + /* duplicate the actual buffer */ + nsa->sa_iid.id_buf = ibuf_dup(nsa->sa_iid.id_buf); + nsa->sa_rid.id_buf = ibuf_dup(nsa->sa_rid.id_buf); + + /* Transfer sa_addrpool address */ + if (sa->sa_addrpool) { + RB_REMOVE(iked_addrpool, &env->sc_addrpool, sa); + nsa->sa_addrpool = sa->sa_addrpool; + sa->sa_addrpool = NULL; + RB_INSERT(iked_addrpool, &env->sc_addrpool, nsa); + } + + log_debug("%s: activating new IKE SA", __func__); + sa_state(env, nsa, IKEV2_STATE_ESTABLISHED); + timer_set(env, &nsa->sa_timer, ikev2_ike_sa_alive, nsa); + timer_add(env, &nsa->sa_timer, IKED_IKE_SA_ALIVE_TIMEOUT); + timer_set(env, &nsa->sa_rekey, ikev2_ike_sa_rekey, nsa); + if (nsa->sa_policy->pol_rekey) + timer_add(env, &nsa->sa_rekey, nsa->sa_policy->pol_rekey); + nsa->sa_stateflags = nsa->sa_statevalid; /* XXX */ + + /* unregister DPD keep alive timer & rekey first */ + if (sa->sa_state == IKEV2_STATE_ESTABLISHED) { + timer_del(env, &sa->sa_rekey); + timer_del(env, &sa->sa_timer); + } + + ikev2_ikesa_delete(env, sa, nsa->sa_hdr.sh_initiator); + return (0); +} + +void +ikev2_ikesa_delete(struct iked *env, struct iked_sa *sa, int initiator) +{ + struct ibuf *buf = NULL; + struct ikev2_delete *del; + + if (initiator) { + /* Send PAYLOAD_DELETE */ + if ((buf = ibuf_static()) == NULL) + goto done; + if ((del = ibuf_advance(buf, sizeof(*del))) == NULL) + goto done; + del->del_protoid = IKEV2_SAPROTO_IKE; + del->del_spisize = 0; + del->del_nspi = 0; + if (ikev2_send_ike_e(env, sa, buf, IKEV2_PAYLOAD_DELETE, + IKEV2_EXCHANGE_INFORMATIONAL, 0) == -1) + goto done; + log_debug("%s: sent delete, closing SA", __func__); +done: + ibuf_release(buf); + sa_state(env, sa, IKEV2_STATE_CLOSED); + } else { + sa_state(env, sa, IKEV2_STATE_CLOSING); + } + + /* Remove IKE-SA after timeout, e.g. if we don't get a delete */ + timer_set(env, &sa->sa_timer, ikev2_ike_sa_timeout, sa); + timer_add(env, &sa->sa_timer, IKED_IKE_SA_DELETE_TIMEOUT); +} + +int +ikev2_resp_create_child_sa(struct iked *env, struct iked_message *msg) +{ + struct iked_childsa *csa; + struct iked_proposal *prop; struct iked_sa *nsa = NULL, *sa = msg->msg_sa; struct iked_spi *spi, *rekey = &msg->msg_rekey; struct ikev2_keyexchange *ke; @@ -2838,60 +3131,9 @@ ikev2_resp_create_child_sa(struct iked *env, struct iked_message *msg) IKEV2_EXCHANGE_CREATE_CHILD_SA, firstpayload, 1)) == -1) goto done; - if (protoid == IKEV2_SAPROTO_IKE) { - /* Transfer all Child SAs and flows from the old IKE SA */ - for (flow = TAILQ_FIRST(&sa->sa_flows); flow != NULL; - flow = nextflow) { - nextflow = TAILQ_NEXT(flow, flow_entry); - TAILQ_REMOVE(&sa->sa_flows, flow, flow_entry); - TAILQ_INSERT_TAIL(&nsa->sa_flows, flow, - flow_entry); - flow->flow_ikesa = nsa; - } - for (csa = TAILQ_FIRST(&sa->sa_childsas); csa != NULL; - csa = nextcsa) { - nextcsa = TAILQ_NEXT(csa, csa_entry); - TAILQ_REMOVE(&sa->sa_childsas, csa, csa_entry); - TAILQ_INSERT_TAIL(&nsa->sa_childsas, csa, - csa_entry); - csa->csa_ikesa = nsa; - } - /* Transfer all non-IKE proposals */ - for (prop = TAILQ_FIRST(&sa->sa_proposals); prop != NULL; - prop = nextprop) { - nextprop = TAILQ_NEXT(prop, prop_entry); - if (prop->prop_protoid == IKEV2_SAPROTO_IKE) - continue; - TAILQ_REMOVE(&sa->sa_proposals, prop, prop_entry); - TAILQ_INSERT_TAIL(&nsa->sa_proposals, prop, - prop_entry); - } - /* Preserve ID information */ - nsa->sa_iid = sa->sa_iid; - nsa->sa_iid.id_buf = ibuf_dup(sa->sa_iid.id_buf); - nsa->sa_rid = sa->sa_rid; - nsa->sa_rid.id_buf = ibuf_dup(sa->sa_rid.id_buf); - - log_debug("%s: activating new IKE SA", __func__); - sa_state(env, nsa, IKEV2_STATE_ESTABLISHED); - timer_set(env, &nsa->sa_timer, ikev2_ike_sa_alive, nsa); - timer_add(env, &nsa->sa_timer, IKED_IKE_SA_ALIVE_TIMEOUT); - nsa->sa_stateflags = sa->sa_statevalid; /* XXX */ - - /* unregister DPD keep alive timer first */ - if (sa->sa_state == IKEV2_STATE_ESTABLISHED) - timer_del(env, &sa->sa_timer); - timer_set(env, &sa->sa_timer, ikev2_ike_sa_timeout, sa); - timer_add(env, &sa->sa_timer, IKED_IKE_SA_REKEY_TIMEOUT); - - if (sa->sa_addrpool) { - /* transfer sa_addrpool address */ - RB_REMOVE(iked_addrpool, &env->sc_addrpool, sa); - nsa->sa_addrpool = sa->sa_addrpool; - sa->sa_addrpool = NULL; - RB_INSERT(iked_addrpool, &env->sc_addrpool, nsa); - } - } else + if (protoid == IKEV2_SAPROTO_IKE) + ret = ikev2_ikesa_enable(env, sa, nsa); + else ret = ikev2_childsa_enable(env, sa); done: @@ -3290,7 +3532,7 @@ ikev2_sa_initiator_dh(struct iked_sa *sa, struct iked_message *msg, u_int proto) int ikev2_sa_initiator(struct iked *env, struct iked_sa *sa, - struct iked_message *msg) + struct iked_sa *osa, struct iked_message *msg) { struct iked_transform *xform; @@ -3374,7 +3616,7 @@ ikev2_sa_initiator(struct iked *env, struct iked_sa *sa, return (-1); } - return (ikev2_sa_keys(env, sa, NULL)); + return (ikev2_sa_keys(env, sa, osa ? osa->sa_key_d : NULL)); } int @@ -4457,7 +4699,8 @@ ikev2_valid_proposal(struct iked_proposal *prop, return (0); } -void +/* return 0 if processed, -1 if busy */ +int ikev2_acquire_sa(struct iked *env, struct iked_flow *acquire) { struct iked_flow *flow; @@ -4465,7 +4708,7 @@ ikev2_acquire_sa(struct iked *env, struct iked_flow *acquire) struct iked_policy pol, *p = NULL; if (env->sc_passive) - return; + return (0); /* First try to find an active flow with IKE SA */ flow = RB_FIND(iked_flows, &env->sc_activeflows, acquire); @@ -4482,7 +4725,7 @@ ikev2_acquire_sa(struct iked *env, struct iked_flow *acquire) if ((p = policy_test(env, &pol)) == NULL) { log_warnx("%s: flow wasn't found", __func__); - return; + return (0); } log_debug("%s: found matching policy '%s'", __func__, @@ -4496,14 +4739,16 @@ ikev2_acquire_sa(struct iked *env, struct iked_flow *acquire) if ((sa = flow->flow_ikesa) == NULL) { log_warnx("%s: flow without SA", __func__); - return; + return (0); } - + if (sa->sa_stateflags & IKED_REQ_CHILDSA) + return (-1); /* busy, retry later */ if (ikev2_send_create_child_sa(env, sa, NULL, flow->flow_saproto) != 0) log_warnx("%s: failed to initiate a " "CREATE_CHILD_SA exchange", __func__); } + return (0); } void @@ -4519,7 +4764,8 @@ ikev2_disable_rekeying(struct iked *env, struct iked_sa *sa) (void)ikev2_childsa_delete(env, sa, 0, 0, NULL, 1); } -void +/* return 0 if processed, -1 if busy */ +int ikev2_rekey_sa(struct iked *env, struct iked_spi *rekey) { struct iked_childsa *csa, key; @@ -4528,28 +4774,32 @@ ikev2_rekey_sa(struct iked *env, struct iked_spi *rekey) key.csa_spi = *rekey; csa = RB_FIND(iked_activesas, &env->sc_activesas, &key); if (!csa) - return; + return (0); if (csa->csa_rekey) /* See if it's already taken care of */ - return; + return (0); if ((sa = csa->csa_ikesa) == NULL) { log_warnx("%s: SA %s doesn't have a parent SA", __func__, print_spi(rekey->spi, rekey->spi_size)); - return; + return (0); } if (!sa_stateok(sa, IKEV2_STATE_ESTABLISHED)) { log_warnx("%s: SA %s is not established", __func__, print_spi(rekey->spi, rekey->spi_size)); - return; + return (0); } + if (sa->sa_stateflags & IKED_REQ_CHILDSA) + return (-1); /* busy, retry later */ if (csa->csa_allocated) /* Peer SPI died first, get the local one */ rekey->spi = csa->csa_peerspi; if (ikev2_send_create_child_sa(env, sa, rekey, rekey->spi_protoid)) log_warnx("%s: failed to initiate a CREATE_CHILD_SA exchange", __func__); + return (0); } -void +/* return 0 if processed, -1 if busy */ +int ikev2_drop_sa(struct iked *env, struct iked_spi *drop) { struct ibuf *buf = NULL; @@ -4561,12 +4811,18 @@ ikev2_drop_sa(struct iked *env, struct iked_spi *drop) key.csa_spi = *drop; csa = RB_FIND(iked_activesas, &env->sc_activesas, &key); if (!csa || csa->csa_rekey) - return; + return (0); + + sa = csa->csa_ikesa; + if (sa && (sa->sa_stateflags & IKED_REQ_CHILDSA)) + return (-1); /* busy, retry later */ + RB_REMOVE(iked_activesas, &env->sc_activesas, csa); csa->csa_loaded = 0; - if ((sa = csa->csa_ikesa) == NULL) { + csa->csa_rekey = 1; /* prevent re-loading */ + if (sa == NULL) { log_debug("%s: failed to find a parent SA", __func__); - return; + return (0); } if (csa->csa_allocated) @@ -4582,7 +4838,7 @@ ikev2_drop_sa(struct iked *env, struct iked_spi *drop) /* Send PAYLOAD_DELETE */ if ((buf = ibuf_static()) == NULL) - return; + return (0); if ((del = ibuf_advance(buf, sizeof(*del))) == NULL) goto done; del->del_protoid = drop->spi_protoid; @@ -4604,7 +4860,7 @@ ikev2_drop_sa(struct iked *env, struct iked_spi *drop) done: ibuf_release(buf); - return; + return (0); } int diff --git a/sbin/iked/ikev2.h b/sbin/iked/ikev2.h index a1daf478d94..652cf875bea 100644 --- a/sbin/iked/ikev2.h +++ b/sbin/iked/ikev2.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ikev2.h,v 1.14 2014/04/29 11:51:13 markus Exp $ */ +/* $OpenBSD: ikev2.h,v 1.15 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -37,7 +37,8 @@ #define IKEV2_STATE_VALID 6 /* authenticated AND validated certs */ #define IKEV2_STATE_EAP_VALID 7 /* EAP validated */ #define IKEV2_STATE_ESTABLISHED 8 /* active IKE SA */ -#define IKEV2_STATE_CLOSED 9 /* delete this SA */ +#define IKEV2_STATE_CLOSING 9 /* expect delete for this SA */ +#define IKEV2_STATE_CLOSED 10 /* delete this SA */ extern struct iked_constmap ikev2_state_map[]; diff --git a/sbin/iked/ikev2_msg.c b/sbin/iked/ikev2_msg.c index 29f609dcc17..85a91d8edeb 100644 --- a/sbin/iked/ikev2_msg.c +++ b/sbin/iked/ikev2_msg.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ikev2_msg.c,v 1.33 2014/05/05 16:14:37 markus Exp $ */ +/* $OpenBSD: ikev2_msg.c,v 1.34 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -190,8 +190,22 @@ ikev2_msg_valid_ike_sa(struct iked *env, struct ike_header *oldhdr, struct iked_sa sa; #endif - if (msg->msg_sa != NULL && msg->msg_policy != NULL) + if (msg->msg_sa != NULL && msg->msg_policy != NULL) { + /* + * Only permit informational requests from initiator + * on closing SAs (for DELETE). + */ + if (msg->msg_sa->sa_state == IKEV2_STATE_CLOSING) { + if (((oldhdr->ike_flags & + (IKEV2_FLAG_INITIATOR|IKEV2_FLAG_RESPONSE)) == + IKEV2_FLAG_INITIATOR) && + (oldhdr->ike_exchange == + IKEV2_EXCHANGE_INFORMATIONAL)) + return (0); + return (-1); + } return (0); + } #if 0 /* diff --git a/sbin/iked/ikev2_pld.c b/sbin/iked/ikev2_pld.c index e46da1b13d5..951085f9d3e 100644 --- a/sbin/iked/ikev2_pld.c +++ b/sbin/iked/ikev2_pld.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ikev2_pld.c,v 1.44 2014/05/06 09:21:50 markus Exp $ */ +/* $OpenBSD: ikev2_pld.c,v 1.45 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -1340,9 +1340,13 @@ ikev2_pld_delete(struct iked *env, struct ikev2_payload *pld, msg->msg_parent->msg_responded = 1; ibuf_release(resp); sa_state(env, sa, IKEV2_STATE_CLOSED); - return (ret); + } else { + /* + * We're sending a delete message. Upper layer + * must deal with deletion of the IKE SA. + */ + ret = 0; } - log_debug("%s: invalid SPI size", __func__); return (ret); } diff --git a/sbin/iked/parse.y b/sbin/iked/parse.y index 1d5ab4f34dd..24cfe6bf9c4 100644 --- a/sbin/iked/parse.y +++ b/sbin/iked/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.37 2014/02/17 15:07:23 markus Exp $ */ +/* $OpenBSD: parse.y,v 1.38 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -321,7 +321,8 @@ void copy_transforms(u_int, const struct ipsec_xf *, int create_ike(char *, int, u_int8_t, struct ipsec_hosts *, struct ipsec_hosts *, struct ipsec_mode *, struct ipsec_mode *, u_int8_t, - u_int8_t, char *, char *, struct iked_lifetime *, + u_int8_t, char *, char *, + u_int32_t, struct iked_lifetime *, struct iked_auth *, struct ipsec_filters *, struct ipsec_addr_wrap *); int create_user(const char *, const char *); @@ -370,7 +371,7 @@ typedef struct { %token PASSIVE ACTIVE ANY TAG TAP PROTO LOCAL GROUP NAME CONFIG EAP USER %token IKEV1 FLOW SA TCPMD5 TUNNEL TRANSPORT COUPLE DECOUPLE SET %token INCLUDE LIFETIME BYTES INET INET6 QUICK SKIP DEFAULT -%token IPCOMP OCSP +%token IPCOMP OCSP IKELIFETIME %token STRING %token NUMBER %type string @@ -392,7 +393,7 @@ typedef struct { %type keyspec %type ike_sa child_sa %type lifetime -%type byte_spec time_spec +%type byte_spec time_spec ikelifetime %type name %type cfg ikecfg ikecfgvals %% @@ -446,9 +447,9 @@ user : USER STRING STRING { ; ikev2rule : IKEV2 name ikeflags satype af proto hosts_list peers - ike_sa child_sa ids lifetime ikeauth ikecfg filters { + ike_sa child_sa ids ikelifetime lifetime ikeauth ikecfg filters { if (create_ike($2, $5, $6, $7, &$8, $9, $10, $4, $3, - $11.srcid, $11.dstid, &$12, &$13, $15, $14) == -1) + $11.srcid, $11.dstid, $12, &$13, &$14, $16, $15) == -1) YYERROR; } ; @@ -895,6 +896,13 @@ lifetime : /* empty */ { } ; +ikelifetime : /* empty */ { + $$ = 0; + } + | IKELIFETIME time_spec { + $$ = $2; + } + keyspec : STRING { u_int8_t *hex; @@ -1084,6 +1092,7 @@ lookup(char *s) { "from", FROM }, { "group", GROUP }, { "ike", IKEV1 }, + { "ikelifetime", IKELIFETIME }, { "ikesa", IKESA }, { "ikev2", IKEV2 }, { "include", INCLUDE }, @@ -2312,6 +2321,9 @@ print_policy(struct iked_policy *pol) if (pol->pol_peerid.id_length != 0) print_verbose(" dstid %s", pol->pol_peerid.id_data); + if (pol->pol_rekey) + print_verbose(" ikelifetime %u", pol->pol_rekey); + print_verbose(" lifetime %llu bytes %llu", pol->pol_lifetime.lt_seconds, pol->pol_lifetime.lt_bytes); @@ -2382,7 +2394,8 @@ int create_ike(char *name, int af, u_int8_t ipproto, struct ipsec_hosts *hosts, struct ipsec_hosts *peers, struct ipsec_mode *ike_sa, struct ipsec_mode *ipsec_sa, u_int8_t saproto, - u_int8_t flags, char *srcid, char *dstid, struct iked_lifetime *lt, + u_int8_t flags, char *srcid, char *dstid, + u_int32_t ikelifetime, struct iked_lifetime *lt, struct iked_auth *authtype, struct ipsec_filters *filter, struct ipsec_addr_wrap *ikecfg) { @@ -2508,6 +2521,9 @@ create_ike(char *name, int af, u_int8_t ipproto, struct ipsec_hosts *hosts, pol.pol_af = ipb->af; } + if (ikelifetime) + pol.pol_rekey = ikelifetime; + if (lt) pol.pol_lifetime = *lt; else diff --git a/sbin/iked/pfkey.c b/sbin/iked/pfkey.c index 892d3d03303..af360cad789 100644 --- a/sbin/iked/pfkey.c +++ b/sbin/iked/pfkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pfkey.c,v 1.33 2014/05/05 18:50:36 markus Exp $ */ +/* $OpenBSD: pfkey.c,v 1.34 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -59,7 +59,7 @@ struct pfkey_message { u_int8_t *pm_data; ssize_t pm_length; }; -SIMPLEQ_HEAD(, pfkey_message) pfkey_postponed = +SIMPLEQ_HEAD(, pfkey_message) pfkey_retry, pfkey_postponed = SIMPLEQ_HEAD_INITIALIZER(pfkey_postponed); struct pfkey_constmap { @@ -120,7 +120,7 @@ struct sadb_ident * void *pfkey_find_ext(u_int8_t *, ssize_t, int); void pfkey_timer_cb(int, short, void *); -void pfkey_process(struct iked *, struct pfkey_message *); +int pfkey_process(struct iked *, struct pfkey_message *); int pfkey_couple(int sd, struct iked_sas *sas, int couple) @@ -1493,7 +1493,7 @@ void pfkey_dispatch(int sd, short event, void *arg) { struct iked *env = (struct iked *)arg; - struct pfkey_message pm; + struct pfkey_message pm, *pmp; struct sadb_msg hdr; ssize_t len; u_int8_t *data; @@ -1521,9 +1521,17 @@ pfkey_dispatch(int sd, short event, void *arg) pm.pm_data = data; pm.pm_length = len; - pfkey_process(env, &pm); - free(data); + if (pfkey_process(env, &pm) == -1 && + (pmp = calloc(1, sizeof(*pmp))) != NULL) { + pmp->pm_data = data; + pmp->pm_length = len; + log_debug("%s: pfkey_process is busy, retry later", __func__); + SIMPLEQ_INSERT_TAIL(&pfkey_postponed, pmp, pm_entry); + evtimer_add(&pfkey_timer_ev, &pfkey_timer_tv); + } else { + free(data); + } } void @@ -1532,16 +1540,32 @@ pfkey_timer_cb(int unused, short event, void *arg) struct iked *env = arg; struct pfkey_message *pm; + SIMPLEQ_INIT(&pfkey_retry); while (!SIMPLEQ_EMPTY(&pfkey_postponed)) { pm = SIMPLEQ_FIRST(&pfkey_postponed); SIMPLEQ_REMOVE_HEAD(&pfkey_postponed, pm_entry); - pfkey_process(env, pm); - free(pm->pm_data); - free(pm); + if (pfkey_process(env, pm) == -1) { + log_debug("%s: pfkey_process is busy, retry later", + __func__); + SIMPLEQ_INSERT_TAIL(&pfkey_retry, pm, pm_entry); + } else { + free(pm->pm_data); + free(pm); + } + } + while ((pm = SIMPLEQ_FIRST(&pfkey_retry)) != NULL) { + SIMPLEQ_REMOVE_HEAD(&pfkey_retry, pm_entry); + SIMPLEQ_INSERT_TAIL(&pfkey_postponed, pm, pm_entry); } + if (!SIMPLEQ_EMPTY(&pfkey_postponed)) + evtimer_add(&pfkey_timer_ev, &pfkey_timer_tv); } -void +/* + * pfkey_process returns 0 if the message has been processed and -1 if + * the system is busy and the the message should be passed again, later. + */ +int pfkey_process(struct iked *env, struct pfkey_message *pm) { struct iked_spi spi; @@ -1556,7 +1580,7 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) struct sadb_x_policy sa_pol; struct sockaddr *ssrc, *sdst, *smask, *dmask, *speer; struct iovec iov[IOV_CNT]; - int iov_cnt, sd; + int ret = 0, iov_cnt, sd; u_int8_t *reply; ssize_t rlen; const char *errmsg = NULL; @@ -1565,7 +1589,7 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) size_t slen; if (!env || !data || !len) - return; + return (0); sd = env->sc_pfkey; hdr = (struct sadb_msg *)data; @@ -1578,20 +1602,20 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) if ((sa_addr = pfkey_find_ext(data, len, SADB_EXT_ADDRESS_DST)) == NULL) { log_debug("%s: no peer address", __func__); - return; + return (0); } speer = (struct sockaddr *)(sa_addr + 1); peer.addr_af = speer->sa_family; peer.addr_port = htons(socket_getport(speer)); if ((slen = speer->sa_len) > sizeof(peer.addr)) { log_debug("%s: invalid peer address len", __func__); - return; + return (0); } memcpy(&peer.addr, speer, slen); if (socket_af((struct sockaddr *)&peer.addr, peer.addr_port) == -1) { log_debug("%s: invalid address", __func__); - return; + return (0); } flow.flow_peer = &peer; @@ -1624,7 +1648,7 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) if (pfkey_write(sd, &smsg, iov, iov_cnt, &reply, &rlen)) { log_warnx("%s: failed to get a policy", __func__); - return; + return (0); } if ((sa_addr = pfkey_find_ext(reply, rlen, @@ -1637,13 +1661,13 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) flow.flow_src.addr_port = htons(socket_getport(ssrc)); if ((slen = ssrc->sa_len) > sizeof(flow.flow_src.addr)) { log_debug("%s: invalid src address len", __func__); - return; + return (0); } memcpy(&flow.flow_src.addr, ssrc, slen); if (socket_af((struct sockaddr *)&flow.flow_src.addr, flow.flow_src.addr_port) == -1) { log_debug("%s: invalid address", __func__); - return; + return (0); } if ((sa_addr = pfkey_find_ext(reply, rlen, @@ -1656,13 +1680,13 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) flow.flow_dst.addr_port = htons(socket_getport(sdst)); if ((slen = sdst->sa_len) > sizeof(flow.flow_dst.addr)) { log_debug("%s: invalid dst address len", __func__); - return; + return (0); } memcpy(&flow.flow_dst.addr, sdst, slen); if (socket_af((struct sockaddr *)&flow.flow_dst.addr, flow.flow_dst.addr_port) == -1) { log_debug("%s: invalid address", __func__); - return; + return (0); } if ((sa_addr = pfkey_find_ext(reply, rlen, @@ -1687,7 +1711,7 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) default: log_debug("%s: bad address family", __func__); free(reply); - return; + return (0); } if ((sa_addr = pfkey_find_ext(reply, rlen, @@ -1712,7 +1736,7 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) default: log_debug("%s: bad address family", __func__); free(reply); - return; + return (0); } if ((sa_proto = pfkey_find_ext(reply, rlen, @@ -1728,7 +1752,7 @@ pfkey_process(struct iked *env, struct pfkey_message *pm) print_host(sdst, NULL, 0), print_host(dmask, NULL, 0), print_host(speer, NULL, 0)); - ikev2_acquire_sa(env, &flow); + ret = ikev2_acquire_sa(env, &flow); out: if (errmsg) @@ -1739,7 +1763,7 @@ out: case SADB_EXPIRE: if ((sa = pfkey_find_ext(data, len, SADB_EXT_SA)) == NULL) { log_warnx("%s: SA extension wasn't found", __func__); - return; + return (0); } if ((sa_ltime = pfkey_find_ext(data, len, SADB_EXT_LIFETIME_SOFT)) == NULL && @@ -1747,7 +1771,7 @@ out: SADB_EXT_LIFETIME_HARD)) == NULL) { log_warnx("%s: lifetime extension wasn't found", __func__); - return; + return (0); } spi.spi = ntohl(sa->sadb_sa_spi); spi.spi_size = 4; @@ -1762,7 +1786,7 @@ out: log_warnx("%s: usupported SA type %d spi %s", __func__, hdr->sadb_msg_satype, print_spi(spi.spi, spi.spi_size)); - return; + return (0); } log_debug("%s: SA %s is expired, pending %s", __func__, @@ -1771,9 +1795,10 @@ out: "rekeying" : "deletion"); if (sa_ltime->sadb_lifetime_exttype == SADB_EXT_LIFETIME_SOFT) - ikev2_rekey_sa(env, &spi); + ret = ikev2_rekey_sa(env, &spi); else - ikev2_drop_sa(env, &spi); + ret = ikev2_drop_sa(env, &spi); break; } + return (ret); } diff --git a/sbin/iked/policy.c b/sbin/iked/policy.c index e5c553ec69e..635e0051f90 100644 --- a/sbin/iked/policy.c +++ b/sbin/iked/policy.c @@ -1,4 +1,4 @@ -/* $OpenBSD: policy.c,v 1.33 2014/05/06 09:48:40 markus Exp $ */ +/* $OpenBSD: policy.c,v 1.34 2014/05/06 10:24:22 markus Exp $ */ /* * Copyright (c) 2010-2013 Reyk Floeter @@ -242,7 +242,8 @@ sa_state(struct iked *env, struct iked_sa *sa, int state) NULL, 0), print_host((struct sockaddr *)&sa->sa_local.addr, NULL, 0), - sa->sa_policy->pol_name); + sa->sa_policy ? sa->sa_policy->pol_name : + ""); break; default: log_debug("%s: %s -> %s", __func__, a, b); @@ -372,6 +373,11 @@ sa_free(struct iked *env, struct iked_sa *sa) print_spi(sa->sa_hdr.sh_ispi, 8), print_spi(sa->sa_hdr.sh_rspi, 8)); + /* IKE rekeying running? */ + if (sa->sa_next) { + RB_REMOVE(iked_sas, &env->sc_sas, sa->sa_next); + config_free_sa(env, sa->sa_next); + } RB_REMOVE(iked_sas, &env->sc_sas, sa); config_free_sa(env, sa); } @@ -401,8 +407,8 @@ sa_address(struct iked_sa *sa, struct iked_addr *addr, { struct iked_policy *pol = sa->sa_policy; - if (pol == NULL) { - log_debug("%s: invalid policy", __func__); + if (sa->sa_state != IKEV2_STATE_CLOSING && pol == NULL) { + log_debug("%s: missing policy", __func__); return (-1); } @@ -415,7 +421,7 @@ sa_address(struct iked_sa *sa, struct iked_addr *addr, return (-1); } - if (addr == &sa->sa_peer) { + if (addr == &sa->sa_peer && pol) { /* XXX Re-insert node into the tree */ RB_REMOVE(iked_sapeers, &pol->pol_sapeers, sa); memcpy(&sa->sa_polpeer, initiator ? &pol->pol_peer : @@ -471,7 +477,7 @@ sa_lookup(struct iked *env, u_int64_t ispi, u_int64_t rspi, struct iked_sa *sa, key; key.sa_hdr.sh_ispi = ispi; - key.sa_hdr.sh_rspi = rspi; + /* key.sa_hdr.sh_rspi = rspi; */ key.sa_hdr.sh_initiator = initiator; if ((sa = RB_FIND(iked_sas, &env->sc_sas, &key)) != NULL) { -- 2.20.1