#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)
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 ||
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
}
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;
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
* 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;
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);
* 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;
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;
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++) {
session->i_sent_data_sc_idle = 1;
}
}
+ if (!idle)
+ session->last_time_data_sc_detected = time(NULL);
+
+ ike_session_monitor_idle(session);
}
void
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) {
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;
+ }
+ }
+}