Merge branch 'fwmarks'

Allows setting a mark on outbound packets and the routing rule
installed by charon.  With those settings it is possible to setup
tunnels with kernel-libipsec where the remote peer is part of the remote
traffic selector.

The following example settings in strongswan.conf show how this can be
configured:

charon {
    plugins {
        kernel-netlink {
            fwmark = !0x42
        }
        socket-default {
            fwmark = 0x42
        }
        kernel-libipsec {
            allow_peer_ts = yes
        }
    }
}

To make it work it is necessary to set

  net.ipv4.conf.all.rp_filter

appropriately, otherwise the kernel drops the packets.

References #380.
This commit is contained in:
Tobias Brunner 2013-10-11 15:33:06 +02:00
commit 1ff63f153e
8 changed files with 160 additions and 53 deletions

View File

@ -623,6 +623,18 @@ Number of ipsecN devices
.BR charon.plugins.kernel-klips.ipsec_dev_mtu " [0]"
Set MTU of ipsecN device
.TP
.BR charon.plugins.kernel-libipsec.allow_peer_ts " [no]"
Allow that the remote traffic selector equals the IKE peer. The route installed
for such traffic (via TUN device) usually prevents further IKE traffic. The
fwmark options for the \fIkernel-netlink\fR and \fIsocket-default\fR plugins can
be used to circumvent that problem.
to
.TP
.BR charon.plugins.kernel-netlink.fwmark
Firewall mark to set on the routing rule that directs traffic to our own routing
table. The format is [!]mark[/mask], where the optional exclamation mark inverts
the meaning (i.e. the rule only applies to packets that don't match the mark).
.TP
.BR charon.plugins.kernel-netlink.roam_events " [yes]"
Whether to trigger roam events when interfaces, addresses or routes change
.TP
@ -656,6 +668,9 @@ is appended to this prefix to make it unique. The result has to be a valid
interface name according to the rules defined by resolvconf. Also, it should
have a high priority according to the order defined in interface-order(5).
.TP
.BR charon.plugins.socket-default.fwmark
Firewall mark to set on outbound packets.
.TP
.BR charon.plugins.socket-default.set_source " [yes]"
Set source address on outbound packets, if possible.
.TP

View File

@ -50,6 +50,11 @@ struct private_kernel_libipsec_ipsec_t {
* List of exclude routes (exclude_route_t)
*/
linked_list_t *excludes;
/**
* Whether the remote TS may equal the IKE peer
*/
bool allow_peer_ts;
};
typedef struct exclude_route_t exclude_route_t;
@ -465,7 +470,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this,
policy->route = NULL;
}
if (dst_ts->is_host(dst_ts, dst))
if (!this->allow_peer_ts && dst_ts->is_host(dst_ts, dst))
{
DBG1(DBG_KNL, "can't install route for %R === %R %N, conflicts with "
"IKE traffic", src_ts, dst_ts, policy_dir_names,
@ -475,7 +480,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this,
return FALSE;
}
/* if remote traffic selector covers the IKE peer, add an exclude route */
if (dst_ts->includes(dst_ts, dst))
if (!this->allow_peer_ts && dst_ts->includes(dst_ts, dst))
{
/* add exclude route for peer */
add_exclude_route(this, route, src, dst);
@ -518,11 +523,6 @@ METHOD(kernel_ipsec_t, add_policy, status_t,
policy_entry_t *policy, *found = NULL;
status_t status;
if (type != POLICY_IPSEC)
{
return SUCCESS;
}
status = ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts,
dst_ts, direction, type, sa, mark, priority);
if (status != SUCCESS)
@ -694,6 +694,8 @@ kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create()
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.policies = linked_list_create(),
.excludes = linked_list_create(),
.allow_peer_ts = lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-libipsec.allow_peer_ts", FALSE, hydra->daemon),
);
ipsec->events->register_listener(ipsec->events, &this->ipsec_listener);

View File

@ -611,6 +611,24 @@ static int open_socket(private_socket_default_socket_t *this,
return -1;
}
}
#ifdef SO_MARK
{ /* set optional MARK on socket (requires CAP_NET_ADMIN) */
char *fwmark;
mark_t mark;
fwmark = lib->settings->get_str(lib->settings,
"%s.plugins.socket-default.fwmark", NULL, charon->name);
if (fwmark && mark_from_string(fwmark, &mark))
{
if (setsockopt(skt, SOL_SOCKET, SO_MARK, &mark.value,
sizeof(mark.value)) < 0)
{
DBG1(DBG_NET, "unable to set SO_MARK on socket: %s",
strerror(errno));
}
}
}
#endif
if (!hydra->kernel_interface->bypass_socket(hydra->kernel_interface,
skt, family))

View File

@ -44,6 +44,7 @@
#include <unistd.h>
#include <errno.h>
#include <net/if.h>
#include <linux/fib_rules.h>
#include "kernel_netlink_net.h"
#include "kernel_netlink_shared.h"
@ -2096,6 +2097,8 @@ static status_t manage_rule(private_kernel_netlink_net_t *this, int nlmsg_type,
struct nlmsghdr *hdr;
struct rtmsg *msg;
chunk_t chunk;
char *fwmark;
mark_t mark;
memset(&request, 0, sizeof(request));
hdr = (struct nlmsghdr*)request;
@ -2117,6 +2120,23 @@ static status_t manage_rule(private_kernel_netlink_net_t *this, int nlmsg_type,
chunk = chunk_from_thing(prio);
netlink_add_attribute(hdr, RTA_PRIORITY, chunk, sizeof(request));
fwmark = lib->settings->get_str(lib->settings,
"%s.plugins.kernel-netlink.fwmark", NULL, hydra->daemon);
if (fwmark)
{
if (fwmark[0] == '!')
{
msg->rtm_flags |= FIB_RULE_INVERT;
fwmark++;
}
if (mark_from_string(fwmark, &mark))
{
chunk = chunk_from_thing(mark.value);
netlink_add_attribute(hdr, FRA_FWMARK, chunk, sizeof(request));
chunk = chunk_from_thing(mark.mask);
netlink_add_attribute(hdr, FRA_FWMASK, chunk, sizeof(request));
}
}
return this->socket->send_ack(this->socket, hdr);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2012-2013 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@ -36,3 +36,38 @@ ENUM(ipcomp_transform_names, IPCOMP_NONE, IPCOMP_LZJH,
"IPCOMP_LZS",
"IPCOMP_LZJH"
);
/*
* See header
*/
bool mark_from_string(const char *value, mark_t *mark)
{
char *endptr;
if (!value)
{
return FALSE;
}
mark->value = strtoul(value, &endptr, 0);
if (*endptr)
{
if (*endptr != '/')
{
DBG1(DBG_APP, "invalid mark value: %s", value);
return FALSE;
}
mark->mask = strtoul(endptr+1, &endptr, 0);
if (*endptr)
{
DBG1(DBG_LIB, "invalid mark mask: %s", endptr);
return FALSE;
}
}
else
{
mark->mask = 0xffffffff;
}
/* apply the mask to ensure the value is in range */
mark->value &= mark->mask;
return TRUE;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2012 Tobias Brunner
* Copyright (C) 2012-2013 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
@ -169,4 +169,13 @@ struct mark_t {
*/
#define MARK_REQID (0xFFFFFFFF)
/**
* Try to parse a mark_t from the given string of the form <mark>[/<mask>].
*
* @param value string to parse
* @param mark mark to fill
* @return TRUE if parsing was successful
*/
bool mark_from_string(const char *value, mark_t *mark);
#endif /** IPSEC_TYPES_H_ @}*/

View File

@ -17,6 +17,7 @@
#include <library.h>
#include <utils/utils.h>
#include <ipsec/ipsec_types.h>
#include <time.h>
@ -442,6 +443,50 @@ START_TEST(test_time_delta_printf_hook)
}
END_TEST
/*******************************************************************************
* mark_from_string
*/
static struct {
char *s;
bool ok;
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 }},
{"0xffffffff/0x0000ffff", TRUE, { 0x0000ffff, 0x0000ffff }},
{"0xffffffff/0xffffffff", TRUE, { 0xffffffff, 0xffffffff }},
};
START_TEST(test_mark_from_string)
{
mark_t mark;
if (mark_from_string(mark_data[_i].s, &mark))
{
ck_assert_int_eq(mark.value, mark_data[_i].m.value);
ck_assert_int_eq(mark.mask, mark_data[_i].m.mask);
}
else
{
ck_assert(!mark_data[_i].ok);
}
}
END_TEST
Suite *utils_suite_create()
{
Suite *s;
@ -496,5 +541,9 @@ Suite *utils_suite_create()
tcase_add_loop_test(tc, test_time_delta_printf_hook, 0, countof(time_delta_data));
suite_add_tcase(s, tc);
tc = tcase_create("mark_from_string");
tcase_add_loop_test(tc, test_mark_from_string, 0, countof(mark_data));
suite_add_tcase(s, tc);
return s;
}

View File

@ -375,47 +375,6 @@ static void handle_firewall(const char *label, starter_end_t *end,
}
}
static bool handle_mark(char *value, mark_t *mark)
{
char *sep, *endptr;
sep = strchr(value, '/');
if (sep)
{
*sep = '\0';
mark->mask = strtoul(sep+1, &endptr, 0);
if (*endptr != '\0')
{
DBG1(DBG_APP, "# invalid mark mask: %s", sep+1);
return FALSE;
}
}
else
{
mark->mask = 0xffffffff;
}
if (value == '\0')
{
mark->value = 0;
}
else
{
mark->value = strtoul(value, &endptr, 0);
if (*endptr != '\0')
{
DBG1(DBG_APP, "# invalid mark value: %s", value);
return FALSE;
}
}
if (sep)
{ /* restore the original text in case also= is used */
*sep = '/';
}
/* apply the mask to ensure the value is in range */
mark->value &= mark->mask;
return TRUE;
}
/*
* parse a conn section
*/
@ -522,7 +481,7 @@ static void load_conn(starter_conn_t *conn, kw_list_t *kw, starter_config_t *cfg
KW_SA_OPTION_FLAG("yes", "no", SA_OPTION_COMPRESS)
break;
case KW_MARK:
if (!handle_mark(kw->value, &conn->mark_in))
if (!mark_from_string(kw->value, &conn->mark_in))
{
cfg->err++;
break;
@ -530,13 +489,13 @@ static void load_conn(starter_conn_t *conn, kw_list_t *kw, starter_config_t *cfg
conn->mark_out = conn->mark_in;
break;
case KW_MARK_IN:
if (!handle_mark(kw->value, &conn->mark_in))
if (!mark_from_string(kw->value, &conn->mark_in))
{
cfg->err++;
}
break;
case KW_MARK_OUT:
if (!handle_mark(kw->value, &conn->mark_out))
if (!mark_from_string(kw->value, &conn->mark_out))
{
cfg->err++;
}