diff --git a/test_suite/CHANGELOG.md b/test_suite/CHANGELOG.md index 0c87228..04d3e3e 100644 --- a/test_suite/CHANGELOG.md +++ b/test_suite/CHANGELOG.md @@ -2,6 +2,23 @@ In this file, the test suite features that have been made possible thanks to [funding from NLnet](./README.md#funding) are documented. +## Double NAT(Oct 21, 2025) +### Added +- Explanation of Double NAT and how it is implemented in the test suite in the [system test documentation](./README.md#double-nat). +- 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. +- 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 +- 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. +- 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). + +### Changed +- 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. + +### Fixed +- Small miscellaneous improvements, such as: + - Less duplicated code for regex validation by placing regular expressions which are used multiple times in [util.sh](util.sh). + - Fix incorrect abbreviation ADPF -> APDF across documentation and code comments. + - Add missing command necessary before running parallel system tests in the [system test requirements](./README.md#system-test-specific-requirements). + ## NAT IP pooling (June 20, 2025) ### Added - Explanation of NAT IP pooling and how it is implemented in the test suite in the [system test documentation](./README.md#ip-address-pooling). diff --git a/test_suite/README.md b/test_suite/README.md index 7963c2f..803fcfb 100644 --- a/test_suite/README.md +++ b/test_suite/README.md @@ -66,7 +66,11 @@ sudo and xargs packages): Optionally, the system tests can be run in parallel. In this mode the system tests are distributed over Docker containers, so it requires -installing [Docker Engine](https://docs.docker.com/engine/install/). +installing [Docker Engine](https://docs.docker.com/engine/install/). To +build the Docker image the containers are based on, run the following +command in the `test_suite` directory: + + docker build -t system_tests . ### Performance test-specific requirements @@ -245,8 +249,26 @@ conditions: public “peers” to be simulated by making a router act as a peer, since the routers have a public IP address. -The next section explains the types of NAT in this network setup, and -describes how they are implemented. +### Double NAT + +Until now, we have assumed there is a single NAT between the peers and +the public network. However, in the real world there may be multiple. +For example, Double NAT describes a scenario where a host is separated +from the public network by two NATs. To use Double NAT in the test +suite, the `-2` flag must be added when calling the +[system_tests.sh](system_tests.sh) script. + +Double NAT is implemented by adding the network namespaces `double1` and +`double2`, respectively between `private1`/`router1` and +`private2`/`router2`. The existing veth pair containing the `router1` +device is used to connect the `double1` and `router1` namespaces, while +a new veth pair containing a device called `double1` is used to connect +the `private1` and `router1` namespaces. This new device has IP address +`172.16.1.254`. The setup for the second private network is similar. + +Each simulated NAT device in the test suite can be configured +separately. The next section describes the types of NAT supported in the +test suite, and how they are implemented. ### Applying NAT @@ -351,7 +373,7 @@ three different types of behaviour with the same naming convention: or port. 2. **Address-Dependent Filtering (ADF):** packets destined to `X':x1'` are filtered only if their source IP address does not equal `Y` -3. **Address and Port-Dependent Filtering (ADPF):** packets destined to +3. **Address and Port-Dependent Filtering (APDF):** packets destined to `X':x1'` are filtered only if their source endpoint does not equal `Y:y1`. @@ -365,7 +387,7 @@ these packets may be filtered, which is indicated by a dashed arrow: destined to a port on the NAT for which a mapping exists. 2. **ADF:** The packet from `Z:z` is filtered, because incoming packets to `X':x1'` are only accepted if they have source IP address `Y`. -3. **ADPF:** The packets from `Y:y2` and `Z:z` are filtered, because +3. **APDF:** The packets from `Y:y2` and `Z:z` are filtered, because incoming packets to `X':x1'` are only accepted if they have source IP address `Y` and source port `y1`. @@ -380,7 +402,7 @@ do not belong to an existing session are filtered. Each time an internal endpoint establishes a connection to a new external endpoint, a new session is also created. Therefore, the above -nftables rule is sufficient to simulate ADPF, since only the original +nftables rule is sufficient to simulate APDF, since only the original session’s endpoint can send packets to the corresponding mapped IP address. @@ -462,9 +484,12 @@ RFC 4787 specifies two types of IP address pooling behaviours: sessions belonging to the same internal IP. We implement the second behaviour in the test suite, as it has more -potential to cause issues for P2P protocols. The implementation relies -on two nftables features: packet marking and maps, which act like -dictionaries. +potential to cause issues for P2P protocols. However, when Double NAT is +enabled, IP address pooling is only supported for the NAT facing the +public network. + +The implementation relies on two nftables features: packet marking and +maps, which act like dictionaries. Each packet that flows through the `nat` table is marked in the `prerouting` chain. The range of possible mark values is equal to the @@ -566,7 +591,7 @@ The following command runs tests from a file named Suppose `performance_test.txt` contains the following line: - run_system_test -k bitrate -v 100,200 -d 5 -b wireguard -r 3 TS_PASS_DIRECT router1-router2 : : + run_system_test -k bitrate -v 100,200 -d 5 -b wireguard -r 3 TS_PASS_DIRECT router1/router2 / / Then, running the system tests with the `-f performance_test.txt` option will execute a performance test with the following parameters: @@ -633,6 +658,9 @@ disables IP address pooling. The old results are followed by a section in which the differences resulting from the addition of IP address pooling are described. +Finally, the system tests results section is concluded by analysing how +Double NAT affects the results. + ## System Test Results Without IP Address Pooling Using the test suite’s system tests, we can get an overview of whether @@ -658,9 +686,9 @@ to: Mapping (EIM) and Address-Dependent Filtering (ADF). - **Port Restricted Cone NAT:** equivalent to a NAT with Endpoint-Independent Mapping (EIM) and Address and Port-Dependent - Filtering (ADPF). + Filtering (APDF). - **Symmetric NAT:** equivalent to a NAT with Address and Port-Dependent - Mapping (ADPM) and Address and Port-Dependent Filtering (ADPF). + Mapping (ADPM) and Address and Port-Dependent Filtering (APDF). The expected results are shown in the table below. A cell is marked with an ‘X’ if UDP hole punching is successful in the scenario where one peer @@ -1077,19 +1105,19 @@ conditions with packet loss. The results of extending the UDP hole punching experiment to all combinations of RFC 4787 mapping (EIM, ADM, ADPM) and filtering (EIF, -ADF, ADPF) behaviours are shown in the table below: +ADF, APDF) behaviours are shown in the table below: -| NAT Type | EIM-EIF | EIM-ADF | EIM-ADPF | ADM-EIF | ADM-ADF | ADM-ADPF | ADPM-EIF | ADPM-ADF | ADPM-ADPF | +| NAT Type | EIM-EIF | EIM-ADF | EIM-APDF | ADM-EIF | ADM-ADF | ADM-APDF | ADPM-EIF | ADPM-ADF | ADPM-APDF | |:---|:---|:---|:---|:---|:---|:---|:---|:---|:---| | **EIM-EIF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **EIM-ADF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| **EIM-ADPF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | +| **EIM-APDF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | | **ADM-EIF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **ADM-ADF** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | -| **ADM-ADPF** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | +| **ADM-APDF** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | | **ADPM-EIF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **ADPM-ADF** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | -| **ADPM-ADPF** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | +| **ADPM-APDF** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | Based on these results, we can conclude that there are three (overlapping) types of NAT scenarios where the UDP hole punching process @@ -1136,7 +1164,7 @@ To substantiate this claim, we examine the general UDP hole punching process for the two least restrictive NAT combinations in the above table where a direct connection could not be established: -1. **One peer behind an EIM-ADPF NAT, and the other behind an ADM-ADF +1. **One peer behind an EIM-APDF NAT, and the other behind an ADM-ADF NAT.** The UDP hole punching between the peers in this NAT scenario is @@ -1147,7 +1175,7 @@ table where a direct connection could not be established: autonumber actor p1 as Peer 1 (X:x) - participant nat1 as EIM-ADPF NAT + participant nat1 as EIM-APDF NAT participant nat2 as ADM-ADF NAT actor p2 as Peer 2 (Y:y) @@ -1175,7 +1203,7 @@ table where a direct connection could not be established: The problem in this scenario is that Peer 1 is sending pings to `Y':y1'`, while Peer 2 is sending them from `Y':y2'`. Peer 1’s NAT - will always drop the packets from Peer 2 because it has ADPF + will always drop the packets from Peer 2 because it has APDF behaviour and `y1'` is not equal to `y2'`. Peer 2’s NAT will accept packets from source IP `X'` destined to `Y':y2'` after sending its first ping from `Y':y2'` to `X':x1'`, but Peer 1 is sending packets @@ -1264,17 +1292,17 @@ destination IP of an existing session. ### Experiment with RFC 4787 NAT mapping & filtering behaviours -| NAT Type | EIM-EIF | EIM-ADF | EIM-ADPF | ADM-EIF | ADM-ADF | ADM-ADPF | ADPM-EIF | ADPM-ADF | ADPM-ADPF | +| NAT Type | EIM-EIF | EIM-ADF | EIM-APDF | ADM-EIF | ADM-ADF | ADM-APDF | ADPM-EIF | ADPM-ADF | ADPM-APDF | |:---|:---|:---|:---|:---|:---|:---|:---|:---|:---| | **EIM-EIF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **EIM-ADF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :question: | :question: | :white_check_mark: | :question: | :question: | -| **EIM-ADPF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | +| **EIM-APDF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | | **ADM-EIF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **ADM-ADF** | :white_check_mark: | :question: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | -| **ADM-ADPF** | :white_check_mark: | :question: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | +| **ADM-APDF** | :white_check_mark: | :question: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | | **ADPM-EIF** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **ADPM-ADF** | :white_check_mark: | :question: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | -| **ADPM-ADPF** | :white_check_mark: | :question: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | +| **ADPM-APDF** | :white_check_mark: | :question: | :x: | :white_check_mark: | :x: | :x: | :white_check_mark: | :x: | :x: | Just like with the RFC 3489 experiment, the change in results caused by NAT IP address pooling is fairly limited. The outcome of UDP hole @@ -1285,6 +1313,63 @@ process described for the RFC 3489 Restricted Cone and Symmetric NAT combination. This is because the Restricted Cone NAT corresponds to an EIM-ADF NAT. +## Effect of Double NAT on System Test Results + +With Double NAT, we do not discuss the RFC 4787 NATs, as the amount of +possible combination would be too high. Even for the RFC 3489 NATs, +there are quite a lot of possible combinations. Therefore, we split this +section in two parts: + +1. We first discuss the Double NAT combinations where the two NATs + separating a host from the public network are of the *same* type. +2. Then, we discuss the combinations where the NATs are of a + *different* type. + +### Same-type Double NAT + +| Double NAT Type | Full Cone | Restricted Cone | Port Restricted Cone | Symmetric | +|----|:---|:---|:---|:---| +| **Full Cone** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| **Restricted Cone** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :question: | +| **Port Restricted Cone** | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | +| **Symmetric** | :white_check_mark: | :question: | :x: | :x: | + +The results in this table are equal to those of the [previous +table](#effect-of-ip-address-pooling-on-system-test-results). Therefore, +if both NATs have the same type, Double NAT does not affect the peers’ +ability to establish a direct connection. + +### Different-type Double NAT + +| Double NAT Type | FC/RC | FC/PRC | FC/Sym | RC/FC | RC/PRC | RC/Sym | PRC/FC | PRC/RC | PRC/Sym | Sym/FC | Sym/RC | Sym/PRC | +|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---|:---| +| **FC/RC** | :white_check_mark: | :white_check_mark: | :question: | :white_check_mark: | :white_check_mark: | :question: | :white_check_mark: | :white_check_mark: | :question: | :question: | :question: | :question: | +| **FC/PRC** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: | :x: | +| **FC/Sym** | :question: | :x: | :x: | :question: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | +| **RC/FC** | :white_check_mark: | :white_check_mark: | :question: | :white_check_mark: | :white_check_mark: | :question: | :white_check_mark: | :white_check_mark: | :question: | :question: | :question: | :question: | +| **RC/PRC** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: | :x: | +| **RC/Sym** | :question: | :x: | :x: | :question: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | +| **PRC/FC** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: | :x: | +| **PRC/RC** | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: | :x: | +| **PRC/Sym** | :question: | :x: | :x: | :question: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | +| **Sym/FC** | :question: | :x: | :x: | :question: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | +| **Sym/RC** | :question: | :x: | :x: | :question: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | +| **Sym/PRC** | :question: | :x: | :x: | :question: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | + +In this table, we see that the row for Double NAT X/Y contains the same +results as the row for Double NAT Y/X. For example, the row for FC/RC is +equal to the row for RC/FC. We can make the same observation about the +columns in the table. This shows that the order of the NAT types in +Double NAT does not affect the peers’ ability to establish a direct +connection + +We also observe that all rows and columns where one of the two NATs is a +Symmetric NAT contain the same results. The reason for this is that the +Symmetric NAT is the most restrictive of the RFC 3489 NATs, so the other +NAT’s behaviour does not impact the test outcome. In general, it holds +that the ability to establish a direct connection is determined by the +combination of each peer’s most restrictive NAT. + ## Performance Test Results The results in this section were measured on my own laptop with the @@ -1304,7 +1389,7 @@ reproducibility. Command used: - run_system_test -k bitrate -v 800,1600,2400,3200,4000 -d 3 -b both -r 5 TS_PASS_DIRECT router1-router2 : wg0:wg0 + run_system_test -k bitrate -v 800,1600,2400,3200,4000 -d 3 -b both -r 5 TS_PASS_DIRECT router1/router2 / wg0/wg0 With this command, we compare the performance of eduP2P, WireGuard and a direct connection between two peers in the test suite’s network setup. @@ -1357,7 +1442,7 @@ differ on other machines. Command used: - run_system_test -k bitrate -v 800,1600,2400,3200,4000 -d 3 -b both -r 5 TS_PASS_DIRECT router1-router2 : : + run_system_test -k bitrate -v 800,1600,2400,3200,4000 -d 3 -b both -r 5 TS_PASS_DIRECT router1/router2 / / This command repeats the performance test of the previous section, with the only difference being that now both peers use userspace WireGuard @@ -1378,7 +1463,7 @@ further, however: Command used: - run_system_test -k delay -v 0,1,2,3 -d 3 -b both -r 3 TS_PASS_DIRECT router1-router2 : : + run_system_test -k delay -v 0,1,2,3 -d 3 -b both -r 3 TS_PASS_DIRECT router1/router2 / / ![](./images/performance_tests/x_ow_delay_y_http_latency.png) diff --git a/test_suite/nat_simulation/setup_nat_filtering_hairpinning.sh b/test_suite/nat_simulation/setup_nat_filtering_hairpinning.sh index a8079da..eea939e 100755 --- a/test_suite/nat_simulation/setup_nat_filtering_hairpinning.sh +++ b/test_suite/nat_simulation/setup_nat_filtering_hairpinning.sh @@ -22,7 +22,7 @@ fi # Configure NAT filtering type with nftables nft add table inet filter nft add chain inet filter input { type filter hook input priority 0\; policy drop\; } -nft add rule inet filter input ct state related,established counter accept # This rule is sufficient to simulate ADPF +nft add rule inet filter input ct state related,established counter accept # This rule is sufficient to simulate APDF # This pattern captures the following info from a conntrack event: # 1) the source IP @@ -36,7 +36,7 @@ pattern=".*src=(\S+).*sport=(\S+).*src=(\S+).*dst=(\S+).*dport=(\S+).*$" 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 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 -# Filtering (not necessary for ADPF because of filter rule above) +# Filtering (not necessary for APDF because of filter rule above) case $nat_filter in 0) # 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 # Only monitor new source NAT connections that are created by the nftables NAT mapping rules if [[ $nat_filter -eq 2 ]]; then - 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 + 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 else 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" fi \ No newline at end of file diff --git a/test_suite/nat_simulation/setup_networks.sh b/test_suite/nat_simulation/setup_networks.sh index 0b23240..fff95f0 100755 --- a/test_suite/nat_simulation/setup_networks.sh +++ b/test_suite/nat_simulation/setup_networks.sh @@ -1,23 +1,43 @@ #!/usr/bin/env bash -usage_str=""" -Usage: ${0} - - specifies the number of IP addresses assigned to each NAT; setting this argument >1 allows NAT IP pooling to be simulated +usage_str="""Usage: ${0} [OPTIONAL ARGUMENTS] Simulates a network setup containing two private networks connected via the public network. Each private network contains two peers using eduP2P -To allow traffic to flow between the public and private networks, the scripts setup_nat_mapping.sh should also be executed -To allow traffic to flow between peers in the same private network, the scripts setup_nat_filtering_hairpinning.sh should also be executed -This script must be run with root permissions""" +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. -if [[ $1 = "-h" || $# -ne 1 ]]; then - echo $usage_str - exit 1 -fi +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 -# Number of IPs per router to test NAT IP pooling -n_pooling_ips=$1 +This script must be run with root permissions""" + +# Use functions and constants from util.sh +. ../util.sh + +# Default arguments +n_pooling_ips=1 + +# Validate optional arguments +while getopts ":2n:h" opt; do + case $opt in + 2) + double_nat=true + ;; + n) + n_pooling_ips=$OPTARG + + # Make sure n_pooling_ips is an integer between 1 and 9 + n_pooling_ips_regex="^[1-9]$" + validate_str $n_pooling_ips $n_pooling_ips_regex + ;; + h) + echo "$usage_str" + exit 0 + ;; + *) + exit_with_error "invalid option" + ;; + esac +done # Enable IP forwarding to allow for routing between namespaces sysctl -w net.ipv4.ip_forward=1 &> /dev/null @@ -73,11 +93,31 @@ for ((i=1; i<=n_priv_nets; i++)); do # Add router's public subnet to list created earlier adm_ips+=($pub_subnet) - # Setup router - ip netns exec $router_name ./setup_router.sh $router_name $priv_name $priv_subnet $router_priv_ip $pub_prefix $n_pooling_ips $switch_ip + if [[ -z $double_nat ]]; then + # Setup router + 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 + + # Setup private network + ip netns exec $priv_name ./setup_private.sh $router_name $router_pub_ip $priv_subnet + else + # Create namespace for the additional private network + double_name="double${i}" + ./create_namespace.sh $double_name + + # Variables related to the additional private network and its router + double_prefix="172.16.${i}" + double_subnet="${priv_prefix}.0/24" + double_ip="${double_prefix}.254" + + # Setup first router + 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 + + # Setup additional router + 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 - # Setup private network - ip netns exec $priv_name ./setup_private.sh $router_name $router_pub_ip $priv_subnet + # Setup private network + ip netns exec $priv_name ./setup_private.sh $double_name $double_ip $priv_subnet + fi # Setup peers in each private network for ((j=1; j<=n_peers; j++)); do diff --git a/test_suite/nat_simulation/setup_router.sh b/test_suite/nat_simulation/setup_router.sh index 19fa922..675034b 100755 --- a/test_suite/nat_simulation/setup_router.sh +++ b/test_suite/nat_simulation/setup_router.sh @@ -2,15 +2,17 @@ router_name=$1 priv_name=$2 -priv_subnet=$3 -priv_ip=$4 -pub_subnet_prefix=$5 -n_ips=$6 -switch_ip=$7 - -if [[ $# -ne 7 ]]; then +public_name=$3 +priv_subnet=$4 +priv_ip=$5 +pub_subnet_prefix=$6 +n_ips=$7 +switch_ip=$8 +router_index=$9 + +if [[ $# -ne 9 ]]; then echo """ -Usage: ${0} +Usage: ${0} This script must be run with root permissions""" exit 1 @@ -25,25 +27,32 @@ ip netns exec $priv_name ip addr add "${priv_ip}/24" dev $router_name ip link set $router_priv up ip netns exec $priv_name ip link set $router_name up -# Create veth pair to place the router's public interface in the public and router namespaces -router_pub="${router_name}_pub" -ip link add $router_pub type veth peer $router_name netns public - -for host in $(seq $((254 - $n_ips + 1)) 254); do - ip="$pub_subnet_prefix.$host/24" - ip addr add $ip dev $router_pub -done +# Add route for traffic to router's private network +ip route add $priv_ip dev $router_priv +ip route add $priv_subnet via $priv_ip dev $router_priv -ip link set $router_pub up -ip netns exec public ip link set $router_name up +# $router_index is equal to 0 for first router, 1 for additional router +if [[ $router_index -eq 0 ]]; then + # Create veth pair to place the router's public interface in the public and router namespaces + router_pub="${router_name}_pub" + ip link add $router_pub type veth peer $router_name netns public + + for host in $(seq $((254 - $n_ips + 1)) 254); do + ip="$pub_subnet_prefix.$host/24" + ip addr add $ip dev $router_pub + done + + ip link set $router_pub up + ip netns exec public ip link set $router_name up + + # Create route to first router in the public network + ip netns exec $public_name ip route add $pub_subnet dev $router_name +else + # Veth pair already created by first router + router_pub=$public_name +fi -# Add switch as default gateway +# Show switch is routable via router as default gateway ip route add $switch_ip dev $router_pub ip route add default via $switch_ip dev $router_pub -# Add route for traffic to router's private network -ip route add $priv_ip dev $router_priv -ip route add $priv_subnet via $priv_ip dev $router_priv - -# Create route to router in the public network -ip netns exec public ip route add $pub_subnet dev $router_name \ No newline at end of file diff --git a/test_suite/performance_test.sh b/test_suite/performance_test.sh index 12096f2..75180f9 100755 --- a/test_suite/performance_test.sh +++ b/test_suite/performance_test.sh @@ -28,7 +28,7 @@ while getopts ":b:h" opt; do case $opt in b) baseline=$OPTARG - validate_str $baseline "^direct|wireguard|both$" + validate_str "$baseline" "^(direct|wireguard|both)$" case $baseline in "direct") diff --git a/test_suite/set_delay.sh b/test_suite/set_delay.sh index c94c4c9..4b29b38 100755 --- a/test_suite/set_delay.sh +++ b/test_suite/set_delay.sh @@ -7,9 +7,9 @@ Usage: ${0} delay=$1 -# Make sure delay is an integer -int_regex="^[0-9]+$" +. ./util.sh +# Make sure delay is an integer if [[ $# -ne 1 || ! ( $delay =~ $int_regex) ]]; then echo $usage_str exit 1 diff --git a/test_suite/set_packet_loss.sh b/test_suite/set_packet_loss.sh index 11d91d8..118e20a 100755 --- a/test_suite/set_packet_loss.sh +++ b/test_suite/set_packet_loss.sh @@ -7,10 +7,10 @@ Usage: ${0} packet_loss=$1 -# Make sure packet_loss is a real number, and get the amount of decimal digits -real_regex="^[0-9]+[.]?([0-9]+)?$" +. ./util.sh -if [[ $# -ne 1 || ! ( $packet_loss =~ $real_regex) ]]; then +# Make sure packet_loss is a real number, and get the amount of decimal digits +if [[ $# -ne 1 || ! ( $packet_loss =~ ^$real_regex$ ) ]]; then echo $usage_str exit 1 fi diff --git a/test_suite/system_test.sh b/test_suite/system_test.sh index 48c8eff..652c885 100755 --- a/test_suite/system_test.sh +++ b/test_suite/system_test.sh @@ -4,14 +4,11 @@ SYSTEM_TEST_TIMEOUT=60 usage_str=""" -Usage: ${0} [OPTIONAL ARGUMENTS] [NAT CONFIGURATION 1]:[NAT CONFIGURATION 2] [WIREGUARD INTERFACE 1]:[WIREGUARD INTERFACE 2] +Usage: ${0} [OPTIONAL ARGUMENTS] [WIREGUARD INTERFACE 1]/[WIREGUARD INTERFACE 2] - is the expected result of the system test: - 1. TS_PASS_DIRECT: the peers have established a direct connection - 2. TS_PASS_RELAY: the peers have established a connection via the eduP2P relay server - 3. TS_FAIL: the peers failed to establish a connection - -[OPTIONAL ARGUMENTS] can be provided for a performance test: +The -2 option can be used to perform a test where both peers are behind two NATs instead of one. + +The remaining optional arguments can be provided for a performance test: -k -v -d @@ -19,19 +16,38 @@ Usage: ${0} [OPTIONAL ARGUMENTS] [NAT CO -b With this flag, eduP2P's performance is compared to the performance of a direct connection and/or a connection using only WireGuard This flag should only be used when both peers reside in the 'public' network + + is the expected result of the system test: + 1. TS_PASS_DIRECT: the peers have established a direct connection + 2. TS_PASS_RELAY: the peers have established a connection via the eduP2P relay server + 3. TS_FAIL: the peers failed to establish a connection + + specifies the peer and NAT namespaces to be used in this system test. It should be a string with one of the following formats: + 1. /, for peers in the public network + 2. :/, for one peer in a private network and the other in the public network + 3. :: for peers in the same private network + 4. :/: for peers in different private networks + +By default, and should be the namespaces of the routers used by the corresponding peers. If Double NAT is enabled with the -2 option, they should both have the format : + + specifies the type of NAT applied by each router. It follows a format similar to : + 1. /, for peers in the public network + 2. /, for one peer in a private network and the other in the public network + 3. for peers in the same private network + 4. / for peers in different private networks + +By default, and , should follow the format -, where both may be one of the following numbers: + 0 - Endpoint-Independent + 1 - Address-Dependent + 2 - Address and Port-Dependent + +If Double NAT is enabled with the -2 option, they should both have the format -:- - specifies the peer and router namespaces to be used in this system test. It should be a string with one of the following formats: - 1. -, for peers in the public network - 2. -:, for one peer in a private network and the other in the public network - 3. -- for peers in the same private network - 4. -:- for peers in different private networks - -[NAT CONFIGURATION 1] and [NAT CONFIGURATION 2] specify the type of NAT applied to packets sent by peer 1 and 2 respectively. They should equal an empty string if the corresponding peer is in the public network, and otherwise follow this format: - -, where both may be one of the following numbers: - 0 - Endpoint-Independent - 1 - Address-Dependent - 2 - Address and Port-Dependent -Examples of valid NAT configurations: 0-1:1-2 (both peers in private networks), 0-1: (peer 2 in public network), : (both peers in public network) +Examples of valid NAT configurations: + '/' for both peers in public network) + '0-1/' for peer 2 in public network + '0-1/1-2' for both peers in private networks + '0-0:1-2/2-1:1-0' for both peers in private networks behind two NATs specifies the number of IP addresses assigned to each NAT; setting this argument >1 allows NAT IP pooling to be simulated @@ -39,7 +55,7 @@ If [WIREGUARD INTERFACE 1] or [WIREGUARD INTERFACE 2] is not provided, the corre is a string of IP addresses separated by a space that may be the destination IP of packets crossing this NAT device, and is necessary to simulate an Address-Dependent Mapping - should be one of {trace|debug|info|warn|error}, and MUST be trace/debug if one of the peers uses userspace WireGuard (the other peer's IP address is not logged otherwise)""" + should be one of {trace/debug/info/warn/error}, and MUST be trace/debug if one of the peers uses userspace WireGuard (the other peer's IP address is not logged otherwise)""" # Use functions and constants from util.sh . ./util.sh @@ -48,11 +64,11 @@ performance_test_duration=5 # Default value in case -d is not used performance_test_reps=1 # Default value in case -r is not used # Validate optional arguments -while getopts ":k:v:d:r:b:h" opt; do +while getopts ":k:v:d:r:b:2h" opt; do case $opt in k) performance_test_var=$OPTARG - validate_str $performance_test_var "^bitrate|delay|packet_loss$" + validate_str "$performance_test_var" "^bitrate$|^delay$|^packet_loss$" ;; v) performance_test_values=$OPTARG @@ -61,25 +77,26 @@ while getopts ":k:v:d:r:b:h" opt; do exit_with_error "-k should be specified before -v" fi - real_regex="[0-9]+(.[0-9]+)?" validate_str "$performance_test_values" "^$real_regex(,$real_regex)*$" ;; d) performance_test_duration=$OPTARG - validate_str $performance_test_duration "^[0-9]+$" + validate_str "$performance_test_duration" "^[0-9]+$" ;; r) performance_test_reps=$OPTARG - validate_str $performance_test_duration "^[0-9]+$" + validate_str "$performance_test_duration" "^[0-9]+$" if [[ $performance_test_reps -eq 0 ]]; then exit_with_error "value of -r should be at least 1" fi ;; - + 2) + double_nat=true + ;; b) performance_test_baseline=$OPTARG - validate_str $performance_test_baseline "^direct|wireguard|both$" + validate_str "$performance_test_baseline" "^direct$|^wireguard$|^both$" baseline="-b $performance_test_baseline" ;; @@ -116,12 +133,19 @@ log_dir=${12} repo_dir=${13} # Validate namespace configuration string -ns_regex="([^-:]+)" # One or more occurence of every character except '-' and ':' (these are used to separate the namespaces) -ns_config1_regex="^${ns_regex}-${ns_regex}$" -ns_config2_regex="^${ns_regex}-${ns_regex}:${ns_regex}$" -ns_config3_regex="^${ns_regex}-${ns_regex}-${ns_regex}$" -ns_config4_regex="^${ns_regex}-${ns_regex}:${ns_regex}-${ns_regex}$" -validate_str $ns_config_str "$ns_config1_regex|$ns_config2_regex|$ns_config3_regex|$ns_config4_regex" +peer_ns_regex="([^:/]+)" # One or more occurence of every character except ':' and '/' (these are used to separate the namespaces) + +if [[ -z $double_nat ]]; then + router_ns_regex=$peer_ns_regex +else + router_ns_regex="${peer_ns_regex}:${peer_ns_regex}" +fi + +ns_config1_regex="^${peer_ns_regex}/${peer_ns_regex}$" +ns_config2_regex="^${peer_ns_regex}:${router_ns_regex}/${peer_ns_regex}$" +ns_config3_regex="^${peer_ns_regex}:${router_ns_regex}:${peer_ns_regex}$" +ns_config4_regex="^${peer_ns_regex}:${router_ns_regex}/${router_ns_regex}:${peer_ns_regex}$" +validate_str "$ns_config_str" "$ns_config1_regex|$ns_config2_regex|$ns_config3_regex|$ns_config4_regex" # Remove empty string elements in BASH_REMATCH, so that it only contains the matches of exactly one configuration BASH_REMATCH=(${BASH_REMATCH[@]}) @@ -141,18 +165,27 @@ fi # NAT configuration parsing depends on the amount of routers n_routers=${#router_ns_list[@]} -nat_config_regex="([0-2])-([0-2])" -nat_map=() -nat_filter=() + +if [[ -z $double_nat ]]; then + n_private=$n_routers + nat_config_regex="([0-2])-([0-2])" +else + n_private=$(($n_routers / 2)) + nat_config_regex="([0-2])-([0-2]):([0-2])-([0-2])" +fi # Ensure the NAT configuration is provided for all routers -case $n_routers in - 0) validate_str $nat_config_str "^:$";; - 1) validate_str $nat_config_str "^$nat_config_regex:$";; - 2) validate_str $nat_config_str "^$nat_config_regex:$nat_config_regex$";; +case $n_private in + 0) validate_str "$nat_config_str" "^/$";; + 1) validate_str "$nat_config_str" "^$nat_config_regex$|^$nat_config_regex/$" + BASH_REMATCH=(${BASH_REMATCH[@]}) ;; + 2) validate_str "$nat_config_str" "^$nat_config_regex/$nat_config_regex$";; esac # Store the individual Mapping and Filtering types +nat_map=() +nat_filter=() + for ((i=0; i<$n_routers; i++)); do map_idx=$((1 + 2 * $i)) filter_idx=$((2 + 2 * $i)) @@ -161,8 +194,8 @@ for ((i=0; i<$n_routers; i++)); do done # Parse WireGuard interfaces string into individual interfaces -wg_interface_regex="^([^:]*):([^:]*)$" -validate_str $wg_interface_str $wg_interface_regex +wg_interface_regex="^([^/]*)/([^/]*)$" +validate_str "$wg_interface_str" $wg_interface_regex wg_interfaces=(${BASH_REMATCH[1]} ${BASH_REMATCH[2]}) # Remove conntrack entries from potential previous tests @@ -176,10 +209,21 @@ NAT_TYPES=("EI" "AD" "APD") function describe_nat() { i=$1 - if [[ $i < $n_routers ]]; then + function helper() { + i=$1 + echo "${NAT_TYPES[${nat_map[$i]}]}M-${NAT_TYPES[${nat_filter[$i]}]}F" - else + } + + if [[ $i -ge $n_private ]]; then echo "No-NAT" + elif [[ -z $double_nat ]]; then + echo $(helper $i) + else + idx1=$((2*i)) + idx2=$((2*i+1)) + + echo "$(helper $idx1):$(helper $idx2)" fi } @@ -189,7 +233,7 @@ if [[ $hairpinning == true ]]; then else nat1_description=$(describe_nat 0) nat2_description=$(describe_nat 1) - nat_setup="$nat1_description <-> $nat2_description" + nat_setup="$nat1_description/$nat2_description" fi # Prepare a string describing the test setup @@ -199,9 +243,13 @@ test_description="Test $test_idx. $nat_setup, target=$test_target, result=" echo -n "$test_description" # Add log subdirectory for this system test -new_dir="${log_dir}/${test_idx}_${nat1_description}_${nat2_description}" -mkdir $new_dir -log_dir=$new_dir +log_dir="${log_dir}/${test_idx}_${nat1_description}" + +if [[ $hairpinning != true ]]; then + log_dir="${log_dir}_${nat2_description}" +fi + +mkdir $log_dir function clean_exit() { exit_code=$1 @@ -254,17 +302,45 @@ trap "clean_exit 1" SIGTERM # Start NAT simulation on each router cd ${repo_dir}/test_suite/nat_simulation -for ((i=0; i<${#router_ns_list[@]}; i++)); do - router_ns=${router_ns_list[$i]} - router_pub="${router_ns}_pub" - router_priv="${router_ns}_priv" - router_pub_prefix="192.168.$((i+1))" - priv_subnet="10.0.$((i+1)).0/24" +for ((i=0; i<$n_private; i++)); do + priv_prefix="10.0.$((i+1)).0/24" - sudo ip netns exec $router_ns ./setup_nat_mapping.sh $router_pub $router_pub_prefix $priv_subnet ${nat_map[$i]} $n_pooling_ips "${adm_ips[@]}" + if [[ -z $double_nat ]]; then + router_pub_prefix="192.168.$((i+1))" + router_ns=${router_ns_list[$i]} + router_pub="${router_ns}_pub" + router_priv="${router_ns}_priv" - sudo ip netns exec $router_ns ./setup_nat_filtering_hairpinning.sh $router_pub $router_priv $priv_subnet ${nat_filter[$i]} 2>&1 | \ - tee ${log_dir}/$router_ns.txt > /dev/null & # Combination of tee and redirect to /dev/null is necessary to avoid weird behaviour caused by redirecting a script run with sudo + sudo ip netns exec $router_ns ./setup_nat_mapping.sh $router_pub $router_pub_prefix $priv_prefix ${nat_map[$i]} $n_pooling_ips "${adm_ips}" + sudo ip netns exec $router_ns ./setup_nat_filtering_hairpinning.sh $router_pub $router_priv $priv_prefix ${nat_filter[$i]} 2>&1 | \ + tee ${log_dir}/$router_ns.txt > /dev/null & # Combination of tee and redirect to /dev/null is necessary to avoid weird behaviour caused by redirecting a script run with sudo + else + # With both peers in private networks, the router order is different between the peers + if [[ $i -eq 1 ]]; then + idx1=$((2*i)) + idx2=$((2*i+1)) + else + idx1=$((2*i+1)) + idx2=$((2*i)) + fi + + router1_pub_prefix="192.168.$((i+1))" + router1_ns=${router_ns_list[$idx1]} + router1_pub="${router1_ns}_pub" + router1_priv="${router1_ns}_priv" + double_prefix="172.16.$((i+1)).0/24" + + sudo ip netns exec $router1_ns ./setup_nat_mapping.sh $router1_pub $router1_pub_prefix $double_prefix ${nat_map[$idx1]} $n_pooling_ips "${adm_ips}" + sudo ip netns exec $router1_ns ./setup_nat_filtering_hairpinning.sh $router1_pub $router1_priv $double_prefix ${nat_filter[$idx1]} 2>&1 | tee ${log_dir}/$router1_ns.txt > /dev/null & + + router2_pub_prefix="172.16.$((i+1))" + router2_ns=${router_ns_list[$idx2]} + router2_pub="${router1_ns}" + router2_priv="${router2_ns}_priv" + + sudo ip netns exec $router2_ns ./setup_nat_mapping.sh $router2_pub $router2_pub_prefix $priv_prefix ${nat_map[$idx2]} 1 "${adm_ips}" + sudo ip netns exec $router2_ns ./setup_nat_filtering_hairpinning.sh $router2_pub $router2_priv $priv_prefix ${nat_filter[$idx2]} 2>&1 | tee ${log_dir}/$router2_ns.txt > /dev/null & + fi done # Execute scripts to start the peers diff --git a/test_suite/system_tests.sh b/test_suite/system_tests.sh index cd2bfec..975d042 100755 --- a/test_suite/system_tests.sh +++ b/test_suite/system_tests.sh @@ -6,6 +6,8 @@ Usage: ${0} [OPTIONAL ARGUMENTS] This script runs system tests between two eduP2P peers sequentially The following options determine the type of tests run: + -2 + Run connectivity tests with Double NAT: both peers are separated from the public network with two NATs, instead of the default single NAT -e Run extended connectivity tests (all combinations of RFC 4787 NAT mapping and filtering behaviour) -f @@ -44,15 +46,14 @@ log_lvl="debug" n_pooling_ips=3 # Validate optional arguments -while getopts ":c:d:ef:l:L:n:t:bph" opt; do +while getopts ":c:d:ef:l:L:n:t:2bph" opt; do case $opt in c) connectivity=true packet_loss=$OPTARG # Make sure packet_loss is a real number - real_regex="^[0-9]+[.]?([0-9]+)?$" - validate_str $packet_loss $real_regex + validate_str "$packet_loss" ^$real_regex$ # Make sure packet loss is in the interval [0, 100) in_interval=$(echo "$packet_loss >= 0 && $packet_loss < 100" | bc) # 1=true, 0=false @@ -65,8 +66,7 @@ while getopts ":c:d:ef:l:L:n:t:bph" opt; do delay=$OPTARG # Make sure delay is an integer - int_regex="^[0-9]+$" - validate_str $delay $int_regex + validate_str "$delay" $int_regex ;; e) extended=true @@ -82,12 +82,12 @@ while getopts ":c:d:ef:l:L:n:t:bph" opt; do l) log_lvl=$OPTARG - log_lvl_regex="^trace|debug|info|warn|error?$" - validate_str $log_lvl $log_lvl_regex + log_lvl_regex="^trace$|^debug$|^info$|^warn$|^error$" + validate_str "$log_lvl" $log_lvl_regex ;; L) alphanum_regex="^[a-zA-Z0-9]+$" - validate_str $OPTARG $alphanum_regex + validate_str "$OPTARG" $alphanum_regex log_dir_rel=system_test_logs/$OPTARG ;; t) @@ -95,7 +95,10 @@ while getopts ":c:d:ef:l:L:n:t:bph" opt; do # Make sure n_threads is an integer between 2 and 8 threads_regex="^[2-8]$" - validate_str $n_threads $int_regex + validate_str "$n_threads" $int_regex + ;; + 2) + double_nat="-2" ;; b) build=true @@ -120,6 +123,8 @@ while getopts ":c:d:ef:l:L:n:t:bph" opt; do esac done +system_test_opts=$@ + # Store repository's root directory for later use repo_dir=$(cd ..; pwd) @@ -155,7 +160,7 @@ function build_go() { function setup_networks() { cd nat_simulation/ - adm_ips=$(sudo ./setup_networks.sh $n_pooling_ips) # setup_networks.sh returns an array of IPs used by hosts in the network simulation setup, this list is needed to simulate a NAT device with an Address-Dependent Mapping + adm_ips=$(sudo ./setup_networks.sh $double_nat -n $n_pooling_ips) # setup_networks.sh returns an array of IPs used by hosts in the network simulation setup, this list is needed to simulate a NAT device with an Address-Dependent Mapping } function extract_server_pub_key() { @@ -265,7 +270,7 @@ function parallel_setup() { Dividing the system tests among $n_threads threads. The output of each thread can be found in the logs.""" # The current system tests command will be run in parallel docker containers with a few modifications: - system_test_opts=$(echo $@ | sed -r -e "s/-f \S+//" `# Potential -f flag is removed, as each docker container will be assigned a file containing a subset of the current system tests` \ + system_test_opts=$(echo $system_test_opts | sed -r -e "s/-f \S+//" `# Potential -f flag is removed, as each docker container will be assigned a file containing a subset of the current system tests` \ -e "s/-t [2-8]//") # -t flag is removed, since each docker container will run the tests in parallel` # Tests will be assigned to the containers in a round-robin manner, so we keep track of the current thread @@ -345,6 +350,27 @@ function run_system_test() { fi } +function filter_nat_combinations { + test_target=$1 + ns_config=$2 + nat_config=$3 + wg_config=$4 + nat1=$5 # Optional + nat2=$6 # Optional + nat3=$7 # Optional + nat4=$8 # Optional + + rfc_3489_nats=("0-0" "0-1" "0-2" "2-2") + + # Only test RFC 3489 NATs unless the extended flag was set + if [[ ( ${rfc_3489_nats[*]} =~ $nat1 && ${rfc_3489_nats[*]} =~ $nat2 && ${rfc_3489_nats[*]} =~ $nat3 && ${rfc_3489_nats[*]} =~ $nat4 ) || $extended == true ]]; then + # Only test Double NAT configurations where the two NATs are different + if [[ -z $double_nat || $nat1 != $nat2 && (-z $nat3 || $nat3 != $nat4 ) ]]; then + run_system_test $double_nat $test_target $ns_config $nat_config $wg_config + fi + fi +} + function connectivity_test_logic() { ns_config=$1 wg_config=$2 @@ -381,17 +407,62 @@ function connectivity_test_logic() { nat1=$nat1_mapping-$nat1_filter nat2=$nat2_mapping-$nat2_filter - # Only test RFC 3489 NATs unless the extended flag was set - if [[ ( ${rfc_3489_nats[*]} =~ $nat1 && ${rfc_3489_nats[*]} =~ $nat2 ) || $extended == true ]]; then - nat_config=$nat1:$nat2 - run_system_test $test_target $ns_config $nat_config $wg_config - fi + filter_nat_combinations $test_target $ns_config $nat1/$nat2 $wg_config $nat1 $nat2 + fi +} + +# This function currently takes only RFC 3489 NATs into account +function connectivity_test_logic_double_nat() { + ns_config=$1 + wg_config=$2 + nat1_mapping=$3 + nat1_filter=$4 + nat2_mapping=$5 + nat2_filter=$6 + nat3_mapping=$7 + nat3_filter=$8 + nat4_mapping=$9 + nat4_filter=${10} + + nat1=$nat1_mapping-$nat1_filter + nat2=$nat2_mapping-$nat2_filter + nat3=$nat3_mapping-$nat3_filter + nat4=$nat4_mapping-$nat4_filter + + # TS_PASS_RELAY if peer 1 is behind at least one Port Restricted Cone/Symmetric NAT, and peer 2 is behind at least one Symmetric NAT + if [[ -n $nat4 && \ + ( ( $nat1_filter -eq 2 || $nat2_filter -eq 2 ) && "$nat3 $nat4" =~ 2-2 || \ + ( $nat3_filter -eq 2 || $nat4_filter -eq 2 ) && "$nat1 $nat2" =~ 2-2 )]]; then + test_target="TS_PASS_RELAY" + # TS_PASS only if one peer is behind at least one Restricted Cone NAT, and the other peer is behind at least one Symmetric NAT + elif [[ -n $nat4 && \ + ( ( $nat1_filter -eq 1 || $nat2_filter -eq 1 ) && "$nat3 $nat4" =~ 2-2 || \ + ( $nat3_filter -eq 1 || $nat4_filter -eq 1 ) && "$nat1 $nat2" =~ 2-2 )]]; then + test_target="TS_PASS" + else + test_target="TS_PASS_DIRECT" + fi + + # Assign a score to each NAT, such that the RFC 3489 NAT types ordered by score are as follows: + ## 1. Full Cone = 0 + 0 = 0 + ## 2. Restricted Cone = 0 + 1 = 1 + ## 3. Port Restricted Cone = 0 + 2 = 2 + ## 4. Symmetric = 2 + 2 = 4 + nat1_score=$(echo "$nat1_mapping+$nat1_filter" | bc) + nat2_score=$(echo "$nat2_mapping+$nat2_filter" | bc) + nat3_score=$(echo "$nat3_mapping+$nat3_filter" | bc) + nat4_score=$(echo "$nat4_mapping+$nat4_filter" | bc) + + + # Use score to skip symmetrical cases + if [[ $nat3_score -gt $nat1_score || $nat3_score -eq $nat1_score && $nat4_score -ge $nat2_score ]]; then + filter_nat_combinations $test_target $ns_config $nat1:$nat2/$nat3:$nat4 $wg_config $nat1 $nat2 $nat3 $nat4 fi } if [[ $performance == true ]]; then log_sequential "\nPerformance tests (without NAT)" - run_system_test -k bitrate -v 100,200,300,400,500 -d 3 -b both TS_PASS_DIRECT router1-router2 : wg0:wg0 + run_system_test $double_nat -k bitrate -v 100,200,300,400,500 -d 3 -b both TS_PASS_DIRECT router1/router2 / wg0/wg0 elif [[ -n $file ]]; then echo -e "\nTests from file: $file" @@ -400,22 +471,27 @@ elif [[ -n $file ]]; then eval $test_cmd done < $file else - rfc_3489_nats=("0-0" "0-1" "0-2" "2-2") - log_sequential """ Starting connectivity tests between two peers (possibly) behind NATs with various combinations of mapping and filtering behaviour: - Endpoint-Independent Mapping/Filtering (EIM/EIF) - Address-Dependent Mapping/Filtering (ADM/ADF) - - Address and Port-Dependent Mapping/Filtering (ADPM/ADPF)""" + - Address and Port-Dependent Mapping/Filtering (ADPM/APDF)""" log_sequential "\nTests with one peer behind a NAT" - for nat_mapping in {0..2}; do - for nat_filter in {0..2}; do - nat=$nat_mapping-$nat_filter + for nat1_mapping in {0..2}; do + for nat1_filter in {0..2}; do + nat1=$nat1_mapping-$nat1_filter - # Only test RFC 3489 NATs unless the extended flag was set - if [[ ${rfc_3489_nats[*]} =~ $nat || $extended == true ]]; then - run_system_test TS_PASS_DIRECT private1_peer1-router1:router2 $nat: wg0: + if [[ -z $double_nat ]]; then + filter_nat_combinations TS_PASS_DIRECT private1_peer1:router1/router2 $nat1/ wg0/ $nat1 + else + for nat2_mapping in {0..2}; do + for nat2_filter in {0..2}; do + nat2=$nat2_mapping-$nat2_filter + + filter_nat_combinations TS_PASS_DIRECT private1_peer1:double1:router1/router2 $nat1:$nat2/ wg0/ $nat1 $nat2 + done + done fi done done @@ -425,20 +501,47 @@ Starting connectivity tests between two peers (possibly) behind NATs with variou for nat1_filter in {0..2}; do for nat2_mapping in {0..2}; do for nat2_filter in {0..2}; do - connectivity_test_logic private1_peer1-router1:router2-private2_peer1 wg0: $nat1_mapping $nat1_filter $nat2_mapping $nat2_filter + if [[ -z $double_nat ]]; then + connectivity_test_logic private1_peer1:router1/router2:private2_peer1 wg0/ $nat1_mapping $nat1_filter $nat2_mapping $nat2_filter + else + for nat3_mapping in {0..2}; do + for nat3_filter in {0..2}; do + for nat4_mapping in {0..2}; do + for nat4_filter in {0..2}; do + connectivity_test_logic_double_nat private1_peer1:double1:router1/router2:double2:private2_peer1 wg0/ $nat1_mapping $nat1_filter $nat2_mapping $nat2_filter $nat3_mapping $nat3_filter $nat4_mapping $nat4_filter + done + done + done + done + fi done done done done log_sequential "\nTest hairpinning" - for nat_mapping in {0..2}; do - for nat_filter in {0..2}; do - nat=$nat_mapping-$nat_filter - - # Only test RFC 3489 NATs unless the extended flag was set - if [[ ${rfc_3489_nats[*]} =~ $nat || $extended == true ]]; then - run_system_test TS_PASS_DIRECT private1_peer1-router1-private1_peer2 $nat: wg0: + for nat1_mapping in {0..2}; do + for nat1_filter in {0..2}; do + nat1=$nat1_mapping-$nat1_filter + + if [[ -z $double_nat ]]; then + filter_nat_combinations TS_PASS_DIRECT private1_peer1:router1:private1_peer2 $nat1 wg0/ $nat1 + else + for nat2_mapping in {0..2}; do + for nat2_filter in {0..2}; do + nat2=$nat2_mapping-$nat2_filter + + if [[ $nat1_mapping -ge 1 && $nat1_filter -eq 2 ]]; then + # Hairpinning is done by nat2, so its mapping/filtering behaviour is irrelevant + # However, if nat1 is A(P)DM-APDF, UDP hole punching will fail because both peers are behind a too restrictive NAT + test_target=TS_PASS_RELAY + else + test_target=TS_PASS_DIRECT + fi + + filter_nat_combinations $test_target private1_peer1:double1:router1:private1_peer2 $nat1:$nat2 wg0/ $nat1 $nat2 + done + done fi done done diff --git a/test_suite/util.sh b/test_suite/util.sh index 8988fb2..a5cb130 100755 --- a/test_suite/util.sh +++ b/test_suite/util.sh @@ -7,6 +7,10 @@ RED="\033[0;31m" GREEN="\033[0;32m" NC="\033[0m" # No color +# Frequently used regular expressions +int_regex="^[0-9]+$" +real_regex="[0-9]+([.][0-9]+)?" # Allows for counting the amount of digits after the decimal point + function exit_with_error() { err_reason=$1