#!/bin/sh
# FreeBSD port of bird-repro-rte-is-valid.sh. Reproduces the BIRD 3 abort
#   Assertion '!new || rte_is_valid(new)' failed at nest/rt-table.c:1562
# by running the same scenario five times, removing one ingredient at a
# time. Needs root, a VIMAGE kernel (GENERIC) and an installed bird3, or
# set BIRD= and BIRDC= to another build. Uses two vnet jails joined by an
# epair, so nothing on the host is touched, and cleans up after itself.
set -eu

RUN=/tmp/bird-repro-rte
BIRD=${BIRD:-bird}
BIRDC=${BIRDC:-birdc}

cleanup() {
	for j in reproR reproU; do
		jls -j "$j" >/dev/null 2>&1 || continue
		# destroying one end removes the pair; otherwise jail -r
		# would hand the interface back to the host
		jexec "$j" ifconfig vR destroy 2>/dev/null || true
		jail -r "$j" 2>/dev/null || true
	done
	rm -rf "$RUN"
}
trap cleanup EXIT
cleanup 2>/dev/null || true

mkdir -p "$RUN"
for j in reproR reproU; do
	jail -c name="$j" host.hostname="$j" path=/ vnet persist
done
ep=$(ifconfig epair create)
ep=${ep%a}
ifconfig "${ep}a" vnet reproR
ifconfig "${ep}b" vnet reproU
jexec reproR ifconfig "${ep}a" name vR >/dev/null
jexec reproU ifconfig "${ep}b" name vU >/dev/null
for j in reproR reproU; do jexec "$j" ifconfig lo0 up; done
jexec reproR ifconfig vR inet6 -ifdisabled up
jexec reproU ifconfig vU inet6 -ifdisabled up
jexec reproR ifconfig vR inet6 2001:db8:f00::1/64
jexec reproU ifconfig vU inet6 2001:db8:f00::2/64
sleep 2

# The upstream: announces two prefixes, one of which the router's import
# filter rejects, so that one route is kept as filtered.
cat > "$RUN/u.conf" <<'CONF'
router id 240.0.0.2;
protocol device { scan time 2; }
protocol static ann {
	ipv6;
	route 2001:db8:beef::/48 unreachable;
	route 3fff:bad::/48 unreachable;
}
protocol bgp up1 {
	local 2001:db8:f00::2 as 65002;
	neighbor 2001:db8:f00::1 as 65001;
	multihop 2;
	ipv6 { import none; export all; };
}
CONF
jexec reproU "$BIRD" -c "$RUN/u.conf" -s "$RUN/u.ctl" -P "$RUN/u.pid"
"$BIRD" --version 2>&1 | head -1

# $1 label   $2 keep-filtered option   $3 multihop?   $4 add|remove
run() {
	{
		echo "router id 240.0.0.1;"
		echo "log \"$RUN/r.log\" all;"
		echo "protocol device { scan time 2; }"
		echo "protocol kernel k { ipv6 { export all; }; }"
		echo 'filter drop_some {'
		echo '        if net ~ [ 3fff::/20+ ] then reject;'
		echo '        accept;'
		echo '}'
		printf 'protocol static nh {\n'
		[ "$4" = add ] && printf '        disabled;\n'
		printf '        ipv6;\n'
		printf '        route 2001:db8:f00::2/128'
		printf ' via "vR";\n}\n'
		echo "protocol bgp up1 {"
		echo "        local 2001:db8:f00::1 as 65001;"
		echo "        neighbor 2001:db8:f00::2 as 65002;"
		[ "$3" = yes ] && echo "        multihop 2;"
		echo "        ipv6 {"
		echo "                import filter drop_some;"
		echo "                export none;"
		[ -n "$2" ] && echo "                $2"
		echo "        };"
		echo "}"
	} > "$RUN/r.conf"

	rm -f "$RUN/r.log"
	jexec reproR "$BIRD" -c "$RUN/r.conf" -s "$RUN/r.ctl" -P "$RUN/r.pid"
	sleep 6
	filtered=$("$BIRDC" -s "$RUN/r.ctl" show route filtered \
		protocol up1 2>/dev/null |
		grep -cE '^[0-9a-f:]+/' || true)
	if [ "$4" = add ]; then
		"$BIRDC" -s "$RUN/r.ctl" enable nh >/dev/null 2>&1 || true
	else
		"$BIRDC" -s "$RUN/r.ctl" disable nh >/dev/null 2>&1 || true
	fi
	sleep 5
	# jails share the host pid space, so the pid file is valid here
	if kill -0 "$(cat "$RUN/r.pid" 2>/dev/null)" 2>/dev/null; then
		verdict=survived
		"$BIRDC" -s "$RUN/r.ctl" down >/dev/null 2>&1 || true
		sleep 2
	else
		verdict="*** ABORTED ***"
		grep -h Assertion "$RUN/r.log" >> "$RUN/asserts" || true
	fi
	printf '  %-36s filtered=%s  %s\n' "$1" "$filtered" "$verdict"
	rm -f "$RUN/r.pid"
	sleep 1
}

run "import table, multihop, remove"  "import table on;"      yes remove
run "keep filtered, multihop, remove" "import keep filtered;" yes remove
run "import table, multihop, ADD"     "import table on;"      yes add
run "import table, NOT multihop, rm"  "import table on;"      no  remove
run "no keep-filtered, multihop, rm"  ""                      yes remove

if [ -s "$RUN/asserts" ]; then
	echo
	echo "  the assertion, deduplicated over the aborted runs:"
	sed 's/^.*<BUG> /  /' "$RUN/asserts" | sort -u
fi
