linkd

Control plane daemon for unos
git clone git://git.finwo.net/app/linkd
Log | Files | Refs | README

test_netlink.sh (3769B)


      1 #!/bin/sh
      2 # Regression guards for the three defects that made the integration suite flaky.
      3 #
      4 # Every check here greps the source tree, which means a check can stop finding
      5 # anything for two very different reasons: the code is correct, or the file
      6 # moved. Only the first should pass, so each grep is preceded by an explicit
      7 # existence assertion. Without that, this whole file degrades into a silent
      8 # pass the moment the layout changes -- which is exactly what would have
      9 # happened when these tests moved into their own repo.
     10 set -eu
     11 HERE=$(cd "$(dirname "$0")" && pwd)
     12 ROOT=$(cd "${HERE}/../.." && pwd)
     13 . "${HERE}/../helpers.sh"
     14 
     15 echo "==> test_netlink: no ip shell-out, blocking accept, client half-close"
     16 
     17 SRC="${ROOT}/src"
     18 assert_path_exists "${SRC}" "source tree present"
     19 
     20 # --- 1. linkd must never shell out to `ip` --------------------------------
     21 # All link/addr/route/vlan/bridge/VRF work goes through src/netlink/rtnl.c. A
     22 # system("ip ...") reintroduces both the PATH dependency and the asynchronous
     23 # behaviour that made tests need sleeps.
     24 #
     25 # Strip the grep file:line: prefix before deciding whether a hit is a comment,
     26 # otherwise every match looks like code and the check cries wolf.
     27 code_hits() {
     28   grep -rn "$1" "${SRC}" 2>/dev/null \
     29     | sed 's/^[^:]*:[0-9]*://' \
     30     | sed 's/^[[:space:]]*//' \
     31     | grep -v '^//' \
     32     | grep -v '^\*' || true
     33 }
     34 
     35 if [ -n "$(code_hits 'system(.*"[^"]*ip ')" ]; then
     36   echo "FAIL: linkd shells out to ip:" >&2
     37   code_hits 'system(.*"[^"]*ip ' | head -5 >&2
     38   exit 1
     39 fi
     40 echo "PASS: no ip shell-out in linkd sources"
     41 
     42 if [ -n "$(code_hits '/sbin/ip')" ]; then
     43   echo "FAIL: /sbin/ip referenced in linkd sources" >&2
     44   code_hits '/sbin/ip' | head -5 >&2
     45   exit 1
     46 fi
     47 echo "PASS: no /sbin/ip reference in linkd sources"
     48 
     49 # Prove the grep above is actually looking at real code. If rtnl.c ever stops
     50 # containing netlink sends, the two checks above are meaningless.
     51 assert_path_exists "${SRC}/netlink/rtnl.c" "rtnl.c present (the netlink path exists)"
     52 
     53 # --- 2. accepted connections must be non-blocking -------------------------
     54 # This is the inverse of what it once asserted. Under the line protocol the
     55 # server read a connection to EOF with stdio, so a non-blocking fd returned
     56 # EAGAIN, stdio reported EOF, and the reply came back empty.
     57 #
     58 # RESP is length-prefixed, so a partial read is detectable rather than
     59 # indistinguishable from end-of-input: resp_read_buf reports "incomplete" and
     60 # the connection waits for more data. Blocking accepts would now let one
     61 # client stall netlink for the whole daemon.
     62 IPC="${SRC}/ipc.c"
     63 assert_path_exists "${IPC}" "ipc.c present"
     64 if ! grep -q 'accept4(.*SOCK_NONBLOCK' "${IPC}"; then
     65   echo "FAIL: accepted connections are blocking; one slow client stalls netlink" >&2
     66   exit 1
     67 fi
     68 echo "PASS: accepted connections are non-blocking"
     69 
     70 # Non-blocking is only correct with incremental parsing behind it.
     71 if ! grep -q 'resp_read_buf' "${IPC}"; then
     72   echo "FAIL: no resp_read_buf; non-blocking reads need incremental parsing" >&2
     73   exit 1
     74 fi
     75 echo "PASS: reads are buffered through resp_read_buf"
     76 
     77 # Partial writes must be buffered too, or a slow reader stalls the daemon
     78 # just as effectively as a slow writer would.
     79 if ! grep -q 'POLLOUT' "${IPC}"; then
     80   echo "FAIL: no POLLOUT handling; a slow reader would block the daemon" >&2
     81   exit 1
     82 fi
     83 echo "PASS: writes are buffered and drained on POLLOUT"
     84 
     85 # --- 3. one client must not be able to wedge the daemon -------------------
     86 # The old server serviced a single connection per poll wakeup, synchronously.
     87 if grep -q 'IPC_MAX_CONNS' "${SRC}/ipc.h"; then
     88   echo "PASS: server keeps a bounded connection table"
     89 else
     90   echo "FAIL: no connection limit; an unbounded fd table is a DoS" >&2
     91   exit 1
     92 fi
     93 
     94 echo "==> test_netlink done"