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