test_resp.sh (6203B)
1 #!/bin/sh 2 # RESP server: protocol, pipelining, concurrency and the authorization model. 3 set -eu 4 HERE=$(cd "$(dirname "$0")" && pwd) 5 ROOT=$(cd "${HERE}/../.." && pwd) 6 . "${HERE}/../helpers.sh" 7 8 echo "==> test_resp: RESP server" 9 10 BUILD_DIR=$(ensure_built "${ROOT}") 11 BIN="${BUILD_DIR}/linkd" 12 13 T=$(mktemp -d) 14 # Kill the daemon by pid on the way out. `kill %1` does not work here: job 15 # control is off in a non-interactive shell, so a failed test used to leave 16 # the daemon running and holding its port. 17 cleanup() { 18 # `|| true` throughout: the daemon is normally already gone, and a failing 19 # kill under `set -e` would abort the trap and fail an otherwise green run. 20 if [ -f "${T}/daemon.pid" ]; then kill "$(cat "${T}/daemon.pid")" 2>/dev/null || true; fi 21 rm -rf "${T}" || true 22 } 23 trap cleanup EXIT 24 mkdir -p "${T}/bin" 25 26 for l in linkd linkctl ifup ifdown ifquery ifreload; do ln -sf "${BIN}" "${T}/bin/${l}"; done 27 28 # No `auto`: the daemon applies nothing, so it needs no privileges and cannot 29 # touch the host's network. 30 cat > "${T}/interfaces" <<EOF 31 iface lo 32 address 127.0.0.1/8 33 address ::1/128 34 EOF 35 36 cat > "${T}/linkd.cnf" <<EOF 37 config_iface ${T}/interfaces 38 authfile ${T}/linkd.passwd 39 listen unix://${T}/linkd.sock 40 listen tcp://127.0.0.1:16793 41 EOF 42 43 # The suite may run as any uid, so it cannot rely on SO_PEERCRED granting 44 # access; it authenticates explicitly instead. 45 HASH=$(printf 'testpass\n' | "${T}/bin/linkctl" hash) 46 printf 'tester:%s:full\n' "${HASH}" > "${T}/linkd.passwd" 47 chmod 600 "${T}/linkd.passwd" 48 49 # Request comes from stdin, not argv: shell quoting mangles CRLF escapes. 50 cat > "${T}/raw.c" <<'EOF' 51 #include <stdio.h> 52 #include <string.h> 53 #include <sys/socket.h> 54 #include <sys/un.h> 55 #include <unistd.h> 56 int main(int argc, char **argv) { 57 (void)argc; 58 char req[65536]; 59 size_t len = fread(req, 1, sizeof(req), stdin); 60 int fd = socket(AF_UNIX, SOCK_STREAM, 0); 61 struct sockaddr_un a; memset(&a, 0, sizeof(a)); a.sun_family = AF_UNIX; 62 strncpy(a.sun_path, argv[1], sizeof(a.sun_path)-1); 63 if (connect(fd, (struct sockaddr*)&a, sizeof(a))) return 1; 64 write(fd, req, len); 65 shutdown(fd, SHUT_WR); 66 char b[65536]; ssize_t n, t = 0; 67 while ((n = read(fd, b+t, sizeof(b)-t-1)) > 0) t += n; 68 b[t] = 0; fwrite(b, 1, t, stdout); 69 return 0; 70 } 71 EOF 72 cc -O2 -o "${T}/raw" "${T}/raw.c" 2>/dev/null || { echo "SKIP: no compiler for raw client"; exit 0; } 73 74 OUT="${T}/out" 75 timeout 60 sh -c " 76 '${T}/bin/linkd' --config '${T}/linkd.cnf' --ready-file '${T}/ready' --resync-interval 0 >'${T}/daemon.log' 2>&1 & 77 DPID=\$! 78 echo \$DPID > '${T}/daemon.pid' 79 for i in \$(seq 1 80); do [ -e '${T}/ready' ] && break; sleep 0.25; done 80 export LINKD_CONFIG='${T}/linkd.cnf' 81 export LINKD_AUTH='tester:testpass' 82 83 echo '--ping--'; '${T}/bin/linkctl' PING 84 echo '--echo--'; '${T}/bin/linkctl' PING hello 85 echo '--query--'; '${T}/bin/ifquery' lo 86 echo '--unknown--'; '${T}/bin/linkctl' FROBNICATE 2>&1 || true 87 echo '--missing--'; '${T}/bin/ifquery' nosuch 2>&1 || true 88 echo '--arity--'; '${T}/bin/linkctl' IFUP 2>&1 || true 89 echo '--mutate--'; '${T}/bin/ifup' lo 2>&1 || true 90 echo '--peercred--'; env -u LINKD_AUTH '${T}/bin/ifup' lo 2>&1 || true 91 92 echo '--pipeline--' 93 printf '*1\r\n\$4\r\nPING\r\n*2\r\n\$4\r\nPING\r\n\$3\r\ntwo\r\n*1\r\n\$4\r\nPING\r\n' \ 94 | '${T}/raw' '${T}/linkd.sock' 95 96 echo '--concurrent--' 97 pids='' 98 for i in \$(seq 1 8); do '${T}/bin/linkctl' PING & pids=\"\$pids \$!\"; done 99 for p in \$pids; do wait \$p; done 100 101 echo '--tcp-ping--' 102 printf '*1\r\n\$4\r\nPING\r\n' | timeout 5 nc 127.0.0.1 16793 2>/dev/null | head -1 103 echo '--tcp-noauth--' 104 printf '*2\r\n\$7\r\nIFQUERY\r\n\$2\r\nlo\r\n' | timeout 5 nc 127.0.0.1 16793 2>/dev/null | head -1 105 echo '--end--' 106 107 kill \$DPID 2>/dev/null; wait \$DPID 2>/dev/null 108 " > "${OUT}" 2>&1 || true 109 110 section() { sed -n "/^--$1--\$/,/^--/p" "${OUT}" | sed '1d;$d'; } 111 112 assert_contains "$(section ping)" "PONG" "PING replies PONG" 113 assert_contains "$(section echo)" "hello" "PING <msg> echoes it" 114 assert_contains "$(section query)" "iface lo" "IFQUERY renders as config syntax" 115 assert_contains "$(section query)" "address 127.0.0.1/8" "IFQUERY returns addresses" 116 assert_contains "$(section query)" "address ::1/128" "IFQUERY returns IPv6 addresses" 117 assert_contains "$(section unknown)" "unknown command" "unknown command rejected" 118 assert_contains "$(section missing)" "no such interface" "missing interface rejected" 119 assert_contains "$(section arity)" "wrong number of arguments" "arity is enforced" 120 121 # Authenticated: the command must get past authorization. Whether netlink then 122 # succeeds depends on privileges, which is not what this asserts. 123 case "$(section mutate)" in 124 *NOAUTH*|*NOPERM*) 125 echo "FAIL: authenticated client denied: $(section mutate)" >&2; exit 1 ;; 126 *) echo "PASS: authenticated client is authorized to mutate" ;; 127 esac 128 129 # Unauthenticated: SO_PEERCRED alone decides, so the answer depends on uid. 130 # Both directions matter -- root must get in without credentials, others must 131 # not. 132 peercred=$(section peercred) 133 if [ "$(id -u)" = "0" ]; then 134 case "${peercred}" in 135 *NOAUTH*|*NOPERM*) 136 echo "FAIL: root over unix was not pre-authenticated: ${peercred}" >&2; exit 1 ;; 137 *) echo "PASS: root over unix is pre-authenticated" ;; 138 esac 139 else 140 assert_contains "${peercred}" "NOAUTH" "non-root over unix is not pre-authenticated" 141 fi 142 143 pipeline=$(section pipeline) 144 n=$(printf '%s' "${pipeline}" | grep -c 'PONG' || true) 145 assert_eq "${n}" "2" "pipelined PINGs answered (2 bare PONG + 1 echo)" 146 assert_contains "${pipeline}" "two" "pipelined echo answered in order" 147 148 n=$(section concurrent | grep -c 'PONG' || true) 149 assert_eq "${n}" "8" "8 concurrent clients all served" 150 151 assert_contains "$(section tcp-ping)" "PONG" "PING is allowed before AUTH" 152 assert_contains "$(section tcp-noauth)" "NOAUTH" "other commands require AUTH over tcp" 153 154 cat > "${T}/noauth.cnf" <<EOF 155 config_iface ${T}/interfaces 156 listen unix://${T}/linkd2.sock 157 listen tcp://127.0.0.1:16794 158 EOF 159 out=$(timeout 15 "${BIN}" --config "${T}/noauth.cnf" 2>&1 || true) 160 assert_contains "${out}" "refusing to listen" "tcp without authfile is refused" 161 162 echo "==> test_resp done"