mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 13:59:40 +00:00
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).
462 lines
16 KiB
Bash
Executable File
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 "$@"
|