Skip to content

Commit 355f88e

Browse files
Double NAT (#153)
* Add Double NAT support * Add logic to calculate expected test result for Double RFC 3489 NATs, and fix compatibility between Double NAT and parallel system tests * Update regex validation to prevent empty string from always passing check * Remove duplicated code by moving frequently used regex patterns to util.sh * Remove trailing underscore in log directory name of system tests with NAT hairpinning * Take IP pooling into account in Double NAT test result logic * Add Double NAT documentation + results, and update example system test commands in documentation to reflect new syntax * Fix incorrect abbreviation for Address and Port-Dependent Filtering: ADPF -> APDF * Add missing docker build command required to run system tests in parallel * Update CHANGELOG.md
1 parent 0a0bd5d commit 355f88e

11 files changed

Lines changed: 503 additions & 169 deletions

test_suite/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
In this file, the test suite features that have been made possible thanks to [funding from NLnet](./README.md#funding) are documented.
44

5+
## Double NAT(Oct 21, 2025)
6+
### Added
7+
- Explanation of Double NAT and how it is implemented in the test suite in the [system test documentation](./README.md#double-nat).
8+
- The `-2` flag to [`system_tests.sh`](system_tests.sh) and [`nat_simulation/setup_networks.sh`](nat_simulation/setup_networks.sh), which enables Double NAT.
9+
- New logic in [`system_tests.sh`](system_tests.sh) which decides the expected test result if the peers are behind Double NAT. This logic assumes the NAT behaviour is limited to the 4 NATs described in RFC 3489
10+
- Functionality in [`nat_simulation/setup_router.sh`](nat_simulation/setup_router.sh) to perform different actions depending on which of the two NATs is being set up.
11+
- Report on the system tests results with Double NAT for each combination of two RFC 3489 NATs in the [system test results](./README.md#effect-of-double-nat-on-system-test-results).
12+
13+
### Changed
14+
- The syntax of the NAT and network namespace configurations which are passed as parameters to [`system_test.sh`](system_test.sh), such that a second NAT layer can be specified.
15+
16+
### Fixed
17+
- Small miscellaneous improvements, such as:
18+
- Less duplicated code for regex validation by placing regular expressions which are used multiple times in [util.sh](util.sh).
19+
- Fix incorrect abbreviation ADPF -> APDF across documentation and code comments.
20+
- Add missing command necessary before running parallel system tests in the [system test requirements](./README.md#system-test-specific-requirements).
21+
522
## NAT IP pooling (June 20, 2025)
623
### Added
724
- Explanation of NAT IP pooling and how it is implemented in the test suite in the [system test documentation](./README.md#ip-address-pooling).

test_suite/README.md

Lines changed: 112 additions & 27 deletions
Large diffs are not rendered by default.

test_suite/nat_simulation/setup_nat_filtering_hairpinning.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fi
2222
# Configure NAT filtering type with nftables
2323
nft add table inet filter
2424
nft add chain inet filter input { type filter hook input priority 0\; policy drop\; }
25-
nft add rule inet filter input ct state related,established counter accept # This rule is sufficient to simulate ADPF
25+
nft add rule inet filter input ct state related,established counter accept # This rule is sufficient to simulate APDF
2626

2727
# This pattern captures the following info from a conntrack event:
2828
# 1) the source IP
@@ -36,7 +36,7 @@ pattern=".*src=(\S+).*sport=(\S+).*src=(\S+).*dst=(\S+).*dport=(\S+).*$"
3636
hairpin_rule1="nat prerouting iif $priv_nat_iface ip saddr $priv_subnet ip daddr \4 meta l4proto {tcp, udp} th dport \5 counter dnat to \1:\2" # All traffic from the private network destined to \4:\5 should be hairpinned pack to \1:\2
3737
hairpin_rule2="nat postrouting iif $priv_nat_iface ip saddr \1 ip daddr $priv_subnet meta l4proto {tcp, udp} th sport \2 counter snat to \4:\5" # For all hairpinned packets from \1:\2, the source becomes \4:\5
3838

39-
# Filtering (not necessary for ADPF because of filter rule above)
39+
# Filtering (not necessary for APDF because of filter rule above)
4040
case $nat_filter in
4141
0)
4242
# If a mapping is created with source IP \1, source port \2 and translated source port \5, all traffic destined to \5 should be DNATed to \1:\2
@@ -48,7 +48,7 @@ esac
4848

4949
# Only monitor new source NAT connections that are created by the nftables NAT mapping rules
5050
if [[ $nat_filter -eq 2 ]]; then
51-
conntrack -En -s $priv_subnet -e NEW | sed -rn -e "s#$pattern#nft add rule $hairpin_rule1; nft add rule $hairpin_rule2#e" # No filter rule for ADPF NAT
51+
conntrack -En -s $priv_subnet -e NEW | sed -rn -e "s#$pattern#nft add rule $hairpin_rule1; nft add rule $hairpin_rule2#e" # No filter rule for APDF NAT
5252
else
5353
conntrack -En -s $priv_subnet -e NEW | sed -rn -e "s#$pattern#nft add rule $hairpin_rule1; nft add rule $hairpin_rule2; nft add rule $filter_rule#e"
5454
fi

test_suite/nat_simulation/setup_networks.sh

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
11
#!/usr/bin/env bash
22

3-
usage_str="""
4-
Usage: ${0} <AMOUNT OF IPS>
5-
6-
<AMOUNT OF IPS> specifies the number of IP addresses assigned to each NAT; setting this argument >1 allows NAT IP pooling to be simulated
3+
usage_str="""Usage: ${0} [OPTIONAL ARGUMENTS]
74
85
Simulates a network setup containing two private networks connected via the public network. Each private network contains two peers using eduP2P
9-
To allow traffic to flow between the public and private networks, the scripts setup_nat_mapping.sh should also be executed
10-
To allow traffic to flow between peers in the same private network, the scripts setup_nat_filtering_hairpinning.sh should also be executed
116
12-
This script must be run with root permissions"""
7+
By default, a single NAT with one IP address facilitates the communication between a private and public network. With the -2 flag, Double NAT is simulated by adding another level of private networks on top of the existing ones. With the -n flag, the amount of IPs on the NAT can be increased up to 9; if Double NAT is enabled, the additional NAT always has 1 IP address.
138
14-
if [[ $1 = "-h" || $# -ne 1 ]]; then
15-
echo $usage_str
16-
exit 1
17-
fi
9+
To allow traffic to flow between the public and private networks, the script setup_nat_mapping.sh should also be executed. To allow traffic to flow between peers in the same private network, the script setup_nat_filtering_hairpinning.sh should also be executed
1810
19-
# Number of IPs per router to test NAT IP pooling
20-
n_pooling_ips=$1
11+
This script must be run with root permissions"""
12+
13+
# Use functions and constants from util.sh
14+
. ../util.sh
15+
16+
# Default arguments
17+
n_pooling_ips=1
18+
19+
# Validate optional arguments
20+
while getopts ":2n:h" opt; do
21+
case $opt in
22+
2)
23+
double_nat=true
24+
;;
25+
n)
26+
n_pooling_ips=$OPTARG
27+
28+
# Make sure n_pooling_ips is an integer between 1 and 9
29+
n_pooling_ips_regex="^[1-9]$"
30+
validate_str $n_pooling_ips $n_pooling_ips_regex
31+
;;
32+
h)
33+
echo "$usage_str"
34+
exit 0
35+
;;
36+
*)
37+
exit_with_error "invalid option"
38+
;;
39+
esac
40+
done
2141

2242
# Enable IP forwarding to allow for routing between namespaces
2343
sysctl -w net.ipv4.ip_forward=1 &> /dev/null
@@ -73,11 +93,31 @@ for ((i=1; i<=n_priv_nets; i++)); do
7393
# Add router's public subnet to list created earlier
7494
adm_ips+=($pub_subnet)
7595

76-
# Setup router
77-
ip netns exec $router_name ./setup_router.sh $router_name $priv_name $priv_subnet $router_priv_ip $pub_prefix $n_pooling_ips $switch_ip
96+
if [[ -z $double_nat ]]; then
97+
# Setup router
98+
ip netns exec $router_name ./setup_router.sh $router_name $priv_name public $priv_subnet $router_priv_ip $pub_prefix $n_pooling_ips $switch_ip 0
99+
100+
# Setup private network
101+
ip netns exec $priv_name ./setup_private.sh $router_name $router_pub_ip $priv_subnet
102+
else
103+
# Create namespace for the additional private network
104+
double_name="double${i}"
105+
./create_namespace.sh $double_name
106+
107+
# Variables related to the additional private network and its router
108+
double_prefix="172.16.${i}"
109+
double_subnet="${priv_prefix}.0/24"
110+
double_ip="${double_prefix}.254"
111+
112+
# Setup first router
113+
ip netns exec $router_name ./setup_router.sh $router_name $double_name public $double_subnet $double_ip $pub_prefix $n_pooling_ips $switch_ip 0
114+
115+
# Setup additional router
116+
ip netns exec $double_name ./setup_router.sh $double_name $priv_name $router_name $priv_subnet $router_priv_ip $double_prefix $n_pooling_ips $router_pub_ip 1
78117

79-
# Setup private network
80-
ip netns exec $priv_name ./setup_private.sh $router_name $router_pub_ip $priv_subnet
118+
# Setup private network
119+
ip netns exec $priv_name ./setup_private.sh $double_name $double_ip $priv_subnet
120+
fi
81121

82122
# Setup peers in each private network
83123
for ((j=1; j<=n_peers; j++)); do

test_suite/nat_simulation/setup_router.sh

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22

33
router_name=$1
44
priv_name=$2
5-
priv_subnet=$3
6-
priv_ip=$4
7-
pub_subnet_prefix=$5
8-
n_ips=$6
9-
switch_ip=$7
10-
11-
if [[ $# -ne 7 ]]; then
5+
public_name=$3
6+
priv_subnet=$4
7+
priv_ip=$5
8+
pub_subnet_prefix=$6
9+
n_ips=$7
10+
switch_ip=$8
11+
router_index=$9
12+
13+
if [[ $# -ne 9 ]]; then
1214
echo """
13-
Usage: ${0} <ROUTER NAME> <PRIVATE NETWORK NAME> <PRIVATE SUBNET> <ROUTER PRIVATE IP> <PUBLIC /24 NETWORK PREFIX> <NUMBER OF IPS> <SWITCH IP>
15+
Usage: ${0} <ROUTER NAME> <PRIVATE NETWORK NAME> <PUBLIC NETWORK NAME> <PRIVATE SUBNET> <ROUTER PRIVATE IP> <ROUTER PUBLIC IP> <NUMBER OF IPS> <SWITCH IP> <ROUTER INDEX>
1416
1517
This script must be run with root permissions"""
1618
exit 1
@@ -25,25 +27,32 @@ ip netns exec $priv_name ip addr add "${priv_ip}/24" dev $router_name
2527
ip link set $router_priv up
2628
ip netns exec $priv_name ip link set $router_name up
2729

28-
# Create veth pair to place the router's public interface in the public and router namespaces
29-
router_pub="${router_name}_pub"
30-
ip link add $router_pub type veth peer $router_name netns public
31-
32-
for host in $(seq $((254 - $n_ips + 1)) 254); do
33-
ip="$pub_subnet_prefix.$host/24"
34-
ip addr add $ip dev $router_pub
35-
done
30+
# Add route for traffic to router's private network
31+
ip route add $priv_ip dev $router_priv
32+
ip route add $priv_subnet via $priv_ip dev $router_priv
3633

37-
ip link set $router_pub up
38-
ip netns exec public ip link set $router_name up
34+
# $router_index is equal to 0 for first router, 1 for additional router
35+
if [[ $router_index -eq 0 ]]; then
36+
# Create veth pair to place the router's public interface in the public and router namespaces
37+
router_pub="${router_name}_pub"
38+
ip link add $router_pub type veth peer $router_name netns public
39+
40+
for host in $(seq $((254 - $n_ips + 1)) 254); do
41+
ip="$pub_subnet_prefix.$host/24"
42+
ip addr add $ip dev $router_pub
43+
done
44+
45+
ip link set $router_pub up
46+
ip netns exec public ip link set $router_name up
47+
48+
# Create route to first router in the public network
49+
ip netns exec $public_name ip route add $pub_subnet dev $router_name
50+
else
51+
# Veth pair already created by first router
52+
router_pub=$public_name
53+
fi
3954

40-
# Add switch as default gateway
55+
# Show switch is routable via router as default gateway
4156
ip route add $switch_ip dev $router_pub
4257
ip route add default via $switch_ip dev $router_pub
4358

44-
# Add route for traffic to router's private network
45-
ip route add $priv_ip dev $router_priv
46-
ip route add $priv_subnet via $priv_ip dev $router_priv
47-
48-
# Create route to router in the public network
49-
ip netns exec public ip route add $pub_subnet dev $router_name

test_suite/performance_test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ while getopts ":b:h" opt; do
2828
case $opt in
2929
b)
3030
baseline=$OPTARG
31-
validate_str $baseline "^direct|wireguard|both$"
31+
validate_str "$baseline" "^direct$|^wireguard$|^both$"
3232

3333
case $baseline in
3434
"direct")

test_suite/set_delay.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ Usage: ${0} <DELAY>
77

88
delay=$1
99

10-
# Make sure delay is an integer
11-
int_regex="^[0-9]+$"
10+
. ./util.sh
1211

12+
# Make sure delay is an integer
1313
if [[ $# -ne 1 || ! ( $delay =~ $int_regex) ]]; then
1414
echo $usage_str
1515
exit 1

test_suite/set_packet_loss.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ Usage: ${0} <PACKET LOSS PERCENTAGE>
77

88
packet_loss=$1
99

10-
# Make sure packet_loss is a real number, and get the amount of decimal digits
11-
real_regex="^[0-9]+[.]?([0-9]+)?$"
10+
. ./util.sh
1211

13-
if [[ $# -ne 1 || ! ( $packet_loss =~ $real_regex) ]]; then
12+
# Make sure packet_loss is a real number, and get the amount of decimal digits
13+
if [[ $# -ne 1 || ! ( $packet_loss =~ ^$real_regex$ ) ]]; then
1414
echo $usage_str
1515
exit 1
1616
fi

0 commit comments

Comments
 (0)