Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion build/services/net-zone/run
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,12 @@ if ! have_nic; then
sleep 3
echo "net-zone: an interface has appeared"
fi
# --ready-fd: nobody waits to hear that the zone is up, but with it the launcher
# keeps this service's log and hands the zone a pipe. Each line the zone
# writes is then marked as its own and cannot pass for the launcher's or a
# zone 0 service's, its control bytes are replaced, and a megabyte a start is
# the most it logs (spawn.rs, ZoneOutput). A zone that says more goes unlogged
# until its next start; filling the allowance again by the hour is the way on
# if that proves too little.
exec /usr/bin/kryptikd run net --zones /usr/lib/kryptik/zones --rootfs /var/lib/kryptik/zones \
-- /usr/libexec/kryptik/netzone-init.sh
--ready-fd 3 -- /usr/libexec/kryptik/netzone-init.sh 3>/dev/null
5 changes: 4 additions & 1 deletion compartments/kryptikd/src/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,11 @@ fn serve_until_exit(pid: libc::pid_t, listen_fd: RawFd, s: &broker::Served, out:
let pfd = pfds[0];
if n > 0 && pfd.revents & libc::POLLIN != 0 {
match broker::serve_one(listen_fd, s) {
/* Not the calls a net zone makes by the minute: they would use
* up the log's bound in days, and what they decide is logged
* where it is decided. */
Ok(Some("update-poll" | "update-put" | "version")) | Ok(None) => {}
Ok(Some(verb)) => log(&format!("kryptikd[zone {zone}]: broker request: {verb}")),
Ok(None) => {}
Err(e) => log(&format!("kryptikd[zone {zone}]: broker: {e}")),
}
}
Expand Down
6 changes: 6 additions & 0 deletions docs/design/net-zone.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ netzone: READY uplink=<addr|none> nat=yes dns=<yes|no> wifi=<ssid|connecting|unc
netzone: NOT READY <reason> (forwarding off)
```

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

## The uplinks' own networks

A zone needs the gateway only as a next hop, and its names go through the
Expand Down
22 changes: 14 additions & 8 deletions tools/net/netzone-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
# time=<offset|no-answer|no-uplink|...> ...
# netzone: NOT READY <reason> (forwarding is off)
set -u
say() { echo "netzone: $*"; }
# printf, not echo: a word from the network (an SSID, a server's reason for a
# refusal) is printed as it came, and dash's echo would act on its backslashes.
say() { printf 'netzone: %s\n' "$*"; }
BR=kryptik0
STATUS=/run/netzone-status
WPA_CONF=/etc/wpa_supplicant.conf
Expand Down Expand Up @@ -326,14 +328,17 @@ UPDATE_CONF=/etc/kryptik/update.conf
UPDATE_FETCH="${KRYPTIK_UPDATE_FETCH:-/usr/libexec/kryptik/update-fetch.py}"
UPDATE_BROUGHT=/run/kryptik-update-statement-brought
UPDATE_PID=""
update_run() { # update_run latest|poll: in the background, one at a time
update_run() { # update_run latest|poll: in the background, one at a time; 1 while the last one runs
{ command -v python3 >/dev/null 2>&1 && [ -r "$UPDATE_FETCH" ] && [ -r "$UPDATE_CONF" ]; } || return 0
[ -n "$UPDATE_PID" ] && kill -0 "$UPDATE_PID" 2>/dev/null && return 0
[ -n "$UPDATE_PID" ] && kill -0 "$UPDATE_PID" 2>/dev/null && return 1
(
out="$(python3 "$UPDATE_FETCH" "$1" --broker "$BROKER" 2>&1 | tail -1)"
case "$1:$out" in
poll:idle|*:) ;;
latest:ok*) : > "$UPDATE_BROUGHT"; say "update: zone 0 on the statement of what is current: ${out}" ;;
out="$(python3 "$UPDATE_FETCH" "$1" --broker "$BROKER" 2>&1)"; rc=$?
out="$(printf '%s\n' "$out" | tail -1)"
# Zone 0's answer counts only from a fetch that ended well: the last
# line of one that failed is the far host's words.
case "$rc:$1:$out" in
0:poll:idle|*:*:) ;;
0:latest:ok*) : > "$UPDATE_BROUGHT"; say "update: zone 0 on the statement of what is current: ${out}" ;;
*) say "update $1: ${out}" ;;
esac
) &
Expand Down Expand Up @@ -411,7 +416,8 @@ while :; do
if [ -n "$(uplink_addr "$@")" ]; then
if [ -e "$UPDATE_BROUGHT" ]; then statement_every=8640; else statement_every=180; fi
if [ "$statement_ticks" -ge "$statement_every" ]; then
statement_ticks=0; update_run latest
# Asked again at the next pass when a poll was still running.
update_run latest && statement_ticks=0
elif [ "$update_ticks" -ge 6 ]; then
update_ticks=0; update_run poll
fi
Expand Down
3 changes: 2 additions & 1 deletion tools/net/update-fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ def main():
try:
return latest(args) if args.what == "latest" else poll(args)
except (OSError, ValueError) as e: # urllib's errors are OSErrors
print("update-fetch: %s" % e, file=sys.stderr)
# One line: the text can hold a host's own words, line ends among them.
print("update-fetch: %s" % " ".join(str(e).split()), file=sys.stderr)
return 1


Expand Down
58 changes: 58 additions & 0 deletions tools/tests/netzone-words.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Tests for the words the net zone prints and takes from the network
# (tools/net/netzone-init.sh, say and update_run): a backslash is printed, not
# acted on, and the update helper's last line counts as zone 0's answer only
# when the helper ended well. Offline, under each POSIX shell here.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SCRIPT="${ROOT}/tools/net/netzone-init.sh"

PASS=0; FAIL=0
green() { printf ' PASS %s\n' "$1"; PASS=$((PASS + 1)); }
red() { printf ' FAIL %s\n' "$1"; FAIL=$((FAIL + 1)); [[ $# -gt 1 ]] && printf ' %s\n' "$2"; }
same() { if [[ "$2" == "$3" ]]; then green "$1"; else red "$1" "got '$(tr '\n' '|' <<<"$2")', want '$(tr '\n' '|' <<<"$3")'"; fi; }
command -v python3 >/dev/null 2>&1 || { echo "python3 is not installed here; cannot run this test"; exit 77; }
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT

{ sed -n '/^say() {/p' "$SCRIPT"; sed -n '/^update_run() {/,/^}/p' "$SCRIPT"; } > "$T/functions.sh"
grep -q '^say() {' "$T/functions.sh" && grep -q '^update_run() {' "$T/functions.sh" \
|| { echo "could not find say or update_run in ${SCRIPT#"$ROOT"/} (did their first lines move?)"; exit 1; }
grep -q -- '--ready-fd 3 -- /usr/libexec/kryptik/netzone-init.sh 3>/dev/null' "${ROOT}/build/services/net-zone/run" \
&& green "the service starts the zone on a pipe, so its launcher marks and bounds what it says" \
|| red "build/services/net-zone/run hands the zone the service's own log"

# The update helper's stand-in: prints what it is told to, ends as it is told to.
cat > "$T/fetch.py" <<'EOF'
import os, sys
sys.stdout.write(os.environ.get("SAYS", ""))
sys.exit(int(os.environ.get("ENDS", "0")))
EOF
: > "$T/update.conf"
cat > "$T/harness.sh" <<EOF
. "$T/functions.sh"
UPDATE_FETCH="$T/fetch.py"; UPDATE_CONF="$T/update.conf"; UPDATE_BROUGHT="$T/brought"; BROKER=/nowhere; UPDATE_PID=""
case "\$1" in
say) shift; say "\$@" ;;
run) update_run "\$2"; rc=\$?; wait; echo "rc=\$rc"; [ -e "\$UPDATE_BROUGHT" ] && echo brought || echo not-brought ;;
busy) sleep 5 & UPDATE_PID=\$!; update_run latest; echo "rc=\$?"; kill "\$UPDATE_PID" ;;
esac
EOF

for sh in sh bash dash; do
command -v "$sh" >/dev/null 2>&1 || continue
run() { rm -f "$T/brought"; "$sh" "$T/harness.sh" "$@" 2>&1; }
echo "-- ${sh}"
same "a backslash from the network is printed as it came" \
"$(run say 'wifi: Caf\x65\nnetzone: READY \033[2J')" 'netzone: wifi: Caf\x65\nnetzone: READY \033[2J'
same "zone 0's ok to a statement is taken, and the statement is not asked for again that day" \
"$(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')"
same "the same words at the end of a fetch that failed are the far host's: said, and not taken" \
"$(SAYS=$'update-fetch: HTTP Error 302: loop\nok current' ENDS=1 run run latest)" "$(printf 'netzone: update latest: ok current\nrc=0\nnot-brought')"
same "an idle poll says nothing" "$(SAYS='idle' ENDS=0 run run poll)" "$(printf 'rc=0\nnot-brought')"
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')"
same "while one runs, the next is not started and says so by its status" "$(run busy)" "rc=1"
done

echo
echo "${PASS} passed, ${FAIL} failed"
[[ "$FAIL" -eq 0 ]]
Loading