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
32 changes: 31 additions & 1 deletion os/v3/puppy/embed/etc/init.d/zz-onboard-ports
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# /etc/init.d/zz-onboard-ports - the appliance's two inbound ports, opened after Puppy's firewall.
# /etc/init.d/zz-onboard-ports - the appliance's inbound ports and its mDNS responder, opened after Puppy's
# firewall.
#
# Measured in a booted image: `iptables -S` shows `-P INPUT DROP` with `tcp_inbound` RETURNing to that
# policy, so every new inbound TCP connection from outside is dropped. lemond binds 0.0.0.0:13305 and the
Expand All @@ -35,6 +36,35 @@ open_port() {
open_port 13305 # Lemonade (U3)
open_port 22 # ssh, the owner's key (U7)

# mDNS (cycle 23; c22 board 4 Nov: avahi logged "1bit-os.local is announced" and a raw mDNS query from ryzen got
# no answer at all). The reason is in the same rc.firewall this file already had to work around, read out of the
# packed c22 puppy sfs:
#
# -P INPUT DROP
# -A INPUT -p ALL -d 224.0.0.1 -j DROP (only .1; a query to 224.0.0.251 gets past this)
# -A INPUT -p UDP -j udp_inbound (drops 137/138, accepts DHCP 67->68, then -j RETURN)
# -A INPUT -m pkttype --pkt-type broadcast -j DROP
#
# so every NEW inbound UDP packet falls through to the DROP policy, and 5353 is UDP. A query is exactly that
# packet, which is why the responder never saw one; its own announcements did reach the wire, because the same
# file ends with `-A OUTPUT -p ALL -j ACCEPT` - so this needs no OUTPUT rule, only the query that confirms it.
#
# LAN-only, the same ranges as hosts.allow below, plus link-local (avahi's fallback when there is no lease):
# announcing to a phone on the LAN is the point, being a public responder is not. `-I INPUT 1` puts these ahead
# of udp_inbound and of the broadcast drop, and -C keeps a re-run a no-op.
open_mdns() {
for src in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16; do
iptables -C INPUT -p udp --dport 5353 -s "$src" -j ACCEPT 2> /dev/null && continue
iptables -I INPUT 1 -p udp --dport 5353 -s "$src" -j ACCEPT 2> /dev/null && continue
echo "zz-onboard-ports: could not open udp/5353 for $src" >&2
return 1
done
}
# shortcut: IPv4 only. 18onboard-mdns's responder answers on both families, but this image's IPv6 is
# unconfigured (no global address, and the board reaches it at 192.168.50.110), so a v6 rule would be untested
# code; add fe80::/10 to this loop when the appliance gets an IPv6 address.
open_mdns

# And the other half of reachability, measured the same way: Puppy ships /etc/hosts.deny = "ALL: ALL" with
# /etc/hosts.allow = "ALL: LOCAL", and LOCAL means this machine, so a client on the LAN is refused by
# tcpwrappers before ssh's key exchange even starts - ssh says `kex_exchange_identification: Connection
Expand Down
78 changes: 78 additions & 0 deletions os/v3/puppy/mdns-ports-selftest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/sh
# Copyright 2026 bong-water-water-bong
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# mdns-ports-selftest.sh [script] - the self-check for zz-onboard-ports' inbound rules, with a stub `iptables`.
#
# No root, no real ruleset, no device: the stub keeps a rule table in a file and answers `-C` from it, so this
# asserts what the shipped script actually issues. The mDNS block is the kind of code that fails silently (a rule
# with the wrong source, or one re-added on every boot), so it gets a check that runs anywhere:
#
# * one insert per LAN range, four of them, with the four sources the file intends;
# * a second run against the same ruleset inserts nothing (`-C` first, which is what keeps a re-run a no-op);
# * the two pre-existing TCP ports are still opened - this script must not have moved them.
#
# The measured behaviour on a booted image (`vm-check`'s `U3 mdns rule` row, and the counter test in #1016's body)
# is what proves the appliance accepts the packet; this proves the script's own shape.
set -u
prog=${0##*/}
here=$(cd "$(dirname "$0")" && pwd)
script=${1:-$here/embed/etc/init.d/zz-onboard-ports}
[ -f "$script" ] || { echo "$prog: no $script" >&2; exit 2; }

work=$(mktemp -d "${TMPDIR:-/tmp}/mdns-ports.XXXXXX") || exit 2
trap 'rm -rf "$work"' EXIT INT TERM
mkdir -p "$work/bin"
export CALLS="$work/calls" STATE="$work/state"
: > "$CALLS"; : > "$STATE"

cat > "$work/bin/iptables" <<'STUB'
#!/bin/sh
# the rule's identity is "<chain> <spec...>", so `-I INPUT 1 <spec>` and `-C INPUT <spec>` compare directly
echo "$*" >> "$CALLS"
cmd=$1; shift
chain=$1; shift
case $cmd in
-C) grep -qxF "$chain $*" "$STATE" 2>/dev/null && exit 0; exit 1 ;;
-I) shift; echo "$chain $*" >> "$STATE"; exit 0 ;;
-S) printf '%s\n' '-A INPUT -p tcp -m tcp --dport 13305 -j ACCEPT' '-A INPUT -p udp -m udp --dport 5353 -j ACCEPT'; exit 0 ;;
*) exit 0 ;;
esac
STUB
chmod +x "$work/bin/iptables"
PATH="$work/bin:$PATH" sh "$script" > "$work/run1.log" 2>&1
i1=$(grep -c -- '^-I ' "$CALLS")
c1=$(grep -c -- '-dport 5353' "$CALLS")
u1=$(grep -c -- '^-I .* -p udp --dport 5353 ' "$CALLS")
PATH="$work/bin:$PATH" sh "$script" > "$work/run2.log" 2>&1
i2=$(grep -c -- '^-I ' "$CALLS")
u2=$(grep -c -- '^-I .* -p udp --dport 5353 ' "$CALLS")
c2=$(grep -c -- '-dport 5353' "$CALLS")

echo "run 1: $i1 insert(s), $u1 of them udp/5353, $c1 udp/5353 command(s)"
echo "run 2 (same ruleset): $((i2 - i1)) insert(s), $((c2 - c1)) udp/5353 command(s) - all -C, none -I"
fail=
[ "$i1" -ge 6 ] || fail="$fail first-run-inserts=$i1(expected 6: two TCP ports, four udp/5353)"
[ "$u1" -eq 4 ] || fail="$fail udp5353-inserts=$u1(expected 4)"
[ "$u2" -eq 4 ] || fail="$fail re-run-inserted-udp5353=$u2(not idempotent)"
[ "$((c2 - c1))" -eq 4 ] || fail="$fail re-run-udp5353-commands=$((c2 - c1))(expected 4 -C and no -I)"
for src in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.0.0/16; do
grep -qxF "INPUT -p udp --dport 5353 -s $src -j ACCEPT" "$STATE" || fail="$fail missing-rule-$src"
done
if [ -n "$fail" ]; then
echo "RESULT: FAIL -$fail"
exit 1
fi
echo "RESULT: PASS (four udp/5353 rules inserted once, the re-run inserts nothing, sources as intended)"
9 changes: 9 additions & 0 deletions os/v3/puppy/vm-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,15 @@ rows = [
# row runs is a race, but an ACCEPT for tcp/13305 is what makes U3 reachable either way.
("U3 port rule", "iptables -C INPUT -p tcp --dport 13305 -j ACCEPT && echo port-13305-open; "
"iptables -C INPUT -p tcp --dport 22 -j ACCEPT && echo port-22-open", None),
# Cycle 23 (c22 board, 4 Nov): avahi logged "1bit-os.local is announced" and a raw mDNS query from ryzen got no
# answer at all. The cause is in the same rc.firewall the two rows above work around: every NEW inbound UDP
# packet goes to `udp_inbound`, which accepts only DHCP and RETURNs to the DROP policy, and a query on 5353 is
# UDP - while the announcements themselves left fine (`-A OUTPUT -p ALL -j ACCEPT`). The durable assertion is
# the rule, for the same reason as the ports above; whether the responder is up is read in the same row.
("U3 mdns rule", "iptables -S INPUT | grep -E -- '-p udp.*--dport 5353.*-j ACCEPT' | head -2; "
"iptables -C INPUT -p udp --dport 5353 -s 10.0.0.0/8 -j ACCEPT 2>/dev/null && echo udp-5353-open; "
"pidof avahi-daemon > /dev/null && echo avahi-up; tail -1 /var/log/mdns.log 2>&1",
"udp-5353-open"),
("net addr", "ip -4 -o addr show 2>/dev/null | tr -s ' '", None),
("net route", "ip -4 route 2>/dev/null | head -3", None),
]
Expand Down
Loading