iked: support MOBIKE (RFC 4555)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

iked: support MOBIKE (RFC 4555)

Patrick Wildt-3
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 | Threaded
Open this post in threaded view
|

Re: iked: support MOBIKE (RFC 4555)

Patrick Wildt-3
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,