secubox-deb/image/sbin/secubox-net-detect
CyberMind-FR a52af3b50a feat(netplan): standardize SecuBox LAN to 192.168.10.0/24; bump 1.10.0 (ref #760)
Default br-lan 192.168.1.1/24 collided with common ISP-router LANs
(Freebox/Livebox 192.168.1.0/24) when the appliance sits behind one:
WAN(DHCP)+LAN land on the same subnet -> duplicate route, ARP ambiguity,
unreachable mgmt IP. Observed live on c3box behind a Freebox.

- All board netplans (mochabin, espressobin-v7/ultra, x64-vm, x64-live)
  + VM LANs (vm-x64, vm-arm64) -> br-lan/LAN 192.168.10.1/24
- Generators: secubox-netmodes (inline + router.yaml.j2 template),
  secubox-hub preview, secubox-net-detect
- dnsmasq (espressobin-v7.conf): dhcp-range + option:router + dns-server
- live-usb build scripts + self-signed cert SANs -> IP:192.168.10.1
- Out of scope (untouched): 192.168.255.1 mgmt/trusted-proxy whitelist,
  WAN-probe GATEWAYS lists, remote-ui/round + tests
- Minor "medium" bump 1.9.0 -> 1.10.0 (core build scripts; mochabin-live
  stays on its 2.0.0 track)

Live: c3box br-lan already 192.168.10.1/24 + netmodes template aligned;
gk2 WAN moved to eth2 DHCP @ .200 (Freebox reservation on eth2 MAC).
2026-06-27 17:11:23 +02:00

462 lines
16 KiB
Bash
Executable File

#!/bin/bash
# ══════════════════════════════════════════════════════════════════
# secubox-net-detect — Automatic Network Interface Detection
# Detects board type and configures WAN/LAN interfaces accordingly
# Supports: EspressoBin, MochaBin, x64 (VM/Bare-metal)
# ══════════════════════════════════════════════════════════════════
set -u # Exit on undefined variables, but allow command failures
SECUBOX_CONF="/etc/secubox/secubox.conf"
NETPLAN_DIR="/etc/netplan"
OUTPUT_JSON="${1:-/run/secubox/net-detect.json}"
# Colors for terminal output
RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m'
log() { echo -e "${CYAN}[net-detect]${NC} $*"; }
ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
# ── Board Detection ────────────────────────────────────────────────
detect_board() {
local board="unknown"
# ARM: Read device-tree model
if [[ -f /proc/device-tree/model ]]; then
local model
model=$(tr -d '\0' < /proc/device-tree/model)
case "$model" in
*"MOCHAbin"*|*"mochabin"*|*"7040"*)
board="mochabin"
;;
*"ESPRESSObin Ultra"*|*"espressobin-ultra"*)
board="espressobin-ultra"
;;
*"ESPRESSObin"*|*"espressobin"*|*"3720"*)
board="espressobin-v7"
;;
*"Marvell Armada"*)
# Generic Marvell detection
if grep -q "7040" /proc/device-tree/compatible 2>/dev/null; then
board="mochabin"
elif grep -q "3720" /proc/device-tree/compatible 2>/dev/null; then
board="espressobin-v7"
fi
;;
esac
# x64: Check DMI or hostname
elif [[ -f /sys/class/dmi/id/product_name ]]; then
local product
product=$(cat /sys/class/dmi/id/product_name 2>/dev/null || echo "")
case "$product" in
*"VirtualBox"*|*"QEMU"*|*"VMware"*|*"KVM"*)
board="x64-vm"
;;
*)
board="x64-baremetal"
;;
esac
fi
echo "$board"
}
# ── Interface Discovery ────────────────────────────────────────────
# Returns all physical ethernet interfaces (excludes lo, veth, docker, bridge, wg)
get_physical_interfaces() {
local ifaces=""
for iface in /sys/class/net/*; do
local name
name=$(basename "$iface")
# Skip non-physical interfaces
[[ "$name" == "lo" ]] && continue
[[ "$name" == veth* ]] && continue
[[ "$name" == docker* ]] && continue
[[ "$name" == br-* ]] && continue
[[ "$name" == wg* ]] && continue
[[ "$name" == tun* ]] && continue
[[ "$name" == tap* ]] && continue
# Check if it's a physical device (has device/driver)
if [[ -d "$iface/device" ]] || [[ "$name" == lan* ]] || [[ "$name" == eth* ]] || [[ "$name" == enp* ]]; then
ifaces="$ifaces $name"
fi
done
echo "$ifaces" | xargs # Trim whitespace
}
# ── Check Interface Connectivity ────────────────────────────────────
# Returns 1 if interface has link (cable connected), 0 otherwise
check_link() {
local iface="$1"
local carrier_file="/sys/class/net/${iface}/carrier"
if [[ -f "$carrier_file" ]]; then
local carrier
carrier=$(cat "$carrier_file" 2>/dev/null || echo "0")
[[ "$carrier" == "1" ]] && return 0
fi
return 1
}
# ── Check if Interface has DHCP ─────────────────────────────────────
# Brief DHCP test on interface to detect WAN
test_dhcp() {
local iface="$1"
local timeout="${2:-5}"
# Bring interface up
ip link set "$iface" up 2>/dev/null || true
sleep 1
# Try dhclient briefly
if command -v dhclient &>/dev/null; then
timeout "$timeout" dhclient -1 -v "$iface" &>/dev/null && return 0
elif command -v udhcpc &>/dev/null; then
timeout "$timeout" udhcpc -i "$iface" -t 3 -n &>/dev/null && return 0
fi
return 1
}
# ── Board-Specific Interface Mapping ─────────────────────────────────
get_interface_config() {
local board="$1"
local wan_iface=""
local lan_ifaces=""
local sfp_ifaces=""
local profile="full"
case "$board" in
mochabin)
# MochaBin: Armada 7040 with DSA switch + SFP+
wan_iface="eth0"
lan_ifaces="eth1 eth2 eth3 eth4"
sfp_ifaces="eth5 eth6"
profile="full"
;;
espressobin-v7)
# ESPRESSObin v7: Armada 3720 with DSA (lan0/lan1)
wan_iface="eth0"
lan_ifaces="lan0 lan1"
profile="lite"
;;
espressobin-ultra)
# ESPRESSObin Ultra: More LAN ports
wan_iface="eth0"
lan_ifaces="lan0 lan1 lan2 lan3"
profile="lite"
;;
x64-vm)
# VMs: All interfaces get DHCP (no bridge/router mode)
# VirtualBox/QEMU host-only adapters have their own DHCP
local all_ifaces
all_ifaces=$(get_physical_interfaces)
log "VM detected - all interfaces will use DHCP: $all_ifaces"
# First interface is WAN, no LAN bridge needed
wan_iface=$(echo "$all_ifaces" | awk '{print $1}')
lan_ifaces="" # Empty = no bridge created
profile="vm"
;;
x64-baremetal)
# Baremetal x64: Auto-detect WAN/LAN based on connectivity
local all_ifaces
all_ifaces=$(get_physical_interfaces)
log "Physical interfaces found: $all_ifaces"
# Try to find WAN: interface with link that gets DHCP
for iface in $all_ifaces; do
if check_link "$iface"; then
log "Interface $iface has carrier (link up)"
# First interface with link is likely WAN
if [[ -z "$wan_iface" ]]; then
wan_iface="$iface"
else
lan_ifaces="$lan_ifaces $iface"
fi
else
log "Interface $iface has no carrier"
fi
done
# If no link detected, use naming convention with priority
if [[ -z "$wan_iface" ]]; then
log "No link detected, using naming convention fallback"
# Sort interfaces to get predictable WAN selection
# Priority: eth0 > enp*s0 > eno1 > ens* > others
local sorted_ifaces
sorted_ifaces=$(echo "$all_ifaces" | tr ' ' '\n' | sort -V | tr '\n' ' ')
for iface in $sorted_ifaces; do
case "$iface" in
# High priority: common WAN interface names
eth0|enp0s3|enp1s0|enp2s0|enp3s0|eno1|eno0|ens33|ens192)
if [[ -z "$wan_iface" ]]; then
wan_iface="$iface"
log "Selected $iface as WAN (naming priority)"
else
lan_ifaces="$lan_ifaces $iface"
fi
;;
# Medium priority: any enp*s0 (first slot on each bus)
enp[0-9]*s0)
if [[ -z "$wan_iface" ]]; then
wan_iface="$iface"
log "Selected $iface as WAN (pattern enp*s0)"
else
lan_ifaces="$lan_ifaces $iface"
fi
;;
# Medium priority: any eno* (onboard)
eno[0-9]*)
if [[ -z "$wan_iface" ]]; then
wan_iface="$iface"
log "Selected $iface as WAN (pattern eno*)"
else
lan_ifaces="$lan_ifaces $iface"
fi
;;
*)
# Everything else goes to LAN
lan_ifaces="$lan_ifaces $iface"
;;
esac
done
# Last resort: use first interface as WAN
if [[ -z "$wan_iface" ]] && [[ -n "$all_ifaces" ]]; then
wan_iface=$(echo "$all_ifaces" | awk '{print $1}')
lan_ifaces=$(echo "$all_ifaces" | awk '{$1=""; print $0}')
log "Fallback: using first interface $wan_iface as WAN"
fi
fi
profile="full"
;;
*)
# Unknown: Use first interface as WAN, rest as LAN
local all_ifaces
all_ifaces=$(get_physical_interfaces)
wan_iface=$(echo "$all_ifaces" | awk '{print $1}')
lan_ifaces=$(echo "$all_ifaces" | awk '{$1=""; print $0}' | xargs)
profile="lite"
;;
esac
# Trim whitespace
lan_ifaces=$(echo "$lan_ifaces" | xargs)
sfp_ifaces=$(echo "$sfp_ifaces" | xargs)
# Output as JSON
cat <<EOF
{
"board": "$board",
"profile": "$profile",
"interfaces": {
"wan": "$wan_iface",
"lan": "$lan_ifaces",
"sfp": "$sfp_ifaces"
},
"detected_at": "$(date -Iseconds)"
}
EOF
}
# ── Generate Netplan Configuration ──────────────────────────────────
generate_netplan() {
local board="$1"
local wan_iface="$2"
local lan_ifaces="$3"
local sfp_ifaces="${4:-}"
local mode="${5:-router}"
local netplan_file="${NETPLAN_DIR}/00-secubox.yaml"
log "Generating netplan: WAN=$wan_iface LAN=[$lan_ifaces] mode=$mode board=$board"
# Build ethernets section (4 spaces for interface name, 6 spaces for properties)
local ethernets=""
# VM special case: ALL interfaces get DHCP
if [[ "$board" == "x64-vm" ]]; then
log "VM mode: configuring all physical interfaces with DHCP"
local all_ifaces
all_ifaces=$(get_physical_interfaces)
for iface in $all_ifaces; do
ethernets+=" ${iface}:
dhcp4: true
dhcp6: false
optional: true
"
done
else
# Standard WAN interface - DHCP
if [[ -n "$wan_iface" ]]; then
ethernets+=" ${wan_iface}:
dhcp4: true
dhcp6: false
dhcp4-overrides:
use-dns: true
use-routes: true
optional: true
"
fi
fi
# LAN interfaces - no IP, will be bridged (not for VMs)
for iface in $lan_ifaces; do
ethernets+=" ${iface}:
dhcp4: false
optional: true
"
done
# SFP interfaces (if any)
for iface in $sfp_ifaces; do
ethernets+=" ${iface}:
dhcp4: false
optional: true
"
done
# Build bridges section
local bridges=""
local lan_array=""
for iface in $lan_ifaces; do
[[ -n "$lan_array" ]] && lan_array+=", "
lan_array+="${iface}"
done
case "$mode" in
router)
if [[ -n "$lan_array" ]]; then
bridges=" bridges:
br-lan:
interfaces: [${lan_array}]
addresses: [192.168.10.1/24]
dhcp4: false
optional: true
parameters:
stp: false
forward-delay: 0
"
else
# No LAN interfaces — DON'T create an empty br-lan with
# 192.168.10.1/24. A member-less bridge with a static IP
# squats the .1 address without routing anything, makes
# systemd-networkd think the interface is "configured",
# and frequently breaks DHCP on the real WAN NIC (the
# router refuses a second .1/24 on its broadcast domain
# or the bridge claims the gateway slot).
# Live USB and single-NIC installs should just stay
# WAN-only via the ethernet block above.
bridges=""
fi
;;
bridge)
# Bridge mode: all interfaces in single bridge, WAN DHCP
bridges=" bridges:
br0:
interfaces: [${lan_array}]
dhcp4: true
dhcp6: false
"
;;
single)
# Single interface mode: WAN only, no bridge
bridges=""
;;
esac
# Write netplan config
cat > "$netplan_file" <<EOF
# /etc/netplan/00-secubox.yaml
# Generated by secubox-net-detect
# Board: ${board} | Mode: ${mode}
network:
version: 2
renderer: networkd
ethernets:
${ethernets}
${bridges}
# DHCP server on br-lan managed by dnsmasq (secubox-nac)
EOF
log "Netplan configuration written to: $netplan_file"
}
# ── Main ────────────────────────────────────────────────────────────
main() {
local action="${1:-detect}"
local mode="${2:-router}"
log "=== SecuBox Network Detection ==="
# Detect board
local board
board=$(detect_board)
log "Board detected: $board"
# Get interface configuration
local config
config=$(get_interface_config "$board")
# Parse JSON (simple extraction)
local wan_iface lan_ifaces sfp_ifaces profile
wan_iface=$(echo "$config" | grep -o '"wan": "[^"]*"' | cut -d'"' -f4)
lan_ifaces=$(echo "$config" | grep -o '"lan": "[^"]*"' | cut -d'"' -f4)
sfp_ifaces=$(echo "$config" | grep -o '"sfp": "[^"]*"' | cut -d'"' -f4)
profile=$(echo "$config" | grep -o '"profile": "[^"]*"' | cut -d'"' -f4)
log "WAN interface: $wan_iface"
log "LAN interfaces: $lan_ifaces"
[[ -n "$sfp_ifaces" ]] && log "SFP interfaces: $sfp_ifaces"
log "Profile: $profile"
# VMs use single mode (no bridge) - override any default
if [[ "$profile" == "vm" ]]; then
mode="single"
log "VM profile detected - using single mode (no bridge)"
fi
case "$action" in
detect)
# Output detection result
mkdir -p "$(dirname "$OUTPUT_JSON")"
echo "$config" > "$OUTPUT_JSON"
ok "Detection result: $OUTPUT_JSON"
echo "$config"
;;
apply)
# Generate and apply netplan configuration
generate_netplan "$board" "$wan_iface" "$lan_ifaces" "$sfp_ifaces" "$mode"
log "Applying netplan..."
# Use timeout to prevent hanging
timeout 5 netplan apply 2>/dev/null || log "netplan apply skipped or failed"
ok "Network configuration applied (mode: $mode)"
;;
test)
# Test WAN connectivity
if check_link "$wan_iface"; then
ok "WAN ($wan_iface) has link"
else
log "WAN ($wan_iface) no link detected"
fi
;;
*)
echo "Usage: $0 {detect|apply|test} [mode]"
echo " detect - Detect interfaces and output JSON"
echo " apply - Generate and apply netplan (modes: router|bridge|single)"
echo " test - Test WAN connectivity"
exit 1
;;
esac
}
main "$@"