github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/packetdrill/packetdrill_test.sh (about) 1 #!/bin/bash 2 3 # Copyright 2020 The gVisor Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 # Run a packetdrill test. Two docker containers are made, one for the 18 # Device-Under-Test (DUT) and one for the test runner. Each is attached with 19 # two networks, one for control packets that aid the test and one for test 20 # packets which are sent as part of the test and observed for correctness. 21 22 set -euxo pipefail 23 24 function failure() { 25 local lineno=$1 26 local msg=$2 27 local filename="$0" 28 echo "FAIL: $filename:$lineno: $msg" 29 } 30 trap 'failure ${LINENO} "$BASH_COMMAND"' ERR 31 32 declare -r LONGOPTS="dut_platform:,init_script:,runtime:,partition:,total_partitions:" 33 34 # Don't use declare below so that the error from getopt will end the script. 35 PARSED=$(getopt --options "" --longoptions=$LONGOPTS --name "$0" -- "$@") 36 37 eval set -- "$PARSED" 38 39 while true; do 40 case "$1" in 41 --dut_platform) 42 # Either "linux" or "netstack". 43 declare -r DUT_PLATFORM="$2" 44 shift 2 45 ;; 46 --init_script) 47 declare -r INIT_SCRIPT="$2" 48 shift 2 49 ;; 50 --runtime) 51 declare RUNTIME="$2" 52 shift 2 53 ;; 54 --partition) 55 # Ignored. 56 shift 2 57 ;; 58 --total_partitions) 59 # Ignored. 60 shift 2 61 ;; 62 --) 63 shift 64 break 65 ;; 66 *) 67 echo "Programming error" 68 exit 3 69 esac 70 done 71 72 # All the other arguments are scripts. 73 declare -r scripts="$@" 74 75 # Check that the required flags are defined in a way that is safe for "set -u". 76 if [[ "${DUT_PLATFORM-}" == "netstack" ]]; then 77 if [[ -z "${RUNTIME-}" ]]; then 78 echo "FAIL: Missing --runtime argument: ${RUNTIME-}" 79 exit 2 80 fi 81 declare -r RUNTIME_ARG="--runtime ${RUNTIME}" 82 elif [[ "${DUT_PLATFORM-}" == "linux" ]]; then 83 declare -r RUNTIME_ARG="" 84 else 85 echo "FAIL: Bad or missing --dut_platform argument: ${DUT_PLATFORM-}" 86 exit 2 87 fi 88 if [[ ! -x "${INIT_SCRIPT-}" ]]; then 89 echo "FAIL: Bad or missing --init_script: ${INIT_SCRIPT-}" 90 exit 2 91 fi 92 93 function new_net_prefix() { 94 # Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24. 95 echo "$(shuf -i 192-223 -n 1).$(shuf -i 0-255 -n 1).$(shuf -i 0-255 -n 1)" 96 } 97 98 # Variables specific to the control network and interface start with CTRL_. 99 # Variables specific to the test network and interface start with TEST_. 100 # Variables specific to the DUT start with DUT_. 101 # Variables specific to the test runner start with TEST_RUNNER_. 102 declare -r PACKETDRILL="/packetdrill/gtests/net/packetdrill/packetdrill" 103 # Use random numbers so that test networks don't collide. 104 declare CTRL_NET="ctrl_net-$(shuf -i 0-99999999 -n 1)" 105 declare CTRL_NET_PREFIX=$(new_net_prefix) 106 declare TEST_NET="test_net-$(shuf -i 0-99999999 -n 1)" 107 declare TEST_NET_PREFIX=$(new_net_prefix) 108 declare -r tolerance_usecs=100000 109 # On both DUT and test runner, testing packets are on the eth2 interface. 110 declare -r TEST_DEVICE="eth2" 111 # Number of bits in the *_NET_PREFIX variables. 112 declare -r NET_MASK="24" 113 # Last bits of the DUT's IP address. 114 declare -r DUT_NET_SUFFIX=".10" 115 # Control port. 116 declare -r CTRL_PORT="40000" 117 # Last bits of the test runner's IP address. 118 declare -r TEST_RUNNER_NET_SUFFIX=".20" 119 declare -r TIMEOUT="60" 120 declare -r IMAGE_TAG="gcr.io/gvisor-presubmit/packetdrill" 121 122 # Make sure that docker is installed. 123 docker --version 124 125 function finish { 126 local cleanup_success=1 127 for net in "${CTRL_NET}" "${TEST_NET}"; do 128 # Kill all processes attached to ${net}. 129 for docker_command in "kill" "rm"; do 130 (docker network inspect "${net}" \ 131 --format '{{range $key, $value := .Containers}}{{$key}} {{end}}' \ 132 | xargs -r docker "${docker_command}") || \ 133 cleanup_success=0 134 done 135 # Remove the network. 136 docker network rm "${net}" || \ 137 cleanup_success=0 138 done 139 140 if ((!$cleanup_success)); then 141 echo "FAIL: Cleanup command failed" 142 exit 4 143 fi 144 } 145 trap finish EXIT 146 147 # Subnet for control packets between test runner and DUT. 148 while ! docker network create \ 149 "--subnet=${CTRL_NET_PREFIX}.0/${NET_MASK}" "${CTRL_NET}"; do 150 sleep 0.1 151 CTRL_NET_PREFIX=$(new_net_prefix) 152 CTRL_NET="ctrl_net-$(shuf -i 0-99999999 -n 1)" 153 done 154 155 # Subnet for the packets that are part of the test. 156 while ! docker network create \ 157 "--subnet=${TEST_NET_PREFIX}.0/${NET_MASK}" "${TEST_NET}"; do 158 sleep 0.1 159 TEST_NET_PREFIX=$(new_net_prefix) 160 TEST_NET="test_net-$(shuf -i 0-99999999 -n 1)" 161 done 162 163 # Create the DUT container and connect to network. 164 DUT=$(docker create ${RUNTIME_ARG} --privileged --rm \ 165 --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG}) 166 docker network connect "${CTRL_NET}" \ 167 --ip "${CTRL_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \ 168 || (docker kill ${DUT}; docker rm ${DUT}; false) 169 docker network connect "${TEST_NET}" \ 170 --ip "${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" "${DUT}" \ 171 || (docker kill ${DUT}; docker rm ${DUT}; false) 172 docker start "${DUT}" 173 174 # Create the test runner container and connect to network. 175 TEST_RUNNER=$(docker create --privileged --rm \ 176 --stop-timeout ${TIMEOUT} -it ${IMAGE_TAG}) 177 docker network connect "${CTRL_NET}" \ 178 --ip "${CTRL_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" "${TEST_RUNNER}" \ 179 || (docker kill ${TEST_RUNNER}; docker rm ${REST_RUNNER}; false) 180 docker network connect "${TEST_NET}" \ 181 --ip "${TEST_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" "${TEST_RUNNER}" \ 182 || (docker kill ${TEST_RUNNER}; docker rm ${REST_RUNNER}; false) 183 docker start "${TEST_RUNNER}" 184 185 # Run tcpdump in the test runner unbuffered, without dns resolution, just on the 186 # interface with the test packets. 187 docker exec -t ${TEST_RUNNER} tcpdump -U -n -i "${TEST_DEVICE}" & 188 189 # Start a packetdrill server on the test_runner. The packetdrill server sends 190 # packets and asserts that they are received. 191 docker exec -d "${TEST_RUNNER}" \ 192 ${PACKETDRILL} --wire_server --wire_server_dev="${TEST_DEVICE}" \ 193 --wire_server_ip="${CTRL_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" \ 194 --wire_server_port="${CTRL_PORT}" \ 195 --local_ip="${TEST_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" \ 196 --remote_ip="${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" 197 198 # Because the Linux kernel receives the SYN-ACK but didn't send the SYN it will 199 # issue a RST. To prevent this IPtables can be used to filter those out. 200 docker exec "${TEST_RUNNER}" \ 201 iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP 202 203 # Wait for the packetdrill server on the test runner to come. Attempt to 204 # connect to it from the DUT every 100 milliseconds until success. 205 while ! docker exec "${DUT}" \ 206 nc -zv "${CTRL_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" "${CTRL_PORT}"; do 207 sleep 0.1 208 done 209 210 # Copy the packetdrill setup script to the DUT. 211 docker cp -L "${INIT_SCRIPT}" "${DUT}:packetdrill_setup.sh" 212 213 # Copy the packetdrill scripts to the DUT. 214 declare -a dut_scripts 215 for script in $scripts; do 216 docker cp -L "${script}" "${DUT}:$(basename ${script})" 217 dut_scripts+=("/$(basename ${script})") 218 done 219 220 # Start a packetdrill client on the DUT. The packetdrill client runs POSIX 221 # socket commands and also sends instructions to the server. 222 docker exec -t "${DUT}" \ 223 ${PACKETDRILL} --wire_client --wire_client_dev="${TEST_DEVICE}" \ 224 --wire_server_ip="${CTRL_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" \ 225 --wire_server_port="${CTRL_PORT}" \ 226 --local_ip="${TEST_NET_PREFIX}${DUT_NET_SUFFIX}" \ 227 --remote_ip="${TEST_NET_PREFIX}${TEST_RUNNER_NET_SUFFIX}" \ 228 --init_scripts=/packetdrill_setup.sh \ 229 --tolerance_usecs="${tolerance_usecs}" "${dut_scripts[@]}" 230 231 echo PASS: No errors.