Merge branch 'connmark'

Introduce a connmark plugin that uses Netfilter conntracks mark to select the
correct return-path SAs for client-initiated connections. This can be used
to distinguish transport mode clients behind the same NAT router.

Fixes #365.
This commit is contained in:
Martin Willi 2015-02-20 15:59:19 +01:00
commit c72fa57a00
27 changed files with 951 additions and 8 deletions

6
NEWS
View File

@ -6,6 +6,12 @@
as any previous strongSwan release) it must be explicitly enabled using
the charon.make_before_break strongswan.conf option.
- The new connmark plugin allows a host to bind conntrack flows to a specific
CHILD_SA by applying and restoring the SA mark to conntrack entries. This
allows a peer to handle multiple transport mode connections coming over the
same NAT device for client-initiated flows. A common use case is to protect
L2TP/IPsec, as supported by some systems.
strongswan-5.2.2
----------------

View File

@ -246,6 +246,7 @@ ARG_ENABL_SET([tnccs-dynamic], [enable dynamic TNCCS protocol discovery module.
# misc plugins
ARG_ENABL_SET([android-log], [enable Android specific logger plugin.])
ARG_ENABL_SET([certexpire], [enable CSV export of expiration dates of used certificates.])
ARG_ENABL_SET([connmark], [enable connmark plugin using conntrack based marks to select return path SA.])
ARG_ENABL_SET([duplicheck], [advanced duplicate checking plugin using liveness checks.])
ARG_ENABL_SET([error-notify], [enable error notification plugin.])
ARG_ENABL_SET([farp], [enable ARP faking plugin that responds to ARP requests to peers virtual IP])
@ -1268,6 +1269,7 @@ ADD_PLUGIN([resolve], [c charon cmd])
ADD_PLUGIN([socket-default], [c charon nm cmd])
ADD_PLUGIN([socket-dynamic], [c charon cmd])
ADD_PLUGIN([socket-win], [c charon])
ADD_PLUGIN([connmark], [c charon])
ADD_PLUGIN([farp], [c charon])
ADD_PLUGIN([stroke], [c charon])
ADD_PLUGIN([vici], [c charon])
@ -1475,6 +1477,7 @@ AM_CONDITIONAL(USE_IMV_SWID, test x$imv_swid = xtrue)
AM_CONDITIONAL(USE_SOCKET_DEFAULT, test x$socket_default = xtrue)
AM_CONDITIONAL(USE_SOCKET_DYNAMIC, test x$socket_dynamic = xtrue)
AM_CONDITIONAL(USE_SOCKET_WIN, test x$socket_win = xtrue)
AM_CONDITIONAL(USE_CONNMARK, test x$connmark = xtrue)
AM_CONDITIONAL(USE_FARP, test x$farp = xtrue)
AM_CONDITIONAL(USE_ADDRBLOCK, test x$addrblock = xtrue)
AM_CONDITIONAL(USE_UNITY, test x$unity = xtrue)
@ -1709,6 +1712,7 @@ AC_CONFIG_FILES([
src/libcharon/plugins/socket_default/Makefile
src/libcharon/plugins/socket_dynamic/Makefile
src/libcharon/plugins/socket_win/Makefile
src/libcharon/plugins/connmark/Makefile
src/libcharon/plugins/farp/Makefile
src/libcharon/plugins/smp/Makefile
src/libcharon/plugins/sql/Makefile

View File

@ -987,7 +987,9 @@ sets an XFRM mark in the inbound and outbound
IPsec SAs and policies. If the mask is missing then a default
mask of
.B 0xffffffff
is assumed.
is assumed. The special value
.B %unique
assigns a unique value to each newly created IPsec SA.
.TP
.BR mark_in " = <value>[/<mask>]"
sets an XFRM mark in the inbound IPsec SA and

View File

@ -50,7 +50,7 @@ all)
DEPS="$DEPS libcurl4-gnutls-dev libsoup2.4-dev libunbound-dev libldns-dev
libmysqlclient-dev libsqlite3-dev clearsilver-dev libfcgi-dev
libnm-glib-dev libnm-glib-vpn-dev libpcsclite-dev libpam0g-dev
binutils-dev libunwind7-dev libjson0-dev"
binutils-dev libunwind7-dev libjson0-dev iptables-dev"
;;
win*)
CONFIG="--disable-defaults --enable-svc --enable-ikev2

View File

@ -209,6 +209,13 @@ if MONOLITHIC
endif
endif
if USE_CONNMARK
SUBDIRS += plugins/connmark
if MONOLITHIC
libcharon_la_LIBADD += plugins/connmark/libstrongswan-connmark.la
endif
endif
if USE_FARP
SUBDIRS += plugins/farp
if MONOLITHIC

View File

@ -755,6 +755,33 @@ METHOD(bus_t, ike_rekey, void,
this->mutex->unlock(this->mutex);
}
METHOD(bus_t, ike_update, void,
private_bus_t *this, ike_sa_t *ike_sa, bool local, host_t *new)
{
enumerator_t *enumerator;
entry_t *entry;
bool keep;
this->mutex->lock(this->mutex);
enumerator = this->listeners->create_enumerator(this->listeners);
while (enumerator->enumerate(enumerator, &entry))
{
if (entry->calling || !entry->listener->ike_update)
{
continue;
}
entry->calling++;
keep = entry->listener->ike_update(entry->listener, ike_sa, local, new);
entry->calling--;
if (!keep)
{
unregister_listener(this, entry, enumerator);
}
}
enumerator->destroy(enumerator);
this->mutex->unlock(this->mutex);
}
METHOD(bus_t, ike_reestablish_pre, void,
private_bus_t *this, ike_sa_t *old, ike_sa_t *new)
{
@ -1006,6 +1033,7 @@ bus_t *bus_create()
.child_keys = _child_keys,
.ike_updown = _ike_updown,
.ike_rekey = _ike_rekey,
.ike_update = _ike_update,
.ike_reestablish_pre = _ike_reestablish_pre,
.ike_reestablish_post = _ike_reestablish_post,
.child_updown = _child_updown,

View File

@ -381,6 +381,15 @@ struct bus_t {
*/
void (*ike_rekey)(bus_t *this, ike_sa_t *old, ike_sa_t *new);
/**
* IKE_SA peer endpoint update hook.
*
* @param ike_sa updated IKE_SA, having old endpoints set
* @param local TRUE if local endpoint gets updated, FALSE for remote
* @param new new endpoint address and port
*/
void (*ike_update)(bus_t *this, ike_sa_t *ike_sa, bool local, host_t *new);
/**
* IKE_SA reestablishing hook (before resolving hosts).
*

View File

@ -127,6 +127,17 @@ struct listener_t {
*/
bool (*ike_rekey)(listener_t *this, ike_sa_t *old, ike_sa_t *new);
/**
* Hook called for IKE_SA peer endpoint updates.
*
* @param ike_sa updated IKE_SA, having old endpoints set
* @param local TRUE if local endpoint gets updated, FALSE for remote
* @param new new endpoint address and port
* @return TRUE to stay registered, FALSE to unregister
*/
bool (*ike_update)(listener_t *this, ike_sa_t *ike_sa,
bool local, host_t *new);
/**
* Hook called when an initiator reestablishes an IKE_SA.
*

View File

@ -0,0 +1,20 @@
AM_CPPFLAGS = \
-I$(top_srcdir)/src/libstrongswan \
-I$(top_srcdir)/src/libhydra \
-I$(top_srcdir)/src/libcharon
AM_CFLAGS = \
$(PLUGIN_CFLAGS)
if MONOLITHIC
noinst_LTLIBRARIES = libstrongswan-connmark.la
else
plugin_LTLIBRARIES = libstrongswan-connmark.la
endif
libstrongswan_connmark_la_SOURCES = \
connmark_listener.h connmark_listener.c \
connmark_plugin.h connmark_plugin.c
libstrongswan_connmark_la_LDFLAGS = -module -avoid-version
libstrongswan_connmark_la_LIBADD = -lip4tc

View File

@ -0,0 +1,538 @@
/*
* Copyright (C) 2014 Martin Willi
* Copyright (C) 2014 revosec AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "connmark_listener.h"
#include <daemon.h>
#include <errno.h>
#include <libiptc/libiptc.h>
#include <linux/netfilter/xt_esp.h>
#include <linux/netfilter/xt_tcpudp.h>
#include <linux/netfilter/xt_MARK.h>
#include <linux/netfilter/xt_policy.h>
#include <linux/netfilter/xt_CONNMARK.h>
typedef struct private_connmark_listener_t private_connmark_listener_t;
/**
* Private data of an connmark_listener_t object.
*/
struct private_connmark_listener_t {
/**
* Public connmark_listener_t interface.
*/
connmark_listener_t public;
};
/**
* Convert an (IPv4) traffic selector to an address and mask
*/
static bool ts2in(traffic_selector_t *ts,
struct in_addr *addr, struct in_addr *mask)
{
u_int8_t bits;
host_t *net;
if (ts->get_type(ts) == TS_IPV4_ADDR_RANGE &&
ts->to_subnet(ts, &net, &bits))
{
memcpy(&addr->s_addr, net->get_address(net).ptr, 4);
net->destroy(net);
mask->s_addr = htonl(0xffffffffU << (32 - bits));
return TRUE;
}
return FALSE;
}
/**
* Convert an (IPv4) host to an address with mask
*/
static bool host2in(host_t *host, struct in_addr *addr, struct in_addr *mask)
{
if (host->get_family(host) == AF_INET)
{
memcpy(&addr->s_addr, host->get_address(host).ptr, 4);
mask->s_addr = ~0;
return TRUE;
}
return FALSE;
}
/**
* Add or remove a rule to/from the specified chain
*/
static bool manage_rule(struct iptc_handle *ipth, const char *chain,
bool add, struct ipt_entry *e)
{
if (add)
{
if (!iptc_insert_entry(chain, e, 0, ipth))
{
DBG1(DBG_CFG, "appending %s rule failed: %s",
chain, iptc_strerror(errno));
return FALSE;
}
}
else
{
if (!iptc_delete_entry(chain, e, "", ipth))
{
DBG1(DBG_CFG, "deleting %s rule failed: %s",
chain, iptc_strerror(errno));
return FALSE;
}
}
return TRUE;
}
/**
* Add rule marking UDP-encapsulated ESP packets to match the correct policy
*/
static bool manage_pre_esp_in_udp(private_connmark_listener_t *this,
struct iptc_handle *ipth, bool add,
u_int mark, u_int32_t spi,
host_t *dst, host_t *src)
{
struct {
struct ipt_entry e;
struct ipt_entry_match m;
struct xt_udp udp;
struct ipt_entry_target t;
struct xt_mark_tginfo2 tm;
} ipt = {
.e = {
.target_offset = XT_ALIGN(sizeof(ipt.e) + sizeof(ipt.m) +
sizeof(ipt.udp)),
.next_offset = sizeof(ipt),
.ip = {
.proto = IPPROTO_UDP,
},
},
.m = {
.u = {
.user = {
.match_size = XT_ALIGN(sizeof(ipt.m) + sizeof(ipt.udp)),
.name = "udp",
},
},
},
.udp = {
.spts = { src->get_port(src), src->get_port(src) },
.dpts = { dst->get_port(dst), dst->get_port(dst) },
},
.t = {
.u = {
.user = {
.target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.tm)),
.name = "MARK",
.revision = 2,
},
},
},
.tm = {
.mark = mark,
.mask = ~0,
},
};
if (!host2in(dst, &ipt.e.ip.dst, &ipt.e.ip.dmsk) ||
!host2in(src, &ipt.e.ip.src, &ipt.e.ip.smsk))
{
return FALSE;
}
return manage_rule(ipth, "PREROUTING", add, &ipt.e);
}
/**
* Add rule marking non-encapsulated ESP packets to match the correct policy
*/
static bool manage_pre_esp(private_connmark_listener_t *this,
struct iptc_handle *ipth, bool add,
u_int mark, u_int32_t spi,
host_t *dst, host_t *src)
{
struct {
struct ipt_entry e;
struct ipt_entry_match m;
struct xt_esp esp;
struct ipt_entry_target t;
struct xt_mark_tginfo2 tm;
} ipt = {
.e = {
.target_offset = XT_ALIGN(sizeof(ipt.e) + sizeof(ipt.m) +
sizeof(ipt.esp)),
.next_offset = sizeof(ipt),
.ip = {
.proto = IPPROTO_ESP,
},
},
.m = {
.u = {
.user = {
.match_size = XT_ALIGN(sizeof(ipt.m) + sizeof(ipt.esp)),
.name = "esp",
},
},
},
.esp = {
.spis = { htonl(spi), htonl(spi) },
},
.t = {
.u = {
.user = {
.target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.tm)),
.name = "MARK",
.revision = 2,
},
},
},
.tm = {
.mark = mark,
.mask = ~0,
},
};
if (!host2in(dst, &ipt.e.ip.dst, &ipt.e.ip.dmsk) ||
!host2in(src, &ipt.e.ip.src, &ipt.e.ip.smsk))
{
return FALSE;
}
return manage_rule(ipth, "PREROUTING", add, &ipt.e);
}
/**
* Add rule marking ESP packets to match the correct policy
*/
static bool manage_pre(private_connmark_listener_t *this,
struct iptc_handle *ipth, bool add,
u_int mark, u_int32_t spi, bool encap,
host_t *dst, host_t *src)
{
if (encap)
{
return manage_pre_esp_in_udp(this, ipth, add, mark, spi, dst, src);
}
return manage_pre_esp(this, ipth, add, mark, spi, dst, src);
}
/**
* Add inbound rule applying CONNMARK to matching traffic
*/
static bool manage_in(private_connmark_listener_t *this,
struct iptc_handle *ipth, bool add,
u_int mark, u_int32_t spi,
traffic_selector_t *dst, traffic_selector_t *src)
{
struct {
struct ipt_entry e;
struct ipt_entry_match m;
struct xt_policy_info p;
struct ipt_entry_target t;
struct xt_connmark_tginfo1 cm;
} ipt = {
.e = {
.target_offset = XT_ALIGN(sizeof(ipt.e) + sizeof(ipt.m) +
sizeof(ipt.p)),
.next_offset = sizeof(ipt),
},
.m = {
.u = {
.user = {
.match_size = XT_ALIGN(sizeof(ipt.m) + sizeof(ipt.p)),
.name = "policy",
},
},
},
.p = {
.pol = {
{
.spi = spi,
.match.spi = 1,
},
},
.len = 1,
.flags = XT_POLICY_MATCH_IN,
},
.t = {
.u = {
.user = {
.target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.cm)),
.name = "CONNMARK",
.revision = 1,
},
},
},
.cm = {
.ctmark = mark,
.ctmask = ~0,
.nfmask = ~0,
.mode = XT_CONNMARK_SET,
},
};
if (!ts2in(dst, &ipt.e.ip.dst, &ipt.e.ip.dmsk) ||
!ts2in(src, &ipt.e.ip.src, &ipt.e.ip.smsk))
{
return FALSE;
}
return manage_rule(ipth, "INPUT", add, &ipt.e);
}
/**
* Add outbund rule restoring CONNMARK on matching traffic
*/
static bool manage_out(private_connmark_listener_t *this,
struct iptc_handle *ipth, bool add,
traffic_selector_t *dst, traffic_selector_t *src)
{
struct {
struct ipt_entry e;
struct ipt_entry_target t;
struct xt_connmark_tginfo1 cm;
} ipt = {
.e = {
.target_offset = XT_ALIGN(sizeof(ipt.e)),
.next_offset = sizeof(ipt),
},
.t = {
.u = {
.user = {
.target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.cm)),
.name = "CONNMARK",
.revision = 1,
},
},
},
.cm = {
.ctmask = ~0,
.nfmask = ~0,
.mode = XT_CONNMARK_RESTORE,
},
};
if (!ts2in(dst, &ipt.e.ip.dst, &ipt.e.ip.dmsk) ||
!ts2in(src, &ipt.e.ip.src, &ipt.e.ip.smsk))
{
return FALSE;
}
return manage_rule(ipth, "OUTPUT", add, &ipt.e);
}
/**
* Initialize iptables handle, log error
*/
static struct iptc_handle* init_handle()
{
struct iptc_handle *ipth;
ipth = iptc_init("mangle");
if (ipth)
{
return ipth;
}
DBG1(DBG_CFG, "initializing iptables failed: %s", iptc_strerror(errno));
return NULL;
}
/**
* Commit iptables rules, log error
*/
static bool commit_handle(struct iptc_handle *ipth)
{
if (iptc_commit(ipth))
{
return TRUE;
}
DBG1(DBG_CFG, "forecast iptables commit failed: %s", iptc_strerror(errno));
return FALSE;
}
/**
* Add/Remove policies for a CHILD_SA using a iptables handle
*/
static bool manage_policies(private_connmark_listener_t *this,
struct iptc_handle *ipth, host_t *dst, host_t *src,
bool encap, child_sa_t *child_sa, bool add)
{
traffic_selector_t *local, *remote;
enumerator_t *enumerator;
u_int32_t spi;
u_int mark;
bool done = TRUE;
spi = child_sa->get_spi(child_sa, TRUE);
mark = child_sa->get_mark(child_sa, TRUE).value;
enumerator = child_sa->create_policy_enumerator(child_sa);
while (enumerator->enumerate(enumerator, &local, &remote))
{
if (!manage_pre(this, ipth, add, mark, spi, encap, dst, src) ||
!manage_in(this, ipth, add, mark, spi, local, remote) ||
!manage_out(this, ipth, add, remote, local))
{
done = FALSE;
break;
}
}
enumerator->destroy(enumerator);
return done;
}
/**
* Check if rules should be installed for given CHILD_SA
*/
static bool handle_sa(child_sa_t *child_sa)
{
return child_sa->get_mark(child_sa, TRUE).value &&
child_sa->get_mark(child_sa, FALSE).value &&
child_sa->get_mode(child_sa) == MODE_TRANSPORT &&
child_sa->get_protocol(child_sa) == PROTO_ESP;
}
METHOD(listener_t, child_updown, bool,
private_connmark_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
bool up)
{
struct iptc_handle *ipth;
host_t *dst, *src;
bool encap;
dst = ike_sa->get_my_host(ike_sa);
src = ike_sa->get_other_host(ike_sa);
encap = child_sa->has_encap(child_sa);
if (handle_sa(child_sa))
{
ipth = init_handle();
if (ipth)
{
if (manage_policies(this, ipth, dst, src, encap, child_sa, up))
{
commit_handle(ipth);
}
iptc_free(ipth);
}
}
return TRUE;
}
METHOD(listener_t, child_rekey, bool,
private_connmark_listener_t *this, ike_sa_t *ike_sa,
child_sa_t *old, child_sa_t *new)
{
struct iptc_handle *ipth;
host_t *dst, *src;
bool oldencap, newencap;
dst = ike_sa->get_my_host(ike_sa);
src = ike_sa->get_other_host(ike_sa);
oldencap = old->has_encap(old);
newencap = new->has_encap(new);
if (handle_sa(old))
{
ipth = init_handle();
if (ipth)
{
if (manage_policies(this, ipth, dst, src, oldencap, old, FALSE) &&
manage_policies(this, ipth, dst, src, newencap, new, TRUE))
{
commit_handle(ipth);
}
iptc_free(ipth);
}
}
return TRUE;
}
METHOD(listener_t, ike_update, bool,
private_connmark_listener_t *this, ike_sa_t *ike_sa,
bool local, host_t *new)
{
struct iptc_handle *ipth;
enumerator_t *enumerator;
child_sa_t *child_sa;
host_t *dst, *src;
bool oldencap, newencap;
if (local)
{
dst = new;
src = ike_sa->get_other_host(ike_sa);
}
else
{
dst = ike_sa->get_my_host(ike_sa);
src = new;
}
/* during ike_update(), has_encap() on the CHILD_SA has not yet been
* updated, but shows the old state. */
newencap = ike_sa->has_condition(ike_sa, COND_NAT_ANY);
enumerator = ike_sa->create_child_sa_enumerator(ike_sa);
while (enumerator->enumerate(enumerator, &child_sa))
{
if (handle_sa(child_sa))
{
oldencap = child_sa->has_encap(child_sa);
ipth = init_handle();
if (ipth)
{
if (manage_policies(this, ipth, dst, src, oldencap,
child_sa, FALSE) &&
manage_policies(this, ipth, dst, src, newencap,
child_sa, TRUE))
{
commit_handle(ipth);
}
iptc_free(ipth);
}
}
}
enumerator->destroy(enumerator);
return TRUE;
}
METHOD(connmark_listener_t, destroy, void,
private_connmark_listener_t *this)
{
free(this);
}
/**
* See header
*/
connmark_listener_t *connmark_listener_create()
{
private_connmark_listener_t *this;
INIT(this,
.public = {
.listener = {
.ike_update = _ike_update,
.child_updown = _child_updown,
.child_rekey = _child_rekey,
},
.destroy = _destroy,
},
);
return &this->public;
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (C) 2014 Martin Willi
* Copyright (C) 2014 revosec AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* @defgroup connmark_listener connmark_listener
* @{ @ingroup connmark
*/
#ifndef CONNMARK_LISTENER_H_
#define CONNMARK_LISTENER_H_
#include <bus/listeners/listener.h>
typedef struct connmark_listener_t connmark_listener_t;
/**
* Listener to install Netfilter rules
*/
struct connmark_listener_t {
/**
* Implements listener_t interface.
*/
listener_t listener;
/**
* Destroy a connmark_listener_t.
*/
void (*destroy)(connmark_listener_t *this);
};
/**
* Create a connmark_listener instance.
*/
connmark_listener_t *connmark_listener_create();
#endif /** CONNMARK_LISTENER_H_ @}*/

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2014 Martin Willi
* Copyright (C) 2014 revosec AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "connmark_plugin.h"
#include "connmark_listener.h"
#include <daemon.h>
typedef struct private_connmark_plugin_t private_connmark_plugin_t;
/**
* private data of connmark plugin
*/
struct private_connmark_plugin_t {
/**
* implements plugin interface
*/
connmark_plugin_t public;
/**
* Listener installing netfilter rules
*/
connmark_listener_t *listener;
};
METHOD(plugin_t, get_name, char*,
private_connmark_plugin_t *this)
{
return "connmark";
}
/**
* Register listener
*/
static bool plugin_cb(private_connmark_plugin_t *this,
plugin_feature_t *feature, bool reg, void *cb_data)
{
if (reg)
{
charon->bus->add_listener(charon->bus, &this->listener->listener);
}
else
{
charon->bus->remove_listener(charon->bus, &this->listener->listener);
}
return TRUE;
}
METHOD(plugin_t, get_features, int,
private_connmark_plugin_t *this, plugin_feature_t *features[])
{
static plugin_feature_t f[] = {
PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL),
PLUGIN_PROVIDE(CUSTOM, "connmark"),
};
*features = f;
return countof(f);
}
METHOD(plugin_t, destroy, void,
private_connmark_plugin_t *this)
{
this->listener->destroy(this->listener);
free(this);
}
/**
* Plugin constructor
*/
plugin_t *connmark_plugin_create()
{
private_connmark_plugin_t *this;
if (!lib->caps->keep(lib->caps, CAP_NET_ADMIN))
{
DBG1(DBG_NET, "connmark plugin requires CAP_NET_ADMIN capability");
return NULL;
}
INIT(this,
.public = {
.plugin = {
.get_name = _get_name,
.get_features = _get_features,
.destroy = _destroy,
},
},
.listener = connmark_listener_create(),
);
return &this->public.plugin;
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (C) 2014 Martin Willi
* Copyright (C) 2014 revosec AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* @defgroup connmark connmark
* @ingroup cplugins
*
* @defgroup connmark_plugin connmark_plugin
* @{ @ingroup connmark
*/
#ifndef CONNMARK_PLUGIN_H_
#define CONNMARK_PLUGIN_H_
#include <plugins/plugin.h>
typedef struct connmark_plugin_t connmark_plugin_t;
/**
* Plugin using marks to select return path SA based on conntrack.
*/
struct connmark_plugin_t {
/**
* implements plugin interface
*/
plugin_t plugin;
};
#endif /** CONNMARK_PLUGIN_H_ @}*/

View File

@ -932,6 +932,7 @@ METHOD(ike_sa_t, update_hosts, void,
/* update our address in any case */
if (force && !me->equals(me, this->my_host))
{
charon->bus->ike_update(charon->bus, &this->public, TRUE, me);
set_my_host(this, me->clone(me));
update = TRUE;
}
@ -945,6 +946,7 @@ METHOD(ike_sa_t, update_hosts, void,
(!has_condition(this, COND_NAT_HERE) ||
!has_condition(this, COND_ORIGINAL_INITIATOR)))
{
charon->bus->ike_update(charon->bus, &this->public, FALSE, other);
set_other_host(this, other->clone(other));
update = TRUE;
}

View File

@ -48,7 +48,15 @@ bool mark_from_string(const char *value, mark_t *mark)
{
return FALSE;
}
mark->value = strtoul(value, &endptr, 0);
if (strcasepfx(value, "%unique"))
{
mark->value = MARK_UNIQUE;
endptr = (char*)value + strlen("%unique");
}
else
{
mark->value = strtoul(value, &endptr, 0);
}
if (*endptr)
{
if (*endptr != '/')

View File

@ -622,9 +622,10 @@ connections.<conn>.children.<child>.reqid = 0
connections.<conn>.children.<child>.mark_in = 0/0x00000000
Netfilter mark and mask for input traffic.
Netfilter mark and mask for input traffic. On Linux Netfilter may apply
marks to each packet coming from a tunnel having that option set. The
mark may then be used by Netfilter to match rules.
Netfilter mark and mask for input traffic. On Linux Netfilter may require
marks on each packet to match an SA having that option set. This allows
Netfilter rules to select specific tunnels for incoming traffic. The
special value _%unique_ sets a unique mark on each CHILD_SA instance.
An additional mask may be appended to the mark, separated by _/_. The
default mask if omitted is 0xffffffff.
@ -634,7 +635,8 @@ connections.<conn>.children.<child>.mark_out = 0/0x00000000
Netfilter mark and mask for output traffic. On Linux Netfilter may require
marks on each packet to match a policy having that option set. This allows
Netfilter rules to select specific tunnels for outgoing traffic.
Netfilter rules to select specific tunnels for outgoing traffic. The
special value _%unique_ sets a unique mark on each CHILD_SA instance.
An additional mask may be appended to the mark, separated by _/_. The
default mask if omitted is 0xffffffff.

View File

@ -21,7 +21,7 @@ INC=$INC,less,acpid,acpi-support-base,libldns-dev,libunbound-dev,dnsutils,screen
INC=$INC,gnat,gprbuild,libahven3-dev,libxmlada4.1-dev,libgmpada3-dev
INC=$INC,libalog0.4.1-base-dev,hostapd,libsoup2.4-dev,ca-certificates,unzip
INC=$INC,python,python-setuptools,python-dev,python-pip
INC=$INC,libjson0-dev,libxslt1-dev,libapache2-mod-wsgi
INC=$INC,libjson0-dev,libxslt1-dev,libapache2-mod-wsgi,iptables-dev
SERVICES="apache2 dbus isc-dhcp-server slapd bind9"
INC=$INC,${SERVICES// /,}

View File

@ -70,6 +70,7 @@ CONFIG_OPTS = \
--enable-socket-dynamic \
--enable-dhcp \
--enable-farp \
--enable-connmark \
--enable-addrblock \
--enable-ctr \
--enable-ccm \

View File

@ -0,0 +1,8 @@
An IPsec <b>transport-mode</b> connection between the natted host <b>alice</b>
and gateway <b>sun</b> is successfully set up. The client <b>venus</b> behind
the same NAT as client <b>alice</b> also establishes the same <b>transport-mode</b>
connection. <b>sun</b> uses the connmark plugin and a <b>%unique</b> mark on
the CHILD_SAs to select the correct return path SA using connection tracking.
This allows <b>sun</b> to talk to both nodes for client initiated flows, even
if the SAs are actually both over <b>moon</b>.<br/>
To test the connection, both hosts establish an SSH connection to <b>sun</b>.

View File

@ -0,0 +1,7 @@
sun:: ipsec status 2> /dev/null::nat-t.*ESTABLISHED.*sun.strongswan.org.*venus.strongswan.org::YES
sun:: ipsec status 2> /dev/null::nat-t.*ESTABLISHED.*sun.strongswan.org.*alice@strongswan.org::YES
alice::ipsec status 2> /dev/null::nat-t.*INSTALLED, TRANSPORT, reqid 1::YES
venus::ipsec status 2> /dev/null::nat-t.*INSTALLED, TRANSPORT, reqid 1::YES
alice::ssh 192.168.0.2 'echo alice-echo && exit'::alice-echo::YES
venus::ssh 192.168.0.2 'echo venus-echo && exit'::venus-echo::YES
sun::iptables -t mangle -L -n -v

View File

@ -0,0 +1,17 @@
# /etc/ipsec.conf - strongSwan IPsec configuration file
config setup
conn %default
ikelifetime=60m
keylife=20m
rekeymargin=3m
keyingtries=1
conn nat-t
leftcert=aliceCert.pem
leftid=alice@strongswan.org
right=192.168.0.2
rightid=@sun.strongswan.org
type=transport
auto=add

View File

@ -0,0 +1,18 @@
# /etc/ipsec.conf - strongSwan IPsec configuration file
config setup
conn %default
ikelifetime=60m
keylife=20m
rekeymargin=3m
keyingtries=1
left=192.168.0.2
leftcert=sunCert.pem
leftid=@sun.strongswan.org
conn nat-t
right=%any
type=transport
mark=%unique
auto=add

View File

@ -0,0 +1,5 @@
# /etc/strongswan.conf - strongSwan configuration file
charon {
load = aes des sha1 sha2 md5 pem pkcs1 gmp random nonce x509 curl revocation hmac xcbc stroke kernel-netlink socket-default connmark
}

View File

@ -0,0 +1,17 @@
# /etc/ipsec.conf - strongSwan IPsec configuration file
config setup
conn %default
ikelifetime=60m
keylife=20m
rekeymargin=3m
keyingtries=1
conn nat-t
leftcert=venusCert.pem
leftid=venus@strongswan.org
right=192.168.0.2
rightid=@sun.strongswan.org
type=transport
auto=add

View File

@ -0,0 +1,5 @@
alice::ipsec stop
venus::ipsec stop
sun::ipsec stop
moon::iptables-restore < /etc/iptables.flush
sun::iptables-restore < /etc/iptables.flush

View File

@ -0,0 +1,11 @@
moon::iptables-restore < /etc/iptables.rules
moon::iptables -t nat -A POSTROUTING -o eth0 -s 10.1.0.0/16 -j MASQUERADE
moon::iptables -A FORWARD -i eth1 -o eth0 -s 10.1.0.0/16 -j ACCEPT
moon::iptables -A FORWARD -i eth0 -o eth1 -d 10.1.0.0/16 -j ACCEPT
alice::ipsec start
venus::ipsec start
sun::ipsec start
alice::expect-connection nat-t
venus::expect-connection nat-t
alice::ipsec up nat-t
venus::ipsec up nat-t

View File

@ -0,0 +1,21 @@
#!/bin/bash
#
# This configuration file provides information on the
# guest instances used for this test
# All guest instances that are required for this test
#
VIRTHOSTS="alice moon winnetou sun"
# Corresponding block diagram
#
DIAGRAM="a-m-w-s-b.png"
# Guest instances on which tcpdump is to be started
#
TCPDUMPHOSTS="sun alice venus moon"
# Guest instances on which IPsec is started
# Used for IPsec logging purposes
#
IPSECHOSTS="alice venus sun"