Merge branch 'xfrm-set-mark'

This adds the ability to configure marks the in- and/or outbound SA
should apply to packets after processing on Linux.  Configuring such a mark
for outbound SAs requires at least a 4.14 kernel.  The ability to set a mask
and configuring a mark/mask for inbound SAs will be added with the upcoming
4.19 kernel.
This commit is contained in:
Tobias Brunner
2018-08-31 12:32:31 +02:00
14 changed files with 241 additions and 58 deletions
+3
View File
@@ -302,8 +302,11 @@ enum xfrm_attr_type_t {
XFRMA_ADDRESS_FILTER, /* struct xfrm_address_filter */
XFRMA_PAD,
XFRMA_OFFLOAD_DEV, /* struct xfrm_state_offload */
XFRMA_SET_MARK, /* __u32 */
XFRMA_SET_MARK_MASK, /* __u32 */
__XFRMA_MAX
#define XFRMA_OUTPUT_MARK XFRMA_SET_MARK /* Compatibility */
#define XFRMA_MAX (__XFRMA_MAX - 1)
};
+24 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Tobias Brunner
* Copyright (C) 2008-2018 Tobias Brunner
* Copyright (C) 2016 Andreas Steffen
* Copyright (C) 2005-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter
@@ -123,6 +123,16 @@ struct private_child_cfg_t {
*/
mark_t mark_out;
/**
* Optional mark to set to packets after inbound processing
*/
mark_t set_mark_in;
/**
* Optional mark to set to packets after outbound processing
*/
mark_t set_mark_out;
/**
* Traffic Flow Confidentiality padding, if enabled
*/
@@ -547,6 +557,12 @@ METHOD(child_cfg_t, get_mark, mark_t,
return inbound ? this->mark_in : this->mark_out;
}
METHOD(child_cfg_t, get_set_mark, mark_t,
private_child_cfg_t *this, bool inbound)
{
return inbound ? this->set_mark_in : this->set_mark_out;
}
METHOD(child_cfg_t, get_tfc, uint32_t,
private_child_cfg_t *this)
{
@@ -620,6 +636,10 @@ METHOD(child_cfg_t, equals, bool,
this->mark_in.mask == other->mark_in.mask &&
this->mark_out.value == other->mark_out.value &&
this->mark_out.mask == other->mark_out.mask &&
this->set_mark_in.value == other->set_mark_in.value &&
this->set_mark_in.mask == other->set_mark_in.mask &&
this->set_mark_out.value == other->set_mark_out.value &&
this->set_mark_out.mask == other->set_mark_out.mask &&
this->tfc == other->tfc &&
this->manual_prio == other->manual_prio &&
this->replay_window == other->replay_window &&
@@ -676,6 +696,7 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
.get_inactivity = _get_inactivity,
.get_reqid = _get_reqid,
.get_mark = _get_mark,
.get_set_mark = _get_set_mark,
.get_tfc = _get_tfc,
.get_manual_prio = _get_manual_prio,
.get_interface = _get_interface,
@@ -698,6 +719,8 @@ child_cfg_t *child_cfg_create(char *name, child_cfg_create_t *data)
.close_action = data->close_action,
.mark_in = data->mark_in,
.mark_out = data->mark_out,
.set_mark_in = data->set_mark_in,
.set_mark_out = data->set_mark_out,
.lifetime = data->lifetime,
.inactivity = data->inactivity,
.tfc = data->tfc,
+14 -2
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2017 Tobias Brunner
* Copyright (C) 2008-2018 Tobias Brunner
* Copyright (C) 2016 Andreas Steffen
* Copyright (C) 2005-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter
@@ -227,13 +227,21 @@ struct child_cfg_t {
uint32_t (*get_reqid)(child_cfg_t *this);
/**
* Optional mark for CHILD_SA.
* Optional mark to set on policies/SAs.
*
* @param inbound TRUE for inbound, FALSE for outbound
* @return mark
*/
mark_t (*get_mark)(child_cfg_t *this, bool inbound);
/**
* Optional mark the SAs should apply after processing packets.
*
* @param inbound TRUE for inbound, FALSE for outbound
* @return mark
*/
mark_t (*get_set_mark)(child_cfg_t *this, bool inbound);
/**
* Get the TFC padding value to use for CHILD_SA.
*
@@ -346,6 +354,10 @@ struct child_cfg_create_t {
mark_t mark_in;
/** Optional outbound mark */
mark_t mark_out;
/** Optional inbound mark the SA should apply to traffic */
mark_t set_mark_in;
/** Optional outbound mark the SA should apply to traffic */
mark_t set_mark_out;
/** Mode to propose for CHILD_SA */
ipsec_mode_t mode;
/** TFC padding size, 0 to disable, -1 to pad to PMTU */
+3 -1
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2016 Andreas Steffen
* Copyright (C) 2006-2016 Tobias Brunner
* Copyright (C) 2006-2018 Tobias Brunner
* Copyright (C) 2006 Daniel Roethlisberger
* Copyright (C) 2005-2006 Martin Willi
* Copyright (C) 2005 Jan Hutter
@@ -93,6 +93,8 @@ struct kernel_ipsec_add_sa_t {
bool encap;
/** no (disabled), yes (enabled), auto (enabled if supported) */
hw_offload_t hw_offload;
/** Mark the SA should apply to packets after processing */
mark_t mark;
/** TRUE to use Extended Sequence Numbers */
bool esn;
/** TRUE to copy the DF bit to the outer IPv4 header in tunnel mode */
@@ -1335,6 +1335,23 @@ static bool add_mark(struct nlmsghdr *hdr, int buflen, mark_t mark)
return TRUE;
}
/**
* Add a uint32 attribute to message
*/
static bool add_uint32(struct nlmsghdr *hdr, int buflen,
enum xfrm_attr_type_t type, uint32_t value)
{
uint32_t *xvalue;
xvalue = netlink_reserve(hdr, buflen, type, sizeof(*xvalue));
if (!xvalue)
{
return FALSE;
}
*xvalue = value;
return TRUE;
}
/**
* Check if kernel supports HW offload
*/
@@ -1616,16 +1633,12 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
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)
/* currently the only extra flag */
if (!add_uint32(hdr, sizeof(request), XFRMA_SA_EXTRA_FLAGS,
XFRM_SA_XFLAG_DONT_ENCAP_DSCP))
{
goto failed;
}
/* currently the only extra flag */
*xflags |= XFRM_SA_XFLAG_DONT_ENCAP_DSCP;
break;
}
default:
@@ -1876,17 +1889,23 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
goto failed;
}
if (ipcomp == IPCOMP_NONE && (data->mark.value | data->mark.mask))
{
if (!add_uint32(hdr, sizeof(request), XFRMA_SET_MARK,
data->mark.value) ||
!add_uint32(hdr, sizeof(request), XFRMA_SET_MARK_MASK,
data->mark.mask))
{
goto failed;
}
}
if (data->tfc && id->proto == IPPROTO_ESP && mode == MODE_TUNNEL)
{ /* the kernel supports TFC padding only for tunnel mode ESP SAs */
uint32_t *tfcpad;
tfcpad = netlink_reserve(hdr, sizeof(request), XFRMA_TFCPAD,
sizeof(*tfcpad));
if (!tfcpad)
if (!add_uint32(hdr, sizeof(request), XFRMA_TFCPAD, data->tfc))
{
goto failed;
}
*tfcpad = data->tfc;
}
if (id->proto != IPPROTO_COMP)
@@ -2925,7 +2925,7 @@ static status_t manage_rule(private_kernel_netlink_net_t *this, int nlmsg_type,
msg->rtm_flags |= FIB_RULE_INVERT;
fwmark++;
}
if (mark_from_string(fwmark, &mark))
if (mark_from_string(fwmark, MARK_OP_NONE, &mark))
{
chunk = chunk_from_thing(mark.value);
netlink_add_attribute(hdr, FRA_FWMARK, chunk, sizeof(request));
@@ -745,7 +745,7 @@ static int open_socket(private_socket_default_socket_t *this,
fwmark = lib->settings->get_str(lib->settings,
"%s.plugins.socket-default.fwmark", NULL, lib->ns);
if (fwmark && mark_from_string(fwmark, &mark))
if (fwmark && mark_from_string(fwmark, MARK_OP_NONE, &mark))
{
if (setsockopt(skt, SOL_SOCKET, SO_MARK, &mark.value,
sizeof(mark.value)) < 0)
+22 -1
View File
@@ -528,6 +528,10 @@ static void log_child_data(child_data_t *data, char *name)
DBG2(DBG_CFG, " mark_in_sa = %u", has_opt(OPT_MARK_IN_SA));
DBG2(DBG_CFG, " mark_out = %u/%u",
cfg->mark_out.value, cfg->mark_out.mask);
DBG2(DBG_CFG, " set_mark_in = %u/%u",
cfg->set_mark_in.value, cfg->set_mark_in.mask);
DBG2(DBG_CFG, " set_mark_out = %u/%u",
cfg->set_mark_out.value, cfg->set_mark_out.mask);
DBG2(DBG_CFG, " inactivity = %llu", cfg->inactivity);
DBG2(DBG_CFG, " proposals = %#P", data->proposals);
DBG2(DBG_CFG, " local_ts = %#R", data->local_ts);
@@ -1177,7 +1181,22 @@ CALLBACK(parse_mark, bool,
{
return FALSE;
}
return mark_from_string(buf, out);
return mark_from_string(buf, MARK_OP_UNIQUE, out);
}
/**
* Parse a mark_t when using it as set_mark.
*/
CALLBACK(parse_set_mark, bool,
mark_t *out, chunk_t v)
{
char buf[32];
if (!vici_stringify(v, buf, sizeof(buf)))
{
return FALSE;
}
return mark_from_string(buf, MARK_OP_SAME, out);
}
/**
@@ -1639,6 +1658,8 @@ CALLBACK(child_kv, bool,
{ "mark_in", parse_mark, &child->cfg.mark_in },
{ "mark_in_sa", parse_opt_mark_in, &child->cfg.options },
{ "mark_out", parse_mark, &child->cfg.mark_out },
{ "set_mark_in", parse_set_mark, &child->cfg.set_mark_in },
{ "set_mark_out", parse_set_mark, &child->cfg.set_mark_out },
{ "tfc_padding", parse_tfc, &child->cfg.tfc },
{ "priority", parse_uint32, &child->cfg.priority },
{ "interface", parse_string, &child->cfg.interface },
+6
View File
@@ -890,6 +890,7 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr,
.cpi = cpi,
.encap = this->encap,
.hw_offload = this->config->get_hw_offload(this->config),
.mark = this->config->get_set_mark(this->config, inbound),
.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),
@@ -899,6 +900,11 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr,
.update = update,
};
if (sa.mark.value == MARK_SAME)
{
sa.mark.value = inbound ? this->mark_in.value : this->mark_out.value;
}
status = charon->kernel->add_sa(charon->kernel, &id, &sa);
my_ts->destroy(my_ts);
+24 -1
View File
@@ -69,7 +69,7 @@ bool ipsec_sa_cfg_equals(ipsec_sa_cfg_t *a, ipsec_sa_cfg_t *b)
/*
* See header
*/
bool mark_from_string(const char *value, mark_t *mark)
bool mark_from_string(const char *value, mark_op_t ops, mark_t *mark)
{
char *endptr;
@@ -79,6 +79,11 @@ bool mark_from_string(const char *value, mark_t *mark)
}
if (strcasepfx(value, "%unique"))
{
if (!(ops & MARK_OP_UNIQUE))
{
DBG1(DBG_APP, "unexpected use of %%unique mark", value);
return FALSE;
}
endptr = (char*)value + strlen("%unique");
if (strcasepfx(endptr, "-dir"))
{
@@ -95,6 +100,24 @@ bool mark_from_string(const char *value, mark_t *mark)
return FALSE;
}
}
else if (strcasepfx(value, "%same"))
{
if (!(ops & MARK_OP_SAME))
{
DBG1(DBG_APP, "unexpected use of %%same mark", value);
return FALSE;
}
endptr = (char*)value + strlen("%same");
if (!*endptr || *endptr == '/')
{
mark->value = MARK_SAME;
}
else
{
DBG1(DBG_APP, "invalid mark value: %s", value);
return FALSE;
}
}
else
{
mark->value = strtoul(value, &endptr, 0);
+16 -1
View File
@@ -28,6 +28,7 @@ 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 enum mark_op_t mark_op_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;
@@ -214,15 +215,29 @@ struct mark_t {
*/
#define MARK_UNIQUE (0xFFFFFFFF)
#define MARK_UNIQUE_DIR (0xFFFFFFFE)
#define MARK_SAME (0xFFFFFFFF)
#define MARK_IS_UNIQUE(m) ((m) == MARK_UNIQUE || (m) == MARK_UNIQUE_DIR)
/**
* Special mark operations to accept when parsing marks.
*/
enum mark_op_t {
/** none of the following */
MARK_OP_NONE = 0,
/** %unique and %unique-dir */
MARK_OP_UNIQUE = (1<<0),
/** %same */
MARK_OP_SAME = (1<<1),
};
/**
* Try to parse a mark_t from the given string of the form mark[/mask].
*
* @param value string to parse
* @param ops operations to accept
* @param mark mark to fill
* @return TRUE if parsing was successful
*/
bool mark_from_string(const char *value, mark_t *mark);
bool mark_from_string(const char *value, mark_op_t ops, mark_t *mark);
#endif /** IPSEC_TYPES_H_ @}*/
+61 -33
View File
@@ -860,47 +860,75 @@ END_TEST
static struct {
char *s;
bool ok;
mark_op_t ops;
mark_t m;
} mark_data[] = {
{NULL, FALSE, { 0 }},
{"", TRUE, { 0, 0xffffffff }},
{"/", TRUE, { 0, 0 }},
{"42", TRUE, { 42, 0xffffffff }},
{"0x42", TRUE, { 0x42, 0xffffffff }},
{"x", FALSE, { 0 }},
{"42/", TRUE, { 0, 0 }},
{"42/0", TRUE, { 0, 0 }},
{"42/x", FALSE, { 0 }},
{"42/42", TRUE, { 42, 42 }},
{"42/0xff", TRUE, { 42, 0xff }},
{"0x42/0xff", TRUE, { 0x42, 0xff }},
{"/0xff", TRUE, { 0, 0xff }},
{"/x", FALSE, { 0 }},
{"x/x", FALSE, { 0 }},
{"0xfffffff0/0x0000ffff", TRUE, { 0x0000fff0, 0x0000ffff }},
{"%unique", TRUE, { MARK_UNIQUE, 0xffffffff }},
{"%unique/", TRUE, { MARK_UNIQUE, 0 }},
{"%unique/0x0000ffff", TRUE, { MARK_UNIQUE, 0x0000ffff }},
{"%unique/0xffffffff", TRUE, { MARK_UNIQUE, 0xffffffff }},
{"%unique0xffffffffff", FALSE, { 0, 0 }},
{"0xffffffff/0x0000ffff", TRUE, { MARK_UNIQUE, 0x0000ffff }},
{"0xffffffff/0xffffffff", TRUE, { MARK_UNIQUE, 0xffffffff }},
{"%unique-dir", TRUE, { MARK_UNIQUE_DIR, 0xffffffff }},
{"%unique-dir/", TRUE, { MARK_UNIQUE_DIR, 0 }},
{"%unique-dir/0x0000ffff", TRUE, { MARK_UNIQUE_DIR, 0x0000ffff }},
{"%unique-dir/0xffffffff", TRUE, { MARK_UNIQUE_DIR, 0xffffffff }},
{"%unique-dir0xffffffff", FALSE, { 0, 0 }},
{"0xfffffffe/0x0000ffff", TRUE, { MARK_UNIQUE_DIR, 0x0000ffff }},
{"0xfffffffe/0xffffffff", TRUE, { MARK_UNIQUE_DIR, 0xffffffff }},
{"%unique-/0xffffffff", FALSE, { 0, 0 }},
{"%unique-foo/0xffffffff", FALSE, { 0, 0 }},
{NULL, FALSE, MARK_OP_NONE, { 0 }},
{"", TRUE, MARK_OP_NONE, { 0, 0xffffffff }},
{"/", TRUE, MARK_OP_NONE, { 0, 0 }},
{"42", TRUE, MARK_OP_NONE, { 42, 0xffffffff }},
{"0x42", TRUE, MARK_OP_NONE, { 0x42, 0xffffffff }},
{"x", FALSE, MARK_OP_NONE, { 0 }},
{"42/", TRUE, MARK_OP_NONE, { 0, 0 }},
{"42/0", TRUE, MARK_OP_NONE, { 0, 0 }},
{"42/x", FALSE, MARK_OP_NONE, { 0 }},
{"42/42", TRUE, MARK_OP_NONE, { 42, 42 }},
{"42/0xff", TRUE, MARK_OP_NONE, { 42, 0xff }},
{"0x42/0xff", TRUE, MARK_OP_NONE, { 0x42, 0xff }},
{"/0xff", TRUE, MARK_OP_NONE, { 0, 0xff }},
{"/x", FALSE, MARK_OP_NONE, { 0 }},
{"x/x", FALSE, MARK_OP_NONE, { 0 }},
{"0xfffffff0/0x0000ffff", TRUE, MARK_OP_UNIQUE,
{ 0x0000fff0, 0x0000ffff }},
{"%unique", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE, 0xffffffff }},
{"%unique/", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE, 0 }},
{"%unique", FALSE, MARK_OP_NONE,
{ 0, 0 }},
{"%unique/0x0000ffff", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE, 0x0000ffff }},
{"%unique/0xffffffff", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE, 0xffffffff }},
{"%unique0xffffffffff", FALSE, MARK_OP_UNIQUE,
{ 0, 0 }},
{"0xffffffff/0x0000ffff", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE, 0x0000ffff }},
{"0xffffffff/0xffffffff", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE, 0xffffffff }},
{"%unique-dir", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE_DIR, 0xffffffff }},
{"%unique-dir/", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE_DIR, 0 }},
{"%unique-dir", FALSE, MARK_OP_NONE,
{ 0, 0 }},
{"%unique-dir/0x0000ffff", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE_DIR, 0x0000ffff }},
{"%unique-dir/0xffffffff", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE_DIR, 0xffffffff }},
{"%unique-dir0xffffffff", FALSE, MARK_OP_UNIQUE,
{ 0, 0 }},
{"0xfffffffe/0x0000ffff", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE_DIR, 0x0000ffff }},
{"0xfffffffe/0xffffffff", TRUE, MARK_OP_UNIQUE,
{ MARK_UNIQUE_DIR, 0xffffffff }},
{"%unique-/0xffffffff", FALSE, MARK_OP_UNIQUE,
{ 0, 0 }},
{"%unique-foo/0xffffffff", FALSE, MARK_OP_UNIQUE,
{ 0, 0 }},
{"%same", TRUE, MARK_OP_SAME,
{ MARK_SAME, 0xffffffff }},
{"%same/0x0000ffff", TRUE, MARK_OP_SAME,
{ MARK_SAME, 0x0000ffff }},
{"%%same", FALSE, MARK_OP_NONE,
{ 0, 0 }},
};
START_TEST(test_mark_from_string)
{
mark_t mark;
if (mark_from_string(mark_data[_i].s, &mark))
if (mark_from_string(mark_data[_i].s, mark_data[_i].ops, &mark))
{
ck_assert_int_eq(mark.value, mark_data[_i].m.value);
ck_assert_int_eq(mark.mask, mark_data[_i].m.mask);
+3 -3
View File
@@ -444,7 +444,7 @@ static void handle_keyword(kw_token_t token, starter_conn_t *conn, char *key,
KW_SA_OPTION_FLAG("yes", "no", SA_OPTION_COMPRESS)
break;
case KW_MARK:
if (!mark_from_string(value, &conn->mark_in))
if (!mark_from_string(value, MARK_OP_UNIQUE, &conn->mark_in))
{
cfg->err++;
break;
@@ -452,13 +452,13 @@ static void handle_keyword(kw_token_t token, starter_conn_t *conn, char *key,
conn->mark_out = conn->mark_in;
break;
case KW_MARK_IN:
if (!mark_from_string(value, &conn->mark_in))
if (!mark_from_string(value, MARK_OP_UNIQUE, &conn->mark_in))
{
cfg->err++;
}
break;
case KW_MARK_OUT:
if (!mark_from_string(value, &conn->mark_out))
if (!mark_from_string(value, MARK_OP_UNIQUE, &conn->mark_out))
{
cfg->err++;
}
+31
View File
@@ -910,6 +910,37 @@ connections.<conn>.children.<child>.mark_out = 0/0x00000000
An additional mask may be appended to the mark, separated by _/_. The
default mask if omitted is 0xffffffff.
connections.<conn>.children.<child>.set_mark_in = 0/0x00000000
Netfilter mark applied to packets after the inbound IPsec SA processed them.
Netfilter mark applied to packets after the inbound IPsec SA processed them.
This way it's not necessary to mark packets via Netfilter before decryption
or right afterwards to match policies or process them differently (e.g. via
policy routing).
An additional mask may be appended to the mark, separated by _/_. The
default mask if omitted is 0xffffffff. The special value _%same_ uses
the value (but not the mask) from **mark_in** as mark value, which can be
fixed, _%unique_ or _%unique-dir_.
Setting marks in XFRM input requires Linux 4.19 or higher.
connections.<conn>.children.<child>.set_mark_out = 0/0x00000000
Netfilter mark applied to packets after the outbound IPsec SA processed
them.
Netfilter mark applied to packets after the outbound IPsec SA processed
them. This allows processing ESP packets differently than the original
traffic (e.g. via policy routing).
An additional mask may be appended to the mark, separated by _/_. The
default mask if omitted is 0xffffffff. The special value _%same_ uses
the value (but not the mask) from **mark_out** as mark value, which can be
fixed, _%unique_ or _%unique-dir_.
Setting marks in XFRM output is supported since Linux 4.14. Setting a mask
requires at least Linux 4.19.
connections.<conn>.children.<child>.tfc_padding = 0
Traffic Flow Confidentiality padding.