Skip to content

Commit 1f28250

Browse files
committed
Merge net-zone-words (#225): The net zone's words reach the log marked, bounded and as text
2 parents 5d84f16 + 6083974 commit 1f28250

6 files changed

Lines changed: 92 additions & 11 deletions

File tree

‎build/services/net-zone/run‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,12 @@ if ! have_nic; then
1515
sleep 3
1616
echo "net-zone: an interface has appeared"
1717
fi
18+
# --ready-fd: nobody waits to hear that the zone is up, but with it the launcher
19+
# keeps this service's log and hands the zone a pipe. Each line the zone
20+
# writes is then marked as its own and cannot pass for the launcher's or a
21+
# zone 0 service's, its control bytes are replaced, and a megabyte a start is
22+
# the most it logs (spawn.rs, ZoneOutput). A zone that says more goes unlogged
23+
# until its next start; filling the allowance again by the hour is the way on
24+
# if that proves too little.
1825
exec /usr/bin/kryptikd run net --zones /usr/lib/kryptik/zones --rootfs /var/lib/kryptik/zones \
19-
-- /usr/libexec/kryptik/netzone-init.sh
26+
--ready-fd 3 -- /usr/libexec/kryptik/netzone-init.sh 3>/dev/null

‎compartments/kryptikd/src/spawn.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,11 @@ fn serve_until_exit(pid: libc::pid_t, listen_fd: RawFd, s: &broker::Served, out:
380380
let pfd = pfds[0];
381381
if n > 0 && pfd.revents & libc::POLLIN != 0 {
382382
match broker::serve_one(listen_fd, s) {
383+
/* Not the calls a net zone makes by the minute: they would use
384+
* up the log's bound in days, and what they decide is logged
385+
* where it is decided. */
386+
Ok(Some("update-poll" | "update-put" | "version")) | Ok(None) => {}
383387
Ok(Some(verb)) => log(&format!("kryptikd[zone {zone}]: broker request: {verb}")),
384-
Ok(None) => {}
385388
Err(e) => log(&format!("kryptikd[zone {zone}]: broker: {e}")),
386389
}
387390
}

‎docs/design/net-zone.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ netzone: READY uplink=<addr|none> nat=yes dns=<yes|no> wifi=<ssid|connecting|unc
112112
netzone: NOT READY <reason> (forwarding off)
113113
```
114114

115+
The zone writes to a pipe, never to the log: its launcher marks each line
116+
`zone net| ` in the catch-all log, replaces control bytes, and logs at most
117+
a megabyte a start. A line without the mark is the launcher's or a zone 0
118+
service's, whatever it says. The zone prints words from the network (an
119+
SSID, a server's refusal) as they came, never as escapes.
120+
115121
## The uplinks' own networks
116122

117123
A zone needs the gateway only as a next hop, and its names go through the

‎tools/net/netzone-init.sh‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
# time=<offset|no-answer|no-uplink|...> ...
77
# netzone: NOT READY <reason> (forwarding is off)
88
set -u
9-
say() { echo "netzone: $*"; }
9+
# printf, not echo: a word from the network (an SSID, a server's reason for a
10+
# refusal) is printed as it came, and dash's echo would act on its backslashes.
11+
say() { printf 'netzone: %s\n' "$*"; }
1012
BR=kryptik0
1113
STATUS=/run/netzone-status
1214
WPA_CONF=/etc/wpa_supplicant.conf # bound in read-only from zone 0 (kryptik wifi add)
@@ -311,14 +313,17 @@ UPDATE_CONF=/etc/kryptik/update.conf
311313
UPDATE_FETCH="${KRYPTIK_UPDATE_FETCH:-/usr/libexec/kryptik/update-fetch.py}"
312314
UPDATE_BROUGHT=/run/kryptik-update-statement-brought
313315
UPDATE_PID=""
314-
update_run() { # update_run latest|poll: in the background, one at a time
316+
update_run() { # update_run latest|poll: in the background, one at a time; 1 while the last one runs
315317
{ command -v python3 >/dev/null 2>&1 && [ -r "$UPDATE_FETCH" ] && [ -r "$UPDATE_CONF" ]; } || return 0
316-
[ -n "$UPDATE_PID" ] && kill -0 "$UPDATE_PID" 2>/dev/null && return 0
318+
[ -n "$UPDATE_PID" ] && kill -0 "$UPDATE_PID" 2>/dev/null && return 1
317319
(
318-
out="$(python3 "$UPDATE_FETCH" "$1" --broker "$BROKER" 2>&1 | tail -1)"
319-
case "$1:$out" in
320-
poll:idle|*:) ;;
321-
latest:ok*) : > "$UPDATE_BROUGHT"; say "update: zone 0 on the statement of what is current: ${out}" ;;
320+
out="$(python3 "$UPDATE_FETCH" "$1" --broker "$BROKER" 2>&1)"; rc=$?
321+
out="$(printf '%s\n' "$out" | tail -1)"
322+
# Zone 0's answer counts only from a fetch that ended well: the last
323+
# line of one that failed is the far host's words.
324+
case "$rc:$1:$out" in
325+
0:poll:idle|*:*:) ;;
326+
0:latest:ok*) : > "$UPDATE_BROUGHT"; say "update: zone 0 on the statement of what is current: ${out}" ;;
322327
*) say "update $1: ${out}" ;;
323328
esac
324329
) &
@@ -393,7 +398,8 @@ while :; do
393398
if [ -n "$(uplink_addr "$@")" ]; then
394399
if [ -e "$UPDATE_BROUGHT" ]; then statement_every=8640; else statement_every=180; fi
395400
if [ "$statement_ticks" -ge "$statement_every" ]; then
396-
statement_ticks=0; update_run latest
401+
# Asked again at the next pass when a poll was still running.
402+
update_run latest && statement_ticks=0
397403
elif [ "$update_ticks" -ge 6 ]; then
398404
update_ticks=0; update_run poll
399405
fi

‎tools/net/update-fetch.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ def main():
112112
try:
113113
return latest(args) if args.what == "latest" else poll(args)
114114
except (OSError, ValueError) as e: # urllib's errors are OSErrors
115-
print("update-fetch: %s" % e, file=sys.stderr)
115+
# One line: the text can hold a host's own words, line ends among them.
116+
print("update-fetch: %s" % " ".join(str(e).split()), file=sys.stderr)
116117
return 1
117118

118119

‎tools/tests/netzone-words.sh‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/env bash
2+
# Tests for the words the net zone prints and takes from the network
3+
# (tools/net/netzone-init.sh, say and update_run): a backslash is printed, not
4+
# acted on, and the update helper's last line counts as zone 0's answer only
5+
# when the helper ended well. Offline, under each POSIX shell here.
6+
set -uo pipefail
7+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
8+
SCRIPT="${ROOT}/tools/net/netzone-init.sh"
9+
10+
PASS=0; FAIL=0
11+
green() { printf ' PASS %s\n' "$1"; PASS=$((PASS + 1)); }
12+
red() { printf ' FAIL %s\n' "$1"; FAIL=$((FAIL + 1)); [[ $# -gt 1 ]] && printf ' %s\n' "$2"; }
13+
same() { if [[ "$2" == "$3" ]]; then green "$1"; else red "$1" "got '$(tr '\n' '|' <<<"$2")', want '$(tr '\n' '|' <<<"$3")'"; fi; }
14+
command -v python3 >/dev/null 2>&1 || { echo "python3 is not installed here; cannot run this test"; exit 77; }
15+
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
16+
17+
{ sed -n '/^say() {/p' "$SCRIPT"; sed -n '/^update_run() {/,/^}/p' "$SCRIPT"; } > "$T/functions.sh"
18+
grep -q '^say() {' "$T/functions.sh" && grep -q '^update_run() {' "$T/functions.sh" \
19+
|| { echo "could not find say or update_run in ${SCRIPT#"$ROOT"/} (did their first lines move?)"; exit 1; }
20+
grep -q -- '--ready-fd 3 -- /usr/libexec/kryptik/netzone-init.sh 3>/dev/null' "${ROOT}/build/services/net-zone/run" \
21+
&& green "the service starts the zone on a pipe, so its launcher marks and bounds what it says" \
22+
|| red "build/services/net-zone/run hands the zone the service's own log"
23+
24+
# The update helper's stand-in: prints what it is told to, ends as it is told to.
25+
cat > "$T/fetch.py" <<'EOF'
26+
import os, sys
27+
sys.stdout.write(os.environ.get("SAYS", ""))
28+
sys.exit(int(os.environ.get("ENDS", "0")))
29+
EOF
30+
: > "$T/update.conf"
31+
cat > "$T/harness.sh" <<EOF
32+
. "$T/functions.sh"
33+
UPDATE_FETCH="$T/fetch.py"; UPDATE_CONF="$T/update.conf"; UPDATE_BROUGHT="$T/brought"; BROKER=/nowhere; UPDATE_PID=""
34+
case "\$1" in
35+
say) shift; say "\$@" ;;
36+
run) update_run "\$2"; rc=\$?; wait; echo "rc=\$rc"; [ -e "\$UPDATE_BROUGHT" ] && echo brought || echo not-brought ;;
37+
busy) sleep 5 & UPDATE_PID=\$!; update_run latest; echo "rc=\$?"; kill "\$UPDATE_PID" ;;
38+
esac
39+
EOF
40+
41+
for sh in sh bash dash; do
42+
command -v "$sh" >/dev/null 2>&1 || continue
43+
run() { rm -f "$T/brought"; "$sh" "$T/harness.sh" "$@" 2>&1; }
44+
echo "-- ${sh}"
45+
same "a backslash from the network is printed as it came" \
46+
"$(run say 'wifi: Caf\x65\nnetzone: READY \033[2J')" 'netzone: wifi: Caf\x65\nnetzone: READY \033[2J'
47+
same "zone 0's ok to a statement is taken, and the statement is not asked for again that day" \
48+
"$(SAYS='ok current' ENDS=0 run run latest)" "$(printf 'netzone: update: zone 0 on the statement of what is current: ok current\nrc=0\nbrought')"
49+
same "the same words at the end of a fetch that failed are the far host's: said, and not taken" \
50+
"$(SAYS=$'update-fetch: HTTP Error 302: loop\nok current' ENDS=1 run run latest)" "$(printf 'netzone: update latest: ok current\nrc=0\nnot-brought')"
51+
same "an idle poll says nothing" "$(SAYS='idle' ENDS=0 run run poll)" "$(printf 'rc=0\nnot-brought')"
52+
same "a poll that failed is said, whatever its last line" "$(SAYS='idle' ENDS=1 run run poll)" "$(printf 'netzone: update poll: idle\nrc=0\nnot-brought')"
53+
same "while one runs, the next is not started and says so by its status" "$(run busy)" "rc=1"
54+
done
55+
56+
echo
57+
echo "${PASS} passed, ${FAIL} failed"
58+
[[ "$FAIL" -eq 0 ]]

0 commit comments

Comments
 (0)