mirror of
https://github.com/MCKero6423/uv-k5-v3-emulator.git
synced 2026-10-02 03:15:36 +00:00
Capture the DN42 firewall rules as a script
The rules restricting port 8080 to DN42 were applied by hand and existed only in the live kernel tables -- lost on reboot, with the port then wide open and nothing in the repo to say it ever had been restricted. The script is idempotent (checks before adding) and has remove/show. show prints packet counters, which is the part that matters: this host's INPUT policy is ACCEPT, so a rule that only allows DN42 does nothing at all. The final DROP is what restricts anything, and a rising DROP count is the only proof it works rather than the traffic simply not arriving. Currently observed: 2203 accepted from DN42 v4, 103 from v6, 22 dropped.
This commit is contained in:
1 parent
2d0fc24b62
commit
2c3a602d9f
2 files changed
+102
-2
No files matched your search
@@ -71,6 +71,7 @@ keypresses silently stop working. Run the test after touching that code;
|
|||||||
tools/ run, screenshot, inject keys, probe state
|
tools/ run, screenshot, inject keys, probe state
|
||||||
keypad_test.py keypad regression test, boots its own instance
|
keypad_test.py keypad regression test, boots its own instance
|
||||||
webui.py web remote control: live LCD plus clickable keypad
|
webui.py web remote control: live LCD plus clickable keypad
|
||||||
|
dn42_firewall.sh restrict the web UI port to DN42 sources
|
||||||
uvk5_qmp.py QMP client
|
uvk5_qmp.py QMP client
|
||||||
uvk5_lcd.py framebuffer decode, PNG encode, frame grabber
|
uvk5_lcd.py framebuffer decode, PNG encode, frame grabber
|
||||||
uvk5_keys.py key names the keypad model accepts
|
uvk5_keys.py key names the keypad model accepts
|
||||||
@@ -181,8 +182,29 @@ Two constraints worth knowing before you use it:
|
|||||||
|
|
||||||
- **The QMP socket takes one client.** While the server is up, `tools/key.py`
|
- **The QMP socket takes one client.** While the server is up, `tools/key.py`
|
||||||
cannot talk to the same emulator.
|
cannot talk to the same emulator.
|
||||||
- **There is no authentication.** It binds loopback, and anyone who reaches the
|
- **There is no authentication.** Anyone who reaches the port has full control of
|
||||||
port has full control of the emulated radio. Do not expose it.
|
the emulated radio. It binds loopback by default for that reason.
|
||||||
|
|
||||||
|
### Reaching it from elsewhere
|
||||||
|
|
||||||
|
`--host ::` makes it reachable off-box, which with no authentication means the
|
||||||
|
port must be filtered by source address. `tools/dn42_firewall.sh` restricts it to
|
||||||
|
DN42:
|
||||||
|
|
||||||
|
tools/dn42_firewall.sh apply 8080 # DN42 + loopback only
|
||||||
|
tools/dn42_firewall.sh show 8080 # rules and packet counts
|
||||||
|
tools/dn42_firewall.sh remove 8080
|
||||||
|
|
||||||
|
One detail that is easy to get wrong: this host's `INPUT` policy is `ACCEPT`, so a
|
||||||
|
rule that only *allows* DN42 changes nothing -- the port is already reachable with
|
||||||
|
no rules at all. The rule that does the work is the final `DROP`. Verify by
|
||||||
|
watching the counters rather than by assuming:
|
||||||
|
|
||||||
|
tools/dn42_firewall.sh show 8080
|
||||||
|
# a rising DROP count means non-DN42 traffic is actually being rejected
|
||||||
|
|
||||||
|
The rules do not survive a reboot. Re-run `apply`, or persist them with
|
||||||
|
`iptables-persistent`.
|
||||||
|
|
||||||
There is no PTT button: the keypad model has no PTT line, so the `press` property
|
There is no PTT button: the keypad model has no PTT line, so the `press` property
|
||||||
rejects the name. Unknown keys are rejected with 400 rather than forwarded.
|
rejects the name. Unknown keys are rejected with 400 rather than forwarded.
|
||||||
|
|||||||
Executable
+78
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Restrict the web UI port to DN42 sources.
|
||||||
|
#
|
||||||
|
# tools/webui.py has no authentication: anyone who reaches the port has full
|
||||||
|
# control of the emulated radio. Binding to :: makes it reachable from the public
|
||||||
|
# internet, so the port has to be filtered by source address instead.
|
||||||
|
#
|
||||||
|
# The important detail: the INPUT policy on this host is ACCEPT, so a rule that
|
||||||
|
# only *allows* DN42 does nothing at all -- without any rules the port is already
|
||||||
|
# reachable. What actually restricts anything is the final DROP. Order matters:
|
||||||
|
# accept loopback and DN42 first, then drop the rest.
|
||||||
|
#
|
||||||
|
# DN42 ranges: 172.20.0.0/14 (v4) and fd00::/8 (v6). This host already had rules
|
||||||
|
# using those same prefixes for SIP, so the convention is established.
|
||||||
|
#
|
||||||
|
# These rules do NOT survive a reboot. Re-run this script, or persist them with
|
||||||
|
# iptables-persistent / a systemd unit.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# tools/dn42_firewall.sh apply [port] # default port 8080
|
||||||
|
# tools/dn42_firewall.sh remove [port]
|
||||||
|
# tools/dn42_firewall.sh show [port]
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ACTION="${1:-show}"
|
||||||
|
PORT="${2:-8080}"
|
||||||
|
TAG="uvk5-webui"
|
||||||
|
|
||||||
|
need_root() {
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "needs root for iptables" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add a rule only when an identical one is absent, so re-running is safe.
|
||||||
|
add4() { iptables -C INPUT "$@" 2>/dev/null || iptables -A INPUT "$@"; }
|
||||||
|
add6() { ip6tables -C INPUT "$@" 2>/dev/null || ip6tables -A INPUT "$@"; }
|
||||||
|
del4() { while iptables -C INPUT "$@" 2>/dev/null; do iptables -D INPUT "$@"; done; }
|
||||||
|
del6() { while ip6tables -C INPUT "$@" 2>/dev/null; do ip6tables -D INPUT "$@"; done; }
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
apply)
|
||||||
|
need_root
|
||||||
|
# Accept first...
|
||||||
|
add4 -p tcp --dport "$PORT" -s 127.0.0.1 -j ACCEPT -m comment --comment "$TAG: loopback"
|
||||||
|
add4 -p tcp --dport "$PORT" -s 172.20.0.0/14 -j ACCEPT -m comment --comment "$TAG: dn42 v4"
|
||||||
|
add6 -p tcp --dport "$PORT" -s ::1 -j ACCEPT -m comment --comment "$TAG: loopback"
|
||||||
|
add6 -p tcp --dport "$PORT" -s fd00::/8 -j ACCEPT -m comment --comment "$TAG: dn42 v6"
|
||||||
|
# ...then drop everything else. This is the rule that does the work.
|
||||||
|
add4 -p tcp --dport "$PORT" -j DROP -m comment --comment "$TAG: deny non-dn42"
|
||||||
|
add6 -p tcp --dport "$PORT" -j DROP -m comment --comment "$TAG: deny non-dn42"
|
||||||
|
echo "applied: port $PORT reachable from DN42 and loopback only"
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
need_root
|
||||||
|
del4 -p tcp --dport "$PORT" -j DROP -m comment --comment "$TAG: deny non-dn42"
|
||||||
|
del6 -p tcp --dport "$PORT" -j DROP -m comment --comment "$TAG: deny non-dn42"
|
||||||
|
del4 -p tcp --dport "$PORT" -s 127.0.0.1 -j ACCEPT -m comment --comment "$TAG: loopback"
|
||||||
|
del4 -p tcp --dport "$PORT" -s 172.20.0.0/14 -j ACCEPT -m comment --comment "$TAG: dn42 v4"
|
||||||
|
del6 -p tcp --dport "$PORT" -s ::1 -j ACCEPT -m comment --comment "$TAG: loopback"
|
||||||
|
del6 -p tcp --dport "$PORT" -s fd00::/8 -j ACCEPT -m comment --comment "$TAG: dn42 v6"
|
||||||
|
echo "removed: port $PORT is no longer filtered by these rules"
|
||||||
|
;;
|
||||||
|
show)
|
||||||
|
echo "--- v4 ---"
|
||||||
|
iptables -S INPUT 2>/dev/null | grep -- "--dport $PORT" || echo "(none)"
|
||||||
|
echo "--- v6 ---"
|
||||||
|
ip6tables -S INPUT 2>/dev/null | grep -- "--dport $PORT" || echo "(none)"
|
||||||
|
echo "--- packet counts (a rising DROP count means the filter is working) ---"
|
||||||
|
iptables -L INPUT -v -n 2>/dev/null | grep "dpt:$PORT" || true
|
||||||
|
ip6tables -L INPUT -v -n 2>/dev/null | grep "dpt:$PORT" || true
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: $0 {apply|remove|show} [port]" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in new issue
Block a user