]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
kernel-wfp: Install inbound ALE IP-in-IP filters
authorMartin Willi <martin@revosec.ch>
Thu, 27 Nov 2014 18:19:09 +0000 (19:19 +0100)
committerMartin Willi <martin@revosec.ch>
Thu, 4 Dec 2014 10:10:48 +0000 (11:10 +0100)
When processing inbound tunnel mode packets, Windows decrypts packets and
filters them as IP-in-IP packets. We therefore require an ALE filter that
calls the FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_ALE_ACCEPT callout to allow them
when using a default-drop policy.

Without these rules, any outbound packet created an ALE state that allows
inbound packets as well. Processing inbound packets without any outbound
traffic fails without these rules.

src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c

index a3ce7e8674bdef0e2400e78d56b6524a85809bc8..94a9269ab134c141394b369ad86fc8d78554af30 100644 (file)
@@ -26,6 +26,8 @@
 #include <collections/hashtable.h>
 #include <processing/jobs/callback_job.h>
 
+#define IPPROTO_IPIP 4
+#define IPPROTO_IPV6 41
 
 typedef struct private_kernel_wfp_ipsec_t private_kernel_wfp_ipsec_t;
 
@@ -188,6 +190,10 @@ typedef struct {
        u_int64_t provider;
        /** WFP allocated LUID for SA context */
        u_int64_t sa_id;
+       /** WFP allocated LUID for tunnel mode IP-IP-v4 filter */
+       u_int64_t policy_ip_ipv4;
+       /** WFP allocated LUID for tunnel mode IP-IPv6 filter */
+       u_int64_t policy_ip_ipv6;
 } entry_t;
 
 /**
@@ -285,6 +291,14 @@ static void cleanup_policies(private_kernel_wfp_ipsec_t *this, entry_t *entry)
  */
 static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry)
 {
+       if (entry->policy_ip_ipv4)
+       {
+               FwpmFilterDeleteById0(this->handle, entry->policy_ip_ipv4);
+       }
+       if (entry->policy_ip_ipv6)
+       {
+               FwpmFilterDeleteById0(this->handle, entry->policy_ip_ipv6);
+       }
        if (entry->sa_id)
        {
                IPsecSaContextDeleteById0(this->handle, entry->sa_id);
@@ -553,49 +567,58 @@ static void free_conditions(FWPM_FILTER_CONDITION0 *conds, int count)
  * Find the callout GUID for given parameters
  */
 static bool find_callout(bool tunnel, bool v6, bool inbound, bool forward,
-                                                GUID *layer, GUID *sublayer, GUID *callout)
+                                                bool ale, GUID *layer, GUID *sublayer, GUID *callout)
 {
        struct {
                bool tunnel;
                bool v6;
                bool inbound;
                bool forward;
+               bool ale;
                const GUID *layer;
                const GUID *sublayer;
                const GUID *callout;
        } map[] = {
-               { 0, 0, 0, 0,   &FWPM_LAYER_OUTBOUND_TRANSPORT_V4, NULL,
-                                               &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4                       },
-               { 0, 0, 1, 0,   &FWPM_LAYER_INBOUND_TRANSPORT_V4, NULL,
-                                               &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4                        },
-               { 0, 1, 0, 0,   &FWPM_LAYER_OUTBOUND_TRANSPORT_V6, NULL,
-                                               &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6                       },
-               { 0, 1, 1, 0,   &FWPM_LAYER_INBOUND_TRANSPORT_V6, NULL,
-                                               &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6                        },
-               { 1, 0, 0, 0,   &FWPM_LAYER_OUTBOUND_TRANSPORT_V4,
-                                               &FWPM_SUBLAYER_IPSEC_TUNNEL,
-                                               &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4                          },
-               { 1, 0, 0, 1,   &FWPM_LAYER_IPFORWARD_V4,
-                                               &FWPM_SUBLAYER_IPSEC_FORWARD_OUTBOUND_TUNNEL,
-                                               &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V4          },
-               { 1, 0, 1, 0,   &FWPM_LAYER_INBOUND_TRANSPORT_V4,
-                                               &FWPM_SUBLAYER_IPSEC_TUNNEL,
-                                               &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4                           },
-               { 1, 0, 1, 1,   &FWPM_LAYER_IPFORWARD_V4,
-                                               &FWPM_SUBLAYER_IPSEC_TUNNEL,
-                                               &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V4           },
-               { 1, 1, 0, 0,   &FWPM_LAYER_OUTBOUND_TRANSPORT_V6,
-                                               &FWPM_SUBLAYER_IPSEC_TUNNEL,
-                                               &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V6                          },
-               { 1, 1, 0, 1,   &FWPM_LAYER_IPFORWARD_V6,
-                                               &FWPM_SUBLAYER_IPSEC_TUNNEL,
-                                               &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V6          },
-               { 1, 1, 1, 0,   &FWPM_LAYER_INBOUND_TRANSPORT_V6,
-                                               &FWPM_SUBLAYER_IPSEC_TUNNEL,
-                                               &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V6                           },
-               { 1, 1, 1, 1,   &FWPM_LAYER_IPFORWARD_V6,
-                                               &FWPM_SUBLAYER_IPSEC_TUNNEL,
-                                               &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V6           },
+               { 0, 0, 0, 0, 0,        &FWPM_LAYER_OUTBOUND_TRANSPORT_V4, NULL,
+                                                       &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4               },
+               { 0, 0, 1, 0, 0,        &FWPM_LAYER_INBOUND_TRANSPORT_V4, NULL,
+                                                       &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4                },
+               { 0, 1, 0, 0, 0,        &FWPM_LAYER_OUTBOUND_TRANSPORT_V6, NULL,
+                                                       &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6               },
+               { 0, 1, 1, 0, 0,        &FWPM_LAYER_INBOUND_TRANSPORT_V6, NULL,
+                                                       &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6                },
+               { 1, 0, 0, 0, 0,        &FWPM_LAYER_OUTBOUND_TRANSPORT_V4,
+                                                       &FWPM_SUBLAYER_IPSEC_TUNNEL,
+                                                       &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4                  },
+               { 1, 0, 0, 1, 0,        &FWPM_LAYER_IPFORWARD_V4,
+                                                       &FWPM_SUBLAYER_IPSEC_FORWARD_OUTBOUND_TUNNEL,
+                                                       &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V4  },
+               { 1, 0, 1, 0, 0,        &FWPM_LAYER_INBOUND_TRANSPORT_V4,
+                                                       &FWPM_SUBLAYER_IPSEC_TUNNEL,
+                                                       &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4                   },
+               { 1, 0, 1, 1, 0,        &FWPM_LAYER_IPFORWARD_V4,
+                                                       &FWPM_SUBLAYER_IPSEC_TUNNEL,
+                                                       &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V4   },
+               { 1, 0, 0, 0, 1,        &FWPM_LAYER_ALE_AUTH_CONNECT_V4, NULL,
+                                                       &FWPM_CALLOUT_IPSEC_ALE_CONNECT_V4                              },
+               { 1, 0, 1, 0, 1,        &FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4, NULL,
+                                                       &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_ALE_ACCEPT_V4},
+               { 1, 1, 0, 0, 0,        &FWPM_LAYER_OUTBOUND_TRANSPORT_V6,
+                                                       &FWPM_SUBLAYER_IPSEC_TUNNEL,
+                                                       &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V6                  },
+               { 1, 1, 0, 1, 0,        &FWPM_LAYER_IPFORWARD_V6,
+                                                       &FWPM_SUBLAYER_IPSEC_FORWARD_OUTBOUND_TUNNEL,
+                                                       &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V6  },
+               { 1, 1, 1, 0, 0,        &FWPM_LAYER_INBOUND_TRANSPORT_V6,
+                                                       &FWPM_SUBLAYER_IPSEC_TUNNEL,
+                                                       &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V6                   },
+               { 1, 1, 1, 1, 0,        &FWPM_LAYER_IPFORWARD_V6,
+                                                       &FWPM_SUBLAYER_IPSEC_TUNNEL,
+                                                       &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V6   },
+               { 1, 1, 0, 0, 1,        &FWPM_LAYER_ALE_AUTH_CONNECT_V6, NULL,
+                                                       &FWPM_CALLOUT_IPSEC_ALE_CONNECT_V6                              },
+               { 1, 1, 1, 0, 1,        &FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6, NULL,
+                                                       &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_ALE_ACCEPT_V6},
        };
        int i;
 
@@ -604,7 +627,8 @@ static bool find_callout(bool tunnel, bool v6, bool inbound, bool forward,
                if (tunnel == map[i].tunnel &&
                        v6 == map[i].v6 &&
                        inbound == map[i].inbound &&
-                       forward == map[i].forward)
+                       forward == map[i].forward &&
+                       ale == map[i].ale)
                {
                        *callout = *map[i].callout;
                        *layer = *map[i].layer;
@@ -647,7 +671,7 @@ static bool install_sp(private_kernel_wfp_ipsec_t *this, sp_entry_t *sp,
        }
 
        v6 = sp->src->get_type(sp->src) == TS_IPV6_ADDR_RANGE;
-       if (!find_callout(context != NULL, v6, inbound, fwd,
+       if (!find_callout(context != NULL, v6, inbound, fwd, FALSE,
                                          &filter.layerKey, &filter.subLayerKey,
                                          &filter.action.calloutKey))
        {
@@ -688,8 +712,68 @@ static bool install_sp(private_kernel_wfp_ipsec_t *this, sp_entry_t *sp,
        free_conditions(conds, count);
        if (res != ERROR_SUCCESS)
        {
-               DBG1(DBG_KNL, "installing %s%sbound WFP filter failed: 0x%08x",
-                        fwd ? "forward " : "", inbound ? "in" : "out", res);
+               DBG1(DBG_KNL, "installing IPv%d %s%sbound %s WFP filter failed: 0x%08x",
+                        v6 ? 6 : 4, fwd ? "forward " : "", inbound ? "in" : "out",
+                        context ? "tunnel" : "transport", res);
+               return FALSE;
+       }
+       return TRUE;
+}
+
+/**
+ * Install an IP-IP allow filter for SA specific hosts
+ */
+static bool install_ipip_ale(private_kernel_wfp_ipsec_t *this,
+                                                        host_t *local, host_t *remote, GUID *context,
+                                                        int proto, u_int64_t *filter_id)
+{
+       traffic_selector_t *lts, *rts;
+       FWPM_FILTER_CONDITION0 *conds = NULL;
+       int count = 0;
+       bool v6;
+       DWORD res;
+       FWPM_FILTER0 filter = {
+               .displayData = {
+                       .name = L"charon IPsec IP-in-IP ALE policy",
+               },
+               .action = {
+                       .type = FWP_ACTION_CALLOUT_TERMINATING,
+               },
+               .flags = FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT,
+               .providerKey = (GUID*)&this->provider.providerKey,
+               .providerContextKey = *context,
+       };
+
+       v6 = local->get_family(local) == AF_INET6;
+       if (!find_callout(TRUE, v6, TRUE, FALSE, TRUE, &filter.layerKey,
+                                         &filter.subLayerKey, &filter.action.calloutKey))
+       {
+               return FALSE;
+       }
+
+       lts = traffic_selector_create_from_subnet(local->clone(local),
+                                                                                       v6 ? 128 : 32 , proto, 0, 65535);
+       rts = traffic_selector_create_from_subnet(remote->clone(remote),
+                                                                                       v6 ? 128 : 32 , proto, 0, 65535);
+       if (!ts2condition(lts, &FWPM_CONDITION_IP_LOCAL_ADDRESS, &conds, &count) ||
+               !ts2condition(rts, &FWPM_CONDITION_IP_REMOTE_ADDRESS, &conds, &count))
+       {
+               free_conditions(conds, count);
+               lts->destroy(lts);
+               rts->destroy(rts);
+               return FALSE;
+       }
+       lts->destroy(lts);
+       rts->destroy(rts);
+
+       filter.numFilterConditions = count;
+       filter.filterCondition = conds;
+
+       res = FwpmFilterAdd0(this->handle, &filter, NULL, filter_id);
+       free_conditions(conds, count);
+       if (res != ERROR_SUCCESS)
+       {
+               DBG1(DBG_KNL, "installing IP-IP ALE WFP filter failed: 0x%08x", res);
                return FALSE;
        }
        return TRUE;
@@ -703,10 +787,21 @@ static bool install_sps(private_kernel_wfp_ipsec_t *this,
 {
        enumerator_t *enumerator;
        sp_entry_t *sp;
+       bool has_v4 = FALSE, has_v6 = FALSE;
 
        enumerator = array_create_enumerator(entry->sps);
        while (enumerator->enumerate(enumerator, &sp))
        {
+               switch (sp->src->get_type(sp->src))
+               {
+                       case TS_IPV4_ADDR_RANGE:
+                               has_v4 = TRUE;
+                               break;
+                       case TS_IPV6_ADDR_RANGE:
+                               has_v6 = TRUE;
+                               break;
+               }
+
                /* inbound policy */
                if (!install_sp(this, sp, context, TRUE, FALSE, &sp->policy_in))
                {
@@ -719,21 +814,22 @@ static bool install_sps(private_kernel_wfp_ipsec_t *this,
                        enumerator->destroy(enumerator);
                        return FALSE;
                }
+
                if (context)
                {
                        if (!sp->src->is_host(sp->src, entry->local) ||
                                !sp->dst->is_host(sp->dst, entry->remote))
                        {
                                /* inbound forward policy, from decapsulation */
-                               if (!install_sp(this, sp, context,
-                                                               TRUE, TRUE, &sp->policy_fwd_in))
+                               if (!install_sp(this, sp, context, TRUE, TRUE,
+                                                               &sp->policy_fwd_in))
                                {
                                        enumerator->destroy(enumerator);
                                        return FALSE;
                                }
                                /* outbound forward policy, to encapsulate */
-                               if (!install_sp(this, sp, context,
-                                                               FALSE, TRUE, &sp->policy_fwd_out))
+                               if (!install_sp(this, sp, context, FALSE, TRUE,
+                                                               &sp->policy_fwd_out))
                                {
                                        enumerator->destroy(enumerator);
                                        return FALSE;
@@ -743,6 +839,28 @@ static bool install_sps(private_kernel_wfp_ipsec_t *this,
        }
        enumerator->destroy(enumerator);
 
+       if (context)
+       {
+               /* In tunnel mode, Windows does firewall filtering on decrypted but
+                * non-unwrapped packets: It sees them as IP-in-IP packets. When using
+                * a default-drop policy, we need to allow such packets explicitly. */
+               if (has_v4)
+               {
+                       if (!install_ipip_ale(this, entry->local, entry->remote, context,
+                                                                 IPPROTO_IPIP, &entry->policy_ip_ipv4))
+                       {
+                               return FALSE;
+                       }
+               }
+               if (has_v6)
+               {
+                       if (!install_ipip_ale(this, entry->local, entry->remote, context,
+                                                                 IPPROTO_IPV6, &entry->policy_ip_ipv6))
+                       {
+                               return FALSE;
+                       }
+               }
+       }
        return TRUE;
 }