diff --git a/src/libcharon/config/child_cfg.c b/src/libcharon/config/child_cfg.c index d8083d433..98c15782b 100644 --- a/src/libcharon/config/child_cfg.c +++ b/src/libcharon/config/child_cfg.c @@ -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; diff --git a/src/libcharon/config/child_cfg.h b/src/libcharon/config/child_cfg.h index 2defd0330..f552dae71 100644 --- a/src/libcharon/config/child_cfg.h +++ b/src/libcharon/config/child_cfg.h @@ -191,6 +191,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. * @@ -319,6 +326,12 @@ enum child_cfg_option_t { /** Set mark on inbound SAs */ OPT_MARK_IN_SA = (1<<6), + + /** Disable copying the DF bit to the outer IPv4 header in tunnel mode */ + OPT_NO_COPY_DF = (1<<7), + + /** Disable copying the ECN header field in tunnel mode */ + OPT_NO_COPY_ECN = (1<<8), }; /** @@ -355,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; }; /** diff --git a/src/libcharon/kernel/kernel_ipsec.h b/src/libcharon/kernel/kernel_ipsec.h index 94b9c284b..195c80cff 100644 --- a/src/libcharon/kernel/kernel_ipsec.h +++ b/src/libcharon/kernel/kernel_ipsec.h @@ -95,6 +95,12 @@ struct kernel_ipsec_add_sa_t { hw_offload_t hw_offload; /** TRUE to use Extended Sequence Numbers */ bool esn; + /** TRUE to copy the DF bit to the outer IPv4 header in tunnel mode */ + 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 */ diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 4926c3de8..70edef6ad 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1586,6 +1586,53 @@ METHOD(kernel_ipsec_t, add_sa, status_t, sa->id.proto = id->proto; sa->family = id->src->get_family(id->src); sa->mode = mode2kernel(mode); + + if (!data->copy_df) + { + sa->flags |= XFRM_STATE_NOPMTUDISC; + } + + if (!data->copy_ecn) + { + 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: diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index dfb4c84f3..7a66af1f4 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -484,7 +484,6 @@ typedef struct { linked_list_t *local_ts; linked_list_t *remote_ts; uint32_t replay_window; - bool policies; child_cfg_create_t cfg; } child_data_t; @@ -511,7 +510,7 @@ static void log_child_data(child_data_t *data, char *name) DBG2(DBG_CFG, " ipcomp = %u", has_opt(OPT_IPCOMP)); DBG2(DBG_CFG, " mode = %N%s", ipsec_mode_names, cfg->mode, has_opt(OPT_PROXY_MODE) ? "_PROXY" : ""); - DBG2(DBG_CFG, " policies = %u", data->policies); + DBG2(DBG_CFG, " policies = %u", !has_opt(OPT_NO_POLICIES)); DBG2(DBG_CFG, " policies_fwd_out = %u", has_opt(OPT_FWD_OUT_POLICIES)); if (data->replay_window != REPLAY_UNDEFINED) { @@ -535,6 +534,9 @@ static void log_child_data(child_data_t *data, char *name) DBG2(DBG_CFG, " remote_ts = %#R", data->remote_ts); DBG2(DBG_CFG, " hw_offload = %N", hw_offload_names, cfg->hw_offload); 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); } /** @@ -847,16 +849,17 @@ CALLBACK(parse_mode, bool, } /** - * Enable a child_cfg_option_t + * Enable a child_cfg_option_t, the flag controls whether the option is enabled + * if the parsed value is TRUE or FALSE. */ static bool parse_option(child_cfg_option_t *out, child_cfg_option_t opt, - chunk_t v) + chunk_t v, bool add_if_true) { bool val; if (parse_bool(&val, v)) { - if (val) + if (val == add_if_true) { *out |= opt; } @@ -871,7 +874,16 @@ static bool parse_option(child_cfg_option_t *out, child_cfg_option_t opt, CALLBACK(parse_opt_haccess, bool, child_cfg_option_t *out, chunk_t v) { - return parse_option(out, OPT_HOSTACCESS, v); + return parse_option(out, OPT_HOSTACCESS, v, TRUE); +} + +/** + * Parse OPT_NO_POLICIES option + */ +CALLBACK(parse_opt_policies, bool, + child_cfg_option_t *out, chunk_t v) +{ + return parse_option(out, OPT_NO_POLICIES, v, FALSE); } /** @@ -880,7 +892,7 @@ CALLBACK(parse_opt_haccess, bool, CALLBACK(parse_opt_fwd_out, bool, child_cfg_option_t *out, chunk_t v) { - return parse_option(out, OPT_FWD_OUT_POLICIES, v); + return parse_option(out, OPT_FWD_OUT_POLICIES, v, TRUE); } /** @@ -889,17 +901,16 @@ CALLBACK(parse_opt_fwd_out, bool, CALLBACK(parse_opt_ipcomp, bool, child_cfg_option_t *out, chunk_t v) { - return parse_option(out, OPT_IPCOMP, v); + return parse_option(out, OPT_IPCOMP, v, TRUE); } - /** * Parse OPT_SHA256_96 option */ CALLBACK(parse_opt_sha256_96, bool, child_cfg_option_t *out, chunk_t v) { - return parse_option(out, OPT_SHA256_96, v); + return parse_option(out, OPT_SHA256_96, v, TRUE); } /** @@ -908,7 +919,47 @@ CALLBACK(parse_opt_sha256_96, bool, CALLBACK(parse_opt_mark_in, bool, child_cfg_option_t *out, chunk_t v) { - return parse_option(out, OPT_MARK_IN_SA, v); + return parse_option(out, OPT_MARK_IN_SA, v, TRUE); +} + +/** + * Parse OPT_NO_COPY_DF option + */ +CALLBACK(parse_opt_copy_df, bool, + child_cfg_option_t *out, chunk_t v) +{ + return parse_option(out, OPT_NO_COPY_DF, v, FALSE); +} + +/** + * Parse OPT_NO_COPY_ECN option + */ +CALLBACK(parse_opt_copy_ecn, bool, + child_cfg_option_t *out, chunk_t v) +{ + return parse_option(out, OPT_NO_COPY_ECN, v, FALSE); +} + +/** + * 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; } /** @@ -1567,7 +1618,7 @@ CALLBACK(child_kv, bool, { "updown", parse_string, &child->cfg.updown }, { "hostaccess", parse_opt_haccess, &child->cfg.options }, { "mode", parse_mode, &child->cfg }, - { "policies", parse_bool, &child->policies }, + { "policies", parse_opt_policies, &child->cfg.options }, { "policies_fwd_out", parse_opt_fwd_out, &child->cfg.options }, { "replay_window", parse_uint32, &child->replay_window }, { "rekey_time", parse_time, &child->cfg.lifetime.time.rekey }, @@ -1593,6 +1644,9 @@ CALLBACK(child_kv, bool, { "interface", parse_string, &child->cfg.interface }, { "hw_offload", parse_hw_offload, &child->cfg.hw_offload }, { "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, @@ -1802,7 +1856,6 @@ CALLBACK(children_sn, bool, .proposals = linked_list_create(), .local_ts = linked_list_create(), .remote_ts = linked_list_create(), - .policies = TRUE, .replay_window = REPLAY_UNDEFINED, .cfg = { .mode = MODE_TUNNEL, @@ -1858,7 +1911,6 @@ CALLBACK(children_sn, bool, child.proposals->insert_last(child.proposals, proposal); } } - child.cfg.options |= child.policies ? 0 : OPT_NO_POLICIES; check_lifetimes(&child.cfg.lifetime); diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 49717703c..d06996e43 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -891,6 +891,9 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr, .encap = this->encap, .hw_offload = this->config->get_hw_offload(this->config), .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, diff --git a/src/libstrongswan/ipsec/ipsec_types.c b/src/libstrongswan/ipsec/ipsec_types.c index 16dbf8d41..d231bb3a4 100644 --- a/src/libstrongswan/ipsec/ipsec_types.c +++ b/src/libstrongswan/ipsec/ipsec_types.c @@ -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 */ diff --git a/src/libstrongswan/ipsec/ipsec_types.h b/src/libstrongswan/ipsec/ipsec_types.h index 4e6e2d9dc..bd5545e22 100644 --- a/src/libstrongswan/ipsec/ipsec_types.h +++ b/src/libstrongswan/ipsec/ipsec_types.h @@ -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; @@ -131,6 +132,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. */ diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt index 120e5812e..3f67b938a 100644 --- a/src/swanctl/swanctl.opt +++ b/src/swanctl/swanctl.opt @@ -937,6 +937,35 @@ connections..children..hw_offload = no enables offloading, if it's supported, but the installation does not fail otherwise. +connections..children..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). Controlling this behavior + is not supported by all kernel interfaces. + +connections..children..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. Controlling this behavior is not + supported by all kernel interfaces. + +connections..children..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..children..start_action = none Action to perform after loading the configuration (_none_, _trap_, _start_).