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:
commit
c72fa57a00
6
NEWS
6
NEWS
|
@ -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
|
||||
----------------
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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).
|
||||
*
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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_ @}*/
|
|
@ -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;
|
||||
}
|
|
@ -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_ @}*/
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 != '/')
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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// /,}
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ CONFIG_OPTS = \
|
|||
--enable-socket-dynamic \
|
||||
--enable-dhcp \
|
||||
--enable-farp \
|
||||
--enable-connmark \
|
||||
--enable-addrblock \
|
||||
--enable-ctr \
|
||||
--enable-ccm \
|
||||
|
|
|
@ -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>.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
Loading…
Reference in New Issue