Skip to content

Commit 47f4f65

Browse files
committed
ci: detect outbound internet traffic generated while running tests
Resolves #31339
1 parent 936a8f8 commit 47f4f65

File tree

6 files changed

+68
-2
lines changed

6 files changed

+68
-2
lines changed

ci/test/00_setup_env.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export BASE_OUTDIR=${BASE_OUTDIR:-$BASE_SCRATCH_DIR/out}
6464
# The folder for previous release binaries.
6565
# This folder exists only on the ci guest, and on the ci host as a volume.
6666
export PREVIOUS_RELEASES_DIR=${PREVIOUS_RELEASES_DIR:-$BASE_ROOT_DIR/prev_releases}
67-
export CI_BASE_PACKAGES=${CI_BASE_PACKAGES:-build-essential pkgconf curl ca-certificates ccache python3-dev rsync git procps bison e2fsprogs cmake ninja-build}
67+
export CI_BASE_PACKAGES=${CI_BASE_PACKAGES:-build-essential pkgconf curl ca-certificates ccache python3-dev rsync git procps bison e2fsprogs cmake ninja-build net-tools tcpdump}
6868
export GOAL=${GOAL:-install}
6969
export DIR_QA_ASSETS=${DIR_QA_ASSETS:-${BASE_SCRATCH_DIR}/qa-assets}
7070
export CI_RETRY_EXE=${CI_RETRY_EXE:-"retry --"}

ci/test/00_setup_env_mac_native.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ export CI_OS_NAME="macos"
1515
export NO_DEPENDS=1
1616
export OSX_SDK=""
1717
export BITCOIN_CMD="bitcoin -m" # Used in functional tests
18+
# Can't run tcpdump: tcpdump: en0: You don't have permission to capture on that device
19+
export CI_TCPDUMP_OK_TO_FAIL=1

ci/test/00_setup_env_mac_native_fuzz.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ export RUN_UNIT_TESTS=false
1616
export RUN_FUNCTIONAL_TESTS=false
1717
export RUN_FUZZ_TESTS=true
1818
export GOAL="all"
19+
# Can't run tcpdump: tcpdump: en0: You don't have permission to capture on that device
20+
export CI_TCPDUMP_OK_TO_FAIL=1

ci/test/00_setup_env_native_alpine_musl.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export LC_ALL=C.UTF-8
88

99
export CONTAINER_NAME=ci_native_alpine_musl
1010
export CI_IMAGE_NAME_TAG="mirror.gcr.io/alpine:3.22"
11-
export CI_BASE_PACKAGES="build-base musl-dev pkgconf curl ccache make ninja git python3-dev py3-pip which patch xz procps rsync util-linux bison e2fsprogs cmake dash linux-headers"
11+
export CI_BASE_PACKAGES="build-base musl-dev pkgconf curl ccache make ninja git python3-dev py3-pip which patch xz procps rsync util-linux bison e2fsprogs cmake dash linux-headers net-tools tcpdump"
1212
export PIP_PACKAGES="--break-system-packages pyzmq pycapnp"
1313
export DEP_OPTS="DEBUG=1"
1414
export GOAL="install"

ci/test/02_run_container.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def main():
124124
cmd_run = ["docker", "run", "--rm", "--interactive", "--detach", "--tty"]
125125
cmd_run += [
126126
"--cap-add=LINUX_IMMUTABLE",
127+
"--cap-add=NET_RAW",
127128
*shlex.split(os.getenv("CI_CONTAINER_CAP", "")),
128129
f"--mount=type=bind,src={os.environ['BASE_READ_ONLY_DIR']},dst={os.environ['BASE_READ_ONLY_DIR']},readonly",
129130
f"--mount={CI_CCACHE_MOUNT}",

ci/test/03_test_script.sh

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,17 +171,73 @@ if [ "$RUN_CHECK_DEPS" = "true" ]; then
171171
"${BASE_ROOT_DIR}/contrib/devtools/check-deps.sh" "${BASE_BUILD_DIR}"
172172
fi
173173

174+
# Return a list of the non-loopback network interfaces on the machine, for example: docker0 enp3s0 wlp2s0
175+
function get_interfaces()
176+
{
177+
set -o pipefail
178+
ifconfig | awk -F ':| ' '/^[^[:space:]]/ { if (!match($1, /^lo/)) { print $1 } }'
179+
set +o pipefail
180+
}
181+
182+
# Generate a file name for storing raw packets captured by tcpdump.
183+
function tcpdump_file()
184+
{
185+
local test_name="$1"
186+
local interface_name="$2"
187+
echo "/tmp/tcpdump_${test_name}_$interface_name"
188+
}
189+
190+
# Start tcpdump on each non-loopback interface in the background.
191+
function traffic_monitor_begin()
192+
{
193+
test_name="$1"
194+
for ifname in $(get_interfaces) ; do
195+
tcpdump -nU -i "$ifname" -w "$(tcpdump_file "$test_name" "$ifname")" &
196+
done
197+
}
198+
199+
# Read tcpdump raw packet files that are generated by traffic_monitor_begin() and if any of them contain
200+
# data, then print the packets and exit with an error.
201+
function traffic_monitor_end()
202+
{
203+
test_name="$1"
204+
205+
for ifname in $(get_interfaces) ; do
206+
f=$(tcpdump_file "$test_name" "$ifname")
207+
if [ ! -e "$f" ] && [ "$CI_TCPDUMP_OK_TO_FAIL" = "1" ] ; then
208+
# In some CI environments this script is not running as root and so the
209+
# tcpdump errors and does not create $f. Skip silently those, but we
210+
# need at least one where tcpdump can run and this is the ASAN one. So
211+
# treat the absence of $f as an error only on the ASAN task.
212+
continue
213+
fi
214+
# We are running as root and those files are created with owner:group =
215+
# tcpdump:tcpdump and then `tcpdump -r` refuses to read them with an error
216+
# "permission denied" if they are not owned by root:root.
217+
chown root:root "$f"
218+
out="$(tcpdump -n -r "$f" --direction=out tcp or udp)"
219+
if [ -n "$out" ] ; then
220+
echo "Error: outbound TCP or UDP packets on the non loopback interface generated during $test_name tests:" >&2
221+
tcpdump -n -r "$f" tcp or udp
222+
exit 1
223+
fi
224+
done
225+
}
226+
174227
if [ "$RUN_UNIT_TESTS" = "true" ]; then
228+
traffic_monitor_begin "unit"
175229
DIR_UNIT_TEST_DATA="${DIR_UNIT_TEST_DATA}" \
176230
LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" \
177231
CTEST_OUTPUT_ON_FAILURE=ON \
178232
ctest --test-dir "${BASE_BUILD_DIR}" \
179233
--stop-on-failure \
180234
"${MAKEJOBS}" \
181235
--timeout $(( TEST_RUNNER_TIMEOUT_FACTOR * 60 ))
236+
traffic_monitor_end "unit"
182237
fi
183238

184239
if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then
240+
traffic_monitor_begin "functional"
185241
# parses TEST_RUNNER_EXTRA as an array which allows for multiple arguments such as TEST_RUNNER_EXTRA='--exclude "rpc_bind.py --ipv6"'
186242
eval "TEST_RUNNER_EXTRA=($TEST_RUNNER_EXTRA)"
187243
LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" \
@@ -194,9 +250,11 @@ if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then
194250
"${TEST_RUNNER_EXTRA[@]}" \
195251
--quiet \
196252
--failfast
253+
traffic_monitor_end "functional"
197254
fi
198255

199256
if [ "${RUN_TIDY}" = "true" ]; then
257+
traffic_monitor_begin "tidy"
200258
cmake -B /tidy-build -DLLVM_DIR=/usr/lib/llvm-"${TIDY_LLVM_V}"/cmake -DCMAKE_BUILD_TYPE=Release -S "${BASE_ROOT_DIR}"/contrib/devtools/bitcoin-tidy
201259
cmake --build /tidy-build "$MAKEJOBS"
202260
cmake --build /tidy-build --target bitcoin-tidy-tests "$MAKEJOBS"
@@ -239,9 +297,11 @@ if [ "${RUN_TIDY}" = "true" ]; then
239297

240298
run_iwyu "compile_commands_iwyu_warnings.json"
241299
git --no-pager diff
300+
traffic_monitor_end "tidy"
242301
fi
243302

244303
if [ "$RUN_FUZZ_TESTS" = "true" ]; then
304+
traffic_monitor_begin "fuzz"
245305
# shellcheck disable=SC2086
246306
LD_LIBRARY_PATH="${DEPENDS_DIR}/${HOST}/lib" \
247307
"${BASE_BUILD_DIR}/test/fuzz/test_runner.py" \
@@ -250,4 +310,5 @@ if [ "$RUN_FUZZ_TESTS" = "true" ]; then
250310
-l DEBUG \
251311
"${DIR_FUZZ_IN}" \
252312
--empty_min_time=60
313+
traffic_monitor_end "fuzz"
253314
fi

0 commit comments

Comments
 (0)