selftests: netfilter: add reverse-clash resolution test case

Add test program that is sending UDP packets in both directions
and check that packets arrive without source port modification.

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
Florian Westphal 2024-09-10 11:38:16 +02:00 committed by Pablo Neira Ayuso
parent a4e6a1031e
commit a57856c0bb
3 changed files with 178 additions and 0 deletions

View file

@ -13,6 +13,7 @@ TEST_PROGS += conntrack_ipip_mtu.sh
TEST_PROGS += conntrack_tcp_unreplied.sh
TEST_PROGS += conntrack_sctp_collision.sh
TEST_PROGS += conntrack_vrf.sh
TEST_PROGS += conntrack_reverse_clash.sh
TEST_PROGS += ipvs.sh
TEST_PROGS += nf_conntrack_packetdrill.sh
TEST_PROGS += nf_nat_edemux.sh
@ -36,6 +37,7 @@ TEST_GEN_PROGS = conntrack_dump_flush
TEST_GEN_FILES = audit_logread
TEST_GEN_FILES += connect_close nf_queue
TEST_GEN_FILES += conntrack_reverse_clash
TEST_GEN_FILES += sctp_collision
include ../../lib.mk

View file

@ -0,0 +1,125 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Needs something like:
*
* iptables -t nat -A POSTROUTING -o nomatch -j MASQUERADE
*
* so NAT engine attaches a NAT null-binding to each connection.
*
* With unmodified kernels, child or parent will exit with
* "Port number changed" error, even though no port translation
* was requested.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define LEN 512
#define PORT 56789
#define TEST_TIME 5
static void die(const char *e)
{
perror(e);
exit(111);
}
static void die_port(uint16_t got, uint16_t want)
{
fprintf(stderr, "Port number changed, wanted %d got %d\n", want, ntohs(got));
exit(1);
}
static int udp_socket(void)
{
static const struct timeval tv = {
.tv_sec = 1,
};
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0)
die("socket");
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
return fd;
}
int main(int argc, char *argv[])
{
struct sockaddr_in sa1 = {
.sin_family = AF_INET,
};
struct sockaddr_in sa2 = {
.sin_family = AF_INET,
};
int s1, s2, status;
time_t end, now;
socklen_t plen;
char buf[LEN];
bool child;
sa1.sin_port = htons(PORT);
sa2.sin_port = htons(PORT + 1);
s1 = udp_socket();
s2 = udp_socket();
inet_pton(AF_INET, "127.0.0.11", &sa1.sin_addr);
inet_pton(AF_INET, "127.0.0.12", &sa2.sin_addr);
if (bind(s1, (struct sockaddr *)&sa1, sizeof(sa1)) < 0)
die("bind 1");
if (bind(s2, (struct sockaddr *)&sa2, sizeof(sa2)) < 0)
die("bind 2");
child = fork() == 0;
now = time(NULL);
end = now + TEST_TIME;
while (now < end) {
struct sockaddr_in peer;
socklen_t plen = sizeof(peer);
now = time(NULL);
if (child) {
if (sendto(s1, buf, LEN, 0, (struct sockaddr *)&sa2, sizeof(sa2)) != LEN)
continue;
if (recvfrom(s2, buf, LEN, 0, (struct sockaddr *)&peer, &plen) < 0)
die("child recvfrom");
if (peer.sin_port != htons(PORT))
die_port(peer.sin_port, PORT);
} else {
if (sendto(s2, buf, LEN, 0, (struct sockaddr *)&sa1, sizeof(sa1)) != LEN)
continue;
if (recvfrom(s1, buf, LEN, 0, (struct sockaddr *)&peer, &plen) < 0)
die("parent recvfrom");
if (peer.sin_port != htons((PORT + 1)))
die_port(peer.sin_port, PORT + 1);
}
}
if (child)
return 0;
wait(&status);
if (WIFEXITED(status))
return WEXITSTATUS(status);
return 1;
}

View file

@ -0,0 +1,51 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source lib.sh
cleanup()
{
cleanup_all_ns
}
checktool "nft --version" "run test without nft"
checktool "conntrack --version" "run test without conntrack"
trap cleanup EXIT
setup_ns ns0
# make loopback connections get nat null bindings assigned
ip netns exec "$ns0" nft -f - <<EOF
table ip nat {
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
oifname "nomatch" counter packets 0 bytes 0 masquerade
}
}
EOF
do_flush()
{
local end
local now
now=$(date +%s)
end=$((now + 5))
while [ $now -lt $end ];do
ip netns exec "$ns0" conntrack -F 2>/dev/null
now=$(date +%s)
done
}
do_flush &
if ip netns exec "$ns0" ./conntrack_reverse_clash; then
echo "PASS: No SNAT performed for null bindings"
else
echo "ERROR: SNAT performed without any matching snat rule"
exit 1
fi
exit 0