# BST-TIMEOUT: 300 # ~55 cases, but ~35 of them are an exec and an exec is a # # fresh wasm module instantiation (~2-3 s) on this arm, so # # the floor is ~90 s before any work. 300 is the headroom # # the nightly needs on a loaded host. . "$BST_LIB" # ifmetric 0.3 (meta-networking recipes-support) -- Lennart Poettering's # rtnetlink tool that sets the metric of EVERY IPv4 main-table route attached to # a named interface in one call. One sbin program, 446 lines of plain C, no # library dependency. It builds this arm unchanged; what this suite adds is the # evidence. # # --------------------------------------------------------------------------- # THE PACKAGE HAS TWO HALVES AND THEY FAIL FOR DIFFERENT REASONS. # # * THE ARGV HALF -- the usage text and its two triggers, the basename printed # in it, the exit status and diagnostic for an interface that does not # exist, atoi()'s silent acceptance of a non-numeric metric, and the # ignoring of argv[3] onward. All of it is arithmetic on argv and strings, # identical on every architecture, and it touches no descriptor. A wasm32 # build that got it wrong has no environmental excuse available. # # * THE NETLINK HALF -- netlink_open -> getifn -> enumerate -> delete/add. # Four requests over an AF_NETLINK socket, and this is where the arm can # genuinely differ. # # --------------------------------------------------------------------------- # WHY THERE IS A PROBE, AND WHY THE CLI COULD NOT REPLACE IT. # # Every failure in the netlink half surfaces as one of exactly two things: # "Interface 'X' not existent." or a bare non-zero exit. So a kernel that # REFUSED the dump, a kernel that answered an EMPTY dump, and a kernel that # answered perfectly but has no route on that interface to re-weight are # indistinguishable from outside -- three different verdicts about this arm, and # only one of them is a defect. /usr/libexec/ifmetric-tests/ifmetric-probe issues # the same four requests ifmetric issues, byte for byte including the flag word, # and reports each one's rc, errno NAME and reply count separately. # # THE FLAG WORD IS THE HINGE. ifmetric spells both dump requests # NLM_F_REQUEST|NLM_F_MATCH (0x201) and never sets NLM_F_ROOT, so its flag word # is HALF of NLM_F_DUMP (== ROOT|MATCH == 0x300). It has worked on Linux for # twenty years only because rtnetlink_rcv_msg tests `flags & NLM_F_DUMP` as a # NON-ZERO test rather than an equality. That is a reading of a different # kernel's source, so the probe issues BOTH spellings and the cases below # compare them: agreeing means the spelling is not the story, disagreeing is a # fact about this arm's rtnetlink that no amount of CLI output would have named. # # THE SECOND HINGE IS A REAL RISK, not a hypothetical. getifn() attaches an # IFLA_IFNAME attribute of IFNAMSIZ+1 = 17 payload bytes; ifla_policy declares # that attribute NLA_STRING with .len = IFNAMSIZ-1 = 15, and NLA_STRING # validation drops one trailing NUL and then refuses a payload longer than .len. # A STRICT parse of getifn's request is therefore -EINVAL, and whether the dump # path reaches that parse depends on netlink_strict_get_check(), off unless a # caller asks for it. The probe issues getifn's request WITH the attribute and # the same request WITHOUT it, so an EINVAL is attributable to the attribute # rather than to the dump. # # THE PROBE'S ORACLE IS A NATIVE x86-64 RUN OF THE SAME BINARY. Every line comes # back rc=0 there, both dump spellings agree (11 links, 36 routes), and getifn's # 17-byte attribute is accepted. A probe that cannot report success has not # measured anything, and this one was checked in the direction that catches # that. # # --------------------------------------------------------------------------- # THE REWRITE IS ASSERTED FOR REAL. NOTHING HERE IS SKIPPED FOR WANT OF AN # INTERFACE. # # The ticket's framing says this image has one virtio interface and a routing # table with nothing to re-weight. Half of that is wrong and the other half does # not have the consequence it looks like it has: # # * There is NO virtio interface. `lo` is the only interface on this image and # no virtio-net device is registered -- 60-net-tools.test already asserts # `ifconfig -a` lists lo alone. The census below records what is really # there rather than repeating either claim. # # * The main table IS empty at boot, and that does not make the rewrite # untestable: a route can be PUT on lo and re-weighted there. 60-net-tools # has been adding and deleting one on lo since it landed. So this suite adds # its own, drives ifmetric over them, and reads the result back. # # THE ORACLE FOR THE WRITES IS iproute2, WHICH IS THE POINT. `ip` is on this # image and is an INDEPENDENT implementation of the same protocol, so # `ip route show` reporting a metric ifmetric wrote is real end-to-end evidence. # A probe that both wrote and read would round-trip through its own mistakes and # report every value correct. # # WHAT IT CANNOT DAMAGE. ifmetric filters on `rtm_table != RT_TABLE_MAIN`, and # lo's own 127.0.0.0/8, 127.0.0.1/32 and 127.255.255.255/32 live in table LOCAL # (255), not MAIN (254) -- confirmed by the probe's route census on the native # control. So the only routes any invocation below can touch are the ones this # suite put there, and the cleanup group asserts they are gone again. # # --------------------------------------------------------------------------- # TWO CASES THIS SUITE DELIBERATELY DOES NOT DRIVE, SAID RATHER THAN OMITTED. # # * A NEGATIVE METRIC. `ifmetric lo -1` reaches addattr32 with -1 and asks the # kernel for priority 0xffffffff. ifmetric DELETES each route before it # re-adds it, so a refused add loses the route outright -- and the tool # reports that as "NETLINK: Error: ..." on a run whose exit status is still # 0, because add_route()'s return value is discarded in go()'s loop. Driving # an undocumented input whose failure mode is silent data loss buys less # than it costs. It is upstream behaviour and it is not arm-specific. # # * THE MAX_ROUTES CEILING. enumerate_callback stops at 64 routes per # interface with "Found too many routes." on stderr. Reaching it needs 65 # `ip route add` calls, which at ~2-3 s per exec on this arm is most of the # suite's time budget for one message. # ---------------------------------------------------------------- 0. the decoy # The first result line of every suite is eaten in transport # (brintos/linux#96 / BRINTOS-1149) and the harness counts a dropped line as a # PASS, so the eaten position must never carry an assertion anyone depends on. bst_ok ifmetric/suite-canary require ifmetric ifmetric/present IFACE=lo NET1=10.99.0.0/24 NET2=10.98.0.0/24 NET3=10.97.0.0/24 ALTTABLE=100 # ------------------------------------------------------------- 1. packaging --- # Where it is matters: Makefile.am declares `sbin_PROGRAMS`, so a build that # quietly relocated it to ${bindir} would still satisfy `require` through PATH # while breaking every caller that names the path. check ifmetric/pkg/sbin-path test -x /usr/sbin/ifmetric # -doc is the one man page upstream installs. Either spelling, because whether # the distro compresses man pages is a policy this port does not own. if [ -f /usr/share/man/man8/ifmetric.8 ] || [ -f /usr/share/man/man8/ifmetric.8.gz ]; then bst_ok ifmetric/pkg/man8 else bst_notok ifmetric/pkg/man8 "neither /usr/share/man/man8/ifmetric.8 nor .gz -- ifmetric-doc is not installed" fi # No busybox or toybox applet carries this name, so a bare `ifmetric` resolving # to anything else would be a surprise worth catching rather than an # alternatives race. WHICH=$(command -v ifmetric 2>/dev/null) check_eq ifmetric/pkg/which /usr/sbin/ifmetric "$WHICH" # --------------------------------------------------- 2. the argv half, no kernel # main() prints usage for argc <= 1 and for argv[1] in {-h, --help}, and returns # 0 from all three. check_out asserts the zero exit as well as the needle. check_out ifmetric/argv/usage-noargs "Usage: ifmetric [metric]" ifmetric check_out ifmetric/argv/usage-h "Usage: ifmetric [metric]" ifmetric -h check_out ifmetric/argv/usage-help "Usage: ifmetric [metric]" ifmetric --help check_out ifmetric/argv/usage-body \ "tool for setting the metrics of all IPv4 routes" ifmetric --help check_out ifmetric/argv/usage-default-metric "The new metric (default: 0)" ifmetric --help # The usage text names argv[0]'s BASENAME, via strrchr(argv[0], '/'). Invoked # through PATH or by absolute path both yield "ifmetric", so neither exercises # the strrchr arm; a symlink under another name does, and it is the only way to # tell the two apart from outside. tmp=${TMPDIR:-/tmp} alias_name=bst-ifm-alias.$$ alias_path=$tmp/$alias_name if ln -s /usr/sbin/ifmetric "$alias_path" 2>/dev/null; then check_out ifmetric/argv/usage-basename "Usage: $alias_name " "$alias_path" else bst_notok ifmetric/argv/usage-basename "could not create the symlink $alias_path" fi rm -f "$alias_path" # THERE IS NO VERSION ARM. This case pins the ABSENCE: --version is not # recognised, so it is taken as an interface name and fails as one. If a future # ifmetric grows --version this reddens, which is the correct signal -- the # ticket asked for version text and there is none to assert. vout=$(ifmetric --version 2>&1); vrc=$? check_eq ifmetric/argv/no-version-rc 1 "$vrc" case "$vout" in *"Interface '--version' not existent."*) bst_ok ifmetric/argv/no-version-text ;; *) bst_notok ifmetric/argv/no-version-text \ "--version was not treated as an interface name: $(_bst_1line "$vout")" ;; esac # The documented failure: man 8 ifmetric's RETURN VALUES section gives 0 for # success and 1 for failure, and getifn() is where an unknown name stops. uout=$(ifmetric nosuchif0 2>&1); urc=$? check_eq ifmetric/argv/unknown-iface-rc 1 "$urc" case "$uout" in *"Interface 'nosuchif0' not existent."*) bst_ok ifmetric/argv/unknown-iface-msg ;; *) bst_notok ifmetric/argv/unknown-iface-msg \ "expected the not-existent diagnostic, got: $(_bst_1line "$uout")" ;; esac # strncpy(gi.ifname, iface, IFNAMSIZ) truncates a name longer than 16, but the # DIAGNOSTIC prints the caller's full string. Both halves are asserted, because # a build that printed the truncated form would be a real difference and would # look like nothing in a rc-only test. lout=$(ifmetric averyveryverylongifname 1 2>&1); lrc=$? check_eq ifmetric/argv/long-iface-rc 1 "$lrc" case "$lout" in *"Interface 'averyveryverylongifname' not existent."*) bst_ok ifmetric/argv/long-iface-msg ;; *) bst_notok ifmetric/argv/long-iface-msg \ "expected the caller's full name in the diagnostic, got: $(_bst_1line "$lout")" ;; esac # ------------------------------------------------------------------ 3. the probe P=/usr/libexec/ifmetric-tests/ifmetric-probe probe_ok=no if [ ! -x "$P" ]; then bst_notok ifmetric/probe/present \ "no probe at $P -- ifmetric-tests is not installed, so nothing below can be attributed" else PO=$("$P" "$IFACE" 2>&1); prc=$? if bst_execfail "$PO"; then bst_unrun ifmetric/probe/runs "$PO" else case "$PO" in *"probe end"*) bst_ok ifmetric/probe/runs; probe_ok=yes ;; *) bst_notok ifmetric/probe/runs \ "rc=$prc, no 'probe end' -- the probe did not finish, so its values below are partial: $(_bst_1line "$PO")" ;; esac fi fi if [ "$probe_ok" = yes ]; then # field . Reads from a VARIABLE, never a redirect: a # top-level stdin redirect on the virtiofs root makes dup2(2) return 513 and # ash exits, ending the suite silently (brintos/linux#73). field() { printf '%s\n' "$PO" | sed -n "s/^$1 / /p" \ | sed -n "s/.*[ ]$2=\\([^ ]*\\).*/\\1/p" | sed -n 1p } inet_rc=$(field inet-dgram rc) raw_rc=$(field nl-open-raw rc); raw_name=$(field nl-open-raw name) dgram_rc=$(field nl-open-dgram rc); dgram_name=$(field nl-open-dgram name) gl_match_rc=$(field getlink-match-only rc); gl_match_n=$(field getlink-match-only links) gl_dump_rc=$(field getlink-full-dump rc); gl_dump_n=$(field getlink-full-dump links) gl_attr_rc=$(field getlink-getifn rc); gl_attr_n=$(field getlink-getifn links) gl_attr_name=$(field getlink-getifn name) gr_match_rc=$(field getroute-match-only rc); gr_match_n=$(field getroute-match-only routes) gr_dump_rc=$(field getroute-full-dump rc); gr_dump_n=$(field getroute-full-dump routes) gr_dump_name=$(field getroute-full-dump name) if_n=$(field ifcensus n) resolve_idx=$(field resolve idx) rt_main=$(field rtcensus main) # Echo what the probe answered, prefixed so it is greppable and plainly not # a result line. Every branch below is a function of these values, and # without them a stored dump records the verdicts without the evidence. printf '%s\n' "$PO" | while read -r _dl; do [ -n "$_dl" ] && printf '#diag: ifmetric probe: %s\n' "$_dl" done # --- the controls --------------------------------------------------- # Three things this kernel certainly does. They are ASSERTED, not # recorded: while they pass, a refusal below is a fact about rtnetlink; # if they fail, the reading is about the process and nothing below may be # attributed to the kernel. check_eq ifmetric/control/inet-dgram 0 "$inet_rc" check_eq ifmetric/control/nl-open-raw 0 "$raw_rc" # ifmetric's OWN socket, and it is the unusual spelling: SOCK_DGRAM rather # than the SOCK_RAW everything else opens, plus an explicit bind naming # getpid() as the netlink port id. netlink_create accepts both types, so # this passing while nl-open-raw also passes says the type is not the # story; this failing ALONE would localise the fault to ifmetric's choice. check_eq ifmetric/control/nl-open-dgram 0 "$dgram_rc" check_eq ifmetric/control/nl-open-dgram-errno OK "$dgram_name" # --- hinge 1: the half-spelled dump flag ---------------------------- check_eq ifmetric/nl/getlink-match-only-rc 0 "$gl_match_rc" check_eq ifmetric/nl/getlink-full-dump-rc 0 "$gl_dump_rc" # The comparison IS the assertion. If MATCH-alone stopped selecting the # dump path on this arm, the first would come back with zero links while # the second still listed them -- and ifmetric would report every # interface non-existent with no other symptom. check_eq ifmetric/nl/dump-flag-equivalence "$gl_dump_n" "$gl_match_n" if [ -n "$gl_match_n" ] && [ "$gl_match_n" -gt 0 ] 2>/dev/null; then bst_ok ifmetric/nl/getlink-nonempty else bst_notok ifmetric/nl/getlink-nonempty \ "ifmetric's own RTM_GETLINK spelling returned $gl_match_n links -- getifn() cannot resolve any name on this arm" fi # --- hinge 2: getifn's 17-byte IFLA_IFNAME -------------------------- check_eq ifmetric/nl/getifn-attr-rc 0 "$gl_attr_rc" check_eq ifmetric/nl/getifn-attr-errno OK "$gl_attr_name" # Same request, one attribute apart. A difference here localises the fault # to the attribute; no difference means the over-long IFLA_IFNAME is # tolerated on this arm and getifn needs no patch. check_eq ifmetric/nl/getifn-attr-equivalence "$gl_match_n" "$gl_attr_n" # --- what getifn would have resolved -------------------------------- check_eq ifmetric/nl/census-has-lo "$IFACE" \ "$(printf '%s\n' "$PO" | sed -n "s/^iface .* name=\\($IFACE\\)$/\\1/p" | sed -n 1p)" if [ -n "$resolve_idx" ] && [ "$resolve_idx" -ge 1 ] 2>/dev/null; then bst_ok ifmetric/nl/resolve-index else bst_notok ifmetric/nl/resolve-index \ "getifn's own request resolved $IFACE to index '$resolve_idx'" fi # Recorded, not asserted: the interface count is 60-net-tools.test's pin, # and re-pinning it here would make one image change red in two places # saying the same thing. printf '#diag: ifmetric: interface census n=%s, main-table routes at entry=%s\n' \ "$if_n" "$rt_main" # --- enumerate()'s request ------------------------------------------ check_eq ifmetric/nl/getroute-match-only-rc 0 "$gr_match_rc" check_eq ifmetric/nl/getroute-full-dump-rc 0 "$gr_dump_rc" check_eq ifmetric/nl/getroute-errno OK "$gr_dump_name" check_eq ifmetric/nl/route-dump-equivalence "$gr_dump_n" "$gr_match_n" else for c in inet-dgram nl-open-raw nl-open-dgram; do bst_notok "ifmetric/control/$c" "the probe did not run, so this control was never taken" done for c in getlink-match-only-rc getlink-full-dump-rc dump-flag-equivalence \ getlink-nonempty getifn-attr-rc getifn-attr-equivalence resolve-index \ getroute-match-only-rc getroute-full-dump-rc route-dump-equivalence; do bst_notok "ifmetric/nl/$c" "the probe did not run, so this measurement does not exist" done fi # ------------------------------------------------- 4. the rewrite, for real --- # Everything below needs iproute2, and it needs it as an ORACLE rather than as a # convenience -- it is the independent reader that makes a metric ifmetric wrote # into evidence rather than a claim. if ! command -v ip >/dev/null 2>&1; then bst_skip ifmetric/rewrite/oracle \ "iproute2 'ip' is not on this image, so nothing can read back what ifmetric wrote" exit 0 fi # rshow -- the whole main table, read ONCE into a variable. Every assertion that # follows is a `case` against it, which is a builtin and forks nothing; the # guest does not return memory on process exit, so a suite's process count is a # resource budget rather than a performance detail. MAIN="" rshow() { MAIN=$(ip -4 route show table main 2>&1); } # rline -- the ONE line of $MAIN describing , or the empty # string. Every metric assertion below scores this rather than the whole table, # and that is not tidiness: a `case` glob against the whole table spans LINES, so # `*10.98.0.0/24*metric 5*` is satisfied by 10.98's prefix and a DIFFERENT # route's metric further down. Measured on the native control -- the no-metric # premise case passed its glob against a table in which the no-metric route had # no metric and the next route had one. One awk, not a grep+sed pair: the guest # does not return memory on process exit, so every avoidable fork is spent # twice. # The trailing blanks matter: `ip route show` ends its line with one, so an # end-anchored `*"metric 7")` never fires against the raw line -- and the metric # is the LAST field, which is exactly where the anchor has to be if `metric 5` # is not to be satisfied by `metric 50`. Stripped here, once, rather than in ten # patterns. rline() { printf '%s\n' "$MAIN" \ | awk -v p="$1 " 'index($0, p) == 1 { sub(/[ \t]+$/, ""); print; exit }' } # THE PREMISE, PROBED RATHER THAN ASSUMED. If a route cannot be put on lo here, # the rewrite genuinely is untestable and every case below skips WITH the reason # -- which is the only form of skip this ticket permits. ip route del "$NET1" 2>/dev/null add_out=$(ip route add "$NET1" dev "$IFACE" metric 7 2>&1); add_rc=$? rshow case "$(rline "$NET1")" in ?*) can_route=yes ;; *) can_route=no ;; esac if [ "$can_route" != yes ]; then bst_skip ifmetric/rewrite/premise \ "a route cannot be added to lo on this image (ip route add rc=$add_rc: $(_bst_1line "$add_out")) -- the metric rewrite has no route to act on" for c in prio-arm-rc prio-arm-metric prio-arm-survives noprio-premise \ noprio-arm-metric all-routes-one-call default-zero atoi-nonnumeric \ extra-args-ignored empty-table-ok cleanup; do bst_skip "ifmetric/rewrite/$c" "no route could be staged on $IFACE" done exit 0 fi bst_ok ifmetric/rewrite/premise # The staged route carries an explicit metric, so the kernel emits RTA_PRIORITY # in the dump and set_route_metric() takes its IN-PLACE rewrite arm. case "$(rline "$NET1")" in *"metric 7") bst_ok ifmetric/rewrite/staged-metric-7 ;; *) bst_notok ifmetric/rewrite/staged-metric-7 \ "ip route add reported rc=$add_rc but the table does not show metric 7: $(_bst_1line "$MAIN")" ;; esac # --- the in-place RTA_PRIORITY arm ------------------------------------------ out=$(ifmetric "$IFACE" 42 2>&1); rc=$? check_eq ifmetric/rewrite/prio-arm-rc 0 "$rc" rshow N1=$(rline "$NET1") case "$N1" in *"metric 42") bst_ok ifmetric/rewrite/prio-arm-metric ;; *) bst_notok ifmetric/rewrite/prio-arm-metric \ "metric is not 42 after 'ifmetric $IFACE 42' (tool said: $(_bst_1line "$out")); table: $(_bst_1line "$MAIN")" ;; esac # ifmetric DELETES each route and re-adds it. A failed re-add loses the route # outright and go() discards add_route()'s return value, so the tool would exit # 0 having destroyed the table. This case is the one that catches that, and it # is not the same assertion as the metric one above. case "$N1" in ?*) bst_ok ifmetric/rewrite/prio-arm-survives ;; *) bst_notok ifmetric/rewrite/prio-arm-survives \ "the route is GONE after the delete/re-add cycle -- add_route() failed and go() swallowed it: $(_bst_1line "$MAIN")" ;; esac case "$N1" in *"dev $IFACE"*) bst_ok ifmetric/rewrite/prio-arm-dev ;; *) bst_notok ifmetric/rewrite/prio-arm-dev \ "the re-added route no longer names dev $IFACE: $(_bst_1line "$MAIN")" ;; esac # --- the realloc()+addattr32 arm -------------------------------------------- # A route added with NO metric has priority 0, and the kernel OMITS RTA_PRIORITY # from the dump for a zero priority (fib_dump_info guards the nla_put on # fi->fib_priority). set_route_metric() therefore finds no attribute to rewrite # and takes its realloc()+addattr32 arm instead. That is different code, and # only a route census can say which arm a given run exercised. ip route del "$NET2" 2>/dev/null ip route add "$NET2" dev "$IFACE" 2>/dev/null rshow case "$(rline "$NET2")" in *"metric"*) bst_notok ifmetric/rewrite/noprio-premise \ "the no-metric route came back WITH a metric, so the addattr32 arm below is not the one being exercised: $(_bst_1line "$MAIN")" ;; ?*) bst_ok ifmetric/rewrite/noprio-premise ;; *) bst_notok ifmetric/rewrite/noprio-premise \ "$NET2 could not be staged without a metric: $(_bst_1line "$MAIN")" ;; esac out=$(ifmetric "$IFACE" 5 2>&1); rc=$? check_eq ifmetric/rewrite/noprio-arm-rc 0 "$rc" rshow case "$(rline "$NET2")" in *"metric 5") bst_ok ifmetric/rewrite/noprio-arm-metric ;; *) bst_notok ifmetric/rewrite/noprio-arm-metric \ "the addattr32 arm did not set metric 5 (tool said: $(_bst_1line "$out")); table: $(_bst_1line "$MAIN")" ;; esac # THE CASE THAT SEPARATES THIS TOOL FROM `ip route change`. One invocation named # no prefix at all and re-weighted BOTH routes on the interface, which is the # whole of what ifmetric is for. case "$(rline "$NET1")" in *"metric 5") bst_ok ifmetric/rewrite/all-routes-one-call ;; *) bst_notok ifmetric/rewrite/all-routes-one-call \ "$NET1 was not re-weighted by the same call that re-weighted $NET2 -- ifmetric acted on one route, not on the interface: $(_bst_1line "$MAIN")" ;; esac # --- the default metric, and atoi() ----------------------------------------- # `metric = argc > 2 ? atoi(argv[2]) : 0` -- with no second argument the metric # is 0, and a zero priority is what makes the kernel omit RTA_PRIORITY again. out=$(ifmetric "$IFACE" 2>&1); rc=$? check_eq ifmetric/rewrite/default-zero-rc 0 "$rc" rshow case "$(rline "$NET1")$(rline "$NET2")" in *"metric"*) bst_notok ifmetric/rewrite/default-zero \ "a bare 'ifmetric $IFACE' should leave metric 0 on both routes, which shows as NO metric field: $(_bst_1line "$MAIN")" ;; *) bst_ok ifmetric/rewrite/default-zero ;; esac # atoi("abc") is 0 and ifmetric neither validates nor complains. This is # upstream behaviour and the case PINS it rather than wishing it away: a future # version that started rejecting it would be a real change worth seeing. ip route del "$NET1" 2>/dev/null ip route add "$NET1" dev "$IFACE" metric 9 2>/dev/null out=$(ifmetric "$IFACE" abc 2>&1); rc=$? check_eq ifmetric/rewrite/atoi-nonnumeric-rc 0 "$rc" rshow case "$(rline "$NET1")" in *"metric"*) bst_notok ifmetric/rewrite/atoi-nonnumeric \ "'ifmetric $IFACE abc' should be atoi()=0 and clear the metric: $(_bst_1line "$MAIN")" ;; *) bst_ok ifmetric/rewrite/atoi-nonnumeric ;; esac # main() reads argv[1] and argv[2] and nothing else; there is no getopt and no # trailing-argument check. out=$(ifmetric "$IFACE" 3 junk extra 2>&1); rc=$? check_eq ifmetric/rewrite/extra-args-rc 0 "$rc" rshow case "$(rline "$NET1")" in *"metric 3") bst_ok ifmetric/rewrite/extra-args-ignored ;; *) bst_notok ifmetric/rewrite/extra-args-ignored \ "argv[3] onward should be ignored and the metric set to 3: $(_bst_1line "$MAIN")" ;; esac # --- the table filter ------------------------------------------------------- # enumerate_callback returns early on `r->rtm_table != RT_TABLE_MAIN`, so a # route in another table must survive untouched. Probed rather than assumed: # policy routing is CONFIG_IP_MULTIPLE_TABLES and this kernel may not have it. ip route del "$NET3" table "$ALTTABLE" 2>/dev/null ip route add "$NET3" dev "$IFACE" metric 3 table "$ALTTABLE" 2>/dev/null alt=$(ip -4 route show table "$ALTTABLE" 2>&1) case "$alt" in *"10.97.0.0/24"*"metric 3"*) out=$(ifmetric "$IFACE" 77 2>&1) alt=$(ip -4 route show table "$ALTTABLE" 2>&1) case "$alt" in *"10.97.0.0/24"*"metric 3"*) bst_ok ifmetric/rewrite/table-filter ;; *) bst_notok ifmetric/rewrite/table-filter \ "a route in table $ALTTABLE was touched by 'ifmetric $IFACE 77' -- the rtm_table filter did not hold: $(_bst_1line "$alt")" ;; esac ip route del "$NET3" table "$ALTTABLE" 2>/dev/null ;; *) bst_skip ifmetric/rewrite/table-filter \ "no second routing table on this kernel (CONFIG_IP_MULTIPLE_TABLES): $(_bst_1line "$alt")" ;; esac # --- an interface with nothing to re-weight --------------------------------- # n_routes == 0 makes go() skip its loop and return 0. "Nothing to re-weight" is # a SUCCESS in this tool, not a failure, and that is worth pinning because the # ticket's framing reads as though it were the other way round. ip route del "$NET1" 2>/dev/null ip route del "$NET2" 2>/dev/null rshow case "$(rline "$NET1")$(rline "$NET2")" in ?*) bst_notok ifmetric/rewrite/cleanup \ "this suite's routes are still in the main table after ip route del: $(_bst_1line "$MAIN")" ;; *) bst_ok ifmetric/rewrite/cleanup ;; esac out=$(ifmetric "$IFACE" 11 2>&1); rc=$? check_eq ifmetric/rewrite/empty-table-rc 0 "$rc" case "$out" in "") bst_ok ifmetric/rewrite/empty-table-quiet ;; *) bst_notok ifmetric/rewrite/empty-table-quiet \ "an interface with no main-table route should produce no output: $(_bst_1line "$out")" ;; esac