tc #236
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: ci | |
on: | |
push: | |
pull_request: | |
env: | |
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true | |
PLATFORMS: "linux/amd64 linux/arm64" | |
permissions: | |
packages: write | |
jobs: | |
make_check: | |
strategy: | |
fail-fast: false | |
matrix: | |
combination: | |
#Debug | |
- { arch: 'linux/amd64', kernel: 'Linux 6.8', runner: 'ubuntu-24.04', clangs: 'clang-14 clang-15 clang-16 clang-17 clang-18', debug: '1' } | |
- { arch: 'linux/arm64', kernel: 'Linux 6.8', runner: 'ubuntu-24.04', clangs: 'clang-14 clang-15 clang-16 clang-17 clang-18', debug: '1' } | |
#NDEBUG | |
- { arch: 'linux/amd64', kernel: 'Linux 6.8', runner: 'ubuntu-24.04', clangs: 'clang-14 clang-15 clang-16 clang-17 clang-18', debug: '0' } | |
- { arch: 'linux/arm64', kernel: 'Linux 6.8', runner: 'ubuntu-24.04', clangs: 'clang-14 clang-15 clang-16 clang-17 clang-18', debug: '0' } | |
runs-on: ${{ matrix.combination.runner }} | |
env: | |
CLANGS: ${{matrix.combination.clangs}} | |
DEBUG: ${{matrix.combination.debug}} | |
steps: | |
- name: "Checkout sfunnel" | |
uses: actions/checkout@v4 | |
with: | |
path: sfunnel | |
fetch-depth: 0 | |
fetch-tags: 1 | |
- name: Set up QEMU for ARM64 | |
if: matrix.combination.arch == 'linux/arm64' | |
uses: docker/setup-qemu-action@v2 | |
with: | |
platforms: ${{matrix.combination.arch}} | |
- name: Set up QMEU run environment if not amd64 | |
if: matrix.combination.arch == 'linux/arm64' | |
run: | | |
docker run --name test --platform linux/arm64 -d arm64v8/ubuntu bash -c "sleep infinity" | |
echo "QEMU_ENV=docker exec -e CLANGS=${CLANGS} -e DEBUG=${DEBUG} test bash -c " >> $GITHUB_ENV | |
docker exec test bash -c "apt-get update && apt-get install -y sudo software-properties-common" | |
- name: "Install deps..." | |
run: | | |
$QEMU_ENV sudo add-apt-repository universe | |
$QEMU_ENV sudo apt-get update | |
$QEMU_ENV sudo apt-get install -y ${{matrix.combination.clangs}} make iproute2 \ | |
bridge-utils python3-scapy python3-pip libbpf-dev \ | |
libelf-dev linux-headers-generic \ | |
linux-libc-dev llvm iptables | |
$QEMU_ENV sudo ln -s /usr/include/x86_64-linux-gnu/asm /usr/include/asm | |
$QEMU_ENV sudo apt-get install -y python3-pytest | |
- name: "Allow test traffic in iptables/nftables" | |
run: | | |
$QEMU_ENV sudo iptables -L -n | |
$QEMU_ENV sudo iptables -t nat -L -n | |
$QEMU_ENV sudo iptables -I FORWARD -s 11.1.1.1 -j ACCEPT | |
- name: "Run tests on '${{matrix.combination.clangs}}'..." | |
run: | | |
$QEMU_ENV cd sfunnel/test; \ | |
for CLANG in ${CLANGS}; do \ | |
echo \"[${CLANG}] Running tests ...\"; \ | |
make VERBOSE=1 CLANG=${CLANG} DEBUG=${DEBUG} || ( echo \"FAILED: test failed with '${CLANG}' debug='${DEBUG}'\" && exit 1 );\ | |
make clean 2>&1 > /dev/null; \ | |
echo \"[${CLANG}]\"; \ | |
done | |
docker_build_test_publish: | |
needs: [make_check] | |
runs-on: ubuntu-22.04 | |
steps: | |
- name: "Checkout sfunnel" | |
uses: actions/checkout@v4 | |
with: | |
path: sfunnel | |
fetch-depth: 0 | |
fetch-tags: 1 | |
- name: "Set up Docker buildx" | |
uses: docker/setup-buildx-action@v3 | |
- name: "Login to GitHub Container Registry (ghcr.io)" | |
uses: docker/login-action@v3 | |
with: | |
registry: ghcr.io | |
username: ${{github.actor}} | |
password: ${{secrets.GITHUB_TOKEN}} | |
- name: "Build container" | |
run: | | |
#Cross-build | |
cd sfunnel | |
echo "Fix mess with tags in actions/checkout..." | |
git fetch -f && git fetch -f --tags | |
docker buildx build --platform ${PLATFORMS} -t sfunnel --build-arg VERSION="$(git describe)" --build-arg COMMIT="${GITHUB_SHA}" --load -f docker/Dockerfile . | |
- name: "[TEST] Run container without env. nor file. Should fail..." | |
run: | | |
set -o pipefail | |
set +e | |
docker run --privileged --network=host -v /var/run/netns:/var/run/netns -e NETNS=test_ns_invalid -e DEBUG=1 -e IFACES=lo sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -eq 0 ]; then | |
echo "ERROR: container succeded when it should have FAILED!" | |
exit 1 | |
fi | |
set -e | |
- name: "[TEST] Run container with ruleset file..." | |
run: | | |
RULE="ip saddr 127.0.0.1 udp dport 80 actions unfunnel udp" | |
echo "$RULE" > ruleset | |
set -o pipefail | |
docker run --privileged -v `pwd`/ruleset:/etc/sfunnel/ruleset 2>&1 sfunnel:latest | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
grep "Recompiling sfunnel BPF program" output || (echo "ERROR: unable to validate it loads the rulset file" && exit 1) | |
grep "$RULE" output || (echo "ERROR: unable to validate it loads the ruleset file" && exit 1) | |
- name: "[TEST] Run container with ruleset via SFUNNEL_RULESET..." | |
run: | | |
RULE="ip saddr 127.0.0.2 udp dport 80 actions unfunnel udp" #Should override ruleset file with 127.0.0.1 | |
set -o pipefail | |
docker run -e SFUNNEL_RULESET="$RULE" --privileged -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
grep "SFUNNEL_RULESET='$RULE'" output || (echo "ERROR: unable to validate it loads ruleset via SFUNNEL_RULESET" && exit 1) | |
grep "Recompiling sfunnel BPF program" output || (echo "ERROR: unable to validate it loads ruleset via SFUNNEL_RULESET" && exit 1) | |
grep "$RULE" output || (echo "ERROR: unable to validate it loads custom ruleset via SFUNNEL_RULESET" && exit 1) | |
- name: "[TEST] Run container with custom params ..." | |
run: | | |
set -o pipefail | |
docker run -e N_ATTEMPTS=7 -e RETRY_DELAY=3 -e IFACES="lo" -e DEBUG=1 -v `pwd`/ruleset:/etc/sfunnel/ruleset --privileged sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
grep "\$DEBUG='1'" output || (echo "ERROR: unable to validate it loads params (DEBUG)" && exit 1) | |
grep "\-DDEBUG=1" output || (echo "ERROR: unable to validate it loads params (DEBUG), CFLAGS" && exit 1) | |
grep "\$N_ATTEMPTS='7'" output || (echo "ERROR: unable to validate it loads params (N_ATTEMPTS)" && exit 1) | |
grep "\$RETRY_DELAY='3'" output || (echo "ERROR: unable to validate it loads params (RETRY_DELAY)" && exit 1) | |
grep "\$IFACES='lo'" output || (echo "ERROR: unable to validate it loads params (IFACES)" && exit 1) | |
#Must recompile due to DEBUG=1 | |
grep "Recompiling sfunnel BPF program" output || (echo "ERROR: unable to validate it loads params (DEBUG)" && exit 1) | |
grep "\-DDEBUG=1" output || (echo "ERROR: unable to validate it loads params (DEBUG, CFLAGS)" && exit 1) | |
- name: "[TEST] Run container with DEBUG=1 ..." | |
run: | | |
RULE="ip saddr 127.0.0.1 udp dport 80 actions unfunnel udp" | |
echo "$RULE" > ruleset | |
set -o pipefail | |
docker run --privileged -e DEBUG=1 -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
grep "\$DEBUG='1'" output || (echo "ERROR: unable to validate it loads custom file ruleset DEBUG=1" && exit 1) | |
grep "\-DDEBUG=1" output || (echo "ERROR: unable to validate it loads custom file ruleset DEBUG=1, CFLAGS" && exit 1) | |
grep "Recompiling sfunnel BPF program" output || (echo "ERROR: unable to validate it loads custom file ruleset DEBUG=1" && exit 1) | |
grep "$RULE" output || (echo "ERROR: unable to validate it loads custom file ruleset DEBUG=1" && exit 1) | |
- name: "[TEST] Run container with IFACES=invalid ..." | |
run: | | |
set -o pipefail | |
set +e | |
docker run --privileged -e DEBUG=1 -e IFACES=invalid -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -eq 0 ]; then | |
echo "ERROR: container succeded when it should have FAILED!" | |
exit 1 | |
fi | |
set -e | |
- name: "[TEST] Run container with DIRECTION=invalid ..." | |
run: | | |
set -o pipefail | |
set +e | |
docker run --privileged -e DEBUG=1 -e DIRECTION=invalid -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -eq 0 ]; then | |
echo "ERROR: container succeded when it should have FAILED!" | |
exit 1 | |
fi | |
set -e | |
grep "FATAL: Invalid traffic direction" output || (echo "ERROR: unable to validate container correctly fails when DIRECTION is invalid" && exit 1) | |
- name: "[TEST] Run container with DIRECTION=ingress ..." | |
run: | | |
set -o pipefail | |
docker run --privileged -e DEBUG=1 -e DIRECTION=ingress -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
(grep "Attaching BPF program" output | grep "direction 'ingress'") || (echo "ERROR: unable to validate container attaches to DIRECTION=ingress" && exit 1) | |
- name: "[TEST] Run container with DIRECTION=egress ..." | |
run: | | |
set -o pipefail | |
docker run --privileged -e DEBUG=1 -e DIRECTION=egress -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
(grep "Attaching BPF program" output | grep "direction 'egress'") || (echo "ERROR: unable to validate container attaches to DIRECTION=egress" && exit 1) | |
- name: "[TEST] Run container with DIRECTION=both ..." | |
run: | | |
set -o pipefail | |
docker run --privileged -e DEBUG=1 -e DIRECTION=both -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
(grep "Attaching BPF program" output | grep "direction 'ingress'") || (echo "ERROR: unable to validate container attaches to DIRECTION=both" && exit 1) | |
(grep "Attaching BPF program" output | grep "direction 'egress'") || (echo "ERROR: unable to validate container attaches to DIRECTION=both" && exit 1) | |
- name: "[TEST] Run container with CLEAN=1 ..." | |
run: | | |
set -o pipefail | |
#Create a pair of veths | |
sudo ip link add type veth | |
sudo ip link set up dev veth0 | |
sudo ip link set up dev veth1 | |
#First attach to veth | |
docker run --privileged --network=host -e DEBUG=1 -e DIRECTION=both -e IFACES=veth0 -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
(tc filter show dev veth0 ingress | grep sfunnel) || (echo "ERROR: unable to validate container attaches to ingress" && exit 1) | |
(tc filter show dev veth0 egress | grep sfunnel) || (echo "ERROR: unable to validate container attaches to egress" && exit 1) | |
#Run CLEAN=1 | |
docker run --privileged --network=host -e DEBUG=1 -e CLEAN=1 -e DIRECTION=both -e IFACES=veth0 sfunnel:latest 2>&1 | tee output | |
[[ "$(tc filter show dev veth0 ingress)" == "" ]] || (echo "ERROR: unable to validate container removes BPF programs from ingress" && exit 1) | |
[[ "$(tc filter show dev veth0 egress)" == "" ]] || (echo "ERROR: unable to validate container removes BPF programs from egress" && exit 1) | |
- name: "[TEST] Run container with NETNS=test_ns ..." | |
run: | | |
set -o pipefail | |
#Create a pair of veths | |
sudo ip netns add test_ns | |
sudo ip netns exec test_ns ip link set up dev lo | |
#First run with an invalid netns, make sure it fails | |
set +e | |
docker run --privileged --network=host -v /var/run/netns:/var/run/netns -v `pwd`/ruleset:/etc/sfunnel/ruleset -e NETNS=test_ns_invalid -e DEBUG=1 -e IFACES=lo sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -eq 0 ]; then | |
echo "ERROR: container succeded when it should have FAILED!" | |
exit 1 | |
fi | |
#Then run it with a valid netns but invalid IFACE, and make sure it propagates the error code | |
docker run --privileged --network=host -v /var/run/netns:/var/run/netns -e NETNS=test_ns -e DEBUG=1 -e IFACES=lo2 sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -eq 0 ]; then | |
echo "ERROR: container succeded when it should have FAILED!" | |
exit 1 | |
fi | |
set -e | |
#Successful run | |
docker run --privileged --network=host -v /var/run/netns:/var/run/netns -v `pwd`/ruleset:/etc/sfunnel/ruleset -e NETNS=test_ns -e DEBUG=1 -e IFACES=lo sfunnel:latest 2>&1 | tee output | |
if [ ${PIPESTATUS[0]} -ne 0 ]; then | |
echo "ERROR: container execution FAILED!" | |
exit 1 | |
fi | |
[[ "$(sudo ip netns exec test_ns tc filter show dev lo ingress | grep sfunnel)" != "" ]] || (echo "ERROR: unable to validate container attaches to ingress when NETNS is set" && exit 1) | |
(grep "\$DEBUG='1'" output) || (echo "ERROR: unable to validate env. variables are passed to the NETNS execution" && exit 1) | |
- name: "Push to ghcr" | |
run: | | |
cd sfunnel | |
export TAG=$(git describe HEAD | sed 's/-.*$//g' | tr -d "v") | |
export EXACT_TAG=$(git describe --exact-match --match "v*" || echo "") | |
echo "TAG=${TAG}, EXACT_TAG=${EXACT_TAG}" | |
if [[ "${EXACT_TAG}" != "" ]]; then | |
echo "Pushing to ghcr.io..." | |
docker buildx build --platform ${PLATFORMS} --push -f docker/Dockerfile . --tag ghcr.io/${GITHUB_REPOSITORY}:${TAG} | |
fi |