On Tue, Nov 14, 2017 at 01:58:41PM +0100, Patrick Wildt wrote:
> 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?
So far the response has been good. Any OKs or should I just put it in
to give it more exposure?
Patrick
> > 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,