NETAVARK=${NETAVARK:-./bin/netavark}
NETAVARK_CONNECTION_TESTER=${NETAVARK_CONNECTION_TESTER:-./bin/netavark-connection-tester}
TESTSDIR=${TESTSDIR:-$(dirname ${BASH_SOURCE})}
export RUST_BACKTRACE=full
HOST_NS_PID=
CONTAINER_NS_PIDS=()
function create_container_ns() {
CONTAINER_NS_PIDS+=("$(create_netns)")
}
function basic_setup() {
HOST_NS_PID=$(create_netns)
create_container_ns
export DBUS_SYSTEM_BUS_ADDRESS=
NETAVARK_TMPDIR=$(mktemp -d --tmpdir=${BATS_TMPDIR:-/tmp} netavark_bats.XXXXXX)
rootless=false
if [[ ! -e "/run/dbus/system_bus_socket" ]]; then
rootless=true
fi
mkdir -p "$NETAVARK_TMPDIR/config"
run_in_host_netns ip link set lo up
}
function basic_teardown() {
teardown_firewalld
kill -9 $HOST_NS_PID
for i in "${!CONTAINER_NS_PIDS[@]}"; do
kill -9 "${CONTAINER_NS_PIDS[$i]}"
done
rm -rf "$NETAVARK_TMPDIR"
}
function setup_firewalld() {
DBUS_SYSTEM_BUS_ADDRESS=unix:path=$NETAVARK_TMPDIR/netavark-firewalld
run_in_host_netns dbus-daemon --address="$DBUS_SYSTEM_BUS_ADDRESS" --print-pid --config-file="${TESTSDIR}/testfiles/firewalld-dbus.conf"
DBUS_PID="$output"
export DBUS_SYSTEM_BUS_ADDRESS
nsenter -n -t $HOST_NS_PID firewalld --nopid --nofork --system-config "$NETAVARK_TMPDIR" &>"$NETAVARK_TMPDIR/firewalld.log" &
FIREWALLD_PID=$!
echo "firewalld pid: $FIREWALLD_PID"
timeout=5
while [ $timeout -gt 0 ]; do
expected_rc="?" run_in_host_netns firewall-cmd --state
if [ "$status" -eq 0 ]; then
break
fi
sleep 1
timeout=$(($timeout - 1))
if [ $timeout -eq 0 ]; then
cat "$NETAVARK_TMPDIR/firewalld.log"
die "failed to start firewalld - timeout"
fi
done
}
function teardown_firewalld() {
if [ -n "${NETAVARK_FIREWALLD_RELOAD_PID}" ]; then
kill -9 $NETAVARK_FIREWALLD_RELOAD_PID
fi
if [ -n "${FIREWALLD_PID}" ]; then
kill -9 $FIREWALLD_PID
fi
if [ -n "${DBUS_PID}" ]; then
kill -9 $DBUS_PID
fi
unset DBUS_SYSTEM_BUS_ADDRESS
}
function setup() {
basic_setup
}
function teardown() {
basic_teardown
}
function create_netns() {
unshare -nm --propagation private sleep inf &>/dev/null &
mkdir -p /run/sysctl.d
nsenter -n -m -w -t $! mount -t tmpfs none /run/sysctl.d
echo $!
}
function get_container_netns_path() {
local which="0"
if [[ $# -eq 1 ]]; then
which=$1
fi
echo /proc/"${CONTAINER_NS_PIDS[$which]}"/ns/net
}
function run_netavark() {
run_in_host_netns $NETAVARK --rootless "$rootless" \
--config "$NETAVARK_TMPDIR/config" "$@"
}
function run_netavark_firewalld_reload() {
nsenter -n -t $HOST_NS_PID $NETAVARK --config "$NETAVARK_TMPDIR/config" firewalld-reload &
NETAVARK_FIREWALLD_RELOAD_PID=$!
}
function run_in_container_netns() {
local i="0"
isnum='^[0-9]+$'
if [[ $1 =~ $isnum ]]; then
i=$1
shift 1
fi
run_helper nsenter -n -m -w -t "${CONTAINER_NS_PIDS[$i]}" "$@"
}
function run_in_host_netns() {
run_helper nsenter -n -m -w -t $HOST_NS_PID "$@"
}
function run_helper() {
expected_rc="${expected_rc-0}"
if [ "$expected_rc" == "?" ]; then
expected_rc=
fi
MOST_RECENT_COMMAND="$*"
echo "$_LOG_PROMPT $*"
run timeout --foreground -v --kill=10 10 "$@" 3>/dev/null
if [ -n "$output" ]; then
echo "$output"
fi
if [ "$status" -ne 0 ]; then
echo -n "[ rc=$status "
if [ -n "$expected_rc" ]; then
if [ "$status" -eq "$expected_rc" ]; then
echo -n "(expected) "
else
echo -n "(** EXPECTED $expected_rc **) "
fi
fi
echo "]"
fi
if [ "$status" -eq 124 ]; then
if expr "$output" : ".*timeout: sending" >/dev/null; then
if [[ "$expected_rc" != "124" ]]; then
echo "*** TIMED OUT ***"
false
fi
fi
fi
if [ -n "$expected_rc" ]; then
if [ "$status" -ne "$expected_rc" ]; then
die "exit code is $status; expected $expected_rc"
fi
fi
unset expected_rc
}
function die() {
echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2
echo "#| FAIL: $*" >&2
echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2
false
}
function assert() {
local actual_string="$output"
local operator='=='
local expect_string="$1"
local testname="$2"
case "${#*}" in
0) die "Internal error: 'assert' requires one or more arguments" ;;
1 | 2) ;;
3 | 4)
actual_string="$1"
operator="$2"
expect_string="$3"
testname="$4"
;;
*) die "Internal error: too many arguments to 'assert'" ;;
esac
local not=
local actual_op="$operator"
if [[ $operator == '!~' ]]; then
not='!'
actual_op='=~'
fi
if [[ $operator == '=' || $operator == '==' ]]; then
if [ "$actual_string" = "$expect_string" ]; then
return
fi
elif [[ $operator == '!=' ]]; then
if [ "$actual_string" != "$expect_string" ]; then
return
fi
else
if eval "[[ $not \$actual_string $actual_op \$expect_string ]]"; then
return
elif [ $? -gt 1 ]; then
die "Internal error: could not process 'actual' $operator 'expect'"
fi
fi
if [ -z "$testname" ]; then
testname="${MOST_RECENT_BUILDAH_COMMAND:-[no test name given]}"
fi
local op=''
local ws=''
if [ "$operator" != '==' ]; then
op="$operator "
ws=$(printf "%*s" ${#op} "")
fi
local actual_split
IFS=$'\n' read -rd '' -a actual_split <<<"$actual_string" || true
printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2
printf "#| FAIL: %s\n" "$testname" >&2
printf "#| expected: %s'%s'\n" "$op" "$expect_string" >&2
printf "#| actual: %s'%s'\n" "$ws" "${actual_split[0]}" >&2
local line
for line in "${actual_split[@]:1}"; do
printf "#| > %s'%s'\n" "$ws" "$line" >&2
done
printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2
false
}
function assert_json() {
local actual_json="$output"
local operator='=='
local jq_query="$1"
local expect_string="$2"
local testname="$3"
case "${#*}" in
0 | 1) die "Internal error: 'assert_json' requires two or more arguments" ;;
2 | 3) ;;
4 | 5)
actual_json="$1"
jq_query="$2"
operator="$3"
expect_string="$4"
testname="$5"
;;
*) die "Internal error: too many arguments to 'assert_json'" ;;
esac
run_helper jq -r "$jq_query" <<<"$actual_json"
assert "$output" "$operator" "$expect_string" "$testname"
}
function test_port_fw() {
local ipv4=true
local ipv6=false
local proto=tcp
local host_ip=""
local host_port=""
local container_port=""
local range=1
local connect_ip=""
local firewalld_reload=false
while [[ "$#" -gt 0 ]]; do
IFS='=' read -r arg value <<<"$1"
case "$arg" in
ip)
case "$value" in
4) ipv4=true ;;
6)
ipv6=true
ipv4=false
;;
dual) ipv6=true ;;
*) die "unknown argument '$value' for ip=" ;;
esac
;;
proto)
proto="$value"
;;
hostip)
host_ip="$value"
;;
connectip)
connect_ip="$value"
;;
hostport)
host_port="$value"
;;
containerport)
container_port="$value"
;;
range)
range="$value"
;;
firewalld_reload)
firewalld_reload="$value"
;;
*) die "unknown argument for '$arg' test_port_fw" ;;
esac
shift
done
if [ -z "$host_port" ]; then
host_port=$(random_port)
fi
if [ -z "$container_port" ]; then
container_port=$(random_port)
fi
local container_id=$(random_string 64)
local container_name="name-$(random_string 10)"
local static_ips=""
local subnets=""
if [ $ipv4 = true ]; then
ipv4_subnet=$(random_subnet)
ipv4_gateway=$(gateway_from_subnet $ipv4_subnet)
ipv4_container_ip=$(random_ip_in_subnet $ipv4_subnet)
static_ips="\"$ipv4_container_ip\""
subnets="{\"subnet\":\"$ipv4_subnet\",\"gateway\":\"$ipv4_gateway\"}"
fi
if [ $ipv6 = true ]; then
ipv6_subnet=$(random_subnet 6)
ipv6_gateway=$(gateway_from_subnet $ipv6_subnet)
ipv6_container_ip=$(random_ip_in_subnet $ipv6_subnet)
if [ $ipv4 = true ]; then
static_ips="$static_ips, "
subnets="$subnets, "
fi
static_ips="$static_ips\"$ipv6_container_ip\""
subnets="$subnets {\"subnet\":\"$ipv6_subnet\",\"gateway\":\"$ipv6_gateway\"}"
fi
read -r -d '\0' config <<EOF
{
"container_id": "$container_id",
"container_name": "$container_name",
"port_mappings": [
{
"host_ip": "$host_ip",
"container_port": $container_port,
"host_port": $host_port,
"range": $range,
"protocol": "$proto"
}
],
"networks": {
"podman1": {
"static_ips": [
$static_ips
],
"interface_name": "eth0"
}
},
"network_info": {
"podman1": {
"name": "podman1",
"id": "ed82e3a703682a9c09629d3cf45c1f1e7da5b32aeff3faf82837ef4d005356e6",
"driver": "bridge",
"network_interface": "podman1",
"subnets": [
$subnets
],
"ipv6_enabled": true,
"internal": false,
"dns_enabled": false,
"ipam_options": {
"driver": "host-local"
}
}
}
}\0
EOF
echo "$config"
if [ $firewalld_reload = true ]; then
setup_firewalld
run_netavark_firewalld_reload
fi
run_netavark setup $(get_container_netns_path) <<<"$config"
result="$output"
if [ $firewalld_reload = true ]; then
run_in_host_netns firewall-cmd --reload
sleep 1
fi
IFS=',' read -ra protocols <<<"$proto"
for proto in "${protocols[@]}"; do
i=0
while [ $i -lt $range ]; do
((cport = container_port + i))
((hport = host_port + i))
if [ $ipv4 = true ]; then
if [[ -z "$connect_ip" ]]; then
connect_ip=$ipv4_gateway
if [[ -n "$host_ip" ]]; then
connect_ip=$host_ip
fi
fi
run_connection_test "0" "$proto" $cport $connect_ip $hport
fi
if [ $ipv6 = true ]; then
if [[ -z "$connect_ip" ]]; then
connect_ip=$ipv6_gateway
if [[ -n "$host_ip" ]]; then
connect_ip=$host_ip
fi
fi
run_connection_test "0" "$proto" $cport $connect_ip $hport
fi
((i = i + 1))
done
done
run_netavark teardown $(get_container_netns_path) <<<"$config"
}
function is_ipv6() {
[[ "$1" == *":"* ]]
}
function is_ipv4() {
[[ "$1" == *"."* ]]
}
function run_connection_test() {
local container_ns=$1
local proto=$2
local container_port=$3
local connect_ip=$4
local host_port=$5
if is_ipv6 "$connect_ip"; then
connect_ip="[$connect_ip]"
fi
data=$(random_string)
run_in_host_netns $NETAVARK_CONNECTION_TESTER --$proto "$(get_container_netns_path $container_ns)" "$connect_ip:$host_port" $container_port <<<"$data"
assert "${lines[1]}" == "Message: $data" "Logged message"
}
function random_port() {
printf $(($RANDOM + 1))
}
function random_string() {
local length=${1:-10}
head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
}
function random_subnet() {
if [[ "$1" == "6" ]]; then
printf "fd%x:%x:%x:%x::/64" $((RANDOM % 256)) $((RANDOM % 65535)) $((RANDOM % 65535)) $((RANDOM % 65535))
else
printf "10.%d.%d.0/24" $((RANDOM % 256)) $((RANDOM % 256))
fi
}
function random_ip_in_subnet() {
local net_ip=${1%/*}
local num=
if [[ "$net_ip" == *":"* ]]; then
num=$(printf "%x" $((RANDOM % 65533 + 2)))
else
net_ip=${net_ip%0}
num=$(printf "%d" $((RANDOM % 252 + 2)))
fi
printf "$net_ip%s" $num
}
function gateway_from_subnet() {
local net_ip=${1%/*}
local num=1
if [[ "$net_ip" == *"."* ]]; then
net_ip=${net_ip%0}
fi
printf "$net_ip%s" $num
}
function setup_sctp_kernel_module() {
modprobe sctp || skip "cannot load sctp kernel module"
}
function add_dummy_interface_on_host() {
name="$1"
ipaddr="$2"
run_in_host_netns ip link add "$name" type dummy
if [ -n "$ipaddr" ]; then
run_in_host_netns ip addr add "$ipaddr" dev "$name"
fi
run_in_host_netns ip link set "$name" up
}