kernel: Add option to control DS field behavior
authorTobias Brunner <tobias@strongswan.org>
Mon, 11 Jun 2018 10:07:48 +0000 (12:07 +0200)
committerTobias Brunner <tobias@strongswan.org>
Wed, 29 Aug 2018 09:36:04 +0000 (11:36 +0200)
src/libcharon/config/child_cfg.c
src/libcharon/config/child_cfg.h
src/libcharon/kernel/kernel_ipsec.h
src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c
src/libcharon/plugins/vici/vici_config.c
src/libcharon/sa/child_sa.c
src/libstrongswan/ipsec/ipsec_types.c
src/libstrongswan/ipsec/ipsec_types.h
src/swanctl/swanctl.opt

index d8083d4..98c1578 100644 (file)
@@ -147,6 +147,11 @@ struct private_child_cfg_t {
         * HW offload mode
         */
        hw_offload_t hw_offload;
+
+       /**
+        * DS header field copy mode
+        */
+       dscp_copy_t copy_dscp;
 };
 
 METHOD(child_cfg_t, get_name, char*,
@@ -487,6 +492,12 @@ METHOD(child_cfg_t, get_hw_offload, hw_offload_t,
        return this->hw_offload;
 }
 
+METHOD(child_cfg_t, get_copy_dscp, dscp_copy_t,
+       private_child_cfg_t *this)
+{
+       return this->copy_dscp;
+}
+
 METHOD(child_cfg_t, get_dpd_action, action_t,
        private_child_cfg_t *this)
 {
@@ -612,6 +623,8 @@ METHOD(child_cfg_t, equals, bool,
                this->tfc == other->tfc &&
                this->manual_prio == other->manual_prio &&
                this->replay_window == other->replay_window &&
+               this->hw_offload == other->hw_offload &&
+               this->copy_dscp == other->copy_dscp &&
                streq(this->updown, other->updown) &&
                streq(this->interface, other->interface);
 }
@@ -673,6 +686,7 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
                        .get_ref = _get_ref,
                        .destroy = _destroy,
                        .get_hw_offload = _get_hw_offload,
+                       .get_copy_dscp = _get_copy_dscp,
                },
                .name = strdup(name),
                .options = data->options,
@@ -696,6 +710,7 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
                .replay_window = lib->settings->get_int(lib->settings,
                                                        "%s.replay_window", DEFAULT_REPLAY_WINDOW, lib->ns),
                .hw_offload = data->hw_offload,
+               .copy_dscp = data->copy_dscp,
        );
 
        return &this->public;
index 2b41a0c..f552dae 100644 (file)
@@ -192,6 +192,13 @@ struct child_cfg_t {
        hw_offload_t (*get_hw_offload) (child_cfg_t *this);
 
        /**
+        * Get the copy mode for the DS header field to use for the CHILD_SA.
+        *
+        * @return                              IP header copy mode
+        */
+       dscp_copy_t (*get_copy_dscp) (child_cfg_t *this);
+
+       /**
         * Action to take if CHILD_SA gets closed.
         *
         * @return                              close action
@@ -361,6 +368,8 @@ struct child_cfg_create_t {
        char *updown;
        /** HW offload mode */
        hw_offload_t hw_offload;
+       /** How to handle the DS header field in tunnel mode */
+       dscp_copy_t copy_dscp;
 };
 
 /**
index 3feacb2..195c80c 100644 (file)
@@ -99,6 +99,8 @@ struct kernel_ipsec_add_sa_t {
        bool copy_df;
        /** TRUE to copy the ECN header field to/from the outer header */
        bool copy_ecn;
+       /** Whether to copy the DSCP header field to/from the outer header */
+       dscp_copy_t copy_dscp;
        /** TRUE if initiator of the exchange creating the SA */
        bool initiator;
        /** TRUE if this is an inbound SA */
index 9751933..70edef6 100644 (file)
@@ -1597,6 +1597,42 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
                sa->flags |= XFRM_STATE_NOECN;
        }
 
+       if (data->inbound)
+       {
+               switch (data->copy_dscp)
+               {
+                       case DSCP_COPY_YES:
+                       case DSCP_COPY_IN_ONLY:
+                               sa->flags |= XFRM_STATE_DECAP_DSCP;
+                               break;
+                       default:
+                               break;
+               }
+       }
+       else
+       {
+               switch (data->copy_dscp)
+               {
+                       case DSCP_COPY_IN_ONLY:
+                       case DSCP_COPY_NO:
+                       {
+                               uint32_t *xflags;
+
+                               xflags = netlink_reserve(hdr, sizeof(request),
+                                                                                XFRMA_SA_EXTRA_FLAGS, sizeof(*xflags));
+                               if (!xflags)
+                               {
+                                       goto failed;
+                               }
+                               /* currently the only extra flag */
+                               *xflags |= XFRM_SA_XFLAG_DONT_ENCAP_DSCP;
+                               break;
+                       }
+                       default:
+                               break;
+               }
+       }
+
        switch (mode)
        {
                case MODE_TUNNEL:
index 394d219..7a66af1 100644 (file)
@@ -536,6 +536,7 @@ static void log_child_data(child_data_t *data, char *name)
        DBG2(DBG_CFG, "   sha256_96 = %u", has_opt(OPT_SHA256_96));
        DBG2(DBG_CFG, "   copy_df = %u", !has_opt(OPT_NO_COPY_DF));
        DBG2(DBG_CFG, "   copy_ecn = %u", !has_opt(OPT_NO_COPY_ECN));
+       DBG2(DBG_CFG, "   copy_dscp = %N", dscp_copy_names, cfg->copy_dscp);
 }
 
 /**
@@ -940,6 +941,28 @@ CALLBACK(parse_opt_copy_ecn, bool,
 }
 
 /**
+ * Parse a dscp_copy_t
+ */
+CALLBACK(parse_copy_dscp, bool,
+       dscp_copy_t *out, chunk_t v)
+{
+       enum_map_t map[] = {
+               { "no",         DSCP_COPY_NO            },
+               { "in",         DSCP_COPY_IN_ONLY       },
+               { "out",        DSCP_COPY_OUT_ONLY      },
+               { "yes",        DSCP_COPY_YES           },
+       };
+       int d;
+
+       if (parse_map(map, countof(map), &d, v))
+       {
+               *out = d;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+/**
  * Parse an action_t
  */
 CALLBACK(parse_action, bool,
@@ -1623,6 +1646,7 @@ CALLBACK(child_kv, bool,
                { "sha256_96",                  parse_opt_sha256_96,&child->cfg.options                                 },
                { "copy_df",                    parse_opt_copy_df,      &child->cfg.options                                     },
                { "copy_ecn",                   parse_opt_copy_ecn,     &child->cfg.options                                     },
+               { "copy_dscp",                  parse_copy_dscp,        &child->cfg.copy_dscp                           },
        };
 
        return parse_rules(rules, countof(rules), name, value,
index b0efec5..d06996e 100644 (file)
@@ -893,6 +893,7 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr,
                .esn = esn,
                .copy_df = !this->config->has_option(this->config, OPT_NO_COPY_DF),
                .copy_ecn = !this->config->has_option(this->config, OPT_NO_COPY_ECN),
+               .copy_dscp = this->config->get_copy_dscp(this->config),
                .initiator = initiator,
                .inbound = inbound,
                .update = update,
index 16dbf8d..d231bb3 100644 (file)
@@ -43,6 +43,13 @@ ENUM(hw_offload_names, HW_OFFLOAD_NO, HW_OFFLOAD_AUTO,
        "auto",
 );
 
+ENUM(dscp_copy_names, DSCP_COPY_OUT_ONLY, DSCP_COPY_NO,
+       "out",
+       "in",
+       "yes",
+       "no",
+);
+
 /*
  * See header
  */
index 4e6e2d9..bd5545e 100644 (file)
@@ -27,6 +27,7 @@ typedef enum policy_type_t policy_type_t;
 typedef enum policy_priority_t policy_priority_t;
 typedef enum ipcomp_transform_t ipcomp_transform_t;
 typedef enum hw_offload_t hw_offload_t;
+typedef enum dscp_copy_t dscp_copy_t;
 typedef struct ipsec_sa_cfg_t ipsec_sa_cfg_t;
 typedef struct lifetime_cfg_t lifetime_cfg_t;
 typedef struct mark_t mark_t;
@@ -132,6 +133,22 @@ enum hw_offload_t {
 extern enum_name_t *hw_offload_names;
 
 /**
+ * DSCP header field copy behavior (the default is not to copy from outer
+ * to inner header)
+ */
+enum dscp_copy_t {
+       DSCP_COPY_OUT_ONLY,
+       DSCP_COPY_IN_ONLY,
+       DSCP_COPY_YES,
+       DSCP_COPY_NO,
+};
+
+/**
+ * enum strings for dscp_copy_t.
+ */
+extern enum_name_t *dscp_copy_names;
+
+/**
  * This struct contains details about IPsec SA(s) tied to a policy.
  */
 struct ipsec_sa_cfg_t {
index d1e823a..3f67b93 100644 (file)
@@ -941,16 +941,30 @@ connections.<conn>.children.<child>.copy_df = yes
        Whether to copy the DF bit to the outer IPv4 header in tunnel mode.
 
        Whether to copy the DF bit to the outer IPv4 header in tunnel mode. This
-       effectively disables Path MTU discovery (PMTUD).  Disabling this is not
-       supported by all kernel interfaces.
+       effectively disables Path MTU discovery (PMTUD).  Controlling this behavior
+       is not supported by all kernel interfaces.
 
 connections.<conn>.children.<child>.copy_ecn = yes
        Whether to copy the ECN header field to/from the outer IP header in tunnel
        mode.
 
        Whether to copy the ECN (Explicit Congestion Notification) header field
-       to/from the outer IP header in tunnel mode.  Disabling this is not supported
-       by all kernel interfaces.
+       to/from the outer IP header in tunnel mode. Controlling this behavior is not
+       supported by all kernel interfaces.
+
+connections.<conn>.children.<child>.copy_dscp = out
+       Whether to copy the DSCP header field to/from the outer IP header in tunnel
+       mode.
+
+       Whether to copy the DSCP (Differentiated Services Field Codepoint) header
+       field to/from the outer IP header in tunnel mode. The value _out_ only
+       copies the field from the inner to the outer header, the value _in_ does the
+       opposite and only copies the field from the outer to the inner header when
+       decapsulating, the value _yes_ copies the field in both directions, and the
+       value _no_ disables copying the field altogether.  Setting this to _yes_ or
+       _in_ could allow an attacker to adversely affect other traffic at the
+       receiver, which is why the default is _out_. Controlling this behavior is
+       not supported by all kernel interfaces.
 
 connections.<conn>.children.<child>.start_action = none
        Action to perform after loading the configuration (_none_, _trap_, _start_).