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"