]> git.saurik.com Git - apple/ipsec.git/blobdiff - ipsec-tools/racoon/ike_session.c
ipsec-93.8.tar.gz
[apple/ipsec.git] / ipsec-tools / racoon / ike_session.c
index 4f688bfb8de8975f1acd96a6f912cfe2c5c59776..97e5be73f7c5e5cf1bc411f4ceba6aeab2ba3357 100644 (file)
 #include "localconf.h"
 #include "remoteconf.h"
 #include "vpn_control.h"
+#include "proposal.h"
+#include "sainfo.h"
 
 const char *ike_session_stopped_by_vpn_disconnect = "Stopped by VPN disconnect";
 const char *ike_session_stopped_by_flush          = "Stopped by Flush";
 const char *ike_session_stopped_by_idle           = "Stopped by Idle";
 const char *ike_session_stopped_by_xauth_timeout  = "Stopped by XAUTH timeout";
 
-static LIST_HEAD(_ike_session_tree_, ike_session) ike_session_tree;
+static LIST_HEAD(_ike_session_tree_, ike_session) ike_session_tree = { NULL };
 
 static ike_session_t *
 new_ike_session (ike_session_id_t *id)
@@ -88,7 +90,7 @@ free_ike_session (ike_session_t *session)
         SCHED_KILL(session->traffic_monitor.sc_idle);
         SCHED_KILL(session->sc_xauth);
                if (session->start_timestamp.tv_sec || session->start_timestamp.tv_usec) {
-                       if (!(session->stop_timestamp.tv_sec || session->start_timestamp.tv_usec)) {
+                       if (!(session->stop_timestamp.tv_sec || session->stop_timestamp.tv_usec)) {
                                gettimeofday(&session->stop_timestamp, NULL);
                        }
             if (session->term_reason != ike_session_stopped_by_vpn_disconnect ||
@@ -314,17 +316,17 @@ ike_session_init_traffic_cop_params (struct ph1handle *iph1)
             min_period = MIN(iph1->rmconf->dpd_interval, iph1->rmconf->idle_timeout);
             max_period = MAX(iph1->rmconf->dpd_interval, iph1->rmconf->idle_timeout);
         } else if (iph1->rmconf->idle_timeout) {
-            min_period = max_period = MIN(0, iph1->rmconf->idle_timeout);
+            min_period = max_period = iph1->rmconf->idle_timeout;
         } else {
             // DPD_ALGO_DEFAULT is configured and there's no idle timeout... we don't need to monitor traffic
             return;
         }
         if (min_period) {
-            sample_period = min_period >> 1;
+            sample_period = min_period / 20;
             if (!sample_period)
                 sample_period = 1; // bad
         } else {
-            sample_period = max_period >> 1;
+            sample_period = max_period / 20;
             if (!sample_period)
                 sample_period = 1; // bad
         }
@@ -791,15 +793,24 @@ ike_session_traffic_cop (void *arg)
 static void
 ike_session_cleanup_idle (void *arg)
 {
-    ike_session_t *session = (ike_session_t *)arg;
+    ike_session_cleanup((ike_session_t *)arg, ike_session_stopped_by_idle);
+}
+
+static void
+ike_session_monitor_idle (ike_session_t *session)
+{
+       if (!session)
+               return;
 
     if (session->traffic_monitor.dir_idle == IPSEC_DIR_INBOUND ||
         session->traffic_monitor.dir_idle == IPSEC_DIR_ANY) {
         if (session->peer_sent_data_sc_idle) {
             SCHED_KILL(session->traffic_monitor.sc_idle);
-            session->traffic_monitor.sc_idle = sched_new(session->traffic_monitor.interv_idle,
-                                                         ike_session_cleanup_idle,
-                                                         session);
+                       if (session->traffic_monitor.interv_idle) {
+                               session->traffic_monitor.sc_idle = sched_new(session->traffic_monitor.interv_idle,
+                                                                                                                        ike_session_cleanup_idle,
+                                                                                                                        session);
+                       }
             session->peer_sent_data_sc_idle = 0;
             session->i_sent_data_sc_idle = 0;
             return;
@@ -809,16 +820,16 @@ ike_session_cleanup_idle (void *arg)
         session->traffic_monitor.dir_idle == IPSEC_DIR_ANY) {
         if (session->i_sent_data_sc_idle) {
             SCHED_KILL(session->traffic_monitor.sc_idle);
-            session->traffic_monitor.sc_idle = sched_new(session->traffic_monitor.interv_idle,
-                                                         ike_session_cleanup_idle,
-                                                         session);
+                       if (session->traffic_monitor.interv_idle) {
+                               session->traffic_monitor.sc_idle = sched_new(session->traffic_monitor.interv_idle,
+                                                                                                                        ike_session_cleanup_idle,
+                                                                                                                        session);
+                       }
             session->peer_sent_data_sc_idle = 0;
             session->i_sent_data_sc_idle = 0;
             return;
         }
     }
-
-    ike_session_cleanup((ike_session_t *)arg, ike_session_stopped_by_idle);
 }
 
 void
@@ -893,7 +904,7 @@ ike_session_cleanup_other_established_ph1s (ike_session_t    *session,
                 * TODO: currently, most recently established SA wins. Need to revisit to see if 
                 * alternative selections is better (e.g. largest p->index stays).
                 */
-               if (p != new_iph1) {
+               if (p != new_iph1 && !p->is_dying) {
                        SCHED_KILL(p->sce);
                        SCHED_KILL(p->sce_rekey);
                        p->is_dying = 1;
@@ -932,6 +943,10 @@ ike_session_cleanup_ph2 (struct ph2handle *iph2)
 
     SCHED_KILL(iph2->sce);
 
+       plog(LLV_ERROR, LOCATION, NULL,
+                "about to cleanup ph2: status %d, seq %d dying %d\n",
+                iph2->status, iph2->seq, iph2->is_dying);
+       
        /* send delete information */
        if (iph2->status == PHASE2ST_ESTABLISHED) {
                isakmp_info_send_d2(iph2);
@@ -982,7 +997,7 @@ ike_session_cleanup_other_established_ph2s (ike_session_t    *session,
                 * TODO: currently, most recently established SA wins. Need to revisit to see if 
                 * alternative selections is better.
                 */
-               if (p != new_iph2 && p->spid == new_iph2->spid) {
+               if (p != new_iph2 && p->spid == new_iph2->spid && !p->is_dying) {
                        SCHED_KILL(p->sce);
                        p->is_dying = 1;
                        
@@ -1056,6 +1071,9 @@ ike_session_purge_ph2s_by_ph1 (struct ph1handle *iph1)
        for (p = LIST_FIRST(&iph1->parent_session->ikev1_state.ph2tree); p; p = next) {
                // take next pointer now, since delete change the underlying ph2tree list
                next = LIST_NEXT(p, ph2ofsession_chain);
+               if (p->is_dying) {
+                       continue;
+               }
         SCHED_KILL(p->sce);
         p->is_dying = 1;
                        
@@ -1140,6 +1158,11 @@ ike_session_update_traffic_idle_status (ike_session_t *session,
         return;
     }
 
+    if (!session->established || session->stopped_by_vpn_controller || session->stop_timestamp.tv_sec || session->stop_timestamp.tv_usec) {
+        plog(LLV_DEBUG2, LOCATION, NULL, "dropping update on invalid session.\n", __FUNCTION__);
+        return;
+    }
+
     for (i = 0; i < max_stats; i++) {
         if (dir == IPSEC_DIR_INBOUND) {
             for (j = 0; j < session->traffic_monitor.num_in_last_poll; j++) {
@@ -1191,6 +1214,10 @@ ike_session_update_traffic_idle_status (ike_session_t *session,
             session->i_sent_data_sc_idle = 1;
         }
     }
+       if (!idle)
+               session->last_time_data_sc_detected = time(NULL);                                                                               
+
+       ike_session_monitor_idle(session);
 }
 
 void
@@ -1203,6 +1230,7 @@ ike_session_cleanup (ike_session_t *session,
     if (!session)
         return;
 
+       SCHED_KILL(session->traffic_monitor.sc_idle);
     // do ph2's first... we need the ph1s for notifications
     for (iph2 = LIST_FIRST(&session->ikev1_state.ph2tree); iph2; iph2 = LIST_NEXT(iph2, ph2ofsession_chain)) {
         if (iph2->status == PHASE2ST_ESTABLISHED) {
@@ -1352,4 +1380,337 @@ ike_session_stop_xauth_timer (struct ph1handle *iph1)
     if (iph1->parent_session) {
         SCHED_KILL(iph1->parent_session->sc_xauth);
     }
-}
\ No newline at end of file
+}
+
+static int
+ike_session_is_id_ipany (vchar_t *ext_id)
+{
+       struct id {
+               u_int8_t type;          /* ID Type */
+               u_int8_t proto_id;      /* Protocol ID */
+               u_int16_t port;         /* Port */
+               u_int32_t addr;         /* IPv4 address */
+               u_int32_t mask;
+       } *id_ptr;
+       
+       /* ignore protocol and port */
+       id_ptr = (struct id *)ext_id->v;
+       if (id_ptr->type == IPSECDOI_ID_IPV4_ADDR &&
+           id_ptr->addr == 0) {
+               return 1;
+       } else if (id_ptr->type == IPSECDOI_ID_IPV4_ADDR_SUBNET &&
+                          id_ptr->mask == 0 &&
+                          id_ptr->addr == 0) {
+               return 1;
+       }
+       plog(LLV_DEBUG2, LOCATION, NULL, "not ipany_ids in %s: type %d, addr %x, mask %x.\n",
+                __FUNCTION__, id_ptr->type, id_ptr->addr, id_ptr->mask);
+       return 0;
+}
+
+static int
+ike_session_is_id_portany (vchar_t *ext_id)
+{
+       struct id {
+               u_int8_t type;          /* ID Type */
+               u_int8_t proto_id;      /* Protocol ID */
+               u_int16_t port;         /* Port */
+               u_int32_t addr;         /* IPv4 address */
+               u_int32_t mask;
+       } *id_ptr;
+       
+       /* ignore addr */
+       id_ptr = (struct id *)ext_id->v;
+       if (id_ptr->type == IPSECDOI_ID_IPV4_ADDR &&
+           id_ptr->port == 0) {
+               return 1;
+       }
+       plog(LLV_DEBUG2, LOCATION, NULL, "not portany_ids in %s: type %d, port %x.\n",
+                __FUNCTION__, id_ptr->type, id_ptr->port);
+       return 0;
+}
+
+static void
+ike_session_set_id_portany (vchar_t *ext_id)
+{
+       struct id {
+               u_int8_t type;          /* ID Type */
+               u_int8_t proto_id;      /* Protocol ID */
+               u_int16_t port;         /* Port */
+               u_int32_t addr;         /* IPv4 address */
+               u_int32_t mask;
+       } *id_ptr;
+       
+       /* ignore addr */
+       id_ptr = (struct id *)ext_id->v;
+       if (id_ptr->type == IPSECDOI_ID_IPV4_ADDR) {
+           id_ptr->port = 0;
+               return;
+       }
+}
+
+static int
+ike_session_cmp_ph2_ids_ipany (vchar_t *ext_id,
+                                                          vchar_t *ext_id_p)
+{
+       if (ike_session_is_id_ipany(ext_id) &&
+           ike_session_is_id_ipany(ext_id_p)) {
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * ipsec rekeys for l2tp-over-ipsec fail particularly when client is behind nat because the client's configs and policies don't 
+ * match the server's view of the client's address and port.
+ * servers behave differently when using this address-port info to generate ids during phase2 rekeys, so try to match the incoming id to 
+ * a variety of info saved in the older phase2.
+ */
+int
+ike_session_cmp_ph2_ids (struct ph2handle *iph2,
+                                                struct ph2handle *older_ph2)
+{
+       vchar_t *portany_id = NULL;
+       vchar_t *portany_id_p = NULL;
+
+       if (iph2->id && older_ph2->id &&
+           iph2->id->l == older_ph2->id->l &&
+           memcmp(iph2->id->v, older_ph2->id->v, iph2->id->l) == 0 &&
+           iph2->id_p && older_ph2->id_p &&
+           iph2->id_p->l == older_ph2->id_p->l &&
+           memcmp(iph2->id_p->v, older_ph2->id_p->v, iph2->id_p->l) == 0) {
+               return 0;
+       }
+       if (iph2->ext_nat_id && older_ph2->ext_nat_id &&
+           iph2->ext_nat_id->l == older_ph2->ext_nat_id->l &&
+           memcmp(iph2->ext_nat_id->v, older_ph2->ext_nat_id->v, iph2->ext_nat_id->l) == 0 &&
+           iph2->ext_nat_id_p && older_ph2->ext_nat_id_p &&
+           iph2->ext_nat_id_p->l == older_ph2->ext_nat_id_p->l &&
+           memcmp(iph2->ext_nat_id_p->v, older_ph2->ext_nat_id_p->v, iph2->ext_nat_id_p->l) == 0) {
+               return 0;
+       }
+       if (iph2->id && older_ph2->ext_nat_id &&
+           iph2->id->l == older_ph2->ext_nat_id->l &&
+           memcmp(iph2->id->v, older_ph2->ext_nat_id->v, iph2->id->l) == 0 &&
+           iph2->id_p && older_ph2->ext_nat_id_p &&
+           iph2->id_p->l == older_ph2->ext_nat_id_p->l &&
+           memcmp(iph2->id_p->v, older_ph2->ext_nat_id_p->v, iph2->id_p->l) == 0) {
+               return 0;
+       }
+       if (iph2->id && older_ph2->ext_nat_id &&
+           iph2->id->l == older_ph2->ext_nat_id->l &&
+           memcmp(iph2->id->v, older_ph2->ext_nat_id->v, iph2->id->l) == 0 &&
+           iph2->id_p && older_ph2->id_p &&
+           iph2->id_p->l == older_ph2->id_p->l &&
+           memcmp(iph2->id_p->v, older_ph2->id_p->v, iph2->id_p->l) == 0) {
+               return 0;
+       }
+       if (iph2->id && older_ph2->id &&
+           iph2->id->l == older_ph2->id->l &&
+           memcmp(iph2->id->v, older_ph2->id->v, iph2->id->l) == 0 &&
+           iph2->id_p && older_ph2->ext_nat_id_p &&
+           iph2->id_p->l == older_ph2->ext_nat_id_p->l &&
+           memcmp(iph2->id_p->v, older_ph2->ext_nat_id_p->v, iph2->id_p->l) == 0) {
+               return 0;
+       }
+
+       /* check if the external id has a wildcard port and compare ids accordingly */
+       if ((older_ph2->ext_nat_id && ike_session_is_id_portany(older_ph2->ext_nat_id)) ||
+               (older_ph2->ext_nat_id_p && ike_session_is_id_portany(older_ph2->ext_nat_id_p))) {
+               // try ignoring ports in iph2->id and iph2->id
+               if (iph2->id && (portany_id = vdup(iph2->id))) {
+                       ike_session_set_id_portany(portany_id);
+               }
+               if (iph2->id_p && (portany_id_p = vdup(iph2->id_p))) {
+                       ike_session_set_id_portany(portany_id_p);
+               }
+               if (portany_id && older_ph2->ext_nat_id &&
+                       portany_id->l == older_ph2->ext_nat_id->l &&
+                       memcmp(portany_id->v, older_ph2->ext_nat_id->v, portany_id->l) == 0 &&
+                       portany_id_p && older_ph2->ext_nat_id_p &&
+                       portany_id_p->l == older_ph2->ext_nat_id_p->l &&
+                       memcmp(portany_id_p->v, older_ph2->ext_nat_id_p->v, portany_id_p->l) == 0) {
+                       if (portany_id) {
+                               vfree(portany_id);
+                       }
+                       if (portany_id_p) {
+                               vfree(portany_id_p);
+                       }
+                       return 0;
+               }
+               if (iph2->id && older_ph2->ext_nat_id &&
+                       iph2->id->l == older_ph2->ext_nat_id->l &&
+                       memcmp(portany_id->v, older_ph2->ext_nat_id->v, portany_id->l) == 0 &&
+                       iph2->id_p && older_ph2->id_p &&
+                       iph2->id_p->l == older_ph2->id_p->l &&
+                       memcmp(iph2->id_p->v, older_ph2->id_p->v, iph2->id_p->l) == 0) {
+                       if (portany_id) {
+                               vfree(portany_id);
+                       }
+                       if (portany_id_p) {
+                               vfree(portany_id_p);
+                       }
+                       return 0;
+               }
+               if (iph2->id && older_ph2->id &&
+                       iph2->id->l == older_ph2->id->l &&
+                       memcmp(iph2->id->v, older_ph2->id->v, iph2->id->l) == 0 &&
+                       iph2->id_p && older_ph2->ext_nat_id_p &&
+                       iph2->id_p->l == older_ph2->ext_nat_id_p->l &&
+                       memcmp(portany_id_p->v, older_ph2->ext_nat_id_p->v, portany_id_p->l) == 0) {
+                       if (portany_id) {
+                               vfree(portany_id);
+                       }
+                       if (portany_id_p) {
+                               vfree(portany_id_p);
+                       }
+                       return 0;
+               }
+               if (portany_id) {
+                       vfree(portany_id);
+               }
+               if (portany_id_p) {
+                       vfree(portany_id_p);
+               }
+       }
+       return -1;
+}
+
+int
+ike_session_get_sainfo_r (struct ph2handle *iph2)
+{
+       if (iph2->parent_session &&
+           iph2->parent_session->is_client &&
+           iph2->id && iph2->id_p) {
+               struct ph2handle *p;
+               int ipany_ids = ike_session_cmp_ph2_ids_ipany(iph2->id, iph2->id_p);
+               plog(LLV_DEBUG2, LOCATION, NULL, "ipany_ids %d in %s.\n", ipany_ids, __FUNCTION__);
+               
+               for (p = LIST_FIRST(&iph2->parent_session->ikev1_state.ph2tree); p; p = LIST_NEXT(p, ph2ofsession_chain)) {
+                       if (iph2 != p && !p->is_dying && p->status >= PHASE2ST_ESTABLISHED &&
+                           p->sainfo && !p->sainfo->to_delete && !p->sainfo->to_remove) {
+                               plog(LLV_DEBUG2, LOCATION, NULL, "candidate ph2 found in %s.\n", __FUNCTION__);
+                               if (ipany_ids ||
+                                   ike_session_cmp_ph2_ids(iph2, p) == 0) {
+                                       plog(LLV_DEBUG2, LOCATION, NULL, "candidate ph2 matched in %s.\n", __FUNCTION__);
+                                       iph2->sainfo = p->sainfo;
+                                       if (p->ext_nat_id) {
+                                               if (iph2->ext_nat_id) {
+                                                       vfree(iph2->ext_nat_id);
+                                               }
+                                               iph2->ext_nat_id = vdup(p->ext_nat_id);
+                                       }
+                                       if (p->ext_nat_id_p) {
+                                               if (iph2->ext_nat_id_p) {
+                                                       vfree(iph2->ext_nat_id_p);
+                                               }
+                                               iph2->ext_nat_id_p = vdup(p->ext_nat_id_p);
+                                       }
+                                       return 0;
+                               }
+                       }
+               }
+       }
+       return -1;
+}
+
+int
+ike_session_get_proposal_r (struct ph2handle *iph2)
+{
+       if (iph2->parent_session &&
+           iph2->parent_session->is_client &&
+           iph2->id && iph2->id_p) {
+               struct ph2handle *p;
+               int ipany_ids = ike_session_cmp_ph2_ids_ipany(iph2->id, iph2->id_p);
+               plog(LLV_DEBUG2, LOCATION, NULL, "ipany_ids %d in %s.\n", ipany_ids, __FUNCTION__);
+
+               for (p = LIST_FIRST(&iph2->parent_session->ikev1_state.ph2tree); p; p = LIST_NEXT(p, ph2ofsession_chain)) {
+                       if (iph2 != p && !p->is_dying && p->status >= PHASE2ST_ESTABLISHED &&
+                           p->approval) {
+                               plog(LLV_DEBUG2, LOCATION, NULL, "candidate ph2 found in %s.\n", __FUNCTION__);
+                               if (ipany_ids ||
+                                   ike_session_cmp_ph2_ids(iph2, p) == 0) {
+                                       plog(LLV_DEBUG2, LOCATION, NULL, "candidate ph2 matched in %s.\n", __FUNCTION__);
+                                       iph2->proposal = dupsaprop(p->approval, 1);
+                                       return 0;
+                               }
+                       }
+               }
+       }
+       return -1;
+}
+
+void
+ike_session_update_natt_version (struct ph1handle *iph1)
+{
+       if (iph1->parent_session) {
+               if (iph1->natt_options) {
+                       iph1->parent_session->natt_version = iph1->natt_options->version;
+               } else {
+                       iph1->parent_session->natt_version = 0;
+               }
+       }
+}
+
+int
+ike_session_get_natt_version (struct ph1handle *iph1)
+{
+       if (iph1->parent_session) {
+               return(iph1->parent_session->natt_version);
+       }
+       return 0;
+}
+
+int
+ike_session_drop_rekey (ike_session_t *session)
+{
+       if (session) {
+               // drop if btmm session is idle) {
+               if (session->is_btmm_ipsec &&
+                       session->last_time_data_sc_detected &&
+                       session->traffic_monitor.interv_mon &&
+                       session->traffic_monitor.interv_idle) {
+                       time_t now = time(NULL);
+
+                       if ((now - session->last_time_data_sc_detected) > (session->traffic_monitor.interv_mon << 1)) {
+                               plog(LLV_DEBUG2, LOCATION, NULL, "session is idle: drop rekey.\n");
+                               return 1;
+                       }
+               }
+       }
+       return 0;
+}
+
+void
+ike_session_ph2_retransmits (struct ph2handle *iph2)
+{
+       int num_retransmits;
+
+       if (!iph2->is_dying &&
+               iph2->is_rekey &&
+               iph2->ph1 &&
+               iph2->ph1->sce_rekey && !iph2->ph1->sce_rekey->dead &&
+               iph2->parent_session &&
+               !iph2->parent_session->is_cisco_ipsec && /* not for Cisco */
+               iph2->parent_session->is_client) {
+               num_retransmits = iph2->ph1->rmconf->retry_counter - iph2->retry_counter;
+               if (num_retransmits == 3) {
+                       /*
+                        * phase2 negotiation is stalling on retransmits, inspite of a valid ph1.
+                        * one of the following is possible:
+                        * - (0) severe packet loss.
+                        * - (1) the peer is dead.
+                        * - (2) the peer is out of sync hence dropping this phase2 rekey (and perhaps responding with insecure 
+                        *       invalid-cookie notifications... but those are untrusted and so we can't rekey phase1 off that)
+                        *              (2.1) the peer rebooted (or process restarted) and is now alive.
+                        *     (2.2) the peer has deleted phase1 without notifying us (or the notification got dropped somehow).
+                        *     (2.3) the peer has a policy/bug stopping this phase2 rekey
+                        *
+                        * in all these cases, one sure way to know is to trigger a phase1 rekey early.
+                        */
+                       plog(LLV_DEBUG2, LOCATION, NULL, "many phase2 retransmits: try phase1 rekey and this phase2 to quit earlier.\n");
+                       isakmp_ph1rekeyexpire(iph2->ph1, TRUE);
+                       iph2->retry_counter = 0;
+               }
+       }
+}