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.