From 9fad2e8b3159a231eba8a1e5d7bdca8175f55a73 Mon Sep 17 00:00:00 2001 From: Rolf Neugebauer Date: Wed, 5 Jul 2017 11:56:11 +0100 Subject: [PATCH] tests: Add a package for network namespace stress tests Signed-off-by: Rolf Neugebauer --- test/pkg/netns/Dockerfile | 16 +++ test/pkg/netns/Makefile | 15 +++ test/pkg/netns/netns.sh | 199 ++++++++++++++++++++++++++++++++++++++ test/pkg/netns/runp.sh | 48 +++++++++ 4 files changed, 278 insertions(+) create mode 100644 test/pkg/netns/Dockerfile create mode 100644 test/pkg/netns/Makefile create mode 100755 test/pkg/netns/netns.sh create mode 100755 test/pkg/netns/runp.sh diff --git a/test/pkg/netns/Dockerfile b/test/pkg/netns/Dockerfile new file mode 100644 index 000000000..978dc312b --- /dev/null +++ b/test/pkg/netns/Dockerfile @@ -0,0 +1,16 @@ +FROM linuxkit/alpine:3d57ded3edd54e616210cf0c17e3bc15eed9d03a AS mirror +RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/ +RUN apk add --no-cache --initdb -p /out \ + alpine-baselayout \ + busybox \ + iperf3 \ + iproute2 \ + musl +RUN rm -rf /out/etc/apk /out/lib/apk /out/var/cache + +FROM scratch +COPY --from=mirror /out/ / +COPY netns.sh runp.sh / + +LABEL org.mobyproject.config='{"pid": "host", "net":"host", "binds": ["/dev:/dev","/sys:/sys"], "capabilities": ["all"]}' + diff --git a/test/pkg/netns/Makefile b/test/pkg/netns/Makefile new file mode 100644 index 000000000..73502cef9 --- /dev/null +++ b/test/pkg/netns/Makefile @@ -0,0 +1,15 @@ +.PHONY: tag push +default: push + +ORG?=linuxkit +IMAGE=test-netns +DEPS=Dockerfile Makefile + +HASH?=$(shell git ls-tree HEAD -- ../$(notdir $(CURDIR)) | awk '{print $$3}') + +tag: $(DEPS) + docker build --squash --no-cache -t $(ORG)/$(IMAGE):$(HASH) . + +push: tag + DOCKER_CONTENT_TRUST=1 docker pull $(ORG)/$(IMAGE):$(HASH) || \ + DOCKER_CONTENT_TRUST=1 docker push $(ORG)/$(IMAGE):$(HASH) diff --git a/test/pkg/netns/netns.sh b/test/pkg/netns/netns.sh new file mode 100755 index 000000000..e64852955 --- /dev/null +++ b/test/pkg/netns/netns.sh @@ -0,0 +1,199 @@ +#! /bin/sh + +# Initialise the Random Number Seed with the PID +RANDOM=$$ + +# defaults +ARG_TYPE=veth +ARG_PROTO=tcp +ARG_IP=4 +ARG_CONN=1 +ARG_ITER=10 +ARG_LEN=20 +ARG_REUSE=0 + +usage() { + echo "Stress test for network namespace using iperf" + echo " -t loopback|veth Type of test [$ARG_TYPE]" + echo " -p tcp|udp Protocol to use [$ARG_PROTO]" + echo " -ip 4|6 IP version to use [$ARG_IP]" + echo " -c Number of concurrent connections in ns [$ARG_CONN]" + echo " -i Number of iterations [$ARG_ITER]" + echo " -l Maximum length of test before killing [$ARG_LEN]" + echo " -r Re-use network namespace name" +} + +# parse arguments +while [[ $# -gt 0 ]]; do + key="$1" + + case $key in + -t) + ARG_TYPE="$2" + shift + ;; + -p) + ARG_PROTO="$2" + shift + ;; + -ip) + ARG_IP="$2" + shift + ;; + -c) + ARG_CONN="$2" + shift + ;; + -i) + ARG_ITER="$2" + shift + ;; + -l) + ARG_LEN="$2" + shift + ;; + -r) + ARG_REUSE=1 + ;; + *) + usage + exit 1 + ;; + esac + shift +done + +echo "PID=$$" + +# Kill a random bit (client, server, network namespace, device) first +# before cleaning up +kill_all() { + ns=$1 + pid_client=$2 + pid_server=$3 + host_dev=$4 + ns_dev=$5 + + R=$(($RANDOM%$#)) + case $R in + 0) + echo "$ns: Remove namespace first" + ip netns del "$ns" > /dev/nukk 2>&1 || true + ;; + 1) + echo "$ns: Kill client processes first" + kill "$pid_client" > /dev/null 2>&1 || true + ;; + 2) + echo "$ns: Kill server process first" + kill "$pid_server" > /dev/null 2>&1 || true + ;; + 3) + echo "$ns: Remove host netdev first" + ip link del "$host_dev" + ;; + 4) + echo "$ns: Remove netns netdev first" + ip netns exec "$ns" ip link del "$ns_dev" + ;; + esac + kill "$pid_client" > /dev/null 2>&1 || true + kill "$pid_server" > /dev/null 2>&1 || true + ip netns del "$ns" > /dev/null 2>&1 || true + [ "$host_dev"x != x ] && (ip link del "$host_dev" > /dev/null 2>&1 || true) || true + [ "$ns_dev"x != x ] && (ip netns exec "$ns" ip link del "$ns_dev" > /dev/null 2>&1 || true) || true +} + +# Run sock_stress in loopback mode in a network namespace +loopback_run() { + id=$1 # unique ID for this run, used to create namespace + ip=$2 # 4 or 6 to select IP version to use + pr=$3 # protocol tcp or udp + + # Use our PID as the ID to get unique namespaces + if [ "$ARG_REUSE" = "1" ]; then + ns="ns_$$" + else + ns="ns_$$_$id" + fi + ip netns add "$ns" + ip netns exec "$ns" ip link set lo up + + ip netns exec "$ns" iperf3 -s --logfile /dev/null & + pid_server=$! + sleep 1 + [ "$pr" = "udp" ] && o="-u" + ip netns exec "$ns" iperf3 -"$ip" "$o" -P "$ARG_CONN" -c localhost -t 10000 -i 20 & + pid_client=$! + + # wait for a while before killing processes + sleep $(((RANDOM % $ARG_LEN )+1)) + + kill_all "$ns" "$pid_client" "$pid_server" +} + +# Run sock_stress in with the client in a namespace +veth_run() { + id=$1 # unique ID for this run, used to create namespace + ip=$2 # 4 or 6 to select IP version to use + pr=$3 # tcp or udp + + # Use our PID as the ID to get unique namespaces + if [ "$ARG_REUSE" = "1" ]; then + ns="ns_$$" + else + ns="ns_$$_$id" + fi + dev_host="h-$$-$id" + dev_ns="n-$$-$id" + ip netns add "$ns" + ip netns exec "$ns" ip link set lo up + + ip link add "$dev_host" type veth peer name "$dev_ns" + ip link set "$dev_ns" netns "$ns" + + # derive IP addresses based on PID and $id + if [ "$ip" = "4" ]; then + sub0=$(($$%255)) + sub1=$(($id%255)) + mask=24 + ip_host="10.$sub0.$sub1.1" + ip_ns="10.$sub0.$sub1.2" + else + # Make sure IPv6 is enabled on the interface + echo 0 > /proc/sys/net/ipv6/conf/"$dev_host"/disable_ipv6 + sub0=$(printf "%x" $(($$%65535))) + sub1=$(printf "%x" $(($id%65535))) + mask=64 + ip_host="2001:$sub0:$sub1::1" + ip_ns="2001:$sub0:$sub1::2" + fi + ip -"$ip" addr add "$ip_host"/"$mask" dev "$dev_host" + ip link set "$dev_host" up + + ip netns exec "$ns" ip -"$ip" addr add "$ip_ns"/"$mask" dev "$dev_ns" + ip netns exec "$ns" ip link set "$dev_ns" up + sleep 2 # for IPv6 it takes a little while for the link to come up + + ip netns exec "$ns" iperf3 -s --logfile /dev/null & + pid_server=$! + sleep 1 + [ "$pr" = "udp" ] && o="-u" + iperf3 -"$ip" "$o" -P "$ARG_CONN" -c "$ip_ns" -t 10000 -i 20 & + pid_client=$! + + # wait for a while before killing processes + sleep $(((RANDOM % $ARG_LEN )+1)) + + kill_all "$ns" "$pid_client" "$pid_server" "$dev_host" "$dev_ns" +} + +for i in $(seq 1 "$ARG_ITER"); do + case $ARG_TYPE in + veth) + veth_run "$i" "$ARG_IP" "$ARG_PROTO" + ;; + loopback) + loopback_run "$i" "$ARG_IP" "$ARG_PROTO" + esac +done diff --git a/test/pkg/netns/runp.sh b/test/pkg/netns/runp.sh new file mode 100755 index 000000000..a81dcb936 --- /dev/null +++ b/test/pkg/netns/runp.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +ITER=$1 +shift + +rm -rf ./logs +mkdir -p ./logs + +fail() { + for f in ./logs/*; do + echo + echo "=== $f ===" + cat $f + done + echo + dmesg + echo "Test FAILED with $1" + exit 1 +} + +ns_before=$(ip netns list | wc -l) + +pids="" +for i in $(seq 1 "$ITER"); do + "$@" > "./logs/$1-$i.log" 2>&1 & + pid=$! + pids="$pids $pid" + echo "Test $i started with PID=$pid" +done + +for pid in $pids; do + wait "$pid" + [ $? -eq 0 ] || fail "$pid return non-zero" +done + +dmesg | grep -q 'Call Trace:' && fail "Kernel backtrace" + +# A message like: +# unregister_netdevice: waiting for lo to become free. Usage count = 1 +# is somewhat benign as it just waits for the ref count to go to 0. However +# it may become a problem if we have to many of them +nd=$(dmesg | grep -q 'unregister_netdevice' | wc -l) +[ "$nd" -gt 10 ] && fail "unregister_netdevice more than 10 times" + +ns_after=$(ip netns list | wc -l) +[ "$ns_before" != "$ns_after" ] && fail "NS leak: $ns_before != $ns_after" + +echo "netns test suite PASSED"