testsAndMisc/phone_focus_mode/lib/tests/curfew_net_cases_lifecycle.sh
Krzysztof kuhy Rudnicki 9f2e208257 curfew_enforcer: split 367 -> 244 + 156, net layer at 100%
The nine network-curfew functions -- the per-UID iptables allow-list that
cuts every app outside $NIGHT_WHITELIST off the network overnight -- move
into curfew_net.sh. NET_BUILT stays in the enforcer: both files write it,
so it is a genuine shared global rather than a seam violation.

Placed as a FLAT sibling, not under lib/. deploy.sh pushes phone scripts
into one directory, so a lib/ path would simply not exist on the device;
dns_iptables.sh and tether_iptables.sh sit beside their enforcers for the
same reason. Added to BOTH deploy lists, and deliberately not to the chmod
list -- sourced files need no exec bit there, matching the other two.

41 assertions, 60/60 lines, 17 mutations all killed. The one that matters
most flips the 10000-19999 REJECT to ACCEPT, which would silently disable
the entire cut-off while leaving a chain that looks populated. The suite
also asserts rule ORDER, not just presence: every ACCEPT must precede that
REJECT, or the whitelist blocks the very apps it exists to permit.

night_uids and fill_net_chain are reported as non-verbatim by
verify_shell_split.sh, correctly: both ended in `done < file`, a line kcov
instruments and bash never reports, which alone held the file at 96.77%.
Both are now piped. Neither loop reads stdin internally, so the subshell is
immaterial -- unlike pull_apks in backup_capture.sh, where the same change
would reintroduce a known bug. Behaviour was checked rather than assumed:
night_uids emits the same uids, and fill_net_chain emits an identical
11-rule sequence, diffed against the pre-split bodies.

UNVERIFIED ON DEVICE -- joins tether_enforcer.sh and dns_enforcer.sh.
Over-cap count 21 -> 20.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-17 16:18:05 +02:00

122 lines
4.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# lib/tests/curfew_net_cases_lifecycle.sh — assertions for the lifecycle half
# of curfew_net.sh: iptw, refresh_uid_cache, rebuild_net_from_cache, net_hold,
# apply_net and teardown_net.
#
# Sourced by test_curfew_net.sh after curfew_net_harness.sh, which owns the
# stubs, the subject and the PASS/FAIL counters this file adds to.
set -euo pipefail
# --- iptw -------------------------------------------------------------------
_reset_ipt
iptw iptables -L "${CURFEW_NET_IPT_CHAIN}" >/dev/null 2>&1 || true
if _calls | grep -q "^iptables -L"; then
_t_pass "iptw dispatches to the named binary"
else
_t_fail "iptw did not call the named binary"
fi
# --- refresh_uid_cache ------------------------------------------------------
printf 'com.foo\ncom.bar\n' >"${STATE_DIR}/night_whitelist.txt"
printf 'com.foo uid:10123\ncom.bar uid:10456\n' >"${PM_FIXTURE}"
rm -f "${CURFEW_NET_UID_CACHE}"
refresh_uid_cache
_t_eq "10123 10456" "$(tr '\n' ' ' <"${CURFEW_NET_UID_CACHE}" | sed 's/ $//')" \
"refresh_uid_cache writes the resolved uids"
if [[ ! -f "${CURFEW_NET_UID_CACHE}.tmp" ]]; then
_t_pass "refresh_uid_cache moves its temp file into place"
else
_t_fail "refresh_uid_cache left a .tmp file behind"
fi
# --- rebuild_net_from_cache -------------------------------------------------
_reset_ipt
rebuild_net_from_cache
if [[ -n "$(_rules_of iptables)" && -n "$(_rules_of ip6tables)" ]]; then
_t_pass "rebuild_net_from_cache builds both v4 and v6 chains"
else
_t_fail "rebuild_net_from_cache should build both address families"
fi
# --- net_hold ---------------------------------------------------------------
# Chain intact for the whole window: no rebuild.
_reset_ipt
_seed_jumps iptables 1
# Read back after assigning: the subject is what consumes these, but each
# file is linted standalone, so an assignment with no reader here is SC2034.
CURFEW_NET_REASSERT_INTERVAL=1
_t_eq "1" "${CURFEW_NET_REASSERT_INTERVAL}" "the reassert interval is set for net_hold"
_t_eq "0" "$(net_hold 2)" "net_hold does not rebuild while the chain survives"
# Chain missing: the first check rebuilds it, and because the rebuild
# actually creates the chain the second check finds it present. One rebuild
# for a single flush is the self-healing behaviour, not a missed one.
_reset_ipt
_t_eq "1" "$(net_hold 2)" "net_hold rebuilds a vanished chain and then settles"
# --- apply_net --------------------------------------------------------------
_reset_ipt
CURFEW_NET_ENABLED=0
apply_net
_t_eq "" "$(_calls)" "apply_net does nothing while the net layer is disabled"
CURFEW_NET_ENABLED=1
_t_eq "1" "${CURFEW_NET_ENABLED}" "the net layer is re-enabled for the remaining cases"
_reset_ipt
NET_BUILT=""
apply_net
_t_eq "1" "${NET_BUILT}" "apply_net marks the chain as built"
if [[ -n "$(_rules_of iptables)" && -n "$(_rules_of ip6tables)" ]]; then
_t_pass "apply_net builds both address families"
else
_t_fail "apply_net should build both address families"
fi
# A chain that vanished between ticks is logged, so a live test can tell
# "external flush + self-heal" from "the daemon died".
_reset_ipt
: >"${STATE_DIR}/log"
NET_BUILT=1
apply_net
if grep -q "vanished since last tick" "${STATE_DIR}/log"; then
_t_pass "apply_net logs an externally flushed chain"
else
_t_fail "apply_net should log when the chain vanished between ticks"
fi
# On a first build there is nothing to have vanished, so nothing is logged.
_reset_ipt
: >"${STATE_DIR}/log"
NET_BUILT=""
apply_net
if grep -q "vanished since last tick" "${STATE_DIR}/log"; then
_t_fail "apply_net logged a vanished chain on its first build"
else
_t_pass "apply_net stays quiet on the first build"
fi
# --- teardown_net -----------------------------------------------------------
_reset_ipt
_seed_jumps iptables 2
_seed_jumps ip6tables 1
teardown_net
# 2 jumps + 1 terminating failure on v4, 1 + 1 on v6 = 5.
_t_eq "5" "$(_calls | grep -c -- "-D OUTPUT -j FOCUS_CURFEW_NET")" \
"teardown_net removes every OUTPUT jump on both families"
_t_eq "2" "$(_calls | grep -c -- "-X FOCUS_CURFEW_NET")" \
"teardown_net deletes the chain on both families"
if [[ -f "${RUN}/ipt/iptables/chain" ]]; then
_t_fail "teardown_net left the v4 chain in place"
else
_t_pass "teardown_net leaves no chain behind"
fi