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?

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,

Reply via email to