Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/c-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@ jobs:
run: |
cd nat46/modules
KCPPFLAGS='-DNAT46_VERSION=\"test\" -Werror' make
- name: Install kvm
run: |
sudo apt-get install qemu-system-x86
- name: Install and run test harness
run: |
cd test-harness
./run-test-harness


12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
test-harness/test-data/captured/*
test-harness/myinit
test-harness/initrd.gz
test-harness/initrd/
test-harness/my-kernel

nat46/modules/.*
nat46/modules/nat46.mod*
nat46/modules/Module.symvers
nat46/modules/modules.order
nat46/modules/*.o
nat46/modules/*.ko
2 changes: 1 addition & 1 deletion nat46/modules/nat46-core.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ typedef struct {
int debug;

int npairs;
nat46_xlate_rulepair_t pairs[0]; /* npairs */
nat46_xlate_rulepair_t pairs[]; /* npairs */
} nat46_instance_t;

int nat46_ipv6_input(struct sk_buff *old_skb);
Expand Down
36 changes: 36 additions & 0 deletions test-harness/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a (long overdue) test harness for the nat46.ko module.

The goal is to trivially run it in two modes:

- as a CI in github
- as a local test bench

The high-level setup is more or less how this module was originally written some years ago:
a kernel running under KVM, mounting the host filesystem via p9, and loading
the module from there.

However, as an experiment, I decided to do it in a much more lightweight fashion -
rather than going the classic route of building the disk image of the root device and
mounting that, with the help of LLM I built a custom /init, which gives enough of
a shell-like experience to do the system bring-up and tests within it.

In part it is done to test-drive another project of mine: https://github.com/ayourtch/oside,
whose purpose in life is to allow to relatively easily do packet manipulations from Rust.

Admittedly, it is a fair bit less feature-complete than Scapy at this point, but not having
to deal with installation and management of Python inside the disk image is arguably worth the hassle.

The init shell has a command "oside", which gives a rudimentary TUI to edit the jsonl files with
the packets. Also one can use "pcap2json" command inside the shell to convert the files inside the shell.

The tests are sitting under tests/ directory, and need to be executed one-by-one from startup.run - this
script is executed immediately at bootup. In the future the tests *may* be moved into autoexec.run, which
is also run at startup, but with a delay that allows the user to break into interactive shell.

Each test should configure nat46 device(s) as it sees fit, inject some packets, and capture the expected
packets into test-data/captured/*testname*.jsonl. After the VM run concludes, each captured file is compared
with its sibling file in test-data/expected/*testname*.jsonl, and is expected to be identical, modulo timestamps.

Admittedly this is not *too* much of a framework, but hopefully should allow for some relatively useful tests
to be done relatively easily.

1 change: 1 addition & 0 deletions test-harness/autoexec.run
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
echo This is a test
13 changes: 13 additions & 0 deletions test-harness/bin/check-test-result
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/sh
set -eux
TESTNAME=$1

rm -f /tmp/expected.jsonl
rm -f /tmp/captured.jsonl


jq 'del(.timestamp_us)' test-data/expected/${TESTNAME}.jsonl >/tmp/expected.jsonl
jq 'del(.timestamp_us)' test-data/captured/${TESTNAME}.jsonl >/tmp/captured.jsonl

diff -c /tmp/expected.jsonl /tmp/captured.jsonl

105 changes: 105 additions & 0 deletions test-harness/run-test-harness
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/bin/bash
set -eux

# latest tag for https://github.com/ayourtch/nat46-kvm-test-harness/
MYINIT_VERSION="v0.0.10"

find_kernel_and_modules() {
local kernel_version=$(uname -r)

# Potential kernel paths
local potential_kernel_paths=(
"/boot/vmlinuz-${kernel_version}"
"/boot/vmlinux-${kernel_version}"
"/boot/linux-${kernel_version}"
)

# Potential module path locations
local potential_module_paths=(
"/lib/modules/${kernel_version}"
"/usr/lib/modules/${kernel_version}"
"/usr/local/lib/modules/${kernel_version}"
)

local kernel_file=""
for path in "${potential_kernel_paths[@]}"; do
if [ -f "$path" ]; then
kernel_file="$path"
break
fi
done

local module_path=""
for path in "${potential_module_paths[@]}"; do
if [ -d "$path" ]; then
module_path="$path"
break
fi
done

if [ -z "$kernel_file" ]; then
echo "Error: Could not find kernel file for version ${kernel_version}" >&2
return 1
fi

if [ -z "$module_path" ]; then
echo "Error: Could not find module directory for version ${kernel_version}" >&2
return 1
fi

# Export environment variables
export KERNEL_FILE="$kernel_file"
export KERNEL_MODULE_PATH="$module_path"

# Print out for verification
echo "Kernel File: $KERNEL_FILE"
echo "Kernel Module Path: $KERNEL_MODULE_PATH"
}

find_kernel_and_modules

# download myinit - if doing local tests, feel free to make a symlink
[ -e myinit ] || (wget https://github.com/ayourtch/nat46-kvm-test-harness/releases/download/${MYINIT_VERSION}/myinit && chmod +x myinit)

# build the initrd
rm -rf initrd && mkdir initrd
find ${KERNEL_MODULE_PATH} -name "netfs.ko*" -exec cp {} initrd/ \;
find ${KERNEL_MODULE_PATH} -name "9pnet.ko*" -exec cp {} initrd/ \;
find ${KERNEL_MODULE_PATH} -name "9pnet_virtio.ko*" -exec cp {} initrd/ \;
find ${KERNEL_MODULE_PATH} -name "9p.ko*" -exec cp {} initrd/ \;
find ${KERNEL_MODULE_PATH} -name "nf_defrag_ipv6.ko*" -exec cp {} initrd/ \;

# copy the nat46 module from the repo
find .. -name nat46.ko -exec cp {} initrd/ \;

cp myinit initrd/init
( cd initrd/ && find . | cpio -o -H newc ) | gzip > initrd.gz

export HOST_SRC_PATH=$(git rev-parse --show-toplevel)

[ -f my-kernel ] || (sudo cp ${KERNEL_FILE} my-kernel && sudo chmod 644 my-kernel)

kvm -kernel my-kernel \
-initrd initrd.gz \
-net nic,model=virtio,macaddr=52:54:00:12:34:56 \
-net user,hostfwd=tcp:127.0.0.1:4444-:22 \
-append 'console=hvc0' \
-chardev stdio,id=stdio,mux=on,signal=off \
-device virtio-serial-pci \
-device virtconsole,chardev=stdio \
-mon chardev=stdio \
-display none \
-fsdev local,id=fs1,path=${HOST_SRC_PATH},security_model=none \
-s \
-device virtio-9p-pci,fsdev=fs1,mount_tag=host-code


# collect the test names
cd tests
ALL_TESTS=$(ls)
cd ..

# verify that all the tests are matching the expectations
for t in $ALL_TESTS; do
bin/check-test-result $t
done
51 changes: 51 additions & 0 deletions test-harness/startup.run
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# This file is executed after the host filesystem is mounted
# Modify this file to customize your test environment

# List root directory to verify mount
ls /

# Load additional kernel modules
insmod /nf_defrag_ipv6.ko
insmod /nat46.ko

ifconfig lo 127.0.0.1 netmask 255.0.0.0
ifconfig lo ::1/128
ifconfig lo up

# Setup TAP interfaces
mknod /dev/net/tun c 10 200
tap add tap0
ifconfig tap0 192.168.1.1 netmask 255.255.255.0
ifconfig tap0 2001:db8:1::1/64
ifconfig tap0 up
tap add tap1
ifconfig tap1 2001:db8::1/64
ifconfig tap1 up
ifconfig

# set the mac addresses
ifconfig tap0 hw ether 0E:86:3C:CD:51:CA
ifconfig tap1 hw ether 00:11:22:33:44:55
ifconfig nat46dev up

# add phantom hosts
fakehost add tap1 fe80::1 icmp router
fakehost add tap1 2001:db8::2 icmp router
fakehost add tap1 fe80::2 icmp
# ping ipv6 default gateway global
# ping 2001:db8::2
ping fe80::1%tap1

ip -6 route add ::/0 via fe80::1 dev tap1
ifconfig tap0 up promisc

# start droptrace for diagnostics
mkdir -p /sys/kernel
droptrace start

echo 1 >/proc/sys/net/ipv4/ip_forward
echo 1 >/proc/sys/net/ipv6/conf/all/forwarding

run /mnt/host/test-harness/tests/basic/test.run


Empty file.
2 changes: 2 additions & 0 deletions test-harness/test-data/expected/basic.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"timestamp_us":1761854670878200,"direction":"tx","layers":[{"layertype":"Ip","version":4,"ihl":5,"tos":0,"len":84,"id":4,"flags":{"reserved":false,"dont_fragment":false,"more_fragments":false,"fragment_offset":0},"ttl":254,"proto":6,"chksum":60035,"src":"192.168.1.100","dst":"8.8.8.8","options":[]},{"layertype":"Tcp","sport":12345,"dport":81,"seq":0,"ack":0,"dataofs":5,"reserved":0,"flags":2,"window":8192,"chksum":60790,"urgptr":0},{"layertype":"raw","data":[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42]}]}
{"timestamp_us":1761854670881112,"direction":"rx","layers":[{"layertype":"Ipv6","version_class":1610612736,"payload_length":64,"next_header":6,"hop_limit":254,"src":"2001:db8:1:1::1","dst":"2001:4860:4860::8888"},{"layertype":"Tcp","sport":12345,"dport":81,"seq":0,"ack":0,"dataofs":5,"reserved":0,"flags":2,"window":8192,"chksum":22663,"urgptr":0},{"layertype":"raw","data":[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42]}]}
5 changes: 5 additions & 0 deletions test-harness/tests/basic/inject-tap0.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{"timestamp_us":0,"layers":[{"layertype":"ether","dst":"FF:FF:FF:FF:FF:FF","src":"00:00:00:00:00:00","etype":0}]}
{"timestamp_us":1000000,"layers":[{"layertype":"ether","dst":"0E:86:3C:CD:51:CA","src":"52:55:0A:00:02:02","etype":2048},{"layertype":"Ip","version":4,"ihl":5,"tos":0,"len":84,"id":4,"flags":{"reserved":false,"dont_fragment":false,"more_fragments":false,"fragment_offset":0},"ttl":255,"proto":6,"chksum":59779,"src":"192.168.1.100","dst":"8.8.8.8","options":[]},{"layertype":"Tcp","sport":12345,"dport":81,"seq":0,"ack":0,"dataofs":5,"reserved":0,"flags":2,"window":8192,"chksum":60790,"urgptr":0},{"layertype":"raw","data":[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42]}]}
{"timestamp_us":1000000,"layers":[{"layertype":"ether","dst":"86:8B:D5:ED:1E:03","src":"52:55:0A:00:02:02","etype":34525},{"layertype":"Ipv6","version_class":1610612736,"payload_length":65,"next_header":17,"hop_limit":64,"src":"2001:db8::1","dst":"2001:db8::2"},{"layertype":"Udp","sport":6666,"dport":888,"len":65,"chksum":49112},{"layertype":"raw","data":[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42]}]}
{"timestamp_us":2000000,"layers":[{"layertype":"ether","dst":"FF:FF:FF:FF:FF:FF","src":"00:00:00:00:00:00","etype":0}]}

21 changes: 21 additions & 0 deletions test-harness/tests/basic/test.run
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
echo add nat46dev > /proc/net/nat46/control
ifconfig nat46dev up
ip route add 0.0.0.0/0 dev nat46dev

echo config nat46dev debug 255 >/proc/net/nat46/control
echo insert nat46dev local.v4 192.168.1.100/32 local.v6 2001:db8:1:1::1/128 local.style NONE remote.v4 8.8.8.8/32 remote.v6 2001:4860:4860::8888/128 remote.style NONE >/proc/net/nat46/control
echo insert nat46dev local.v4 192.168.1.101/32 local.v6 2001:db8:1:1::2/128 local.style NONE remote.v4 8.8.8.8/32 remote.v6 2001:4860:4860::8888/128 remote.style NONE >/proc/net/nat46/control

capture start nat46dev /mnt/host/test-harness/test-data/captured/basic.jsonl

# inject packets
inject /mnt/host/test-harness/tests/basic/inject-tap0.jsonl tap0

# remove the nat46 device
echo del nat46dev >/proc/net/nat46/control

capture stop all

echo nat46 packets:
cat /mnt/host/test-harness/test-data/captured/basic.jsonl