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,

Reply via email to