mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 19:18:25 +00:00
chore(openwrt): ship the radio transports enabled and optional
The mesh0/mesh1 and ap0/ap1 Ethernet transports shipped commented out, and fips-mesh-setup / fips-ap-setup awk-toggled the comment prefix in fips.yaml when they created or removed an interface. That existed for one reason: a transport whose interface was missing at startup was skipped and never retried, so a stock install that never ran the helpers would have logged a bind warning every boot. The daemon now waits for the interface and binds it when it appears, so the toggling has nothing left to protect. The blocks ship enabled with optional: true — which is the honest statement about a radio the router may never configure — and the helpers create the interface and stop there. No config rewrite, and no "restart fips AFTER the interface is up" step anywhere in either procedure. phy0-sta0 (wwan) gets optional: true for the same reason: it only exists while a radio is in station mode. eth0 and br-lan stay required, and the test pins that they do — marking the whole ethernet block optional would silence exactly the failures this policy exists to surface. With presence on IFF_UP rather than IFF_UP|IFF_RUNNING, that stays correct for a router with nothing plugged into its LAN ports: absence now means the netdev is gone or admin-down, a real fault, rather than an empty switch port. fips-ap-setup still edits node.rendezvous.lan, and that one does still need a restart: it is a config value, not an interface.
This commit is contained in:
@@ -72,9 +72,9 @@ fips-mesh-setup radio1
|
||||
|
||||
This creates an open 802.11s interface with mesh ID `fips-mesh` and
|
||||
HWMP forwarding off, attaches it to an unmanaged netifd interface (no
|
||||
IP configuration — none is needed), uncomments the matching `meshN`
|
||||
transport entry in `/etc/fips/fips.yaml` (see Step 2), and reloads the
|
||||
radio. Interfaces are named by radio index: `radio0` → `fips-mesh0`,
|
||||
IP configuration — none is needed), and reloads the radio. It does not
|
||||
touch `/etc/fips/fips.yaml`: the matching `meshN` transport ships
|
||||
enabled and the daemon binds the interface once it exists (see Step 2). Interfaces are named by radio index: `radio0` → `fips-mesh0`,
|
||||
`radio1` → `fips-mesh1`. Pass a second argument to use a different
|
||||
mesh ID.
|
||||
|
||||
@@ -133,18 +133,20 @@ wifi reload
|
||||
## Step 2 — check the FIPS transport binding
|
||||
|
||||
The `fips.yaml` shipped in the OpenWrt package carries one transport
|
||||
entry per radio, but **commented out** — so a stock install that never
|
||||
runs this helper logs no per-boot "interface missing" warning.
|
||||
`fips-mesh-setup` uncommented the matching `meshN` entry in Step 1, so
|
||||
there is normally nothing to do here. If you maintain your own config
|
||||
(or ran the manual UCI above instead of the helper), make sure the
|
||||
entries are present and uncommented:
|
||||
entry per radio, **enabled** and marked `optional: true`. The daemon
|
||||
treats a named interface that is not there as absent rather than as a
|
||||
failure, and `optional: true` is what keeps a stock install that never
|
||||
runs this helper quiet and un-`Degraded` about a radio it was never
|
||||
going to have. There is normally nothing to do here. If you maintain
|
||||
your own config (or ran the manual UCI above instead of the helper),
|
||||
make sure the entries are present:
|
||||
|
||||
```yaml
|
||||
transports:
|
||||
ethernet:
|
||||
mesh0:
|
||||
interface: "fips-mesh0"
|
||||
optional: true
|
||||
listen: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
@@ -161,19 +163,33 @@ transports:
|
||||
parses as an alias, so an existing config keeps working (see
|
||||
[../reference/configuration.md](../reference/configuration.md)).
|
||||
|
||||
## Step 3 — restart the daemon (order matters)
|
||||
## Step 3 — no restart needed
|
||||
|
||||
The daemon binds an interface when it appears. A transport whose
|
||||
interface is missing is *absent*, not skipped: it waits, binds within
|
||||
a second of the interface coming up, unbinds if it goes away, and
|
||||
rebinds when it returns. Order does not matter, and neither
|
||||
`/etc/init.d/fips restart` nor any hotplug rule is part of this
|
||||
procedure.
|
||||
|
||||
Watch it happen:
|
||||
|
||||
```sh
|
||||
fipsctl show transports
|
||||
```
|
||||
|
||||
The transport's `interface` block reports `presence` (`absent` /
|
||||
`binding` / `present`), `policy` (`required` / `optional`) and how
|
||||
long it has held that state.
|
||||
|
||||
If you *changed a config value* above rather than only creating an
|
||||
interface, that does need a restart — configuration is read at
|
||||
startup, interfaces are not:
|
||||
|
||||
```sh
|
||||
/etc/init.d/fips restart
|
||||
```
|
||||
|
||||
Restart fips **after** the mesh interface is up. A transport whose
|
||||
interface is missing at startup is logged and skipped, not retried —
|
||||
so if the daemon comes up before the radio, the mesh transport stays
|
||||
dead until the next restart. (An interface that *vanishes and
|
||||
returns* after startup is recovered automatically; only the missing-
|
||||
at-startup case needs this ordering.)
|
||||
|
||||
## Verify
|
||||
|
||||
L2 first — the 802.11s peering, with a second configured router in
|
||||
|
||||
@@ -161,15 +161,17 @@ TCP 8443).
|
||||
## Step 2 — check the FIPS transport binding
|
||||
|
||||
The `fips.yaml` shipped in the OpenWrt package carries one transport
|
||||
entry per access interface, but **commented out** — so a stock install
|
||||
that never runs this helper logs no per-boot "interface missing"
|
||||
warning. `fips-ap-setup` uncommented the matching `apN` entry in Step 1,
|
||||
and also enabled `node.rendezvous.lan` (the daemon's mDNS/DNS-SD
|
||||
rendezvous — phone FIPS apps cannot see raw-Ethernet beacons, so mDNS
|
||||
is how they find the daemon; the switch is daemon-wide and stays on if
|
||||
you later remove the AP). So there is normally nothing to do here. If
|
||||
you maintain your own config (or ran the manual UCI above instead of
|
||||
the helper), make sure both are present and uncommented:
|
||||
entry per access interface, **enabled** and marked `optional: true` —
|
||||
the daemon binds the interface once `fips-ap-setup` creates it, and
|
||||
`optional: true` keeps a stock install that never runs the helper quiet
|
||||
and un-`Degraded`. `fips-ap-setup` does still edit one thing: it enables
|
||||
`node.rendezvous.lan` (the daemon's mDNS/DNS-SD rendezvous — phone FIPS
|
||||
apps cannot see raw-Ethernet beacons, so mDNS is how they find the
|
||||
daemon; the switch is daemon-wide and stays on if you later remove the
|
||||
AP). That one is a config value rather than an interface, so it needs a
|
||||
restart to take effect. Otherwise there is normally nothing to do here.
|
||||
If you maintain your own config (or ran the manual UCI above instead of
|
||||
the helper), make sure both are present:
|
||||
|
||||
```yaml
|
||||
node:
|
||||
@@ -183,6 +185,7 @@ transports:
|
||||
ethernet:
|
||||
ap0:
|
||||
interface: "fips-ap0"
|
||||
optional: true
|
||||
listen: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
@@ -199,19 +202,33 @@ transports:
|
||||
parses as an alias, so an existing config keeps working (see
|
||||
[../reference/configuration.md](../reference/configuration.md)).
|
||||
|
||||
## Step 3 — restart the daemon (order matters)
|
||||
## Step 3 — no restart needed
|
||||
|
||||
The daemon binds an interface when it appears. A transport whose
|
||||
interface is missing is *absent*, not skipped: it waits, binds within
|
||||
a second of the interface coming up, unbinds if it goes away, and
|
||||
rebinds when it returns. Order does not matter, and neither
|
||||
`/etc/init.d/fips restart` nor any hotplug rule is part of this
|
||||
procedure.
|
||||
|
||||
Watch it happen:
|
||||
|
||||
```sh
|
||||
fipsctl show transports
|
||||
```
|
||||
|
||||
The transport's `interface` block reports `presence` (`absent` /
|
||||
`binding` / `present`), `policy` (`required` / `optional`) and how
|
||||
long it has held that state.
|
||||
|
||||
If you *changed a config value* above rather than only creating an
|
||||
interface, that does need a restart — configuration is read at
|
||||
startup, interfaces are not:
|
||||
|
||||
```sh
|
||||
/etc/init.d/fips restart
|
||||
```
|
||||
|
||||
Restart fips **after** the AP interface is up. A transport whose
|
||||
interface is missing at startup is logged and skipped, not retried —
|
||||
so if the daemon comes up before the radio, the access transport
|
||||
stays dead until the next restart. (An interface that *vanishes and
|
||||
returns* after startup is recovered automatically; only the missing-
|
||||
at-startup case needs this ordering.)
|
||||
|
||||
## Verify
|
||||
|
||||
L2 and addressing first, with a phone or laptop connected to `!FIPS`:
|
||||
|
||||
@@ -101,6 +101,9 @@ transports:
|
||||
accept_connections: true
|
||||
wwan:
|
||||
interface: "phy0-sta0"
|
||||
# Only exists while a radio is in station mode. Absence is normal, so
|
||||
# it must not report the router Degraded.
|
||||
optional: true
|
||||
listen: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
@@ -113,54 +116,55 @@ transports:
|
||||
accept_connections: true
|
||||
|
||||
# 802.11s mesh backhaul between FIPS routers. These entries ship
|
||||
# commented out so a stock install that never creates fips-mesh*
|
||||
# logs no per-boot "interface missing" bind warning. Running
|
||||
# 'fips-mesh-setup <radio>' creates the interface AND uncomments the
|
||||
# matching block here (once per radio; radio0 -> fips-mesh0, radio1 ->
|
||||
# fips-mesh1); 'fips-mesh-setup remove' re-comments it. Restart fips
|
||||
# after — a transport whose interface is missing at startup is skipped,
|
||||
# not retried. Dual-band routers can mesh on both bands at once —
|
||||
# failover, not multipath: FIPS keeps one active link per peer, the
|
||||
# other band stands by. The mesh runs OPEN (no SAE) with 802.11s
|
||||
# forwarding off: FIPS's Noise handshake is the encryption and
|
||||
# authentication, and FIPS is the routing layer. See
|
||||
# docs/how-to/set-up-80211s-mesh-backhaul.md.
|
||||
# mesh0:
|
||||
# interface: "fips-mesh0"
|
||||
# listen: true
|
||||
# announce: true
|
||||
# auto_connect: true
|
||||
# accept_connections: true
|
||||
# mesh1:
|
||||
# interface: "fips-mesh1"
|
||||
# listen: true
|
||||
# announce: true
|
||||
# auto_connect: true
|
||||
# accept_connections: true
|
||||
# ENABLED and marked 'optional: true'. The daemon treats a named
|
||||
# interface that is not there as absent rather than as a failure: it
|
||||
# waits, binds the moment 'fips-mesh-setup <radio>' creates the
|
||||
# interface, and unbinds again if it goes away — no config edit and no
|
||||
# restart, and 'optional: true' keeps a stock install that never runs
|
||||
# that script quiet and un-Degraded.
|
||||
#
|
||||
# Dual-band routers can mesh on both bands at once — failover, not
|
||||
# multipath: FIPS keeps one active link per peer, the other band stands
|
||||
# by. The mesh runs OPEN (no SAE) with 802.11s forwarding off: FIPS's
|
||||
# Noise handshake is the encryption and authentication, and FIPS is the
|
||||
# routing layer. See docs/how-to/set-up-80211s-mesh-backhaul.md.
|
||||
mesh0:
|
||||
interface: "fips-mesh0"
|
||||
optional: true
|
||||
listen: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
accept_connections: true
|
||||
mesh1:
|
||||
interface: "fips-mesh1"
|
||||
optional: true
|
||||
listen: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
accept_connections: true
|
||||
|
||||
# Open "!FIPS" access SSID for phones and laptops running FIPS. These
|
||||
# entries ship commented out so a stock install that never creates
|
||||
# fips-ap* logs no per-boot "interface missing" bind warning. Running
|
||||
# 'fips-ap-setup <radio>' creates the interface AND uncomments the
|
||||
# matching block here (once per radio; radio0 -> fips-ap0, radio1 ->
|
||||
# fips-ap1); 'fips-ap-setup remove' re-comments it. Restart fips after
|
||||
# — a transport whose interface is missing at startup is skipped, not
|
||||
# retried. The SSID is OPEN and isolated on purpose: FIPS's Noise
|
||||
# handshake is the only security layer, and associated clients reach
|
||||
# nothing but the FIPS handshake surface. See
|
||||
# docs/how-to/set-up-open-access-ssid.md.
|
||||
# ap0:
|
||||
# interface: "fips-ap0"
|
||||
# listen: true
|
||||
# announce: true
|
||||
# auto_connect: true
|
||||
# accept_connections: true
|
||||
# ap1:
|
||||
# interface: "fips-ap1"
|
||||
# listen: true
|
||||
# announce: true
|
||||
# auto_connect: true
|
||||
# accept_connections: true
|
||||
# Open "!FIPS" access SSID for phones and laptops running FIPS. Ships
|
||||
# ENABLED and marked 'optional: true', for the same reason as the mesh
|
||||
# blocks above: 'fips-ap-setup <radio>' creates the interface and the
|
||||
# daemon binds it when it appears, with no config edit and no restart.
|
||||
#
|
||||
# The SSID is OPEN and isolated on purpose: FIPS's Noise handshake is the
|
||||
# only security layer, and associated clients reach nothing but the FIPS
|
||||
# handshake surface. See docs/how-to/set-up-open-access-ssid.md.
|
||||
ap0:
|
||||
interface: "fips-ap0"
|
||||
optional: true
|
||||
listen: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
accept_connections: true
|
||||
ap1:
|
||||
interface: "fips-ap1"
|
||||
optional: true
|
||||
listen: true
|
||||
announce: true
|
||||
auto_connect: true
|
||||
accept_connections: true
|
||||
|
||||
# Bluetooth Low Energy transport — requires BlueZ and the 'ble' feature.
|
||||
# ble:
|
||||
|
||||
@@ -43,14 +43,17 @@
|
||||
# access channels freely.
|
||||
#
|
||||
# The shipped /etc/fips/fips.yaml carries 'ap0' and 'ap1' entries under
|
||||
# 'transports.ethernet' bound to these names, but commented out — a stock
|
||||
# install that never creates fips-ap* then logs no bind warning. This
|
||||
# helper uncomments the matching entry when it creates an interface and
|
||||
# re-comments it on remove, so the daemon binds the transport without a
|
||||
# manual config edit. It also uncomments the node.rendezvous.lan block
|
||||
# (mDNS/DNS-SD — how phone FIPS apps discover the daemon); that switch is
|
||||
# daemon-wide and stays on at remove. After an interface is up, restart
|
||||
# fips.
|
||||
# 'transports.ethernet' bound to these names, enabled and marked
|
||||
# 'optional: true'. The daemon treats a named interface that is not there as
|
||||
# ABSENT rather than as a start failure: it waits, binds the moment the
|
||||
# interface appears, and unbinds again when it goes away — so creating the
|
||||
# interface is all this helper has to do for the transport, with no config
|
||||
# rewrite and no daemon restart.
|
||||
#
|
||||
# It does still edit one thing: the node.rendezvous.lan block (mDNS/DNS-SD —
|
||||
# how phone FIPS apps discover the daemon). That is a config value rather than
|
||||
# an interface, so it does need a restart to take effect. The switch is
|
||||
# daemon-wide and stays on at remove.
|
||||
# See docs/how-to/set-up-open-access-ssid.md for the full guide.
|
||||
|
||||
DEFAULT_SSID="!FIPS"
|
||||
@@ -63,19 +66,14 @@ ap_config_write() {
|
||||
chmod 600 "$CONFIG.tmp" && mv "$CONFIG.tmp" "$CONFIG"
|
||||
}
|
||||
|
||||
# Uncomment the 'ap<idx>' transports.ethernet block in $CONFIG (created by
|
||||
# 'fips-ap-setup'). Reversible with ap_config_disable. Returns:
|
||||
# 0 enabled (or already active) 1 no config file 2 no such block
|
||||
ap_config_enable() {
|
||||
# Whether $CONFIG carries an enabled 'ap<idx>' transports.ethernet block.
|
||||
# Reports only; the daemon owns the binding. Returns:
|
||||
# 0 present 1 no config file 2 no such block
|
||||
ap_config_present() {
|
||||
idx="$1"
|
||||
[ -f "$CONFIG" ] || return 1
|
||||
grep -q "^ ap$idx:" "$CONFIG" && return 0
|
||||
grep -q "^ # ap$idx:" "$CONFIG" || return 2
|
||||
awk -v idx="$idx" '
|
||||
$0 ~ ("^ # ap" idx ":[ \t]*$") { blk = 1; sub(/^ # /, " "); print; next }
|
||||
blk && /^ # / { sub(/^ # /, " "); print; next }
|
||||
{ blk = 0; print }
|
||||
' "$CONFIG" > "$CONFIG.tmp" && ap_config_write
|
||||
grep -q "^ ap$idx:" "$CONFIG" || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
# Uncomment the 'lan' block under node.rendezvous in $CONFIG — the daemon's
|
||||
@@ -107,19 +105,6 @@ lan_rendezvous_enable() {
|
||||
' "$CONFIG" > "$CONFIG.tmp" && ap_config_write
|
||||
}
|
||||
|
||||
# Re-comment the 'ap<idx>' block so the daemon stops binding it (and stops
|
||||
# warning about the now-missing interface). Inverse of ap_config_enable.
|
||||
ap_config_disable() {
|
||||
idx="$1"
|
||||
[ -f "$CONFIG" ] || return 1
|
||||
grep -q "^ ap$idx:" "$CONFIG" || return 0
|
||||
awk -v idx="$idx" '
|
||||
$0 ~ ("^ ap" idx ":[ \t]*$") { blk = 1; sub(/^ /, " # "); print; next }
|
||||
blk && /^ / { sub(/^ /, " # "); print; next }
|
||||
{ blk = 0; print }
|
||||
' "$CONFIG" > "$CONFIG.tmp" && ap_config_write
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: fips-ap-setup <radio> [ssid]" >&2
|
||||
echo " fips-ap-setup remove [radio]" >&2
|
||||
@@ -154,10 +139,9 @@ if [ "$1" = "remove" ]; then
|
||||
uci -q delete "network.$section"
|
||||
uci -q delete "dhcp.$section"
|
||||
uci -q del_list "firewall.fips_ap.network=$section"
|
||||
# Re-comment the matching ap<N> transport in fips.yaml so the
|
||||
# daemon stops warning about the interface we just removed.
|
||||
idx="$(printf '%s' "$ifname" | sed -n 's/.*[^0-9]\([0-9]\{1,\}\)$/\1/p')"
|
||||
[ -n "$idx" ] && ap_config_disable "$idx"
|
||||
# The fips.yaml block stays as it is. The daemon notices the
|
||||
# interface going away, unbinds, and waits for it — silently,
|
||||
# because the block is marked 'optional: true'.
|
||||
echo "Removed ${ifname:-$section}."
|
||||
done
|
||||
# Drop the shared zone and its rules once the last instance is gone.
|
||||
@@ -356,12 +340,12 @@ wifi reload
|
||||
/etc/init.d/odhcpd reload
|
||||
/etc/init.d/firewall reload
|
||||
|
||||
# Enable the matching ap<N> transport in the shipped fips.yaml (it ships
|
||||
# commented out). Tailor the restart hint to what we could do.
|
||||
ap_config_enable "$IDX"
|
||||
# Report whether the shipped fips.yaml still carries the matching transport.
|
||||
# It ships enabled, so this is a check, not an edit.
|
||||
ap_config_present "$IDX"
|
||||
case $? in
|
||||
0) TRANSPORT_NOTE="The ap$IDX transport in $CONFIG that binds '$AP_IFNAME' is
|
||||
now uncommented and enabled." ;;
|
||||
0) TRANSPORT_NOTE="The ap$IDX transport in $CONFIG binds '$AP_IFNAME'; the
|
||||
daemon picks the interface up on its own, with no restart." ;;
|
||||
1) TRANSPORT_NOTE="No $CONFIG found — add a transports.ethernet entry binding
|
||||
interface '$AP_IFNAME' by hand." ;;
|
||||
*) TRANSPORT_NOTE="No 'ap$IDX' entry in $CONFIG — add a transports.ethernet
|
||||
@@ -398,9 +382,9 @@ per SSID, so it covers every FIPS router.
|
||||
|
||||
Next steps:
|
||||
1. $TRANSPORT_NOTE
|
||||
Watch it bind with: fipsctl show transports
|
||||
2. $MDNS_NOTE
|
||||
Restart the daemon AFTER the interface is up — a transport whose
|
||||
interface is missing at startup is skipped, not retried:
|
||||
mDNS is a config value, not an interface, so it needs a restart:
|
||||
/etc/init.d/fips restart
|
||||
3. Associate a phone or laptop running FIPS and verify:
|
||||
iw dev $AP_IFNAME station dump
|
||||
|
||||
@@ -27,49 +27,26 @@
|
||||
# Ethernet transport binds each directly and runs discovery beacons over it.
|
||||
#
|
||||
# The shipped /etc/fips/fips.yaml carries 'mesh0' and 'mesh1' entries under
|
||||
# 'transports.ethernet' bound to these names, but commented out — a stock
|
||||
# install that never creates fips-mesh* then logs no bind warning. This
|
||||
# helper uncomments the matching entry when it creates an interface and
|
||||
# re-comments it on remove, so the daemon binds the transport without a
|
||||
# manual config edit. After an interface is up, restart fips.
|
||||
# 'transports.ethernet' bound to these names, enabled and marked
|
||||
# 'optional: true'. The daemon treats a named interface that is not there as
|
||||
# ABSENT rather than as a start failure: it waits, binds the moment the
|
||||
# interface appears, and unbinds again when it goes away. So this helper
|
||||
# creates the interface and nothing else — no config rewrite, and no daemon
|
||||
# restart. 'optional: true' is what keeps a stock install that never runs this
|
||||
# script from reporting Degraded for an interface it was never going to have.
|
||||
# See docs/how-to/set-up-80211s-mesh-backhaul.md for the full guide.
|
||||
|
||||
DEFAULT_MESH_ID="fips-mesh"
|
||||
CONFIG="/etc/fips/fips.yaml"
|
||||
|
||||
# Replace $CONFIG with the rewritten $CONFIG.tmp. Force mode 0600 first: the
|
||||
# package installs fips.yaml 0600 (it may hold an inline 'nsec' private key),
|
||||
# and a fresh tmp file would otherwise land world-readable after the move.
|
||||
mesh_config_write() {
|
||||
chmod 600 "$CONFIG.tmp" && mv "$CONFIG.tmp" "$CONFIG"
|
||||
}
|
||||
|
||||
# Uncomment the 'mesh<idx>' transports.ethernet block in $CONFIG (created by
|
||||
# 'fips-mesh-setup'). Reversible with mesh_config_disable. Returns:
|
||||
# 0 enabled (or already active) 1 no config file 2 no such block
|
||||
mesh_config_enable() {
|
||||
# Whether $CONFIG carries an enabled 'mesh<idx>' transports.ethernet block.
|
||||
# Reports only; the daemon owns the binding. Returns:
|
||||
# 0 present 1 no config file 2 no such block
|
||||
mesh_config_present() {
|
||||
idx="$1"
|
||||
[ -f "$CONFIG" ] || return 1
|
||||
grep -q "^ mesh$idx:" "$CONFIG" && return 0
|
||||
grep -q "^ # mesh$idx:" "$CONFIG" || return 2
|
||||
awk -v idx="$idx" '
|
||||
$0 ~ ("^ # mesh" idx ":[ \t]*$") { blk = 1; sub(/^ # /, " "); print; next }
|
||||
blk && /^ # / { sub(/^ # /, " "); print; next }
|
||||
{ blk = 0; print }
|
||||
' "$CONFIG" > "$CONFIG.tmp" && mesh_config_write
|
||||
}
|
||||
|
||||
# Re-comment the 'mesh<idx>' block so the daemon stops binding it (and stops
|
||||
# warning about the now-missing interface). Inverse of mesh_config_enable.
|
||||
mesh_config_disable() {
|
||||
idx="$1"
|
||||
[ -f "$CONFIG" ] || return 1
|
||||
grep -q "^ mesh$idx:" "$CONFIG" || return 0
|
||||
awk -v idx="$idx" '
|
||||
$0 ~ ("^ mesh" idx ":[ \t]*$") { blk = 1; sub(/^ /, " # "); print; next }
|
||||
blk && /^ / { sub(/^ /, " # "); print; next }
|
||||
{ blk = 0; print }
|
||||
' "$CONFIG" > "$CONFIG.tmp" && mesh_config_write
|
||||
grep -q "^ mesh$idx:" "$CONFIG" || return 2
|
||||
return 0
|
||||
}
|
||||
|
||||
usage() {
|
||||
@@ -103,10 +80,9 @@ if [ "$1" = "remove" ]; then
|
||||
ifname="$(uci -q get "wireless.$section.ifname")"
|
||||
uci -q delete "wireless.$section"
|
||||
uci -q delete "network.$section"
|
||||
# Re-comment the matching mesh<N> transport in fips.yaml so the
|
||||
# daemon stops warning about the interface we just removed.
|
||||
idx="$(printf '%s' "$ifname" | sed -n 's/.*[^0-9]\([0-9]\{1,\}\)$/\1/p')"
|
||||
[ -n "$idx" ] && mesh_config_disable "$idx"
|
||||
# The fips.yaml block stays as it is. The daemon notices the
|
||||
# interface going away, unbinds, and waits for it — silently,
|
||||
# because the block is marked 'optional: true'.
|
||||
echo "Removed ${ifname:-$section}."
|
||||
done
|
||||
uci commit wireless
|
||||
@@ -114,7 +90,7 @@ if [ "$1" = "remove" ]; then
|
||||
# 'wifi reload' re-applies the whole wireless config, so it briefly drops
|
||||
# every client AP on all radios (a few seconds) — expected on remove.
|
||||
wifi reload
|
||||
echo "Restart fips: /etc/init.d/fips restart"
|
||||
echo "No fips restart needed — the daemon unbinds the interface itself."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -226,12 +202,12 @@ uci commit network
|
||||
# every client AP on all radios (a few seconds) — expected when adding a mesh.
|
||||
wifi reload
|
||||
|
||||
# Enable the matching mesh<N> transport in the shipped fips.yaml (it ships
|
||||
# commented out). Tailor the restart hint to what we could do.
|
||||
mesh_config_enable "$IDX"
|
||||
# Report whether the shipped fips.yaml still carries the matching transport.
|
||||
# It ships enabled, so this is a check, not an edit.
|
||||
mesh_config_present "$IDX"
|
||||
case $? in
|
||||
0) TRANSPORT_NOTE="The mesh$IDX transport in $CONFIG that binds '$MESH_IFNAME' is
|
||||
now uncommented and enabled." ;;
|
||||
0) TRANSPORT_NOTE="The mesh$IDX transport in $CONFIG binds '$MESH_IFNAME'; the
|
||||
daemon picks the interface up on its own." ;;
|
||||
1) TRANSPORT_NOTE="No $CONFIG found — add a transports.ethernet entry binding
|
||||
interface '$MESH_IFNAME' by hand." ;;
|
||||
*) TRANSPORT_NOTE="No 'mesh$IDX' entry in $CONFIG — add a transports.ethernet
|
||||
@@ -248,9 +224,9 @@ radio too — second band is a standby path (failover, not multipath).
|
||||
|
||||
Next steps:
|
||||
1. $TRANSPORT_NOTE
|
||||
Restart the daemon AFTER the interface is up — a transport whose
|
||||
interface is missing at startup is skipped, not retried:
|
||||
/etc/init.d/fips restart
|
||||
No restart: the daemon binds an interface when it appears and
|
||||
rebinds it if it goes away. Watch it happen with:
|
||||
fipsctl show transports
|
||||
2. Verify L2 peering with a second FIPS router in range:
|
||||
iw dev $MESH_IFNAME station dump
|
||||
and the FIPS link on top of it:
|
||||
|
||||
+42
-60
@@ -1353,83 +1353,65 @@ node:
|
||||
}
|
||||
|
||||
/// The fips.yaml shipped in the OpenWrt package must keep parsing as the
|
||||
/// config schema evolves. Both the 802.11s mesh backhaul entries
|
||||
/// config schema evolves, and must keep the absence policy it depends on.
|
||||
///
|
||||
/// The 802.11s mesh backhaul entries
|
||||
/// (docs/how-to/set-up-80211s-mesh-backhaul.md) and the open-access SSID
|
||||
/// entries (docs/how-to/set-up-open-access-ssid.md) ship commented out —
|
||||
/// one per radio, so dual-band routers can run either on both bands — so
|
||||
/// a stock install that never creates fips-mesh*/fips-ap* logs no
|
||||
/// per-boot bind warning; `fips-mesh-setup`/`fips-ap-setup` uncomment the
|
||||
/// matching block when they create the interface. Verify both states
|
||||
/// parse: as shipped (both inactive), and after the uncomment the helpers
|
||||
/// perform.
|
||||
/// entries (docs/how-to/set-up-open-access-ssid.md) ship **enabled** — one
|
||||
/// per radio, so dual-band routers can run either on both bands — and
|
||||
/// marked `optional: true`. They used to ship commented out, with
|
||||
/// `fips-mesh-setup`/`fips-ap-setup` uncommenting the matching block; that
|
||||
/// was a workaround for a transport whose missing interface was skipped
|
||||
/// for the life of the process. The daemon now waits for the interface and
|
||||
/// binds it when it appears, and `optional: true` is what keeps a stock
|
||||
/// install that never runs those helpers quiet and un-`Degraded` about a
|
||||
/// radio it was never going to have.
|
||||
#[test]
|
||||
fn shipped_openwrt_config_parses() {
|
||||
let yaml = include_str!("../../packaging/openwrt-ipk/files/etc/fips/fips.yaml");
|
||||
|
||||
// As shipped: parses, and the mesh/ap entries are commented out (a
|
||||
// running daemon binds no fips-mesh*/fips-ap* transport, no warning).
|
||||
let config: Config = serde_yaml::from_str(yaml).expect("shipped OpenWrt fips.yaml");
|
||||
for name in ["mesh0", "mesh1", "ap0", "ap1"] {
|
||||
assert!(
|
||||
!config
|
||||
.transports
|
||||
.ethernet
|
||||
.iter()
|
||||
.any(|(n, _)| n == Some(name)),
|
||||
"{name} must ship commented out, not active, in fips.yaml"
|
||||
);
|
||||
}
|
||||
|
||||
// What `fips-mesh-setup`/`fips-ap-setup` produce: uncomment each
|
||||
// block, which must still parse into a transport bound to the right
|
||||
// netdev.
|
||||
let uncommented =
|
||||
uncomment_transport_blocks(&uncomment_transport_blocks(yaml, "mesh"), "ap");
|
||||
let config: Config = serde_yaml::from_str(&uncommented)
|
||||
.expect("fips.yaml with mesh and ap transports uncommented");
|
||||
let eth = |name: &str| {
|
||||
config
|
||||
.transports
|
||||
.ethernet
|
||||
.iter()
|
||||
.find(|(n, _)| *n == Some(name))
|
||||
.map(|(_, cfg)| cfg.clone())
|
||||
};
|
||||
|
||||
// The interfaces the setup helpers create: present, bound to the right
|
||||
// netdev, and optional. A regression to `optional: false` here would
|
||||
// report every stock router `Degraded` for a mesh it never configured.
|
||||
for (name, interface) in [
|
||||
("mesh0", "fips-mesh0"),
|
||||
("mesh1", "fips-mesh1"),
|
||||
("ap0", "fips-ap0"),
|
||||
("ap1", "fips-ap1"),
|
||||
] {
|
||||
let cfg = eth(name).unwrap_or_else(|| panic!("{name} entry missing from fips.yaml"));
|
||||
assert_eq!(cfg.interface, interface, "{name} binds the wrong netdev");
|
||||
assert!(cfg.optional(), "{name} must ship optional");
|
||||
}
|
||||
|
||||
// `phy0-sta0` only exists while a radio is in station mode.
|
||||
assert!(
|
||||
eth("wwan").expect("wwan entry").optional(),
|
||||
"wwan must ship optional"
|
||||
);
|
||||
|
||||
// The wired ports exist on every supported board, so their absence is
|
||||
// a real fault and must stay loud. Marking these optional too would
|
||||
// make the whole ethernet block silent, which is the failure mode the
|
||||
// presence mechanism exists to stop hiding.
|
||||
for name in ["wan", "lan"] {
|
||||
assert!(
|
||||
config
|
||||
.transports
|
||||
.ethernet
|
||||
.iter()
|
||||
.any(|(n, eth)| n == Some(name) && eth.interface == interface),
|
||||
"{name} entry missing after uncommenting shipped fips.yaml"
|
||||
!eth(name).expect("wired entry").optional(),
|
||||
"{name} must stay required"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirror the setup helpers' block uncomment: strip the ` # ` prefix
|
||||
/// from each `# <prefix><N>:` header and its ` # ` continuation
|
||||
/// lines, leaving every other comment untouched.
|
||||
fn uncomment_transport_blocks(yaml: &str, prefix: &str) -> String {
|
||||
let header = format!(" # {prefix}");
|
||||
let mut out = String::new();
|
||||
let mut in_block = false;
|
||||
for line in yaml.lines() {
|
||||
let is_header = line
|
||||
.strip_prefix(&header)
|
||||
.and_then(|r| r.strip_suffix(':'))
|
||||
.is_some_and(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit()));
|
||||
if is_header {
|
||||
in_block = true;
|
||||
out.push_str(&line.replacen(" # ", " ", 1));
|
||||
} else if in_block && line.starts_with(" # ") {
|
||||
out.push_str(&line.replacen(" # ", " ", 1));
|
||||
} else {
|
||||
in_block = false;
|
||||
out.push_str(line);
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_yaml_with_hex() {
|
||||
let yaml = r#"
|
||||
|
||||
Reference in New Issue
Block a user