#!/bin/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, iproute2 and an installed bird3, or set BIRD= and
# BIRDC= to another build. Uses two network namespaces, 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 ns in reproR reproU; do
                ip netns pids "$ns" 2>/dev/null |
                        xargs -r kill 2>/dev/null || true
                ip netns del "$ns" 2>/dev/null || true
        done
        rm -rf "$RUN"
}
trap cleanup EXIT
cleanup 2>/dev/null || true

mkdir -p "$RUN"
ip netns add reproR
ip netns add reproU
ip link add vR netns reproR type veth peer name vU netns reproU
for ns in reproR reproU; do ip netns exec "$ns" ip link set lo up; done
ip netns exec reproR ip link set vR up
ip netns exec reproU ip link set vU up
ip netns exec reproR ip -6 addr add 2001:db8:f00::1/64 dev vR
ip netns exec reproU ip -6 addr add 2001:db8:f00::2/64 dev vU
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
ip netns exec 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"
        ip netns exec 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
        else
                "$BIRDC" -s "$RUN/r.ctl" disable nh >/dev/null 2>&1
        fi
        sleep 5
        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
