From c3fe152a48c122f74e3a4faab45305d8759008f6 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Mon, 24 Jun 2024 18:22:37 -0700 Subject: [PATCH] test: add the rtnetlink test suit It is much easier to create complex configurations from bash scripts with help of the iproute2 tools. PiperOrigin-RevId: 646286415 --- Makefile | 2 +- images/default/Dockerfile | 3 +- test/rtnetlink/BUILD | 24 ++++++++ test/rtnetlink/linux/BUILD | 40 +++++++++++++ test/rtnetlink/linux/bridge_test.sh | 59 +++++++++++++++++++ test/rtnetlink/linux/rtnetlink_test.sh | 80 ++++++++++++++++++++++++++ test/rtnetlink/linux/tcp_serv.go | 65 +++++++++++++++++++++ test/rtnetlink/linux/veth_test.sh | 41 +++++++++++++ test/runner/defs.bzl | 4 ++ test/runner/main.go | 2 +- 10 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 test/rtnetlink/BUILD create mode 100644 test/rtnetlink/linux/BUILD create mode 100755 test/rtnetlink/linux/bridge_test.sh create mode 100644 test/rtnetlink/linux/rtnetlink_test.sh create mode 100644 test/rtnetlink/linux/tcp_serv.go create mode 100755 test/rtnetlink/linux/veth_test.sh diff --git a/Makefile b/Makefile index 401874da7..cf1018b47 100644 --- a/Makefile +++ b/Makefile @@ -242,7 +242,7 @@ network-tests: iptables-tests packetdrill-tests packetimpact-tests .PHONY: network-tests syscall-tests: $(RUNTIME_BIN) ## Run all system call tests. - @$(call test,--test_env=RUNTIME=$(RUNTIME_BIN) --cxxopt=-Werror $(PARTITIONS) test/syscalls/...) + @$(call test,--test_env=RUNTIME=$(RUNTIME_BIN) --cxxopt=-Werror $(PARTITIONS) test/syscalls/... test/rtnetlink/...) .PHONY: syscall-tests packetimpact-tests: diff --git a/images/default/Dockerfile b/images/default/Dockerfile index c45e25786..9ae7005d0 100644 --- a/images/default/Dockerfile +++ b/images/default/Dockerfile @@ -8,7 +8,8 @@ RUN apt-get update && apt-get install -y curl gnupg2 git \ apt-transport-https ca-certificates gnupg-agent \ software-properties-common \ pkg-config libffi-dev patch diffutils libssl-dev iptables kmod \ - clang crossbuild-essential-amd64 erofs-utils busybox-static libbpf-dev + clang crossbuild-essential-amd64 erofs-utils busybox-static libbpf-dev \ + iproute2 netcat # This package is needed to build eBPF on amd64, but not on arm64 where it # doesn't exist. diff --git a/test/rtnetlink/BUILD b/test/rtnetlink/BUILD new file mode 100644 index 000000000..8b95ca704 --- /dev/null +++ b/test/rtnetlink/BUILD @@ -0,0 +1,24 @@ +load("//test/runner:defs.bzl", "syscall_test") + +package( + default_applicable_licenses = ["//:license"], + licenses = ["notice"], +) + +syscall_test( + size = "small", + container = True, + overlay = True, + save = False, + test = "//test/rtnetlink/linux:veth_test", + use_tmpfs = True, +) + +syscall_test( + size = "small", + container = True, + overlay = True, + save = False, + test = "//test/rtnetlink/linux:bridge_test", + use_tmpfs = True, +) diff --git a/test/rtnetlink/linux/BUILD b/test/rtnetlink/linux/BUILD new file mode 100644 index 000000000..e79d7b78d --- /dev/null +++ b/test/rtnetlink/linux/BUILD @@ -0,0 +1,40 @@ +load("//tools:defs.bzl", "go_binary") + +package( + default_applicable_licenses = ["//:license"], + default_visibility = [ + "//:sandbox", + ], + licenses = ["notice"], +) + +sh_library( + name = "rtnetlink_test", + srcs = ["rtnetlink_test.sh"], +) + +sh_binary( + name = "veth_test", + srcs = ["veth_test.sh"], + deps = [":rtnetlink_test"], +) + +sh_binary( + name = "bridge_test", + srcs = ["bridge_test.sh"], + data = [ + ":tcp_serv", + ], + deps = [ + ":rtnetlink_test", + ], +) + +go_binary( + name = "tcp_serv", + srcs = ["tcp_serv.go"], + pure = True, + deps = [ + "@org_golang_x_sys//unix:go_default_library", + ], +) diff --git a/test/rtnetlink/linux/bridge_test.sh b/test/rtnetlink/linux/bridge_test.sh new file mode 100755 index 000000000..4cf3ab32d --- /dev/null +++ b/test/rtnetlink/linux/bridge_test.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# Copyright 2024 The gVisor Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -xeo pipefail +source "$(dirname "$0")/rtnetlink_test.sh" +TCP_SRV="$(dirname "$0")/tcp_serv" +if [[ ! -f "$TCP_SRV" ]]; then + TCP_SRV="$(dirname "$0")/tcp_serv_/tcp_serv" +fi + +ip netns attach rootns "$$" +ip link add br0 type bridge +ip netns add test0 +ip netns add test1 + +ip link add hveth0 type veth peer name veth0 netns test0 +ip link add hveth1 type veth peer name veth1 netns test1 +ip link set up dev hveth0 +ip link set up dev hveth1 + +ip link set hveth0 master br0 +ip link set hveth1 master br0 + +ip addr add 192.168.0.3/24 dev br0 +ip link set up dev br0 +ip netns exec test0 ip link set up dev veth0 +ip netns exec test1 ip link set up dev veth1 +ip netns exec test0 ip addr add 192.168.0.1/24 dev veth0 +ip netns exec test1 ip addr add 192.168.0.2/24 dev veth1 + +check_connectivity test1 192.168.0.2 8800 test0 "ping from test0" +check_connectivity test1 192.168.0.2 8801 rootns "ping from rootns" + +# Destroy namespaces and the bridge. +ip netns del test0 +ip link del br0 +ip netns del test1 +if ! wait_for ! ip link show hveth0 2>/dev/null; then + fail "hveth0 hasn't been destroyed" +fi +if ! wait_for ! ip link show hveth1 2>/dev/null; then + fail "hveth1 hasn't been destroyed" +fi +if ! wait_for ! ip link show br0 2>/dev/null; then + fail "br0 hasn't been destroyed" +fi diff --git a/test/rtnetlink/linux/rtnetlink_test.sh b/test/rtnetlink/linux/rtnetlink_test.sh new file mode 100644 index 000000000..4e161228c --- /dev/null +++ b/test/rtnetlink/linux/rtnetlink_test.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# Copyright 2024 The gVisor Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -xeo pipefail + +fail() { + echo "FAIL: $*" + exit 1 +} + +# Don't report any test cases. +echo "$@" | grep list_tests && exit 1 + +if [[ -z "$TEST_ON_GVISOR" ]]; then + if [[ ! -d /var/run ]]; then + echo "SKIP: /var/run doesn't exist but it is required for the ip tool." + exit 0 + fi + mount -t tmpfs test /var/run +else + mkdir -p /var/run + mount -t tmpfs test /var/run +fi + +# check_connectivity checks that a TCP connection can be established between two +# specified namespaces. +# Arguments: +# * network namespace where a server will be started +# * IP address in the server network namespace. +# * port for a test TCP connection +# * network namespace where a client will be started +# * test message that will be send from server to client +check_connectivity() { + local srv_netns="$1" + local srv_ip="$2" + local port="$3" + local clt_netns="$4" + local test_msg="$5" + + # Create a sync pipe that will be closed when TCP_SRV creates a listen socket. + exec {pipe}<> <(:) + exec {pipe_r}/proc/self/fd/$pipe + exec {pipe}>&- + echo "$test_msg" | ip netns exec "$srv_netns" "$TCP_SRV" "--port=$port" "--sync-fd=$pipe_w" 1>&2 & + pid=$! + exec {pipe_w}>&- + cat <&$pipe_r + exec {pipe_r}>&- + # The server has been started. + + out=$(ip netns exec "$clt_netns" nc -nvd "$srv_ip" "$port") + if [[ "$out" != "$test_msg" ]]; then + fail "unexpected output '$out' (expected '$test_msg'" + fi + wait "$pid" +} + +wait_for() { + for _ in $(seq 50); do + if eval "$@"; then + return 0 + fi + sleep 0.1 + done + return 1 +} diff --git a/test/rtnetlink/linux/tcp_serv.go b/test/rtnetlink/linux/tcp_serv.go new file mode 100644 index 000000000..447180ea6 --- /dev/null +++ b/test/rtnetlink/linux/tcp_serv.go @@ -0,0 +1,65 @@ +// Copyright 2024 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// tcp_serv creates a TCP server socket, reads data from stdin, and sends that +// data to the socket. The same thing can be done with the nc tool, but +// tcp_serv can signal when a tcp socket is created. +package main + +import ( + "flag" + "fmt" + "io" + "log" + "net" + "os" + + "golang.org/x/sys/unix" +) + +func main() { + port := flag.Int("port", 8888, "port to listen on") + syncFD := flag.Int("sync-fd", -1, "file descriptor that will be closed after creating a socket") + flag.Parse() + address := fmt.Sprintf(":%d", *port) + + listener, err := net.Listen("tcp", address) + if err != nil { + log.Fatalf("Error creating listener: %s", err) + return + } + defer listener.Close() + if *syncFD >= 0 { + unix.Close(*syncFD) + } + + conn, err := listener.Accept() + if err != nil { + log.Fatalf("Error accepting connection: %s", err) + } + defer conn.Close() + + data := make([]byte, 1024) + for { + count, err := os.Stdin.Read(data) + if err == io.EOF { + break + } + + _, err = conn.Write(data[:count]) + if err != nil { + log.Fatalf("Error sending data: %s", err) + } + } +} diff --git a/test/rtnetlink/linux/veth_test.sh b/test/rtnetlink/linux/veth_test.sh new file mode 100755 index 000000000..d1c10fffa --- /dev/null +++ b/test/rtnetlink/linux/veth_test.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# Copyright 2024 The gVisor Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -xeo pipefail +source "$(dirname "$0")/rtnetlink_test.sh" + +# Create a new veth pair in the current namespace. +ip link add name test_veth01 type veth peer name test_veth02 +ip a +ip a | grep test_veth01 +ip a | grep test_veth02 +ip link del name test_veth01 +ip a +# Check that test_veth02 has been destroyed. +if ! wait_for ! ip link show test_veth02; then + fail "test_veth02 hasn't been destroyed" + exit 1 +fi + +ip netns add test +ip link add test_veth01 type veth peer name test_veth02 netns test +ip link show test_veth01 +ip netns exec test ip link show test_veth02 +ip netns del test +if ! wait_for ! ip link show test_veth01; then + fail "test_veth01 hasn't been destroyed" +fi + diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl index b147019ea..fa83fb258 100644 --- a/test/runner/defs.bzl +++ b/test/runner/defs.bzl @@ -211,6 +211,7 @@ def syscall_test_variants( save_resume = False, size = "medium", timeout = None, + overlay = False, **kwargs): """Generates syscall tests for all variants. @@ -258,6 +259,7 @@ def syscall_test_variants( save_resume = save_resume, size = size, timeout = timeout, + overlay = overlay, **kwargs ) @@ -367,6 +369,7 @@ def syscall_test( tags = None, save = True, size = "medium", + overlay = False, **kwargs): """syscall_test is a macro that will create targets for all platforms. @@ -430,6 +433,7 @@ def syscall_test( False, # save, generate all tests without save variant. False, # save_resume, generate all tests without save_resume variant. size, + overlay = overlay, **kwargs ) diff --git a/test/runner/main.go b/test/runner/main.go index 88ae581c8..b8f4b5659 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -186,7 +186,7 @@ func runTestCaseNative(testBin string, tc *gtest.TestCase, args []string, t *tes // setup_container takes in its target argv as positional arguments. cmd.Path = getSetupContainerPath() cmd.Args = append([]string{cmd.Path}, cmd.Args...) - cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUSER | unix.CLONE_NEWNET | unix.CLONE_NEWIPC | unix.CLONE_NEWUTS + cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUSER | unix.CLONE_NEWNET | unix.CLONE_NEWIPC | unix.CLONE_NEWUTS | unix.CLONE_NEWNS // Set current user/group as root inside the namespace. cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{ {ContainerID: 0, HostID: os.Getuid(), Size: 1},