Merge branch 'icmp-forwarding'

Adds support for ICMP error forwarding that the kernel supports properly
since v6.9 (it still sends locally generated errors from the wrong source
IP, though).
This commit is contained in:
Tobias Brunner
2026-02-12 16:23:07 +01:00
35 changed files with 776 additions and 16 deletions
+3
View File
@@ -420,6 +420,9 @@ enum child_cfg_option_t {
/** Enable UDP encapsulation for per-CPU CHILD_SAs */
OPT_PER_CPU_SAS_ENCAP = (1<<10),
/** Enable automatic forwarding of certain ICMP errors */
OPT_FORWARD_ICMP = (1<<11),
};
/**
+4
View File
@@ -115,6 +115,8 @@ struct kernel_ipsec_add_sa_t {
dscp_copy_t copy_dscp;
/** TRUE if the peer doesn't support receiving fragments in AGGFRAG pkts */
bool iptfs_dont_frag;
/** Whether to automatically forward certain ICMP error messages */
bool forward_icmp;
/** TRUE if initiator of the exchange creating the SA */
bool initiator;
/** TRUE if this is an inbound SA */
@@ -190,6 +192,8 @@ struct kernel_ipsec_manage_policy_t {
hw_offload_t hw_offload;
/** Enable per-CPU acquires */
bool pcpu_acquires;
/** Whether to automatically forward certain ICMP error messages */
bool forward_icmp;
/** Source address of the SA(s) tied to this policy */
host_t *src;
/** Destination address of the SA(s) tied to this policy */
@@ -470,7 +470,7 @@ static u_int ipsec_sa_hash(ipsec_sa_t *sa)
chunk_hash_inc(chunk_from_thing(sa->mark),
chunk_hash_inc(chunk_from_thing(sa->if_id),
chunk_hash_inc(chunk_from_thing(sa->hw_offload),
chunk_hash(chunk_from_thing(sa->cfg)))))));
ipsec_sa_cfg_hash(&sa->cfg))))));
}
/**
@@ -554,6 +554,9 @@ struct policy_sa_t {
/** Whether to trigger per-CPU acquires for this policy */
bool pcpu_acquires;
/** Whether to forward certain ICMP error messages for this policy */
bool forward_icmp;
/** Assigned SA */
ipsec_sa_t *sa;
};
@@ -577,19 +580,16 @@ struct policy_sa_out_t {
* Create a policy_sa(_out)_t object
*/
static policy_sa_t *policy_sa_create(private_kernel_netlink_ipsec_t *this,
policy_dir_t dir, policy_type_t type, host_t *src, host_t *dst,
traffic_selector_t *src_ts, traffic_selector_t *dst_ts, mark_t mark,
uint32_t if_id, hw_offload_t hw_offload, bool pcpu_acquires,
ipsec_sa_cfg_t *cfg)
kernel_ipsec_policy_id_t *id, kernel_ipsec_manage_policy_t *data)
{
policy_sa_t *policy;
if (dir == POLICY_OUT)
if (id->dir == POLICY_OUT)
{
policy_sa_out_t *out;
INIT(out,
.src_ts = src_ts->clone(src_ts),
.dst_ts = dst_ts->clone(dst_ts),
.src_ts = id->src_ts->clone(id->src_ts),
.dst_ts = id->dst_ts->clone(id->dst_ts),
);
policy = &out->generic;
}
@@ -597,9 +597,11 @@ static policy_sa_t *policy_sa_create(private_kernel_netlink_ipsec_t *this,
{
INIT(policy, .priority = 0);
}
policy->type = type;
policy->pcpu_acquires = pcpu_acquires;
policy->sa = ipsec_sa_create(this, src, dst, mark, if_id, hw_offload, cfg);
policy->type = data->type;
policy->pcpu_acquires = data->pcpu_acquires;
policy->forward_icmp = data->forward_icmp;
policy->sa = ipsec_sa_create(this, data->src, data->dst, id->mark,
id->if_id, data->hw_offload, data->sa);
return policy;
}
@@ -1807,6 +1809,11 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
sa->flags |= XFRM_STATE_NOECN;
}
if (data->inbound && data->forward_icmp)
{
sa->flags |= XFRM_STATE_ICMP;
}
if (data->inbound)
{
switch (data->copy_dscp)
@@ -3065,6 +3072,12 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this,
: XFRM_POLICY_BLOCK;
policy_info->share = XFRM_SHARE_ANY;
if (mapping->type == POLICY_IPSEC && policy->direction != POLICY_IN &&
mapping->forward_icmp)
{
policy_info->flags |= XFRM_POLICY_ICMP;
}
/* policies don't expire */
policy_info->lft.soft_byte_limit = XFRM_INF;
policy_info->lft.soft_packet_limit = XFRM_INF;
@@ -3262,10 +3275,7 @@ METHOD(kernel_ipsec_t, add_policy, status_t,
}
/* cache the assigned IPsec SA */
assigned_sa = policy_sa_create(this, id->dir, data->type, data->src,
data->dst, id->src_ts, id->dst_ts, id->mark,
id->if_id, data->hw_offload,
data->pcpu_acquires, data->sa);
assigned_sa = policy_sa_create(this, id, data);
assigned_sa->auto_priority = get_priority(policy, data->prio, id->interface);
assigned_sa->priority = this->get_priority ? this->get_priority(id, data)
: data->manual_prio;
@@ -3499,6 +3509,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
auto_priority == mapping->auto_priority &&
data->type == mapping->type &&
data->pcpu_acquires == mapping->pcpu_acquires &&
data->forward_icmp == mapping->forward_icmp &&
ipsec_sa_equals(mapping->sa, &assigned_sa))
{
current->used_by->remove_at(current->used_by, enumerator);
@@ -348,7 +348,7 @@ static u_int ipsec_sa_hash(ipsec_sa_t *sa)
{
return chunk_hash_inc(sa->src->get_address(sa->src),
chunk_hash_inc(sa->dst->get_address(sa->dst),
chunk_hash(chunk_from_thing(sa->cfg))));
ipsec_sa_cfg_hash(&sa->cfg)));
}
/**
+11
View File
@@ -591,6 +591,7 @@ static void log_child_data(child_data_t *data, char *name)
DBG2(DBG_CFG, " copy_df = %u", !has_opt(cfg, OPT_NO_COPY_DF));
DBG2(DBG_CFG, " copy_ecn = %u", !has_opt(cfg, OPT_NO_COPY_ECN));
DBG2(DBG_CFG, " copy_dscp = %N", dscp_copy_names, cfg->copy_dscp);
DBG2(DBG_CFG, " icmp = %u", has_opt(cfg, OPT_FORWARD_ICMP));
}
/**
@@ -1105,6 +1106,15 @@ CALLBACK(parse_copy_dscp, bool,
return FALSE;
}
/**
* Parse OTP_FORWARD_ICMP option
*/
CALLBACK(parse_opt_icmp, bool,
child_cfg_option_t *out, chunk_t v)
{
return parse_option(out, OPT_FORWARD_ICMP, v, TRUE);
}
/**
* Parse an action_t
*/
@@ -1953,6 +1963,7 @@ CALLBACK(child_kv, bool,
{ "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 },
{ "icmp", parse_opt_icmp, &child->cfg.options },
{ "if_id_in", parse_if_id, &child->cfg.if_id_in },
{ "if_id_out", parse_if_id, &child->cfg.if_id_out },
{ "label", parse_label, &child->cfg.label },
+3
View File
@@ -1121,6 +1121,7 @@ static status_t install_internal(private_child_sa_t *this, chunk_t encr,
.copy_ecn = !this->config->has_option(this->config, OPT_NO_COPY_ECN),
.copy_dscp = this->config->get_copy_dscp(this->config),
.iptfs_dont_frag = this->iptfs_dont_frag,
.forward_icmp = this->config->has_option(this->config, OPT_FORWARD_ICMP),
.label = label_for(this, LABEL_USE_SA),
.initiator = initiator,
.inbound = inbound,
@@ -1295,6 +1296,7 @@ static status_t install_policies_outbound(private_child_sa_t *this,
.src = my_addr,
.dst = other_addr,
.pcpu_acquires = this->per_cpu,
.forward_icmp = this->config->has_option(this->config, OPT_FORWARD_ICMP),
.sa = other_sa,
};
uint32_t reqid = other_sa->reqid;
@@ -1409,6 +1411,7 @@ static void del_policies_outbound(private_child_sa_t *this,
.src = my_addr,
.dst = other_addr,
.pcpu_acquires = this->per_cpu,
.forward_icmp = this->config->has_option(this->config, OPT_FORWARD_ICMP),
.sa = other_sa,
};
uint32_t reqid = other_sa->reqid;
+16
View File
@@ -53,6 +53,22 @@ ENUM(dscp_copy_names, DSCP_COPY_OUT_ONLY, DSCP_COPY_NO,
"no",
);
/*
* See header
*/
u_int ipsec_sa_cfg_hash(ipsec_sa_cfg_t *this)
{
return chunk_hash_inc(chunk_from_thing(this->mode),
chunk_hash_inc(chunk_from_thing(this->reqid),
chunk_hash_inc(chunk_from_thing(this->policy_count),
chunk_hash_inc(chunk_from_thing(this->esp.use),
chunk_hash_inc(chunk_from_thing(this->esp.spi),
chunk_hash_inc(chunk_from_thing(this->ah.use),
chunk_hash_inc(chunk_from_thing(this->ah.spi),
chunk_hash_inc(chunk_from_thing(this->ipcomp.transform),
chunk_hash(chunk_from_thing(this->ipcomp.cpi))))))))));
}
/*
* See header
*/
+8
View File
@@ -179,6 +179,14 @@ struct ipsec_sa_cfg_t {
} ipcomp;
};
/**
* Hash an ipsec_sa_cfg_t object.
*
* @param this object to hash
* @return hash value
*/
u_int ipsec_sa_cfg_hash(ipsec_sa_cfg_t *this);
/**
* Compare two ipsec_sa_cfg_t objects for equality.
*
+12
View File
@@ -1182,6 +1182,18 @@ connections.<conn>.children.<child>.copy_dscp = out
receiver, which is why the default is _out_. Controlling this behavior is
not supported by all kernel interfaces.
connections.<conn>.children.<child>.icmp = no
Whether to forward certain ICMP error messages even if their source IP
doesn't match the negotiated IPsec policies.
ICMP error messages, such as Destination Unreachable, Time Exceeded or
Fragmentation Needed, may be generated by a host whose IP address isn't
included in the negotiated traffic selectors and therefore doesn't match the
IPsec policies. If this option is enabled and the kernel supports it, such
packets may still be forwarded. As ICMP errors contain parts of the IP
packet that triggered them, the kernel will base its decision on a reverse
policy lookup using that IP header.
connections.<conn>.children.<child>.start_action = none
Action to perform after loading the configuration (_none_, _trap_, _start_).
+18
View File
@@ -0,0 +1,18 @@
A connection between the gateways <b>moon</b> and <b>sun</b> is set up, which
connects <b>alice</b> and <b>moon</b> with a small subnet behind gateway <b>sun</b>.
A second tunnel is created between client <b>venus</b> and gateway <b>moon</b>,
which connects <b>venus</b> with another small subnet behind, in this case,
non-IPsec gateway <b>sun</b>.
The authentication is based on <b>X.509 certificates</b>.
<p/>
In order to test ICMP error forwarding, which <b>venus</b>, <b>moon</b> and
<b>sun</b> all enabled, <b>alice</b> and <b>moon</b> ping unreachable IPs behind
IPsec gateway <b>sun</b> and behind host <b>bob</b>, respectively. The
corresponding ICMP Destination Unreachable messages, with source IP addresses
outside the IPsec tunnel traffic selectors are expected to be forwarded
by <b>sun</b> and <b>moon</b>.
<p/>
Similar tests are run from <b>venus</b>, in which case the non-IPsec router
<b>sun</b> generates ICMP Time Exceeded (TTL expired) and Fragmentation Needed
(MTU exceeded) messages with a source IP outside of the traffic selectors.
These messages should again get forwarded by <b>moon</b>.
+18
View File
@@ -0,0 +1,18 @@
moon::swanctl --list-sas --raw 2> /dev/null::gw-gw.*version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=500 local-id=moon.strongswan.org remote-host=192.168.0.2 remote-port=500 remote-id=sun.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.0/16] remote-ts=\[10.2.0.16/28]::YES
sun:: swanctl --list-sas --raw 2> /dev/null::gw-gw.*version=2 state=ESTABLISHED local-host=192.168.0.2 local-port=500 local-id=sun.strongswan.org remote-host=192.168.0.1 remote-port=500 remote-id=moon.strongswan.org.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.2.0.16/28] remote-ts=\[10.1.0.0/16]::YES
moon::swanctl --list-sas --raw 2> /dev/null::venus.*version=2 state=ESTABLISHED local-host=10.1.0.1 local-port=500 local-id=moon.strongswan.org remote-host=10.1.0.20 remote-port=500 remote-id=venus.strongswan.org.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.2.0.8/30] remote-ts=\[10.1.0.20/32]::YES
venus::swanctl --list-sas --raw 2> /dev/null::rw.*version=2 state=ESTABLISHED local-host=10.1.0.20 local-port=500 local-id=venus.strongswan.org remote-host=10.1.0.1 remote-port=500 remote-id=moon.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*rw.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.20/32] remote-ts=\[10.2.0.8/30]::YES
# the kernel currently sets the source incorrectly to the non-existent IP
# allow both in case it gets fixed
alice::ping -c 1 -W 4 10.2.0.25::From \(PH_IP_SUN\|10.2.0.25\) icmp_seq=1 Destination Host Unreachable::YES
moon::ping -c 1 -W 4 10.2.0.17::From PH_IP_BOB icmp_seq=1 Destination Host Unreachable::YES
# test other types of ICMPs with venus
venus::ping -c 1 PH_IP_BOB::64 bytes from PH_IP_BOB: icmp_.eq=1::YES
venus::ping -t 2 -c 1 PH_IP_BOB::From PH_IP_SUN.*Time to live exceeded::YES
venus::ping -M do -s 1320 -c 1 PH_IP_BOB::From PH_IP_SUN.*Frag needed and DF set (mtu = 1280)::YES
sun::tcpdump::IP moon.strongswan.org > sun.strongswan.org: ESP::2
sun::tcpdump::IP sun.strongswan.org > moon.strongswan.org: ESP::2
sun::tcpdump::IP sun.strongswan.org > alice.strongswan.org: ICMP host 10.2.0.25 unreachable::NO
sun::tcpdump::IP bob.strongswan.org > moon1.strongswan.org: ICMP host 10.2.0.17 unreachable::NO
venus::tcpdump::IP venus.strongswan.org > moon1.strongswan.org: ESP::3
venus::tcpdump::IP moon1.strongswan.org > venus.strongswan.org: ESP::3
@@ -0,0 +1,48 @@
*filter
# default policy is DROP
-P INPUT DROP
-P OUTPUT DROP
-P FORWARD DROP
# allow esp
-A INPUT -i eth0 -p 50 -j ACCEPT
-A OUTPUT -o eth0 -p 50 -j ACCEPT
# allow IKE
-A INPUT -i eth0 -p udp --sport 500 --dport 500 -j ACCEPT
-A OUTPUT -o eth0 -p udp --dport 500 --sport 500 -j ACCEPT
# allow MobIKE
-A INPUT -i eth0 -p udp --sport 4500 --dport 4500 -j ACCEPT
-A OUTPUT -o eth0 -p udp --dport 4500 --sport 4500 -j ACCEPT
# allow ssh
-A INPUT -p tcp --dport 22 -j ACCEPT
-A OUTPUT -p tcp --sport 22 -j ACCEPT
# allow crl fetch from winnetou
-A INPUT -i eth0 -p tcp --sport 80 -s 192.168.0.150 -j ACCEPT
-A OUTPUT -o eth0 -p tcp --dport 80 -d 192.168.0.150 -j ACCEPT
# allow decrypted ICMPs from any source IP (updown script is too specific)
-A INPUT -p icmp --icmp-type destination-unreachable -m policy --dir in -j ACCEPT
-A FORWARD -p icmp --icmp-type destination-unreachable -m policy --dir in -j ACCEPT
# allow venus to connect and fetch crls
-A INPUT -i eth1 -p udp --sport 500 --dport 500 -j ACCEPT
-A OUTPUT -o eth1 -p udp --dport 500 --sport 500 -j ACCEPT
-A INPUT -i eth1 -p 50 -j ACCEPT
-A OUTPUT -o eth1 -p 50 -j ACCEPT
-A FORWARD -i eth0 -o eth1 -p tcp --sport 80 -s 192.168.0.150 -j ACCEPT
-A FORWARD -o eth0 -i eth1 -p tcp --dport 80 -d 192.168.0.150 -j ACCEPT
# forward specific errors to venus
-A FORWARD -d 10.1.0.20/32 -p icmp --icmp-type time-exceeded -j ACCEPT
-A FORWARD -d 10.1.0.20/32 -p icmp --icmp-type fragmentation-needed -j ACCEPT
# log dropped packets
-A INPUT -j LOG --log-prefix " IN: "
-A OUTPUT -j LOG --log-prefix " OUT: "
COMMIT
@@ -0,0 +1,9 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 revocation curl kernel-netlink socket-default updown vici
}
@@ -0,0 +1,48 @@
connections {
gw-gw {
local_addrs = PH_IP_MOON
remote_addrs = PH_IP_SUN
local {
auth = pubkey
certs = moonCert.pem
id = moon.strongswan.org
}
remote {
auth = pubkey
id = sun.strongswan.org
}
children {
net-net {
local_ts = 10.1.0.0/16
remote_ts = 10.2.0.0/16
icmp = yes
updown = /usr/local/libexec/ipsec/_updown iptables
hostaccess = yes
esp_proposals = aes128gcm128-x25519
}
}
version = 2
mobike = no
proposals = aes128-sha256-x25519
}
venus : connections.gw-gw {
local_addrs = PH_IP_MOON1
remote_addrs = PH_IP_VENUS
remote {
id = venus.strongswan.org
}
children {
net-net {
# only include bob
local_ts = 10.2.0.8/30
remote_ts = dynamic
hostaccess = no
}
}
}
}
@@ -0,0 +1,42 @@
*filter
# default policy is DROP
-P INPUT DROP
-P OUTPUT DROP
-P FORWARD DROP
# allow esp
-A INPUT -i eth0 -p 50 -j ACCEPT
-A OUTPUT -o eth0 -p 50 -j ACCEPT
# allow IKE
-A INPUT -i eth0 -p udp --sport 500 --dport 500 -j ACCEPT
-A OUTPUT -o eth0 -p udp --dport 500 --sport 500 -j ACCEPT
# allow MobIKE
-A INPUT -i eth0 -p udp --sport 4500 --dport 4500 -j ACCEPT
-A OUTPUT -o eth0 -p udp --dport 4500 --sport 4500 -j ACCEPT
# allow ssh
-A INPUT -p tcp --dport 22 -j ACCEPT
-A OUTPUT -p tcp --sport 22 -j ACCEPT
# allow crl fetch from winnetou
-A INPUT -i eth0 -p tcp --sport 80 -s 192.168.0.150 -j ACCEPT
-A OUTPUT -o eth0 -p tcp --dport 80 -d 192.168.0.150 -j ACCEPT
# explicitly allow ICMP responses we don't actually want to see as they should get encrypted
-A OUTPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
-A FORWARD -p icmp --icmp-type destination-unreachable -j ACCEPT
# allow only specific ICMPs from/to venus for this scenario
-A FORWARD -s 10.1.0.20/32 -p icmp --icmp-type echo-request -j ACCEPT
-A FORWARD -d 10.1.0.20/32 -p icmp --icmp-type echo-reply -j ACCEPT
-A OUTPUT -d 10.1.0.20/32 -p icmp --icmp-type time-exceeded -j ACCEPT
-A OUTPUT -d 10.1.0.20/32 -p icmp --icmp-type fragmentation-needed -j ACCEPT
# log dropped packets
-A INPUT -j LOG --log-prefix " IN: "
-A OUTPUT -j LOG --log-prefix " OUT: "
COMMIT
@@ -0,0 +1,9 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 revocation curl kernel-netlink socket-default updown vici
}
@@ -0,0 +1,31 @@
connections {
gw-gw {
local_addrs = PH_IP_SUN
remote_addrs = PH_IP_MOON
local {
auth = pubkey
certs = sunCert.pem
id = sun.strongswan.org
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
net-net {
# exclude sun's and bob's IPs
local_ts = 10.2.0.16/28
remote_ts = 10.1.0.0/16
icmp = yes
updown = /usr/local/libexec/ipsec/_updown iptables
esp_proposals = aes128gcm128-x25519
}
}
version = 2
mobike = no
proposals = aes128-sha256-x25519
}
}
@@ -0,0 +1,9 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 revocation curl kernel-netlink socket-default updown vici
}
@@ -0,0 +1,29 @@
connections {
rw {
remote_addrs = PH_IP_MOON1
local {
auth = pubkey
certs = venusCert.pem
id = venus.strongswan.org
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
rw {
remote_ts = 10.2.0.0/16
icmp = yes
updown = /usr/local/libexec/ipsec/_updown iptables
hostaccess = yes
esp_proposals = aes128gcm128-x25519
}
}
version = 2
mobike = no
proposals = aes128-sha256-x25519
}
}
+13
View File
@@ -0,0 +1,13 @@
moon::swanctl --terminate --ike gw-gw 2> /dev/null
moon::swanctl --terminate --ike venus 2> /dev/null
venus::systemctl stop strongswan
moon::systemctl stop strongswan
sun::systemctl stop strongswan
venus::iptables-restore < /etc/iptables.flush
moon::iptables-restore < /etc/iptables.flush
sun::iptables-restore < /etc/iptables.flush
sun::ip route del 10.2.0.16/30 via PH_IP_BOB
moon::ip route del 10.2.0.8/30 via PH_IP_SUN
sun::ip route del PH_IP_VENUS/32 via PH_IP_MOON
sun::ip route del 10.2.0.8/30 dev eth1 src PH_IP_SUN1
winnetou::ip route del 10.1.0.0/16 via PH_IP_MOON
+21
View File
@@ -0,0 +1,21 @@
venus::iptables-restore < /etc/iptables.rules
moon::iptables-restore < /etc/iptables.rules
sun::iptables-restore < /etc/iptables.rules
venus::systemctl start --no-block strongswan
moon::systemctl start --no-block strongswan
sun::systemctl start --no-block strongswan
# add a route for part of the private subnet via bob
sun::ip route add 10.2.0.16/30 via PH_IP_BOB
# setup routes to test with venus
winnetou::ip route add 10.1.0.0/16 via PH_IP_MOON
moon::ip route add 10.2.0.8/30 via PH_IP_SUN
sun::ip route add PH_IP_VENUS/32 via PH_IP_MOON
# force a lower MTU for traffic to bob
sun::ip route add 10.2.0.8/30 dev eth1 src PH_IP_SUN1 mtu 1280
# allow decrypted ICMPs from any source IP (rules for sun and moon are in iptables.rules)
venus::iptables -A INPUT -p icmp -m policy --dir in -j ACCEPT
venus::expect-connection rw
moon::expect-connection gw-gw
sun::expect-connection gw-gw
moon::swanctl --initiate --child net-net 2> /dev/null
venus::swanctl --initiate --child rw 2> /dev/null
+25
View File
@@ -0,0 +1,25 @@
#!/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 venus moon winnetou sun bob"
# Corresponding block diagram
#
DIAGRAM="a-v-m-w-s-b.png"
# Guest instances on which tcpdump is to be started
#
TCPDUMPHOSTS="venus sun"
# Guest instances on which IPsec is started
# Used for IPsec logging purposes
#
IPSECHOSTS="venus moon sun"
# charon controlled by swanctl
#
SWANCTL=1
@@ -0,0 +1,18 @@
A connection between the gateways <b>moon</b> and <b>sun</b> is set up, which
connects <b>alice</b> and <b>moon</b> with a small subnet behind gateway <b>sun</b>.
A second tunnel is created between client <b>venus</b> and gateway <b>moon</b>,
which connects <b>venus</b> with another small subnet behind, in this case,
non-IPsec gateway <b>sun</b>.
The authentication is based on <b>X.509 certificates</b>.
<p/>
In order to test ICMPv6 error forwarding, which <b>venus</b>, <b>moon</b> and
<b>sun</b> all enabled, <b>alice</b> and <b>moon</b> ping unreachable IPs behind
IPsec gateway <b>sun</b> and behind host <b>bob</b>, respectively. The
corresponding ICMPv6 Destination Unreachable messages, with source IP addresses
outside the IPsec tunnel traffic selectors are expected to be forwarded
by <b>sun</b> and <b>moon</b>.
<p/>
Similar tests are run from <b>venus</b>, in which case the non-IPsec router
<b>sun</b> generates ICMPv6 Time Exceeded (TTL expired) and Packet too big
(MTU exceeded) messages with a source IP outside of the traffic selectors.
These messages should again get forwarded by <b>moon</b>.
@@ -0,0 +1,16 @@
moon::swanctl --list-sas --raw 2> /dev/null::net-net.*version=2 state=ESTABLISHED local-host=fec0:\:1 local-port=500 local-id=moon.strongswan.org remote-host=fec0:\:2 remote-port=500 remote-id=sun.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*reqid=1 state=INSTALLED mode=TUNNEL protocol=ESP.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128.*local-ts=\[fec1:\:/16] remote-ts=\[fec2:\:20/124]::YES
sun ::swanctl --list-sas --raw 2> /dev/null::net-net.*version=2 state=ESTABLISHED local-host=fec0:\:2 local-port=500 local-id=sun.strongswan.org remote-host=fec0:\:1 remote-port=500 remote-id=moon.strongswan.org.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*reqid=1 state=INSTALLED mode=TUNNEL protocol=ESP.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128.*local-ts=\[fec2:\:20/124] remote-ts=\[fec1:\:/16]::YES
moon::swanctl --list-sas --raw 2> /dev/null::venus.*version=2 state=ESTABLISHED local-host=fec1:\:1 local-port=500 local-id=moon.strongswan.org remote-host=fec1:\:20 remote-port=500 remote-id=venus.strongswan.org.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*net-net.*reqid=2 state=INSTALLED mode=TUNNEL protocol=ESP.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128.*local-ts=\[fec2:\:10/124] remote-ts=\[fec1:\:20/128]::YES
venus::swanctl --list-sas --raw 2> /dev/null::rw.*version=2 state=ESTABLISHED local-host=fec1:\:20 local-port=500 local-id=venus.strongswan.org remote-host=fec1:\:1 remote-port=500 remote-id=moon.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=CURVE_25519.*child-sas.*rw.*reqid=1 state=INSTALLED mode=TUNNEL protocol=ESP.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128.*local-ts=\[fec1:\:20/128] remote-ts=\[fec2:\:10/124]::YES
alice::ping6 -c 1 -W 4 -p deadbeef fec2:\:25::From fec0:\:2 icmp_seq=1 Destination unreachable: Address unreachable::YES
moon::ping6 -c 1 -W 4 -p deadbeef fec2:\:22::From fec2:\:10 icmp_seq=1 Destination unreachable: Address unreachable::YES
# test other types of ICMPs with venus
venus::ping6 -c 1 fec2:\:10::64 bytes from fec2:\:10: icmp_.eq=1::YES
venus::ping6 -t 2 -c 1 fec2:\:10::From fec0:\:2.*Time exceeded: Hop limit::YES
venus::ping6 -M do -s 1320 -c 1 fec2:\:10::From fec0:\:2.*Packet too big: mtu=1280::YES
sun::tcpdump::IP6 ip6-moon.strongswan.org > ip6-sun.strongswan.org: ESP::2
sun::tcpdump::IP6 ip6-sun.strongswan.org > ip6-moon.strongswan.org: ESP::2
sun::tcpdump::IP6 ip6-sun.strongswan.org > ip6-alice.strongswan.org: ICMP6, destination unreachable, unreachable address fec2:\:25::NO
sun::tcpdump::IP6 ip6-bob.strongswan.org > ip6-moon1.strongswan.org: ICMP6, destination unreachable, unreachable address fec2:\:22::NO
venus::tcpdump::IP6 ip6-venus.strongswan.org > ip6-moon1.strongswan.org: ESP::3
venus::tcpdump::IP6 ip6-moon1.strongswan.org > ip6-venus.strongswan.org: ESP::3
@@ -0,0 +1,55 @@
*filter
# default policy is DROP
-P INPUT DROP
-P OUTPUT DROP
-P FORWARD DROP
# allow esp
-A INPUT -i eth0 -p 50 -j ACCEPT
-A OUTPUT -o eth0 -p 50 -j ACCEPT
# allow IKE
-A INPUT -i eth0 -p udp --sport 500 --dport 500 -j ACCEPT
-A OUTPUT -o eth0 -p udp --dport 500 --sport 500 -j ACCEPT
# allow MobIKE
-A INPUT -i eth0 -p udp --sport 4500 --dport 4500 -j ACCEPT
-A OUTPUT -o eth0 -p udp --dport 4500 --sport 4500 -j ACCEPT
# allow last UDP fragment
-A INPUT -i eth0 -p udp -m frag --fraglast -j ACCEPT
# allow ICMPv6 neighbor-solicitations
-A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT
-A OUTPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT
# allow ICMPv6 neighbor-advertisements
-A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT
-A OUTPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT
# allow crl and certificate fetch from winnetou
-A INPUT -i eth0 -p tcp --sport 80 -s fec0::15 -j ACCEPT
-A OUTPUT -o eth0 -p tcp --dport 80 -d fec0::15 -j ACCEPT
# allow decrypted ICMPv6s from any source IP
-A INPUT -p icmpv6 --icmpv6-type destination-unreachable -m policy --dir in -j ACCEPT
-A FORWARD -p icmpv6 --icmpv6-type destination-unreachable -m policy --dir in -j ACCEPT
# allow venus to connect and fetch crls
-A INPUT -i eth1 -p udp --sport 500 --dport 500 -j ACCEPT
-A OUTPUT -o eth1 -p udp --dport 500 --sport 500 -j ACCEPT
-A INPUT -i eth1 -p 50 -j ACCEPT
-A OUTPUT -o eth1 -p 50 -j ACCEPT
-A FORWARD -i eth0 -o eth1 -p tcp --sport 80 -s fec0::15 -j ACCEPT
-A FORWARD -o eth0 -i eth1 -p tcp --dport 80 -d fec0::15 -j ACCEPT
# forward specific errors to venus
-A FORWARD -d fec1::20/128 -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT
-A FORWARD -d fec1::20/128 -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT
# log dropped packets
-A INPUT -j LOG --log-prefix " IN: "
-A OUTPUT -j LOG --log-prefix " OUT: "
COMMIT
@@ -0,0 +1,10 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 curl revocation vici kernel-netlink socket-default updown
fragment_size = 1400
}
@@ -0,0 +1,55 @@
connections {
net-net {
local_addrs = fec0::1
remote_addrs = fec0::2
local {
auth = pubkey
certs = moonCert.pem
id = moon.strongswan.org
}
remote {
auth = pubkey
id = sun.strongswan.org
}
children {
net-net {
local_ts = fec1::0/16
remote_ts = fec2::0/16
icmp = yes
updown = /usr/local/libexec/ipsec/_updown iptables
hostaccess = yes
esp_proposals = aes128-sha256-x25519
}
}
version = 2
mobike = no
proposals = aes128-sha256-x25519
}
venus : connections.net-net {
local_addrs = fec1::1
remote_addrs = fec1::20
remote {
id = venus.strongswan.org
}
children {
net-net {
# exclude sun but include bob
local_ts = fec2::10/124
remote_ts = dynamic
hostaccess = no
}
}
}
}
authorities {
strongswan {
cacert = strongswanCert.pem
crl_uris = http://ip6-winnetou.strongswan.org/strongswan.crl
}
}
@@ -0,0 +1,49 @@
*filter
# default policy is DROP
-P INPUT DROP
-P OUTPUT DROP
-P FORWARD DROP
# allow esp
-A INPUT -i eth0 -p 50 -j ACCEPT
-A OUTPUT -o eth0 -p 50 -j ACCEPT
# allow IKE
-A INPUT -i eth0 -p udp --sport 500 --dport 500 -j ACCEPT
-A OUTPUT -o eth0 -p udp --dport 500 --sport 500 -j ACCEPT
# allow MobIKE
-A INPUT -i eth0 -p udp --sport 4500 --dport 4500 -j ACCEPT
-A OUTPUT -o eth0 -p udp --dport 4500 --sport 4500 -j ACCEPT
# allow last UDP fragment
-A INPUT -i eth0 -p udp -m frag --fraglast -j ACCEPT
# allow ICMPv6 neighbor-solicitations
-A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT
-A OUTPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT
# allow ICMPv6 neighbor-advertisements
-A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT
-A OUTPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT
# allow crl and certificate fetch from winnetou
-A INPUT -i eth0 -p tcp --sport 80 -s fec0::15 -j ACCEPT
-A OUTPUT -o eth0 -p tcp --dport 80 -d fec0::15 -j ACCEPT
# explicitly allow ICMPv6 responses we don't actually want to see as they should get encrypted
-A OUTPUT -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT
-A FORWARD -p icmpv6 --icmpv6-type destination-unreachable -j ACCEPT
# allow only specific ICMPs from/to venus for this scenario
-A FORWARD -s fec1::20/128 -p icmpv6 --icmpv6-type echo-request -j ACCEPT
-A FORWARD -d fec1::20/128 -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
-A OUTPUT -d fec1::20/128 -p icmpv6 --icmpv6-type time-exceeded -j ACCEPT
-A OUTPUT -d fec1::20/128 -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT
# log dropped packets
-A INPUT -j LOG --log-prefix " IN: "
-A OUTPUT -j LOG --log-prefix " OUT: "
COMMIT
@@ -0,0 +1,10 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 curl revocation vici kernel-netlink socket-default updown
fragment_size = 1400
}
@@ -0,0 +1,38 @@
connections {
net-net {
local_addrs = fec0::2
remote_addrs = fec0::1
local {
auth = pubkey
certs = sunCert.pem
id = sun.strongswan.org
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
net-net {
# exclude sun's and bob's IPs
local_ts = fec2::20/124
remote_ts = fec1::0/16
icmp = yes
updown = /usr/local/libexec/ipsec/_updown iptables
esp_proposals = aes128-sha256-x25519
}
}
version = 2
mobike = no
proposals = aes128-sha256-x25519
}
}
authorities {
strongswan {
cacert = strongswanCert.pem
crl_uris = http://ip6-winnetou.strongswan.org/strongswan.crl
}
}
@@ -0,0 +1,10 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 curl revocation vici kernel-netlink socket-default updown
fragment_size = 1400
}
@@ -0,0 +1,37 @@
connections {
rw {
local_addrs = fec1::20
remote_addrs = fec1::1
local {
auth = pubkey
certs = venusCert.pem
id = venus.strongswan.org
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
rw {
remote_ts = fec2::0/16
icmp = yes
updown = /usr/local/libexec/ipsec/_updown iptables
hostaccess = yes
esp_proposals = aes128-sha256-x25519
}
}
version = 2
mobike = no
proposals = aes128-sha256-x25519
}
}
authorities {
strongswan {
cacert = strongswanCert.pem
crl_uris = http://ip6-winnetou.strongswan.org/strongswan.crl
}
}
@@ -0,0 +1,18 @@
venus::systemctl stop strongswan
moon::systemctl stop strongswan
sun::systemctl stop strongswan
alice::"ip route del fec2:\:/16 via fec1:\:1"
venus::"ip route del fec2:\:/16 via fec1:\:1"
moon::"ip route del fec2:\:/16 via fec0:\:2"
sun::"ip route del fec1:\:/16 via fec0:\:1"
bob::"ip route del fec1:\:/16 via fec2:\:1"
sun::"ip route del fec2:\:20/126 via fec2:\:10"
sun::"ip route del fec2:\:10/128 dev eth1"
venus::"ip route del fec0:\:/16 via fec1:\:1"
winnetou::"ip route del fec1:\:/16 via fec0:\:1"
venus::iptables-restore < /etc/iptables.flush
moon::iptables-restore < /etc/iptables.flush
sun::iptables-restore < /etc/iptables.flush
venus::ip6tables-restore < /etc/ip6tables.flush
moon::ip6tables-restore < /etc/ip6tables.flush
sun::ip6tables-restore < /etc/ip6tables.flush
@@ -0,0 +1,27 @@
venus::iptables-restore < /etc/iptables.drop
moon::iptables-restore < /etc/iptables.drop
sun::iptables-restore < /etc/iptables.drop
venus::ip6tables-restore < /etc/ip6tables.rules
moon::ip6tables-restore < /etc/ip6tables.rules
sun::ip6tables-restore < /etc/ip6tables.rules
venus::systemctl start --no-block strongswan
moon::systemctl start --no-block strongswan
sun::systemctl start --no-block strongswan
alice::"ip route add fec2:\:/16 via fec1:\:1"
venus::"ip route add fec2:\:/16 via fec1:\:1"
moon::"ip route add fec2:\:/16 via fec0:\:2"
sun::"ip route add fec1:\:/16 via fec0:\:1"
bob::"ip route add fec1:\:/16 via fec2:\:1"
venus::"ip route add fec0:\:/16 via fec1:\:1"
winnetou::"ip route add fec1:\:/16 via fec0:\:1"
# add a route for part of the private subnet via bob
sun::"ip route add fec2:\:20/126 via fec2:\:10"
# force a lower MTU for traffic to bob
sun::"ip route add fec2:\:10/128 dev eth1 src fec2:\:1 mtu 1280"
# allow decrypted ICMPv6s from any source IP (rules for sun and moon are in ip6tables.rules)
venus::ip6tables -A INPUT -p icmpv6 -m policy --dir in -j ACCEPT
venus::expect-connection rw
moon::expect-connection net-net
sun::expect-connection net-net
moon::swanctl --initiate --child net-net
venus::swanctl --initiate --child rw
@@ -0,0 +1,29 @@
#!/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 venus moon winnetou sun bob"
# Corresponding block diagram
#
DIAGRAM="a-m-w-s-b-ip6.png"
# Guest instances on which tcpdump is to be started
#
TCPDUMPHOSTS="venus sun"
# Guest instances on which IPsec is started
# Used for IPsec logging purposes
#
IPSECHOSTS="venus moon sun"
# IP protocol used by IPsec is IPv6
#
IPV6=1
# charon controlled by swanctl
#
SWANCTL=1