linkd

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

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"