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
This commit is contained in:
Oliver Smith 2023-10-26 15:14:16 +02:00
parent 791a620e4d
commit 72ddb01219
14 changed files with 419 additions and 1 deletions

2
.gitignore vendored
View File

@ -37,3 +37,5 @@ contrib/libgtpnl.spec
tools/gtp-link
tools/gtp-tunnel
tests/qemu/_*

View File

@ -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

View File

@ -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()

15
tests/Makefile.am Normal file
View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

36
tests/qemu/README.md Normal file
View File

@ -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
```

19
tests/qemu/check-depends.sh Executable file
View File

@ -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"

110
tests/qemu/initrd-build.sh Executable file
View File

@ -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

35
tests/qemu/initrd-init.sh Executable file
View File

@ -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

51
tests/qemu/run-qemu.sh Executable file
View File

@ -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