From 72ddb012191dc825effe6cd287a47299c87148c6 Mon Sep 17 00:00:00 2001 From: Oliver Smith Date: Thu, 26 Oct 2023 15:14:16 +0200 Subject: [PATCH] Add QEMU tests Add tests to ensure libgtpnl + kernel driver work as expected. Right now a kernel needs to be built from source, using Pablo's tree: https://git.kernel.org/pub/scm/linux/kernel/git/pablo/gtp.git/ Make sure to enable: CONFIG_GTP=y CONFIG_NET_NS=y CONFIG_VETH=y $ cp bzImage tests/qemu/_linux $ ./configure --enable-qemu-tests $ make $ make check Once patches are upstreamed, it will be possible to use a pre-built kernel from jenkins with: make -C tests qemu-download-kernel Related: OS#1952 Change-Id: Ibf75514b866fffb11e90529e4705f126b23d7415 --- .gitignore | 2 + Makefile.am | 7 +- configure.ac | 14 ++++ tests/Makefile.am | 15 +++++ tests/qemu/00_test_functions.sh | 75 +++++++++++++++++++++ tests/qemu/01_ms_ip4_sgsn_ip4.sh | 14 ++++ tests/qemu/02_ms_ip4_sgsn_ip6.sh | 14 ++++ tests/qemu/03_ms_ip6_sgsn_ip4.sh | 14 ++++ tests/qemu/04_ms_ip6_sgsn_ip6.sh | 14 ++++ tests/qemu/README.md | 36 ++++++++++ tests/qemu/check-depends.sh | 19 ++++++ tests/qemu/initrd-build.sh | 110 +++++++++++++++++++++++++++++++ tests/qemu/initrd-init.sh | 35 ++++++++++ tests/qemu/run-qemu.sh | 51 ++++++++++++++ 14 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 tests/Makefile.am create mode 100644 tests/qemu/00_test_functions.sh create mode 100644 tests/qemu/01_ms_ip4_sgsn_ip4.sh create mode 100644 tests/qemu/02_ms_ip4_sgsn_ip6.sh create mode 100644 tests/qemu/03_ms_ip6_sgsn_ip4.sh create mode 100644 tests/qemu/04_ms_ip6_sgsn_ip6.sh create mode 100644 tests/qemu/README.md create mode 100755 tests/qemu/check-depends.sh create mode 100755 tests/qemu/initrd-build.sh create mode 100755 tests/qemu/initrd-init.sh create mode 100755 tests/qemu/run-qemu.sh diff --git a/.gitignore b/.gitignore index bf5732d..56eefa6 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ contrib/libgtpnl.spec tools/gtp-link tools/gtp-tunnel + +tests/qemu/_* diff --git a/Makefile.am b/Makefile.am index c34b6db..c6b9682 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,7 +4,12 @@ include $(top_srcdir)/Make_global.am ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = src include tools +SUBDIRS = \ + include \ + src \ + tests \ + tools \ + $(NULL) pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libgtpnl.pc diff --git a/configure.ac b/configure.ac index 2665b70..b846c8c 100644 --- a/configure.ac +++ b/configure.ac @@ -72,6 +72,19 @@ then CPPFLAGS="$CPPFLAGS $WERROR_FLAGS" fi +AC_ARG_ENABLE(qemu_tests, + [AS_HELP_STRING( + [--enable-qemu-tests], + [Run automated tests in QEMU] + )], + [qemu_tests=$enableval], [qemu_tests="no"]) +AC_MSG_CHECKING([whether to enable QEMU tests]) +AC_MSG_RESULT([$qemu_tests]) +AM_CONDITIONAL(ENABLE_QEMU_TESTS, test x"$qemu_tests" = x"yes") +if test x"$qemu_tests" = x"yes" && ! $srcdir/tests/qemu/check-depends.sh; then + AC_MSG_ERROR([missing programs for --enable-qemu-tests]) +fi + AC_SUBST([CPPFLAGS]) AC_SUBST([CFLAGS]) AC_CONFIG_FILES([ @@ -82,6 +95,7 @@ AC_CONFIG_FILES([ include/linux/Makefile libgtpnl.pc src/Makefile + tests/Makefile tools/Makefile ]) AC_OUTPUT() diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..54af52d --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,15 @@ +check-local: + $(MAKE) qemu-tests + +if ENABLE_QEMU_TESTS +qemu-download-kernel: + rm -f qemu/_linux + wget -O qemu/_linux \ + https://jenkins.osmocom.org/jenkins/job/ttcn3-ggsn-test-kernel-latest-net-next/ws/_cache/kernel-test/linux +qemu-tests: + qemu/initrd-build.sh + qemu/run-qemu.sh +else +qemu-tests: + @echo "Not running QEMU tests (determined at configure-time)" +endif diff --git a/tests/qemu/00_test_functions.sh b/tests/qemu/00_test_functions.sh new file mode 100644 index 0000000..c4813ab --- /dev/null +++ b/tests/qemu/00_test_functions.sh @@ -0,0 +1,75 @@ +#!/bin/sh -ex + +# Use ip from iproute2 instead of busybox ip, because iproute2's version has +# "ip netns" implemented. Calling /bin/ip explicitly is needed here, otherwise +# busybox sh will use busybox ip, regardless of PATH. +alias ip="/bin/ip" +alias ggsn_side="ip netns exec ggsn_side" + +# MS - SGSN -gtp- GGSN - WEBSERVER +tunnel_start() { + test -n "$MS" + test -n "$MS_PREFLEN" + test -n "$SGSN_GGSN_PROTO" + test -n "$SGSN" + test -n "$SGSN_PREFLEN" + test -n "$GGSN" + test -n "$WEBSERVER" + + ip netns add ggsn_side + + # SGSN side: prepare veth_sgsn (SGSN), lo (MS) + ip link add veth_sgsn type veth peer name veth_ggsn + ip addr add "$SGSN"/"$SGSN_PREFLEN" dev veth_sgsn + ip link set veth_sgsn up + ip addr add "$MS"/"$MS_PREFLEN" dev lo + ip link set lo up + + # SGSN side: prepare gtp-tunnel + gtp-link add gtp_sgsn "$SGSN_GGSN_PROTO" --sgsn & + sleep 1 + gtp-tunnel add gtp_sgsn v1 200 100 "$MS" "$GGSN" + ip route add "$WEBSERVER"/"$MS_PREFLEN" dev gtp_sgsn + + # GGSN side: prepare veth_ggsn (GGSN), lo (WEBSERVER) + ip link set veth_ggsn netns ggsn_side + ggsn_side ip addr add "$GGSN"/"$SGSN_PREFLEN" dev veth_ggsn + ggsn_side ip link set veth_ggsn up + ggsn_side ip addr add "$WEBSERVER"/"$MS_PREFLEN" dev lo + ggsn_side ip link set lo up + + # GGSN side: prepare gtp-tunnel + ggsn_side gtp-link add gtp_ggsn "$SGSN_GGSN_PROTO" & + sleep 1 + ggsn_side gtp-tunnel add gtp_ggsn v1 100 200 "$MS" "$SGSN" + ggsn_side ip route add "$MS"/"$MS_PREFLEN" dev gtp_ggsn + + # List tunnel from both sides + gtp-tunnel list + ggsn_side gtp-tunnel list +} + +tunnel_ping() { + ip addr show + ping -c 1 "$WEBSERVER" + ggsn_side ping -c 1 "$MS" +} + +tunnel_stop() { + killall gtp-link + + ip addr del "$MS"/"$MS_PREFLEN" dev lo + ip link set veth_sgsn down + + if [ "$SGSN_GGSN_PROTO" == "ip" ]; then # FIXME: doesn't work with ip6 + ip addr del "$SGSN"/"$SGSN_PREFLEN" dev veth_sgsn + fi + + ip link del veth_sgsn + ip route del "$WEBSERVER"/"$MS_PREFLEN" dev gtp_sgsn + gtp-tunnel delete gtp_sgsn v1 200 "$SGSN_GGSN_PROTO" + gtp-link del gtp_sgsn + ggsn_side gtp-tunnel delete gtp_ggsn v1 100 "$SGSN_GGSN_PROTO" + ggsn_side gtp-link del gtp_ggsn + ip netns del ggsn_side +} diff --git a/tests/qemu/01_ms_ip4_sgsn_ip4.sh b/tests/qemu/01_ms_ip4_sgsn_ip4.sh new file mode 100644 index 0000000..42e2842 --- /dev/null +++ b/tests/qemu/01_ms_ip4_sgsn_ip4.sh @@ -0,0 +1,14 @@ +#!/bin/sh -ex +. /tests/00_test_functions.sh + +MS="172.99.0.1" +MS_PREFLEN="32" +SGSN_GGSN_PROTO="ip" +SGSN="172.0.0.1" +SGSN_PREFLEN="24" +GGSN="172.0.0.2" +WEBSERVER="172.99.0.2" + +tunnel_start +tunnel_ping +tunnel_stop diff --git a/tests/qemu/02_ms_ip4_sgsn_ip6.sh b/tests/qemu/02_ms_ip4_sgsn_ip6.sh new file mode 100644 index 0000000..b5858ab --- /dev/null +++ b/tests/qemu/02_ms_ip4_sgsn_ip6.sh @@ -0,0 +1,14 @@ +#!/bin/sh -ex +. /tests/00_test_functions.sh + +MS="172.99.0.1" +MS_PREFLEN="32" +SGSN_GGSN_PROTO="ip6" +SGSN="fd00::1" +SGSN_PREFLEN="7" +GGSN="fd00::2" +WEBSERVER="172.99.0.2" + +tunnel_start +tunnel_ping +tunnel_stop diff --git a/tests/qemu/03_ms_ip6_sgsn_ip4.sh b/tests/qemu/03_ms_ip6_sgsn_ip4.sh new file mode 100644 index 0000000..12793b0 --- /dev/null +++ b/tests/qemu/03_ms_ip6_sgsn_ip4.sh @@ -0,0 +1,14 @@ +#!/bin/sh -ex +. /tests/00_test_functions.sh + +MS="fd00::" +MS_PREFLEN="64" +SGSN_GGSN_PROTO="ip" +SGSN="172.0.0.1" +SGSN_PREFLEN="24" +GGSN="172.0.0.2" +WEBSERVER="fe00::2" + +tunnel_start +tunnel_ping +tunnel_stop diff --git a/tests/qemu/04_ms_ip6_sgsn_ip6.sh b/tests/qemu/04_ms_ip6_sgsn_ip6.sh new file mode 100644 index 0000000..6c2f13f --- /dev/null +++ b/tests/qemu/04_ms_ip6_sgsn_ip6.sh @@ -0,0 +1,14 @@ +#!/bin/sh -ex +. /tests/00_test_functions.sh + +MS="fc00::" +MS_PREFLEN="64" +SGSN_GGSN_PROTO="ip6" +SGSN="fd00::1" +SGSN_PREFLEN="7" +GGSN="fd00::2" +WEBSERVER="fe00::2" + +tunnel_start +tunnel_ping +tunnel_stop diff --git a/tests/qemu/README.md b/tests/qemu/README.md new file mode 100644 index 0000000..e0dd344 --- /dev/null +++ b/tests/qemu/README.md @@ -0,0 +1,36 @@ +# QEMU tests for libgtpnl + +The tests simulate how a GGSN would use libgtpnl, to set up a GTP tunnel +between SGSN and GGSN, so a MS on the SGSN side can talk to a webserver on the +GGSN side. + +## Running the tests + +``` +$ autoreconf -fi +$ ./configure --enable-qemu-tests +$ make +$ make -C tests qemu-download-kernel # or build your own, see below +$ make check +``` + +## Building your own kernel + +Clone a kernel tree, then: +``` +$ make defconfig +$ make menuconfig +``` + +Set the following options: +``` +CONFIG_GTP=y +CONFIG_NET_NS=y +CONFIG_VETH=y +``` + +Build the kernel and copy it to the tests dir: +``` +$ make -j$(nproc) +$ cp arch/x86/boot/bzImage /path/to/libgtpnl/tests/qemu/_linux +``` diff --git a/tests/qemu/check-depends.sh b/tests/qemu/check-depends.sh new file mode 100755 index 0000000..b63b389 --- /dev/null +++ b/tests/qemu/check-depends.sh @@ -0,0 +1,19 @@ +#!/bin/sh +RET=0 + +require_program() { + if [ -z "$(command -v "$1")" ]; then + RET=1 + echo "ERROR: missing program: $1" + fi +} + +require_program busybox +require_program cpio +require_program find +require_program gzip +require_program ip +require_program lddtree +require_program qemu-system-x86_64 + +exit "$RET" diff --git a/tests/qemu/initrd-build.sh b/tests/qemu/initrd-build.sh new file mode 100755 index 0000000..34d3bc5 --- /dev/null +++ b/tests/qemu/initrd-build.sh @@ -0,0 +1,110 @@ +#!/bin/sh -e +DIR="$(cd "$(dirname "$0")" && pwd)" +DIR_INITRD="$DIR/_initrd" +SRC_LIBS="$(realpath "$DIR/../../src/.libs/")" +TOOLS_LIBS="$(realpath "$DIR/../../tools/.libs/")" + +# Add one or more files to the initramfs, with parent directories. +# usr-merge: resolve symlinks for /lib -> /usr/lib etc. so "cp --parents" does +# not fail with "cp: cannot make directory '/tmp/initrd/lib': File exists" +# $@: path to files +initrd_add_file() { + local i + + for i in "$@"; do + case "$i" in + /bin/*|/sbin/*|/lib/*|/lib64/*) + cp -a --parents "$i" "$DIR_INITRD"/usr + ;; + *) + cp -a --parents "$i" "$DIR_INITRD" + ;; + esac + done +} + +# Add binaries with depending libraries +# $@: paths to binaries +initrd_add_bin() { + local bin + local bin_path + local file + + for bin in "$@"; do + local bin_path="$(which "$bin")" + if [ -z "$bin_path" ]; then + echo "ERROR: file not found: $bin" + exit 1 + fi + + lddtree_out="$(lddtree -l "$bin_path")" + if [ -z "$lddtree_out" ]; then + echo "ERROR: lddtree failed on '$bin_path'" + exit 1 + fi + + for file in $lddtree_out; do + initrd_add_file "$file" + + # Copy resolved symlink + if [ -L "$file" ]; then + initrd_add_file "$(realpath "$file")" + fi + done + done +} + +# Add command to run inside the initramfs +# $@: commands +initrd_add_cmd() { + local i + + if ! [ -e "$DIR_INITRD"/cmd.sh ]; then + echo "#!/bin/sh -ex" > "$DIR_INITRD"/cmd.sh + chmod +x "$DIR_INITRD"/cmd.sh + fi + + for i in "$@"; do + echo "$i" >> "$DIR_INITRD"/cmd.sh + done +} + +rm -rf "$DIR_INITRD" +mkdir -p "$DIR_INITRD" +cd "$DIR_INITRD" + +for dir in bin sbin lib lib64; do + ln -s usr/"$dir" "$dir" +done + +mkdir -p \ + dev/net \ + proc \ + run \ + sys \ + tmp \ + usr/bin \ + usr/sbin + +initrd_add_bin \ + busybox \ + ip + +initrd_add_cmd \ + "export LD_LIBRARY_PATH=$SRC_LIBS:$LD_LIBRARY_PATH" + +export LD_LIBRARY_PATH="$SRC_LIBS:$LD_LIBRARY_PATH" + +for i in gtp-link gtp-tunnel; do + initrd_add_bin "$TOOLS_LIBS"/"$i" + ln -s "$TOOLS_LIBS"/"$i" usr/bin/"$i" +done + +mkdir tests +cp "$DIR"/*.sh tests + +cp "$DIR"/initrd-init.sh init + +find . -print0 \ + | cpio --quiet -o -0 -H newc \ + | gzip -1 > "$DIR"/_initrd.gz diff --git a/tests/qemu/initrd-init.sh b/tests/qemu/initrd-init.sh new file mode 100755 index 0000000..ba20592 --- /dev/null +++ b/tests/qemu/initrd-init.sh @@ -0,0 +1,35 @@ +#!/bin/busybox sh +echo "Running initrd-init.sh" +set -x + +run_test() { + echo + echo "QEMU test: $1" + echo + if ! sh -ex "/tests/$1"; then + poweroff -f + fi +} + +export HOME=/root +export LD_LIBRARY_PATH=/usr/local/lib +export PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/local/sbin:/usr/sbin +export TERM=screen + +/bin/busybox --install -s +hostname qemu +mount -t proc proc /proc +mount -t sysfs sys /sys +mknod /dev/null c 1 3 +. /cmd.sh +set +x + +# Run all tests +run_test 01_ms_ip4_sgsn_ip4.sh +run_test 02_ms_ip4_sgsn_ip6.sh +run_test 03_ms_ip6_sgsn_ip4.sh +run_test 04_ms_ip6_sgsn_ip6.sh + +# Success (run-qemu.sh checks for this line) +echo "QEMU_TEST_SUCCESSFUL" +poweroff -f diff --git a/tests/qemu/run-qemu.sh b/tests/qemu/run-qemu.sh new file mode 100755 index 0000000..35bf756 --- /dev/null +++ b/tests/qemu/run-qemu.sh @@ -0,0 +1,51 @@ +#!/bin/sh -e +DIR="$(cd "$(dirname "$0")" && pwd)" + +if [ -e /dev/kvm ]; then + MACHINE_ARG="-machine pc,accel=kvm" +else + echo "WARNING: /dev/kvm not found, emulation will be slower" + MACHINE_ARG="-machine pc" +fi + +if ! [ -e "$DIR"/_linux ]; then + echo "ERROR: linux kernel not found: $DIR/_linux" + echo "Put a kernel there, either download it from the Osmocom jenkins:" + echo "$ make -C tests qemu-download-kernel" + echo + echo "Or build your own kernel. Make sure to set:" + echo " CONFIG_GTP=y" + echo " CONFIG_NET_NS=y" + echo " CONFIG_VETH=y" + exit 1 +fi + +KERNEL_CMDLINE="root=/dev/ram0 console=ttyS0 panic=-1 init=/init" + +set -x +qemu-system-x86_64 \ + $MACHINE_ARG \ + -smp 1 \ + -m 512M \ + -no-user-config -nodefaults -display none \ + -gdb unix:"$DIR"/_gdb.pipe,server=on,wait=off \ + -no-reboot \ + -kernel "$DIR"/_linux \ + -initrd "$DIR"/_initrd.gz \ + -append "${KERNEL_CMDLINE}" \ + -serial stdio \ + -chardev socket,id=charserial1,path="$DIR"/_gdb-serial.pipe,server=on,wait=off \ + -device isa-serial,chardev=charserial1,id=serial1 \ + 2>&1 | tee "$DIR/_output" + +set +x +if grep -q "QEMU_TEST_SUCCESSFUL" "$DIR/_output"; then + echo + echo "QEMU tests: successful" + echo +else + echo + echo "QEMU tests: failed" + echo + exit 1 +fi