#!/usr/bin/env bash # ============================================================================== # meshinit_eth_bridge.sh — Initialize an 802.11s mesh and **bridge eth2** into it # ============================================================================== # PURPOSE # This script brings up an IEEE 802.11s mesh interface and bridges it with # Ethernet port `eth2` so that any device connected to `eth2` becomes part of # the same L2 broadcast domain carried over the mesh. In other words, frames # from the Ethernet device are transparently forwarded through the wireless # mesh and vice versa. # # WHAT'S SPECIAL HERE # • We **reuse your existing helper scripts** (freq.sh, node_addresses.sh, # topology.sh) — no duplicate frequency maps, no new inventories. ✅ # • We keep the script **idempotent**: safe to re-run; it cleans previous # state and recreates the intended final configuration. # • We provide **extensive in-place comments** explaining every step. # # PARAMETERS (positional) # 1) participating_nodes Comma-separated list of hostnames (short) or 'all' # 2) channel Numeric Wi‑Fi channel (e.g., 36, 44, 149) # 3) power_dbm Transmit power in dBm (e.g., 17) # 4) noise_floor_dbm Desired NF override (driver-dependent; e.g., -95) # # EXAMPLES # sudo ./meshinit_eth_bridge.sh all 36 17 -95 # sudo ./meshinit_eth_bridge.sh apu00,apu01,apu02 44 15 -96 # # OPTIONAL ENVIRONMENT OVERRIDES # MESH_ID="stratumesh" # 802.11s mesh ID to join # BASE_IF="wlan0" # Underlying Wi‑Fi interface used to spawn mesh0 # MESH_IF="mesh0" # Name for the mesh interface (created as type mp) # PHY="" # Force a specific phy (e.g., phy0). If empty, auto # HT_MODE="HT40+" # Channel width; '+', '-' or omit for 20 MHz # COUNTRY="DE" # Regulatory domain # BR_NAME="br-mesh" # Bridge name that will enslave {mesh_if, eth2} # BR_MESH_ADDR= # Optional IP assigned to the bridge (e.g., 192.168.50.2) # BR_MESH_PREFIX= # Optional CIDR prefix for BR_MESH_ADDR (e.g., 24) # # RUNTIME DEPENDENCIES # • iproute2 (ip, bridge) • iw • awk • bash ≥ 4 (assoc arrays in helpers) # (ethtool/iwpriv not strictly needed; we use debugfs for ath9k NF if available) # # EXIT BEHAVIOR # • If current host is not in `participating_nodes`, we exit **successfully** # with a no-op (useful when running the same command across many nodes). # # ------------------------------------------------------------------------------ set -euo pipefail # ---------- Pretty logging helpers ---------- log() { echo "[INFO] $*"; } warn() { echo "[WARN] $*" >&2; } err() { echo "[ERROR] $*" >&2; exit 1; } # ---------- Parse CLI arguments ---------- if [[ ${1:-} == "-h" || ${1:-} == "--help" || $# -lt 4 ]]; then cat <<'USAGE' Usage: meshinit_eth_bridge.sh Examples: meshinit_eth_bridge.sh all 36 17 -95 meshinit_eth_bridge.sh apu00,apu01,apu02 44 15 -96 Environment overrides: MESH_ID, BASE_IF, MESH_IF, PHY, HT_MODE, COUNTRY, BR_NAME, BR_MESH_ADDR, BR_MESH_PREFIX USAGE exit 1 fi PARTICIPATING=$1 # comma-separated list or 'all' CHAN=$2 # numeric channel (we map to freq via freq.sh) POWER_DBM=$3 # desired transmit power in dBm NOISE_FLOOR=$4 # desired noise floor override in dBm (ath9k best-effort) HOST_SHORT=$(hostname -s || hostname) # ---------- Gate: only run on participating nodes ---------- if [[ "$PARTICIPATING" != "all" ]]; then IFS=',' read -r -a ALLOWED <<<"$PARTICIPATING" IN_SET="no" for n in "${ALLOWED[@]}"; do [[ "$n" == "$HOST_SHORT" ]] && IN_SET="yes" && break done if [[ "$IN_SET" != "yes" ]]; then log "Host $HOST_SHORT not in participating set; exiting (no-op)." exit 0 fi fi # ---------- Privilege check ---------- [[ $EUID -eq 0 ]] || err "Please run as root." # ---------- Defaults (can be overridden by environment) ---------- MESH_ID=${MESH_ID:-stratumesh} BASE_IF=${BASE_IF:-} # leave empty to auto-detect below MESH_IF=${MESH_IF:-mesh0} PHY=${PHY:-} # leave empty to auto-detect from BASE_IF HT_MODE=${HT_MODE:-HT40+} COUNTRY=${COUNTRY:-DE} BR_NAME=${BR_NAME:-br-mesh} # ---------- Bring in your existing helper scripts ---------- # We source them relative to this script's directory so you can run from anywhere. SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) # freq.sh provides an associative array `chan` that maps channel → frequency MHz # e.g., chan[36]=5180 # node_addresses.sh / topology.sh are optional here but available for future use. # We don't consume IP maps directly in this bridge scenario, but having them # sourced early avoids surprises if you later extend the script (e.g., L3 on br). source "$SCRIPT_DIR/freq.sh" # provides: declare -A chan[...] fileciteturn1file0 source "$SCRIPT_DIR/node_addresses.sh" # provides MAC/IP maps & neighbors fileciteturn1file1 source "$SCRIPT_DIR/topology.sh" # provides set_topology() fileciteturn1file2 # ---------- Utility helpers ---------- require_bin() { command -v "$1" >/dev/null 2>&1 || err "Missing dependency: $1"; } iface_exists() { ip link show "$1" >/dev/null 2>&1; } first_wifi_if() { iw dev 2>/dev/null | awk '/Interface/ {print $2}' | head -n1; } get_phy_for_if() { local ifc=$1; iw dev "$ifc" info 2>/dev/null | awk '/wiphy/ {print "phy"$2; exit}'; } flush_ip() { ip addr flush dev "$1" || true; } set_regdom() { iw reg set "$COUNTRY" 2>/dev/null || true; } apply_txpower() { # Set TX power in mBm (1 dBm = 100 mBm). Some drivers ignore fixed settings. local ifc=$1 dbm=$2; local mbm=$((dbm * 100)) if iw dev "$ifc" set txpower fixed "$mbm" 2>/dev/null; then log "Set txpower on $ifc to ${dbm} dBm" else warn "Driver refused fixed txpower on $ifc; continuing" fi } apply_noise_floor_override() { # Best-effort for ath9k via debugfs. This does **not** exist on all kernels. local phy=$1 nf=$2 local base="/sys/kernel/debug/ieee80211/${phy}/ath9k" if [[ -d "$base" ]]; then for f in nf_override noise_floor_override ani_noise_floor; do if [[ -w "$base/$f" ]]; then echo "$nf" > "$base/$f" || true log "Applied noise floor override $nf dBm via $base/$f" return 0 fi done warn "ath9k debugfs present but no known NF override nodes writable" else warn "No ath9k debugfs at $base; skipping noise floor override" fi } cleanup_existing() { # Idempotent teardown so re-runs leave us in a good state. if iface_exists "$BR_NAME"; then log "Cleaning existing bridge $BR_NAME" # Detach ports before deleting the bridge (avoids lingering masters) for p in $(bridge link 2>/dev/null | awk -F': ' '/master '"$BR_NAME"'/ {print $2}' | awk '{print $1}'); do ip link set "$p" nomaster || true done ip link set "$BR_NAME" down || true ip link del "$BR_NAME" type bridge || true fi if iface_exists "$MESH_IF"; then log "Removing existing mesh interface $MESH_IF" ip link set "$MESH_IF" down || true iw dev "$MESH_IF" del || true fi } # ---------- Sanity checks ---------- require_bin ip require_bin iw # Verify eth2 exists early; we warn (and continue) if temporarily absent so the # mesh can still come up. You can hot-plug eth2 later and re-run just the bridge # part if needed. if ! iface_exists eth2; then warn "Interface eth2 not present right now; proceeding with mesh-only, will still create bridge" fi # ---------- Auto-detect base Wi‑Fi interface & phy (unless provided) ---------- if [[ -z "$BASE_IF" ]]; then BASE_IF=$(first_wifi_if || true) fi [[ -n "$BASE_IF" ]] || err "Could not auto-detect a Wi‑Fi interface. Set BASE_IF=wlanX." if [[ -z "$PHY" ]]; then PHY=$(get_phy_for_if "$BASE_IF" || true) fi [[ -n "$PHY" ]] || err "Could not resolve phy for $BASE_IF." log "Host: $HOST_SHORT | base if: $BASE_IF | phy: $PHY | mesh if: $MESH_IF" # ---------- Clean previous state (idempotency) ---------- cleanup_existing # ---------- Regulatory setup and base bring-up ---------- set_regdom ip link set "$BASE_IF" up || true # ---------- Create the 802.11s mesh interface ---------- # We create a new interface of type 'mp' (mesh point) derived from BASE_IF. log "Creating mesh interface $MESH_IF from $BASE_IF" iw dev "$BASE_IF" interface add "$MESH_IF" type mp || err "Failed to create mesh interface" ip link set "$MESH_IF" up # ---------- Channel and mesh join ---------- # Use your freq map from freq.sh. If a channel is missing, we attempt a best-effort join. FREQ_MHZ=${chan[$CHAN]:-} if [[ -n "$FREQ_MHZ" ]]; then log "Joining mesh ID '$MESH_ID' on channel $CHAN (${FREQ_MHZ} MHz, $HT_MODE)" # Set channel + width first (where supported), then join specifying frequency. iw dev "$MESH_IF" set channel "$CHAN" $HT_MODE || true iw dev "$MESH_IF" mesh join "$MESH_ID" freq "$FREQ_MHZ" || err "mesh join failed" else warn "Channel $CHAN not found in freq.sh; attempting join without explicit frequency" iw dev "$MESH_IF" mesh join "$MESH_ID" || err "mesh join failed" fi # ---------- RF tuning ---------- apply_txpower "$MESH_IF" "$POWER_DBM" apply_noise_floor_override "$PHY" "$NOISE_FLOOR" # ---------- Bridge creation: mesh <-> eth2 ---------- # Why bridge? Because we want transparent L2 forwarding between the Ethernet # segment (eth2) and the wireless mesh (MESH_IF). The Linux bridge `BR_NAME` # acts like a software switch connecting the two. log "Creating bridge $BR_NAME and enslaving { $MESH_IF, eth2 }" # Create the bridge (no STP by default; enable if you expect loops elsewhere) ip link add name "$BR_NAME" type bridge || true ip link set "$BR_NAME" up # Enslave ports: flush any IPs (bridge members should be pure L2) and add to master for p in "$MESH_IF" eth2; do if iface_exists "$p"; then ip link set "$p" up || true flush_ip "$p" ip link set "$p" master "$BR_NAME" else warn "Interface $p not present; skipping enslave for now" fi done # Optionally assign an IP to the BRIDGE itself (if you want L3 mgmt on this L2) if [[ -n "${BR_MESH_ADDR:-}" && -n "${BR_MESH_PREFIX:-}" ]]; then ip addr add "${BR_MESH_ADDR}/${BR_MESH_PREFIX}" dev "$BR_NAME" || true log "Assigned ${BR_MESH_ADDR}/${BR_MESH_PREFIX} to $BR_NAME" else log "No IP assigned to $BR_NAME (pure L2 bridge). Set BR_MESH_ADDR/BR_MESH_PREFIX if desired." fi # ---------- Final status snapshot (for quick troubleshooting) ---------- bridge link 2>/dev/null || true ip -br addr show "$BR_NAME" || true ip -br link show "$BR_NAME" "$MESH_IF" eth2 2>/dev/null || true log "Mesh + bridge are up. You can now connect an Ethernet device to eth2 and reach it over the mesh." exit 0