On Thu, Nov 09, 2017 at 11:40:30AM +0100, Patrick Wildt wrote:
> Hi,
>
> this diff implements MOBIKE (RFC 4555) support in iked(8), with us
> acting as responder. In practice this support means that clients like
> iPhones can roam in different networks (LTE, WiFi) and change their
> external address without having to re-do the whole handshake. It allows
> the client to choose how and when to change the external tunnel endpoint
> addresses on demand, depending on which network is better or even is
> connected at all.
>
> This diff already had a few iterations where race conditions were found
> and ironed out, so this should be a rather stable version which has been
> working really well for me now.
>
> Key changes with this diff are:
>
> * MOBIKE is on by default, but can be turned off (if neccessary)
> using a config key.
> * MOBIKE support is then announced to the initiator.
> * Peers can use different IP addresses in the IKE messages, but the
> Tunnel is only updated when we receive IKEV2_N_UPDATE_SA_ADDRESSES.
> * This means we need to store two peer addresses. The one he talked to us
> last, so that we know where to respond to. And the one that the kernel
> knows, so that we know if we have to update the SAs and flows.
> * We need to update the SA as well as the flow. For the SAs I added
> a way of updating destination addresses of an existing TDB in June.
> The flows simply need a reload after the SA changed.
> * COOKIE2 is used for return routability checks. strongswan seems
> to use it. It's encrypted and should be sent back to the initiator.
>
> When you, for instance, use your phone and turn on/off WiFi, you should
> see that the VPN stays connected and iked should log that the address
> has changed. Especially `ipsecctl -sa` should show updated SAs and
> flows.
>
> OKs? Objections? Feedback?
I have heard from two more people running it and seeing it work. Anyone
else?
> Patrick
>
> diff --git a/sbin/iked/config.c b/sbin/iked/config.c
> index 590e4d7f4da..da8d0745f3c 100644
> --- a/sbin/iked/config.c
> +++ b/sbin/iked/config.c
> @@ -814,6 +814,29 @@ config_getcompile(struct iked *env, struct imsg *imsg)
> return (0);
> }
>
> +int
> +config_setmobike(struct iked *env)
> +{
> + unsigned int boolval;
> +
> + boolval = env->sc_mobike;
> + proc_compose(&env->sc_ps, PROC_IKEV2, IMSG_CTL_MOBIKE,
> + &boolval, sizeof(boolval));
> + return (0);
> +}
> +
> +int
> +config_getmobike(struct iked *env, struct imsg *imsg)
> +{
> + unsigned int boolval;
> +
> + IMSG_SIZE_CHECK(imsg, &boolval);
> + memcpy(&boolval, imsg->data, sizeof(boolval));
> + env->sc_mobike = boolval;
> + log_debug("%s: %smobike", __func__, env->sc_mobike ? "" : "no ");
> + return (0);
> +}
> +
> int
> config_setocsp(struct iked *env)
> {
> diff --git a/sbin/iked/iked.c b/sbin/iked/iked.c
> index 09fef3ea877..548da77d014 100644
> --- a/sbin/iked/iked.c
> +++ b/sbin/iked/iked.c
> @@ -250,6 +250,7 @@ parent_configure(struct iked *env)
> if (pledge("stdio rpath proc dns inet route sendfd", NULL) == -1)
> fatal("pledge");
>
> + config_setmobike(env);
> config_setcoupled(env, env->sc_decoupled ? 0 : 1);
> config_setmode(env, env->sc_passive ? 1 : 0);
> config_setocsp(env);
> @@ -280,6 +281,7 @@ parent_reload(struct iked *env, int reset, const char
> *filename)
> /* Re-compile policies and skip steps */
> config_setcompile(env, PROC_IKEV2);
>
> + config_setmobike(env);
> config_setcoupled(env, env->sc_decoupled ? 0 : 1);
> config_setmode(env, env->sc_passive ? 1 : 0);
> config_setocsp(env);
> diff --git a/sbin/iked/iked.conf.5 b/sbin/iked/iked.conf.5
> index 8c77f24d603..be0e8ef30f6 100644
> --- a/sbin/iked/iked.conf.5
> +++ b/sbin/iked/iked.conf.5
> @@ -136,6 +136,15 @@ This is the default.
> .It Ic set decouple
> Don't load the negotiated SAs and flows from the kernel.
> This mode is only useful for testing and debugging.
> +.It Ic set mobike
> +Enable MOBIKE (RFC 4555) support.
> +This is the default.
> +MOBIKE allows the change of the peer IP address for IKE and IPsec SAs.
> +Currenly
> +.Xr iked 8
> +only supports MOBIKE when acting as a responder.
> +.It Ic set nomobike
> +Disables MOBIKE support.
> .It Ic set ocsp Ar URL
> Enable OCSP and set the URL of the OCSP responder.
> Please note that the matching responder and issuer certificates
> diff --git a/sbin/iked/iked.h b/sbin/iked/iked.h
> index b536d58e157..c2030ea7460 100644
> --- a/sbin/iked/iked.h
> +++ b/sbin/iked/iked.h
> @@ -157,6 +157,8 @@ struct iked_flow {
>
> RB_ENTRY(iked_flow) flow_node;
> TAILQ_ENTRY(iked_flow) flow_entry;
> +
> + int flow_replacing; /* cf flow_replace() */
> int flow_ipcomp;
> };
> RB_HEAD(iked_flows, iked_flow);
> @@ -371,6 +373,7 @@ struct iked_sa {
> #define IKED_SATYPE_LOCAL 1 /* Local SA */
>
> struct iked_addr sa_peer;
> + struct iked_addr sa_peer_loaded;/* MOBIKE */
> struct iked_addr sa_local;
> int sa_fd;
>
> @@ -441,6 +444,8 @@ struct iked_sa {
> uint16_t sa_cpi_out; /* IPcomp outgoing */
> uint16_t sa_cpi_in; /* IPcomp incoming*/
>
> + int sa_mobike; /* MOBIKE */
> +
> struct iked_timer sa_timer; /* SA timeouts */
> #define IKED_IKE_SA_EXCHANGE_TIMEOUT 300 /* 5 minutes */
> #define IKED_IKE_SA_REKEY_TIMEOUT 120 /* 2 minutes */
> @@ -487,6 +492,7 @@ struct iked_message {
> int msg_response;
> int msg_responded;
> int msg_natt;
> + int msg_natt_rcvd;
> int msg_error;
> int msg_e;
> struct iked_message *msg_parent;
> @@ -508,6 +514,10 @@ struct iked_message {
> struct iked_id msg_cert;
> struct ibuf *msg_cookie;
>
> + /* MOBIKE */
> + int msg_update_sa_addresses;
> + struct ibuf *msg_cookie2;
> +
> /* Parse stack */
> struct iked_proposal *msg_prop;
> uint16_t msg_attrlength;
> @@ -590,6 +600,8 @@ struct iked {
> uint8_t sc_passive;
> uint8_t sc_decoupled;
>
> + uint8_t sc_mobike; /* MOBIKE */
> +
> struct iked_policies sc_policies;
> struct iked_policy *sc_defaultcon;
>
> @@ -687,6 +699,8 @@ int config_setocsp(struct iked *);
> int config_getocsp(struct iked *, struct imsg *);
> int config_setkeys(struct iked *);
> int config_getkey(struct iked *, struct imsg *);
> +int config_setmobike(struct iked *);
> +int config_getmobike(struct iked *, struct imsg *);
>
> /* policy.c */
> void policy_init(struct iked *);
> @@ -711,6 +725,7 @@ struct iked_childsa *
> childsa_lookup(struct iked_sa *, uint64_t, uint8_t);
> void flow_free(struct iked_flow *);
> int flow_equal(struct iked_flow *, struct iked_flow *);
> +int flow_replace(struct iked *, struct iked_flow *);
> struct iked_sa *
> sa_lookup(struct iked *, uint64_t, uint64_t, unsigned int);
> struct iked_user *
> @@ -860,6 +875,7 @@ int pfkey_flow_delete(int fd, struct iked_flow *);
> int pfkey_block(int, int, unsigned int);
> int pfkey_sa_init(int, struct iked_childsa *, uint32_t *);
> int pfkey_sa_add(int, struct iked_childsa *, struct iked_childsa *);
> +int pfkey_sa_update_addresses(int, struct iked_childsa *);
> int pfkey_sa_delete(int, struct iked_childsa *);
> int pfkey_sa_last_used(int, struct iked_childsa *, uint64_t *);
> int pfkey_flush(int);
> diff --git a/sbin/iked/ikev2.c b/sbin/iked/ikev2.c
> index e908b6230a5..ffbea0eccc3 100644
> --- a/sbin/iked/ikev2.c
> +++ b/sbin/iked/ikev2.c
> @@ -140,6 +140,13 @@ ssize_t ikev2_add_sighashnotify(struct ibuf *, struct
> ikev2_payload **,
> ssize_t ikev2_add_nat_detection(struct iked *, struct ibuf *,
> struct ikev2_payload **, struct iked_message *, ssize_t);
>
> +ssize_t ikev2_add_mobike(struct iked *, struct ibuf *,
> + struct ikev2_payload **, ssize_t, struct iked_sa *);
> +int ikev2_update_sa_addresses(struct iked *, struct iked_sa *);
> +int ikev2_resp_informational(struct iked *, struct iked_sa *,
> + struct iked_message *);
> +
> +
> static struct privsep_proc procs[] = {
> { "parent", PROC_PARENT, ikev2_dispatch_parent },
> { "certstore", PROC_CERT, ikev2_dispatch_cert }
> @@ -189,6 +196,8 @@ ikev2_dispatch_parent(int fd, struct privsep_proc *p,
> struct imsg *imsg)
> IKED_INITIATOR_INITIAL);
> }
> return (0);
> + case IMSG_CTL_MOBIKE:
> + return (config_getmobike(env, imsg));
> case IMSG_UDP_SOCKET:
> return (config_getsocket(env, imsg, ikev2_msg_cb));
> case IMSG_PFKEY_SOCKET:
> @@ -1584,6 +1593,30 @@ ikev2_add_ipcompnotify(struct iked *env, struct ibuf
> *e,
> return (len);
> }
>
> +ssize_t
> +ikev2_add_mobike(struct iked *env, struct ibuf *e,
> + struct ikev2_payload **pld, ssize_t len, struct iked_sa *sa)
> +{
> + struct ikev2_notify *n;
> + uint8_t *ptr;
> +
> + if (*pld)
> + if (ikev2_next_payload(*pld, len, IKEV2_PAYLOAD_NOTIFY) == -1)
> + return (-1);
> + if ((*pld = ikev2_add_payload(e)) == NULL)
> + return (-1);
> + len = sizeof(*n);
> + if ((ptr = ibuf_advance(e, len)) == NULL)
> + return (-1);
> + n = (struct ikev2_notify *)ptr;
> + n->n_protoid = 0;
> + n->n_spisize = 0;
> + n->n_type = htobe16(IKEV2_N_MOBIKE_SUPPORTED);
> + log_debug("%s: done", __func__);
> +
> + return (len);
> +}
> +
> ssize_t
> ikev2_add_sighashnotify(struct ibuf *e, struct ikev2_payload **pld,
> ssize_t len)
> @@ -2055,6 +2088,84 @@ ikev2_add_buf(struct ibuf *buf, struct ibuf *data)
> return (0);
> }
>
> +int
> +ikev2_resp_informational(struct iked *env, struct iked_sa *sa,
> + struct iked_message *msg)
> +{
> + struct ikev2_notify *n;
> + struct ikev2_payload *pld = NULL;
> + struct ike_header *hdr;
> + struct ibuf *buf = NULL;
> + ssize_t len = 0;
> + int ret = -1;
> + int oflags = 0;
> + uint8_t firstpayload = IKEV2_PAYLOAD_NONE;
> +
> + if (!sa_stateok(sa, IKEV2_STATE_AUTH_REQUEST) ||
> + msg->msg_responded || msg->msg_error)
> + goto done;
> +
> + if ((buf = ibuf_static()) == NULL)
> + goto done;
> + /*
> + * Include NAT_DETECTION notification on UPDATE_SA_ADDRESSES or if
> + * the peer did include them, too (RFC 455, 3.8).
> + */
> + if (sa->sa_mobike &&
> + (msg->msg_update_sa_addresses || msg->msg_natt_rcvd)) {
> + /*
> + * XXX workaround so ikev2_msg_frompeer() fails for
> + * XXX ikev2_nat_detection(), and the correct src/dst are
> + * XXX used for the nat detection payload.
> + */
> + if (msg->msg_parent == NULL)
> + goto done;
> + if ((hdr = ibuf_seek(msg->msg_parent->msg_data, 0,
> + sizeof(*hdr))) == NULL)
> + goto done;
> + oflags = hdr->ike_flags;
> + if (sa->sa_hdr.sh_initiator)
> + hdr->ike_flags |= IKEV2_FLAG_INITIATOR;
> + else
> + hdr->ike_flags &= ~IKEV2_FLAG_INITIATOR;
> + /* NAT-T notify payloads */
> + len = ikev2_add_nat_detection(env, buf, &pld, msg, len);
> + hdr->ike_flags = oflags; /* XXX undo workaround */
> + if (len == -1)
> + goto done;
> + firstpayload = IKEV2_PAYLOAD_NOTIFY;
> + }
> + /* Reflect COOKIE2 */
> + if (msg->msg_cookie2) {
> + if (ikev2_next_payload(pld, len, IKEV2_PAYLOAD_NOTIFY) == -1)
> + goto done;
> + if ((pld = ikev2_add_payload(buf)) == NULL)
> + goto done;
> + if ((n = ibuf_advance(buf, sizeof(*n))) == NULL)
> + goto done;
> + n->n_protoid = IKEV2_SAPROTO_IKE;
> + n->n_spisize = 0;
> + n->n_type = htobe16(IKEV2_N_COOKIE2);
> + if (ikev2_add_buf(buf, msg->msg_cookie2) == -1)
> + goto done;
> + len = sizeof(*n) + ibuf_size(msg->msg_cookie2);
> + log_debug("%s: added cookie2", __func__);
> + if (firstpayload == IKEV2_PAYLOAD_NONE)
> + firstpayload = IKEV2_PAYLOAD_NOTIFY;
> + }
> + /* add terminator, if there is already a payload */
> + if (firstpayload != IKEV2_PAYLOAD_NONE)
> + if (ikev2_next_payload(pld, len, IKEV2_PAYLOAD_NONE) == -1)
> + goto done;
> + ret = ikev2_msg_send_encrypt(env, sa, &buf,
> + IKEV2_EXCHANGE_INFORMATIONAL, firstpayload, 1);
> + if (ret != -1)
> + msg->msg_responded = 1;
> + done:
> + ibuf_release(buf);
> + return (ret);
> +}
> +
> void
> ikev2_resp_recv(struct iked *env, struct iked_message *msg,
> struct ike_header *hdr)
> @@ -2152,13 +2263,9 @@ ikev2_resp_recv(struct iked *env, struct iked_message
> *msg,
> ikev2_send_error(env, sa, msg, hdr->ike_exchange);
> break;
> case IKEV2_EXCHANGE_INFORMATIONAL:
> - if (sa_stateok(sa, IKEV2_STATE_AUTH_REQUEST) &&
> - !msg->msg_responded && !msg->msg_error) {
> - (void)ikev2_send_ike_e(env, sa, NULL,
> - IKEV2_PAYLOAD_NONE, IKEV2_EXCHANGE_INFORMATIONAL,
> - 1);
> - msg->msg_responded = 1;
> - }
> + if (msg->msg_update_sa_addresses)
> + ikev2_update_sa_addresses(env, sa);
> + (void)ikev2_resp_informational(env, sa, msg);
> break;
> default:
> break;
> @@ -2451,6 +2558,11 @@ ikev2_resp_ike_auth(struct iked *env, struct iked_sa
> *sa)
> (len = ikev2_add_ipcompnotify(env, e, &pld, len, sa)) == -1)
> goto done;
>
> + /* MOBIKE */
> + if (sa->sa_mobike &&
> + (len = ikev2_add_mobike(env, e, &pld, len, sa)) == -1)
> + goto done;
> +
> if (ikev2_next_payload(pld, len, IKEV2_PAYLOAD_SA) == -1)
> goto done;
>
> @@ -3151,10 +3263,13 @@ ikev2_ikesa_enable(struct iked *env, struct iked_sa
> *sa, struct iked_sa *nsa)
> nsa->sa_natt = sa->sa_natt;
> nsa->sa_udpencap = sa->sa_udpencap;
> nsa->sa_usekeepalive = sa->sa_usekeepalive;
> + nsa->sa_mobike = sa->sa_mobike;
>
> /* Transfer old addresses */
> memcpy(&nsa->sa_local, &sa->sa_local, sizeof(nsa->sa_local));
> memcpy(&nsa->sa_peer, &sa->sa_peer, sizeof(nsa->sa_peer));
> + memcpy(&nsa->sa_peer_loaded, &sa->sa_peer_loaded,
> + sizeof(nsa->sa_peer_loaded));
>
> /* Transfer all Child SAs and flows from the old IKE SA */
> for (flow = TAILQ_FIRST(&sa->sa_flows); flow != NULL;
> @@ -5104,6 +5219,7 @@ ikev2_childsa_enable(struct iked *env, struct iked_sa
> *sa)
> {
> struct iked_childsa *csa, *ocsa;
> struct iked_flow *flow, *oflow;
> + int peer_changed, reload;
>
> if (sa->sa_ipcomp && sa->sa_cpi_in && sa->sa_cpi_out &&
> ikev2_ipcomp_enable(env, sa) == -1)
> @@ -5136,9 +5252,26 @@ ikev2_childsa_enable(struct iked *env, struct iked_sa
> *sa)
> print_spi(csa->csa_spi.spi, csa->csa_spi.spi_size));
> }
>
> + peer_changed = (memcmp(&sa->sa_peer_loaded, &sa->sa_peer,
> + sizeof(sa->sa_peer_loaded)) != 0);
> +
> TAILQ_FOREACH(flow, &sa->sa_flows, flow_entry) {
> - if (flow->flow_loaded)
> - continue;
> + /* re-load the flow if the peer for the flow has changed */
> + reload = 0;
> + if (flow->flow_loaded) {
> + if (!peer_changed) {
> + log_debug("%s: flow already loaded %p",
> + __func__, flow);
> + continue;
> + }
> + RB_REMOVE(iked_flows, &env->sc_activeflows, flow);
> + /* flow might be shared w/other SA */
> + if (!flow->flow_replacing ||
> + flow_replace(env, flow) != 0)
> + (void)pfkey_flow_delete(env->sc_pfkey, flow);
> + flow->flow_loaded = 0; /* we did RB_REMOVE */
> + reload = 1;
> + }
>
> if (pfkey_flow_add(env->sc_pfkey, flow) != 0) {
> log_debug("%s: failed to load flow", __func__);
> @@ -5150,12 +5283,24 @@ ikev2_childsa_enable(struct iked *env, struct iked_sa
> *sa)
> log_debug("%s: replaced old flow %p with %p",
> __func__, oflow, flow);
> oflow->flow_loaded = 0;
> + oflow->flow_replacing = 0;
> RB_REMOVE(iked_flows, &env->sc_activeflows, oflow);
> + flow->flow_replacing = 1;
> }
>
> RB_INSERT(iked_flows, &env->sc_activeflows, flow);
>
> - log_debug("%s: loaded flow %p", __func__, flow);
> + log_debug("%s: %sloaded flow %p", __func__,
> + reload ? "re" : "", flow);
> + }
> +
> + /* remember the current address for ikev2_update_sa_addresses() */
> + if (peer_changed) {
> + memcpy(&sa->sa_peer_loaded, &sa->sa_peer,
> + sizeof(sa->sa_peer_loaded));
> + log_debug("%s: remember SA peer %s", __func__,
> + print_host((struct sockaddr *)&sa->sa_peer_loaded.addr,
> + NULL, 0));
> }
>
> return (0);
> @@ -5721,3 +5866,81 @@ ikev2_cp_fixaddr(struct iked_sa *sa, struct iked_addr
> *addr,
> }
> return (0);
> }
> +
> +int
> +ikev2_update_sa_addresses(struct iked *env, struct iked_sa *sa)
> +{
> + struct iked_childsa *csa;
> + struct iked_flow *flow, *oflow;
> + struct iked_message *msg;
> +
> + if (!sa_stateok(sa, IKEV2_STATE_ESTABLISHED))
> + return -1;
> +
> + log_info("%s: old %s new %s", __func__,
> + print_host((struct sockaddr *)&sa->sa_peer_loaded.addr, NULL, 0),
> + print_host((struct sockaddr *)&sa->sa_peer.addr, NULL, 0));
> +
> + TAILQ_FOREACH(csa, &sa->sa_childsas, csa_entry) {
> + if (!csa->csa_loaded)
> + continue;
> + if (pfkey_sa_update_addresses(env->sc_pfkey, csa) != 0)
> + log_debug("%s: failed to update sa", __func__);
> + }
> +
> + /* delete and re-add flows */
> + TAILQ_FOREACH(flow, &sa->sa_flows, flow_entry) {
> + if (flow->flow_loaded) {
> + RB_REMOVE(iked_flows, &env->sc_activeflows, flow);
> + /* flow might be shared w/other SA */
> + if (!flow->flow_replacing ||
> + flow_replace(env, flow) != 0)
> + (void)pfkey_flow_delete(env->sc_pfkey, flow);
> + flow->flow_loaded = 0;
> + }
> + /* update IPcomp flows */
> + if (flow->flow_ipcomp) {
> + struct iked_addr *addr =
> + (flow->flow_dir == IPSP_DIRECTION_OUT) ?
> + &flow->flow_dst :
> + &flow->flow_src;
> + memcpy(addr, &sa->sa_peer, sizeof(sa->sa_peer));
> + socket_setport((struct sockaddr *)&addr->addr, 0);
> + addr->addr_port = 0;
> + addr->addr_mask = (addr->addr_af == AF_INET) ? 32 : 128;
> + }
> + if (pfkey_flow_add(env->sc_pfkey, flow) != 0)
> + log_debug("%s: failed to add flow %p", __func__, flow);
> + if (!flow->flow_loaded)
> + continue;
> + if ((oflow = RB_FIND(iked_flows, &env->sc_activeflows, flow))
> + != NULL) {
> + log_debug("%s: replaced old flow %p with %p",
> + __func__, oflow, flow);
> + oflow->flow_loaded = 0;
> + oflow->flow_replacing = 0;
> + RB_REMOVE(iked_flows, &env->sc_activeflows, oflow);
> + flow->flow_replacing = 1;
> + }
> + RB_INSERT(iked_flows, &env->sc_activeflows, flow);
> + }
> +
> + /* update pending requests and responses */
> + TAILQ_FOREACH(msg, &sa->sa_requests, msg_entry) {
> + msg->msg_local = sa->sa_local.addr;
> + msg->msg_locallen = sa->sa_local.addr.ss_len;
> + msg->msg_peer = sa->sa_peer.addr;
> + msg->msg_peerlen = sa->sa_peer.addr.ss_len;
> + }
> + TAILQ_FOREACH(msg, &sa->sa_responses, msg_entry) {
> + msg->msg_local = sa->sa_local.addr;
> + msg->msg_locallen = sa->sa_local.addr.ss_len;
> + msg->msg_peer = sa->sa_peer.addr;
> + msg->msg_peerlen = sa->sa_peer.addr.ss_len;
> + }
> +
> + /* Update sa_peer_loaded, to match in-kernel information */
> + memcpy(&sa->sa_peer_loaded, &sa->sa_peer, sizeof(sa->sa_peer_loaded));
> +
> + return 0;
> +}
> diff --git a/sbin/iked/ikev2_msg.c b/sbin/iked/ikev2_msg.c
> index 79c96316946..c94808fe773 100644
> --- a/sbin/iked/ikev2_msg.c
> +++ b/sbin/iked/ikev2_msg.c
> @@ -184,6 +184,7 @@ ikev2_msg_cleanup(struct iked *env, struct iked_message
> *msg)
> ibuf_release(msg->msg_id.id_buf);
> ibuf_release(msg->msg_cert.id_buf);
> ibuf_release(msg->msg_cookie);
> + ibuf_release(msg->msg_cookie2);
>
> msg->msg_nonce = NULL;
> msg->msg_ke = NULL;
> @@ -191,6 +192,7 @@ ikev2_msg_cleanup(struct iked *env, struct iked_message
> *msg)
> msg->msg_id.id_buf = NULL;
> msg->msg_cert.id_buf = NULL;
> msg->msg_cookie = NULL;
> + msg->msg_cookie2 = NULL;
>
> config_free_proposals(&msg->msg_proposals, 0);
> }
> diff --git a/sbin/iked/ikev2_pld.c b/sbin/iked/ikev2_pld.c
> index 5724520f696..6ccae7b6654 100644
> --- a/sbin/iked/ikev2_pld.c
> +++ b/sbin/iked/ikev2_pld.c
> @@ -1153,6 +1153,8 @@ ikev2_pld_notify(struct iked *env, struct ikev2_payload
> *pld,
> msg->msg_sa->sa_usekeepalive = 1;
> }
> print_hex(md, 0, sizeof(md));
> + /* remember for MOBIKE */
> + msg->msg_parent->msg_natt_rcvd = 1;
> break;
> case IKEV2_N_AUTHENTICATION_FAILED:
> if (!msg->msg_e) {
> @@ -1292,6 +1294,66 @@ ikev2_pld_notify(struct iked *env, struct
> ikev2_payload *pld,
> msg->msg_sa->sa_cpi_out = betoh16(cpi);
> }
> break;
> + case IKEV2_N_MOBIKE_SUPPORTED:
> + if (!msg->msg_e) {
> + log_debug("%s: N_MOBIKE_SUPPORTED not encrypted",
> + __func__);
> + return (-1);
> + }
> + if (len != 0) {
> + log_debug("%s: ignoring malformed mobike"
> + " notification: %zu", __func__, len);
> + return (0);
> + }
> + if (!env->sc_mobike) {
> + log_debug("%s: mobike disabled", __func__);
> + return (0);
> + }
> + msg->msg_sa->sa_mobike = 1;
> + /* enforce natt */
> + msg->msg_sa->sa_natt = 1;
> + break;
> + case IKEV2_N_UPDATE_SA_ADDRESSES:
> + if (!msg->msg_e) {
> + log_debug("%s: N_UPDATE_SA_ADDRESSES not encrypted",
> + __func__);
> + return (-1);
> + }
> + if (!msg->msg_sa->sa_mobike) {
> + log_debug("%s: ignoring update sa addresses"
> + " notification w/o mobike: %zu", __func__, len);
> + return (0);
> + }
> + if (len != 0) {
> + log_debug("%s: ignoring malformed update sa addresses"
> + " notification: %zu", __func__, len);
> + return (0);
> + }
> + msg->msg_parent->msg_update_sa_addresses = 1;
> + break;
> + case IKEV2_N_COOKIE2:
> + if (!msg->msg_e) {
> + log_debug("%s: N_COOKIE2 not encrypted",
> + __func__);
> + return (-1);
> + }
> + if (!msg->msg_sa->sa_mobike) {
> + log_debug("%s: ignoring cookie2 notification"
> + " w/o mobike: %zu", __func__, len);
> + return (0);
> + }
> + if (len < IKED_COOKIE2_MIN || len > IKED_COOKIE2_MAX) {
> + log_debug("%s: ignoring malformed cookie2"
> + " notification: %zu", __func__, len);
> + return (0);
> + }
> + ibuf_release(msg->msg_cookie2); /* should not happen */
> + if ((msg->msg_cookie2 = ibuf_new(buf, len)) == NULL) {
> + log_debug("%s: failed to get peer cookie2", __func__);
> + return (-1);
> + }
> + msg->msg_parent->msg_cookie2 = msg->msg_cookie2;
> + break;
> case IKEV2_N_COOKIE:
> if (msg->msg_e) {
> log_debug("%s: N_COOKIE encrypted",
> diff --git a/sbin/iked/parse.y b/sbin/iked/parse.y
> index 419a5996f36..708165a7808 100644
> --- a/sbin/iked/parse.y
> +++ b/sbin/iked/parse.y
> @@ -99,6 +99,7 @@ static int debug = 0;
> static int rules = 0;
> static int passive = 0;
> static int decouple = 0;
> +static int mobike = 1;
> static char *ocsp_url = NULL;
>
> struct ipsec_xf {
> @@ -384,7 +385,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 IKELIFETIME
> +%token IPCOMP OCSP IKELIFETIME MOBIKE NOMOBIKE
> %token <v.string> STRING
> %token <v.number> NUMBER
> %type <v.string> string
> @@ -445,6 +446,8 @@ set : SET ACTIVE { passive = 0; }
> | SET PASSIVE { passive = 1; }
> | SET COUPLE { decouple = 0; }
> | SET DECOUPLE { decouple = 1; }
> + | SET MOBIKE { mobike = 1; }
> + | SET NOMOBIKE { mobike = 0; }
> | SET OCSP STRING {
> if ((ocsp_url = strdup($3)) == NULL) {
> yyerror("cannot set ocsp_url");
> @@ -1126,7 +1129,9 @@ lookup(char *s)
> { "ipcomp", IPCOMP },
> { "lifetime", LIFETIME },
> { "local", LOCAL },
> + { "mobike", MOBIKE },
> { "name", NAME },
> + { "nomobike", NOMOBIKE },
> { "ocsp", OCSP },
> { "passive", PASSIVE },
> { "peer", PEER },
> @@ -1495,6 +1500,7 @@ parse_config(const char *filename, struct iked *x_env)
> return (-1);
>
> decouple = passive = 0;
> + mobike = 1;
>
> if (env->sc_opts & IKED_OPT_PASSIVE)
> passive = 1;
> @@ -1505,6 +1511,7 @@ parse_config(const char *filename, struct iked *x_env)
>
> env->sc_passive = passive ? 1 : 0;
> env->sc_decoupled = decouple ? 1 : 0;
> + env->sc_mobike = mobike;
> env->sc_ocsp_url = ocsp_url;
>
> if (!rules)
> diff --git a/sbin/iked/pfkey.c b/sbin/iked/pfkey.c
> index bcee41e29f3..9720bac174f 100644
> --- a/sbin/iked/pfkey.c
> +++ b/sbin/iked/pfkey.c
> @@ -45,6 +45,9 @@
> #define PFKEYV2_CHUNK sizeof(uint64_t)
> #define PFKEY_REPLY_TIMEOUT 1000
>
> +/* only used internally */
> +#define IKED_SADB_UPDATE_SA_ADDRESSES 0xff
> +
> static uint32_t sadb_msg_seq = 0;
> static unsigned int sadb_decoupled = 0;
> static unsigned int sadb_ipv6refcnt = 0;
> @@ -435,17 +438,18 @@ pfkey_sa(int sd, uint8_t satype, uint8_t action, struct
> iked_childsa *sa)
> {
> struct sadb_msg smsg;
> struct sadb_sa sadb;
> - struct sadb_address sa_src, sa_dst;
> + struct sadb_address sa_src, sa_dst, sa_pxy;
> struct sadb_key sa_authkey, sa_enckey;
> struct sadb_lifetime sa_ltime_hard, sa_ltime_soft;
> struct sadb_x_udpencap udpencap;
> struct sadb_x_tag sa_tag;
> char *tag = NULL;
> struct sadb_x_tap sa_tap;
> - struct sockaddr_storage ssrc, sdst;
> + struct sockaddr_storage ssrc, sdst, spxy;
> struct sadb_ident *sa_srcid, *sa_dstid;
> struct iked_lifetime *lt;
> struct iked_policy *pol;
> + struct iked_addr *dst;
> struct iovec iov[IOV_CNT];
> uint32_t jitter;
> int iov_cnt;
> @@ -467,13 +471,26 @@ pfkey_sa(int sd, uint8_t satype, uint8_t action, struct
> iked_childsa *sa)
> return (-1);
> }
>
> + dst = (action == IKED_SADB_UPDATE_SA_ADDRESSES &&
> + sa->csa_dir == IPSP_DIRECTION_OUT) ?
> + &sa->csa_ikesa->sa_peer_loaded :
> + sa->csa_peer;
> bzero(&sdst, sizeof(sdst));
> - memcpy(&sdst, &sa->csa_peer->addr, sizeof(sdst));
> + memcpy(&sdst, &dst->addr, sizeof(sdst));
> if (socket_af((struct sockaddr *)&sdst, 0) == -1) {
> log_warn("%s: invalid address", __func__);
> return (-1);
> }
>
> + bzero(&spxy, sizeof(spxy));
> + if (dst != sa->csa_peer) {
> + memcpy(&spxy, &sa->csa_peer->addr, sizeof(spxy));
> + if (socket_af((struct sockaddr *)&spxy, 0) == -1) {
> + log_warn("%s: invalid address", __func__);
> + return (-1);
> + }
> + }
> +
> bzero(&smsg, sizeof(smsg));
> smsg.sadb_msg_version = PF_KEY_V2;
> smsg.sadb_msg_seq = ++sadb_msg_seq;
> @@ -503,6 +520,10 @@ pfkey_sa(int sd, uint8_t satype, uint8_t action, struct
> iked_childsa *sa)
> sa_dst.sadb_address_len = (sizeof(sa_dst) + ROUNDUP(sdst.ss_len)) / 8;
> sa_dst.sadb_address_exttype = SADB_EXT_ADDRESS_DST;
>
> + bzero(&sa_pxy, sizeof(sa_pxy));
> + sa_pxy.sadb_address_len = (sizeof(sa_pxy) + ROUNDUP(spxy.ss_len)) / 8;
> + sa_pxy.sadb_address_exttype = SADB_EXT_ADDRESS_PROXY;
> +
> bzero(&sa_authkey, sizeof(sa_authkey));
> bzero(&sa_enckey, sizeof(sa_enckey));
> bzero(&udpencap, sizeof udpencap);
> @@ -524,6 +545,11 @@ pfkey_sa(int sd, uint8_t satype, uint8_t action, struct
> iked_childsa *sa)
> ntohs(udpencap.sadb_x_udpencap_port));
> }
>
> + if (action == IKED_SADB_UPDATE_SA_ADDRESSES) {
> + smsg.sadb_msg_type = SADB_UPDATE;
> + goto send;
> + }
> +
> if ((action == SADB_ADD || action == SADB_UPDATE) &&
> !sa->csa_persistent && (lt->lt_bytes || lt->lt_seconds)) {
> sa_ltime_hard.sadb_lifetime_exttype = SADB_EXT_LIFETIME_HARD;
> @@ -655,6 +681,17 @@ pfkey_sa(int sd, uint8_t satype, uint8_t action, struct
> iked_childsa *sa)
> smsg.sadb_msg_len += sa_dst.sadb_address_len;
> iov_cnt++;
>
> + if (spxy.ss_len) {
> + /* pxy addr */
> + iov[iov_cnt].iov_base = &sa_pxy;
> + iov[iov_cnt].iov_len = sizeof(sa_pxy);
> + iov_cnt++;
> + iov[iov_cnt].iov_base = &spxy;
> + iov[iov_cnt].iov_len = ROUNDUP(spxy.ss_len);
> + smsg.sadb_msg_len += sa_pxy.sadb_address_len;
> + iov_cnt++;
> + }
> +
> if (sa_ltime_soft.sadb_lifetime_len) {
> /* soft lifetime */
> iov[iov_cnt].iov_base = &sa_ltime_soft;
> @@ -1342,6 +1379,24 @@ pfkey_sa_add(int fd, struct iked_childsa *sa, struct
> iked_childsa *last)
> return (0);
> }
>
> +int
> +pfkey_sa_update_addresses(int fd, struct iked_childsa *sa)
> +{
> + uint8_t satype;
> +
> + if (!sa->csa_ikesa)
> + return (-1);
> + /* check if peer has changed */
> + if (sa->csa_ikesa->sa_peer_loaded.addr.ss_family == AF_UNSPEC ||
> + memcmp(&sa->csa_ikesa->sa_peer_loaded, &sa->csa_ikesa->sa_peer,
> + sizeof(sa->csa_ikesa->sa_peer_loaded)) == 0)
> + return (0);
> + if (pfkey_map(pfkey_satype, sa->csa_saproto, &satype) == -1)
> + return (-1);
> + log_debug("%s: spi %s", __func__, print_spi(sa->csa_spi.spi, 4));
> + return pfkey_sa(fd, satype, IKED_SADB_UPDATE_SA_ADDRESSES, sa);
> +}
> +
> int
> pfkey_sa_delete(int fd, struct iked_childsa *sa)
> {
> diff --git a/sbin/iked/policy.c b/sbin/iked/policy.c
> index 0719323cc6f..52e5572c15b 100644
> --- a/sbin/iked/policy.c
> +++ b/sbin/iked/policy.c
> @@ -404,6 +404,45 @@ sa_free(struct iked *env, struct iked_sa *sa)
> config_free_sa(env, sa);
> }
>
> +/* oflow did replace active flow, so we need to re-activate a matching flow
> */
> +int
> +flow_replace(struct iked *env, struct iked_flow *oflow)
> +{
> + struct iked_sa *sa;
> + struct iked_flow *flow, *other;
> +
> + if (!oflow->flow_loaded)
> + return (-1);
> + RB_FOREACH(sa, iked_sas, &env->sc_sas) {
> + if (oflow->flow_ikesa == sa ||
> + sa->sa_state != IKEV2_STATE_ESTABLISHED)
> + continue;
> + TAILQ_FOREACH(flow, &sa->sa_flows, flow_entry) {
> + if (flow == oflow ||
> + flow->flow_loaded || !flow_equal(flow, oflow))
> + continue;
> + if ((other = RB_FIND(iked_flows, &env->sc_activeflows,
> + flow)) != NULL) {
> + /* XXX should not happen */
> + log_debug("%s: found flow %p for %p/%p",
> + __func__, other, flow, other);
> + return (-1);
> + }
> + if (pfkey_flow_add(env->sc_pfkey, flow) != 0) {
> + log_debug("%s: failed to load flow", __func__);
> + return (-1);
> + }
> + RB_INSERT(iked_flows, &env->sc_activeflows, flow);
> + log_debug("%s: loaded flow %p replaces %p", __func__,
> + flow, oflow);
> + /* check for matching flow if we get deleted, too */
> + flow->flow_replacing = 1;
> + return (0);
> + }
> + }
> + return (-1);
> +}
> +
> void
> sa_free_flows(struct iked *env, struct iked_saflows *head)
> {
> @@ -417,7 +456,9 @@ sa_free_flows(struct iked *env, struct iked_saflows *head)
> if (flow->flow_loaded)
> RB_REMOVE(iked_flows, &env->sc_activeflows, flow);
> TAILQ_REMOVE(head, flow, flow_entry);
> - (void)pfkey_flow_delete(env->sc_pfkey, flow);
> + if (!flow->flow_replacing ||
> + flow_replace(env, flow) != 0)
> + (void)pfkey_flow_delete(env->sc_pfkey, flow);
> flow_free(flow);
> }
> }
> diff --git a/sbin/iked/types.h b/sbin/iked/types.h
> index df358ee4e7d..2f32a8f2ffb 100644
> --- a/sbin/iked/types.h
> +++ b/sbin/iked/types.h
> @@ -59,6 +59,9 @@
> #define IKED_COOKIE_MIN 1 /* min 1 bytes */
> #define IKED_COOKIE_MAX 64 /* max 64 bytes */
>
> +#define IKED_COOKIE2_MIN 8 /* min 8 bytes */
> +#define IKED_COOKIE2_MAX 64 /* max 64 bytes */
> +
> #define IKED_ID_SIZE 1024 /* XXX should be dynamic */
> #define IKED_PSK_SIZE 1024 /* XXX should be dynamic */
> #define IKED_MSGBUF_MAX 8192
> @@ -99,6 +102,7 @@ enum imsg_type {
> IMSG_CTL_DECOUPLE,
> IMSG_CTL_ACTIVE,
> IMSG_CTL_PASSIVE,
> + IMSG_CTL_MOBIKE,
> IMSG_COMPILE,
> IMSG_UDP_SOCKET,
> IMSG_PFKEY_SOCKET,