mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-05 19:18:25 +00:00
Five files conflicted and each needed a different call, because the two lines had rewritten different halves of the same code. The handshake handler takes next's version whole. master's entire change there was three comment blocks and one widened debug_assert, and the assert names HandshakePhase::ReceivedMsg1, a variant next's XX rewrite does not have. Nothing semantic was dropped. The peer reaper takes master's: reap_peers_on_transport is new and its route_link_dead doc now describes both callers, which is true on this line too. The ethernet transport takes master's binder rewrite with next's wire format re-applied on top. The send path, the receive path and the frame tests merged to the 4-byte header on their own, but three sites are new in master's rewrite and had never seen it: the Binding default and both arms of the binder's MTU calculation still subtracted 3. The transports snapshot fixture moved with them, 1499 to 1496, and that single field was the whole diff. Beacons carry no pubkey here, so local_pubkey leaves the transport, its binder context and the node's transport construction with it. The changelog keeps both sides' entries, with master's Added subsection lifted back out of Changed where the merge had left it. Two tests do not come across. a_transient_msg2_failure_keeps_the_link_for_ the_retry and its restart-path sibling assert that the machine rests at ReceivedMsg1. This line's nearest state is SentMsg2, and it means something else: the inbound leg parks there awaiting msg3, where on the other line that phase was the last stop before promotion. Renaming it would produce a test that passes without exercising the deferral. The behaviour they guard did merge and sits in the transient arm of the msg2 send failure; what is missing is coverage shaped for this handshake, which is tracked separately. The two connected-socket tests did come across. Their helper took the responder's session straight after msg2, which is an IK assumption; it now runs msg3 as well. Both pass here and both go red when the clear is removed or made unconditional. The test-harness fixes arrive through master rather than as follow-ups here, so this line never carries the versions that failed: the interface-binding suite's veth naming, and the chaos veth restore, random streams, settle wait, netem restore and shared down-node set.
238 lines
10 KiB
Bash
Executable File
238 lines
10 KiB
Bash
Executable File
#!/bin/sh
|
|
# fips-mesh-setup — configure open 802.11s mesh interfaces for FIPS backhaul.
|
|
#
|
|
# Usage:
|
|
# fips-mesh-setup <radio> [mesh-id] e.g. fips-mesh-setup radio1
|
|
# fips-mesh-setup remove [radio] no radio: remove all instances
|
|
#
|
|
# Creates a mesh-point interface on the given radio and leaves everything
|
|
# above L2 to FIPS. Run once per radio: dual-band routers can mesh on both
|
|
# bands at once (2.4 GHz reaches further, 5 GHz carries more). Note this is
|
|
# failover, not multipath — FIPS keeps one active link per peer; the other
|
|
# band stands by and reconnects the peer if the active link dies.
|
|
#
|
|
# - encryption 'none' — the mesh is OPEN on purpose. FIPS's Noise
|
|
# handshake authenticates and encrypts every peer link, so SAE would
|
|
# only duplicate that (and on ath10k it forces the slower raw Tx/Rx
|
|
# firmware mode). A stranger can form an 802.11s peering AND a FIPS
|
|
# peer link — the Noise handshake authenticates each link (no
|
|
# impersonation of another identity, no MITM), it does not gate who
|
|
# may peer. Admission is open up to the daemon's max-peers cap.
|
|
# - mesh_fwding '0' — disables 802.11s HWMP forwarding so each mesh
|
|
# link is a plain L2 neighbor link. FIPS is the routing layer; two
|
|
# routing layers would fight.
|
|
#
|
|
# Interfaces are named per radio index (radio0 -> fips-mesh0, radio1 ->
|
|
# fips-mesh1) and are intentionally NOT bridged into br-lan: the FIPS
|
|
# 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, 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"
|
|
|
|
# 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 2
|
|
return 0
|
|
}
|
|
|
|
usage() {
|
|
echo "Usage: fips-mesh-setup <radio> [mesh-id]" >&2
|
|
echo " fips-mesh-setup remove [radio]" >&2
|
|
echo "Radios on this device:" >&2
|
|
uci show wireless 2>/dev/null | sed -n "s/^wireless\.\([^.]*\)=wifi-device$/ \1/p" >&2
|
|
exit 1
|
|
}
|
|
|
|
# List the UCI section names of fips-managed mesh wifi-ifaces.
|
|
mesh_sections() {
|
|
uci show wireless 2>/dev/null | sed -n "s/^wireless\.\(fips_mesh[^.=]*\)=wifi-iface$/\1/p"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# remove [radio] — delete the wireless and network sections created below
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [ "$1" = "remove" ]; then
|
|
if [ -n "$2" ]; then
|
|
SECTIONS="fips_mesh_$(printf '%s' "$2" | tr -c 'a-zA-Z0-9_' '_')"
|
|
else
|
|
SECTIONS="$(mesh_sections)"
|
|
fi
|
|
[ -n "$SECTIONS" ] || {
|
|
echo "No fips mesh instances configured."
|
|
exit 0
|
|
}
|
|
for section in $SECTIONS; do
|
|
ifname="$(uci -q get "wireless.$section.ifname")"
|
|
uci -q delete "wireless.$section"
|
|
uci -q delete "network.$section"
|
|
# 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
|
|
uci commit network
|
|
# '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 "No fips restart needed — the daemon unbinds the interface itself."
|
|
exit 0
|
|
fi
|
|
|
|
RADIO="$1"
|
|
MESH_ID="${2:-$DEFAULT_MESH_ID}"
|
|
|
|
[ -n "$RADIO" ] || usage
|
|
|
|
if [ "$(uci -q get "wireless.$RADIO")" != "wifi-device" ]; then
|
|
echo "Error: '$RADIO' is not a wifi-device in /etc/config/wireless." >&2
|
|
usage
|
|
fi
|
|
|
|
# One instance per radio: section fips_mesh_<radio>, netdev fips-mesh<N>
|
|
# where N is the radio's trailing index (radio0 -> fips-mesh0). For radios
|
|
# named without a trailing number, fall back to the first free index.
|
|
SECTION="fips_mesh_$(printf '%s' "$RADIO" | tr -c 'a-zA-Z0-9_' '_')"
|
|
IDX="$(printf '%s' "$RADIO" | sed -n 's/.*[^0-9]\([0-9]\{1,\}\)$/\1/p')"
|
|
[ -n "$IDX" ] || IDX="$(printf '%s' "$RADIO" | sed -n 's/^\([0-9]\{1,\}\)$/\1/p')"
|
|
if [ -z "$IDX" ]; then
|
|
IDX=0
|
|
while uci show wireless 2>/dev/null | grep -q "\.ifname='fips-mesh$IDX'"; do
|
|
IDX=$((IDX + 1))
|
|
done
|
|
fi
|
|
MESH_IFNAME="fips-mesh$IDX"
|
|
|
|
# Refuse a name collision from another radio's instance (e.g. two radios
|
|
# whose names end in the same digit) rather than silently hijacking it.
|
|
OWNER="$(uci show wireless 2>/dev/null \
|
|
| sed -n "s/^wireless\.\(fips_mesh[^.=]*\)\.ifname='$MESH_IFNAME'$/\1/p")"
|
|
if [ -n "$OWNER" ] && [ "$OWNER" != "$SECTION" ]; then
|
|
echo "Error: $MESH_IFNAME is already used by section '$OWNER'." >&2
|
|
echo "Remove it first: fips-mesh-setup remove" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Driver capability check (advisory — config below is harmless either way)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if command -v iw >/dev/null 2>&1; then
|
|
if ! iw list 2>/dev/null | grep -q "\* mesh point"; then
|
|
echo "Warning: no radio on this device advertises 'mesh point' support" >&2
|
|
echo "(iw list | grep 'mesh point'). The interface may fail to come up." >&2
|
|
fi
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Wireless: open 802.11s mesh point, HWMP forwarding off
|
|
# ---------------------------------------------------------------------------
|
|
|
|
uci -q delete "wireless.$SECTION"
|
|
uci set "wireless.$SECTION=wifi-iface"
|
|
uci set "wireless.$SECTION.device=$RADIO"
|
|
uci set "wireless.$SECTION.mode=mesh"
|
|
uci set "wireless.$SECTION.mesh_id=$MESH_ID"
|
|
uci set "wireless.$SECTION.encryption=none"
|
|
uci set "wireless.$SECTION.mesh_fwding=0"
|
|
uci set "wireless.$SECTION.ifname=$MESH_IFNAME"
|
|
uci set "wireless.$SECTION.network=$SECTION"
|
|
|
|
# Radios ship disabled on fresh OpenWrt installs; a disabled radio would
|
|
# leave the mesh interface down with no error anywhere visible.
|
|
if [ "$(uci -q get "wireless.$RADIO.disabled")" = "1" ]; then
|
|
echo "Note: enabling $RADIO (was disabled)."
|
|
uci -q delete "wireless.$RADIO.disabled"
|
|
fi
|
|
|
|
# The mesh inherits the radio's channel, and mesh points only peer on the
|
|
# same channel. 'auto' lets each router pick its own — the classic silent
|
|
# non-peering cause — so surface the setting loudly.
|
|
CHANNEL="$(uci -q get "wireless.$RADIO.channel")"
|
|
BAND="$(uci -q get "wireless.$RADIO.band")"
|
|
if [ -z "$CHANNEL" ] || [ "$CHANNEL" = "auto" ]; then
|
|
echo "Warning: $RADIO channel is '${CHANNEL:-unset}' — each router may" >&2
|
|
echo "auto-select a different channel and mesh points only peer on the" >&2
|
|
echo "same one. Pin the same channel on every backhaul router, e.g.:" >&2
|
|
echo " uci set wireless.$RADIO.channel='36' && uci commit wireless && wifi reload" >&2
|
|
fi
|
|
|
|
# A client (sta) interface on the same radio follows its upstream AP's
|
|
# channel and drags every other interface with it — a mesh pinned to a
|
|
# different channel silently never joins, and does not recover when the
|
|
# STA disconnects.
|
|
for s in $(uci show wireless 2>/dev/null | sed -n "s/^wireless\.\([^.]*\)\.mode='sta'$/\1/p"); do
|
|
if [ "$(uci -q get "wireless.$s.device")" = "$RADIO" ]; then
|
|
echo "Warning: $RADIO also carries client interface '$s' (mode 'sta')." >&2
|
|
echo "The whole radio follows that STA's upstream channel — a mesh" >&2
|
|
echo "pinned to a different channel stays down silently. Align the" >&2
|
|
echo "mesh channel with the upstream AP, or put the mesh on a radio" >&2
|
|
echo "without a STA (a roaming uplink is incompatible with a" >&2
|
|
echo "fixed-channel mesh on the same radio)." >&2
|
|
fi
|
|
done
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Network: unmanaged interface so netifd brings the netdev up. No IP config —
|
|
# the FIPS Ethernet transport speaks raw frames on it.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
uci -q delete "network.$SECTION"
|
|
uci set "network.$SECTION=interface"
|
|
uci set "network.$SECTION.proto=none"
|
|
|
|
uci commit wireless
|
|
uci commit network
|
|
# 'wifi reload' re-applies the whole wireless config, so it briefly drops
|
|
# every client AP on all radios (a few seconds) — expected when adding a mesh.
|
|
wifi reload
|
|
|
|
# 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 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
|
|
entry binding interface '$MESH_IFNAME' by hand (copy the mesh0 block)." ;;
|
|
esac
|
|
|
|
cat <<EOF
|
|
Created open 802.11s mesh '$MESH_ID' as $MESH_IFNAME on $RADIO \
|
|
(band ${BAND:-?}, channel ${CHANNEL:-auto}).
|
|
|
|
ALL routers in this backhaul must share this mesh ID AND channel
|
|
(per band). On a dual-band router, run fips-mesh-setup for the other
|
|
radio too — second band is a standby path (failover, not multipath).
|
|
|
|
Next steps:
|
|
1. $TRANSPORT_NOTE
|
|
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:
|
|
fipsctl show peers
|
|
|
|
Run 'fips-mesh-setup remove' to undo all instances, or
|
|
'fips-mesh-setup remove $RADIO' for just this one.
|
|
EOF
|