secubox-openwrt/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes
2025-12-29 09:03:49 +01:00

2428 lines
80 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# Network Modes Dashboard RPCD backend
# Copyright (C) 2024 CyberMind.fr - Gandalf
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
get_pkg_version() {
local ctrl="/usr/lib/opkg/info/luci-app-network-modes.control"
if [ -f "$ctrl" ]; then
awk -F': ' '/^Version/ { print $2; exit }' "$ctrl"
else
echo "unknown"
fi
}
PKG_VERSION="$(get_pkg_version)"
CONFIG_FILE="/etc/config/network-modes"
BACKUP_DIR="/etc/network-modes-backup"
PCAP_DIR="/var/log/pcap"
# Get current status
get_status() {
json_init
# Current mode
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
local last_change=$(uci -q get network-modes.config.last_change || echo "Never")
json_add_string "version" "$PKG_VERSION"
json_add_string "current_mode" "$current_mode"
json_add_string "last_change" "$last_change"
# Get mode details
local mode_name=$(uci -q get network-modes.$current_mode.name || echo "$current_mode")
local mode_desc=$(uci -q get network-modes.$current_mode.description || echo "")
json_add_string "mode_name" "$mode_name"
json_add_string "mode_description" "$mode_desc"
# System info
json_add_string "hostname" "$(uci -q get system.@system[0].hostname || hostname)"
json_add_int "uptime" "$(cat /proc/uptime | cut -d. -f1)"
# Network interfaces status
json_add_array "interfaces"
for iface in $(ip -o link show | awk -F': ' '{print $2}' | grep -v "^lo$"); do
local state="down"
ip link show $iface 2>/dev/null | grep -q "UP" && state="up"
local ip=$(ip -4 addr show $iface 2>/dev/null | grep -oP 'inet \K[\d.]+' | head -1)
local mac=$(ip link show $iface 2>/dev/null | grep -oP 'link/ether \K[a-f0-9:]+')
json_add_object
json_add_string "name" "$iface"
json_add_string "state" "$state"
json_add_string "ip" "${ip:-N/A}"
json_add_string "mac" "${mac:-N/A}"
json_close_object
done
json_close_array
# WiFi status
local wifi_enabled=0
[ -d /sys/class/net/wlan0 ] && wifi_enabled=1
json_add_boolean "wifi_available" "$wifi_enabled"
# WireGuard status
local wg_enabled=0
command -v wg >/dev/null 2>&1 && wg_enabled=1
json_add_boolean "wireguard_available" "$wg_enabled"
# Services status
json_add_object "services"
json_add_boolean "firewall" "$(pgrep -x fw4 >/dev/null && echo 1 || echo 0)"
json_add_boolean "dnsmasq" "$(pgrep -x dnsmasq >/dev/null && echo 1 || echo 0)"
json_add_boolean "netifyd" "$(pgrep -x netifyd >/dev/null && echo 1 || echo 0)"
json_add_boolean "nginx" "$(pgrep -x nginx >/dev/null && echo 1 || echo 0)"
json_add_boolean "squid" "$(pgrep -x squid >/dev/null && echo 1 || echo 0)"
json_close_object
json_dump
}
# Get all modes configuration
get_modes() {
json_init
json_add_array "modes"
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
# Sniffer mode
json_add_object
json_add_string "id" "sniffer"
json_add_string "name" "$(uci -q get network-modes.sniffer.name || echo 'Sniffer / Passthrough')"
json_add_string "description" "$(uci -q get network-modes.sniffer.description)"
json_add_string "icon" "🔍"
json_add_boolean "active" "$([ "$current_mode" = "sniffer" ] && echo 1 || echo 0)"
json_add_string "bridge_interface" "$(uci -q get network-modes.sniffer.bridge_interface)"
json_add_boolean "netifyd_enabled" "$(uci -q get network-modes.sniffer.netifyd_enabled || echo 0)"
json_add_boolean "promiscuous" "$(uci -q get network-modes.sniffer.promiscuous || echo 0)"
json_close_object
# Access Point mode
json_add_object
json_add_string "id" "accesspoint"
json_add_string "name" "$(uci -q get network-modes.accesspoint.name || echo 'Access Point')"
json_add_string "description" "$(uci -q get network-modes.accesspoint.description)"
json_add_string "icon" "📶"
json_add_boolean "active" "$([ "$current_mode" = "accesspoint" ] && echo 1 || echo 0)"
json_add_string "wifi_channel" "$(uci -q get network-modes.accesspoint.wifi_channel)"
json_add_string "wifi_htmode" "$(uci -q get network-modes.accesspoint.wifi_htmode)"
json_add_int "wifi_txpower" "$(uci -q get network-modes.accesspoint.wifi_txpower || echo 20)"
json_add_boolean "roaming_enabled" "$(uci -q get network-modes.accesspoint.roaming_enabled || echo 0)"
json_add_boolean "band_steering" "$(uci -q get network-modes.accesspoint.band_steering || echo 0)"
json_close_object
# Relay mode
json_add_object
json_add_string "id" "relay"
json_add_string "name" "$(uci -q get network-modes.relay.name || echo 'Relay / Extender')"
json_add_string "description" "$(uci -q get network-modes.relay.description)"
json_add_string "icon" "🔄"
json_add_boolean "active" "$([ "$current_mode" = "relay" ] && echo 1 || echo 0)"
json_add_boolean "wireguard_enabled" "$(uci -q get network-modes.relay.wireguard_enabled || echo 0)"
json_add_string "wireguard_interface" "$(uci -q get network-modes.relay.wireguard_interface)"
json_add_boolean "mtu_optimization" "$(uci -q get network-modes.relay.mtu_optimization || echo 0)"
json_add_boolean "mss_clamping" "$(uci -q get network-modes.relay.mss_clamping || echo 0)"
json_close_object
# Router mode
json_add_object
json_add_string "id" "router"
json_add_string "name" "$(uci -q get network-modes.router.name || echo 'Router')"
json_add_string "description" "$(uci -q get network-modes.router.description)"
json_add_string "icon" "🌐"
json_add_boolean "active" "$([ "$current_mode" = "router" ] && echo 1 || echo 0)"
json_add_string "wan_protocol" "$(uci -q get network-modes.router.wan_protocol)"
json_add_boolean "nat_enabled" "$(uci -q get network-modes.router.nat_enabled || echo 1)"
json_add_boolean "firewall_enabled" "$(uci -q get network-modes.router.firewall_enabled || echo 1)"
json_add_boolean "proxy_enabled" "$(uci -q get network-modes.router.proxy_enabled || echo 0)"
json_add_string "proxy_type" "$(uci -q get network-modes.router.proxy_type)"
json_add_boolean "https_frontend" "$(uci -q get network-modes.router.https_frontend || echo 0)"
json_add_string "frontend_type" "$(uci -q get network-modes.router.frontend_type)"
json_close_object
# Travel mode
json_add_object
json_add_string "id" "travel"
json_add_string "name" "$(uci -q get network-modes.travel.name || echo 'Travel Router')"
json_add_string "description" "$(uci -q get network-modes.travel.description || echo 'Portable router with WiFi uplink and personal hotspot')"
json_add_string "icon" "✈️"
json_add_boolean "active" "$([ "$current_mode" = "travel" ] && echo 1 || echo 0)"
json_add_string "client_interface" "$(uci -q get network-modes.travel.client_interface || echo 'wlan1')"
json_add_string "hotspot_interface" "$(uci -q get network-modes.travel.hotspot_interface || echo 'wlan0')"
json_add_boolean "mac_clone_enabled" "$([ -n "$(uci -q get network-modes.travel.clone_mac)" ] && echo 1 || echo 0)"
json_add_string "hotspot_ssid" "$(uci -q get network-modes.travel.hotspot_ssid || echo 'SecuBox-Travel')"
json_close_object
json_close_array
json_dump
}
# Get sniffer mode configuration
get_sniffer_config() {
json_init
json_add_string "mode" "sniffer"
json_add_string "name" "Sniffer / Passthrough Mode"
json_add_string "description" "Transparent Ethernet bridge without IP for passive network analysis"
# Current settings
json_add_string "bridge_interface" "$(uci -q get network-modes.sniffer.bridge_interface || echo 'br-lan')"
json_add_string "capture_interface" "$(uci -q get network-modes.sniffer.capture_interface || echo 'eth0')"
json_add_boolean "netifyd_enabled" "$(uci -q get network-modes.sniffer.netifyd_enabled || echo 1)"
json_add_boolean "promiscuous" "$(uci -q get network-modes.sniffer.promiscuous || echo 1)"
# Available interfaces for bridging
json_add_array "available_interfaces"
for iface in $(ls /sys/class/net/ | grep -v "^lo$"); do
json_add_string "" "$iface"
done
json_close_array
# Netifyd status
local netifyd_running=0
pgrep -x netifyd >/dev/null && netifyd_running=1
json_add_boolean "netifyd_running" "$netifyd_running"
# Sample config preview
json_add_string "config_preview" "# /etc/config/network bridge config
config interface 'lan'
option proto 'none'
option type 'bridge'
option ifname 'eth0 eth1'
option delegate '0'"
json_dump
}
# Get access point mode configuration
get_ap_config() {
json_init
json_add_string "mode" "accesspoint"
json_add_string "name" "Access Point Mode"
json_add_string "description" "WiFi Access Point with advanced optimizations"
# Current settings
json_add_string "upstream_interface" "$(uci -q get network-modes.accesspoint.upstream_interface || echo 'eth0')"
json_add_boolean "dhcp_client" "$(uci -q get network-modes.accesspoint.dhcp_client || echo 1)"
json_add_string "wifi_channel" "$(uci -q get network-modes.accesspoint.wifi_channel || echo 'auto')"
json_add_string "wifi_htmode" "$(uci -q get network-modes.accesspoint.wifi_htmode || echo 'VHT80')"
json_add_int "wifi_txpower" "$(uci -q get network-modes.accesspoint.wifi_txpower || echo 20)"
# WiFi tweaks
json_add_object "wifi_tweaks"
json_add_boolean "roaming_80211r" "$(uci -q get network-modes.accesspoint.roaming_enabled || echo 0)"
json_add_boolean "rrm_80211k" "$(uci -q get network-modes.accesspoint.rrm_enabled || echo 0)"
json_add_boolean "wnm_80211v" "$(uci -q get network-modes.accesspoint.wnm_enabled || echo 0)"
json_add_boolean "band_steering" "$(uci -q get network-modes.accesspoint.band_steering || echo 0)"
json_add_boolean "airtime_fairness" "$(uci -q get network-modes.accesspoint.airtime_fairness || echo 0)"
json_add_boolean "beamforming" "$(uci -q get network-modes.accesspoint.beamforming || echo 1)"
json_close_object
# Available channels
json_add_array "available_channels_2g"
for ch in 1 6 11; do
json_add_int "" "$ch"
done
json_close_array
json_add_array "available_channels_5g"
for ch in 36 40 44 48 149 153 157 161; do
json_add_int "" "$ch"
done
json_close_array
# HT modes
json_add_array "available_htmodes"
json_add_string "" "HT20"
json_add_string "" "HT40"
json_add_string "" "VHT40"
json_add_string "" "VHT80"
json_add_string "" "VHT160"
json_add_string "" "HE40"
json_add_string "" "HE80"
json_add_string "" "HE160"
json_close_array
# WiFi info
local wifi_phy=""
local wifi_driver=""
if [ -d /sys/class/ieee80211/phy0 ]; then
wifi_phy="phy0"
wifi_driver=$(cat /sys/class/ieee80211/phy0/device/uevent 2>/dev/null | grep DRIVER | cut -d= -f2)
fi
json_add_string "wifi_phy" "$wifi_phy"
json_add_string "wifi_driver" "$wifi_driver"
json_dump
}
# Get relay mode configuration
get_relay_config() {
json_init
json_add_string "mode" "relay"
json_add_string "name" "Relay / Extender Mode"
json_add_string "description" "Network relay with LAN optimization and WireGuard tunneling"
# Current settings
json_add_string "relay_interface" "$(uci -q get network-modes.relay.relay_interface || echo 'wlan0')"
json_add_string "lan_interface" "$(uci -q get network-modes.relay.lan_interface || echo 'eth0')"
# WireGuard settings
json_add_object "wireguard"
json_add_boolean "enabled" "$(uci -q get network-modes.relay.wireguard_enabled || echo 0)"
json_add_string "interface" "$(uci -q get network-modes.relay.wireguard_interface || echo 'wg0')"
json_add_int "mtu" "$(uci -q get network-modes.relay.wireguard_mtu || echo 1420)"
json_add_boolean "persistent_keepalive" "$(uci -q get network-modes.relay.persistent_keepalive || echo 1)"
json_close_object
# Optimization settings
json_add_object "optimizations"
json_add_boolean "mtu_optimization" "$(uci -q get network-modes.relay.mtu_optimization || echo 1)"
json_add_boolean "mss_clamping" "$(uci -q get network-modes.relay.mss_clamping || echo 1)"
json_add_boolean "tcp_optimization" "$(uci -q get network-modes.relay.tcp_optimization || echo 1)"
json_add_int "conntrack_max" "$(uci -q get network-modes.relay.conntrack_max || echo 16384)"
json_close_object
# Relayd status
local relayd_installed=0
opkg list-installed 2>/dev/null | grep -q "^relayd" && relayd_installed=1
json_add_boolean "relayd_available" "$relayd_installed"
# WireGuard interfaces
json_add_array "wg_interfaces"
if command -v wg >/dev/null 2>&1; then
for wgiface in $(wg show interfaces 2>/dev/null); do
json_add_string "" "$wgiface"
done
fi
json_close_array
json_dump
}
# Get router mode configuration
get_router_config() {
json_init
json_add_string "mode" "router"
json_add_string "name" "Router Mode"
json_add_string "description" "Full router with WAN, proxy, and HTTPS frontends"
# WAN settings
json_add_object "wan"
json_add_string "interface" "$(uci -q get network-modes.router.wan_interface || echo 'eth1')"
json_add_string "protocol" "$(uci -q get network-modes.router.wan_protocol || echo 'dhcp')"
json_add_boolean "nat_enabled" "$(uci -q get network-modes.router.nat_enabled || echo 1)"
json_close_object
# LAN settings
json_add_object "lan"
json_add_string "interface" "$(uci -q get network-modes.router.lan_interface || echo 'br-lan')"
json_add_string "ip_address" "$(uci -q get network.lan.ipaddr || echo '192.168.1.1')"
json_add_string "netmask" "$(uci -q get network.lan.netmask || echo '255.255.255.0')"
json_close_object
# Firewall
json_add_object "firewall"
json_add_boolean "enabled" "$(uci -q get network-modes.router.firewall_enabled || echo 1)"
json_add_boolean "syn_flood" "$(uci -q get firewall.@defaults[0].syn_flood || echo 1)"
json_add_string "input" "$(uci -q get firewall.@zone[0].input || echo 'ACCEPT')"
json_add_string "output" "$(uci -q get firewall.@zone[0].output || echo 'ACCEPT')"
json_add_string "forward" "$(uci -q get firewall.@zone[0].forward || echo 'REJECT')"
json_close_object
# Proxy settings
json_add_object "proxy"
json_add_boolean "enabled" "$(uci -q get network-modes.router.proxy_enabled || echo 0)"
json_add_string "type" "$(uci -q get network-modes.router.proxy_type || echo 'squid')"
json_add_int "port" "$(uci -q get network-modes.router.proxy_port || echo 3128)"
json_add_boolean "transparent" "$(uci -q get network-modes.router.proxy_transparent || echo 0)"
json_add_boolean "dns_over_https" "$(uci -q get network-modes.router.dns_over_https || echo 0)"
json_close_object
# HTTPS Frontend settings
json_add_object "https_frontend"
json_add_boolean "enabled" "$(uci -q get network-modes.router.https_frontend || echo 0)"
json_add_string "type" "$(uci -q get network-modes.router.frontend_type || echo 'nginx')"
json_add_int "http_port" "$(uci -q get network-modes.router.frontend_http_port || echo 80)"
json_add_int "https_port" "$(uci -q get network-modes.router.frontend_https_port || echo 443)"
json_add_boolean "letsencrypt" "$(uci -q get network-modes.router.letsencrypt || echo 0)"
json_close_object
# Virtual hosts
json_add_array "virtual_hosts"
local idx=0
while true; do
local domain=$(uci -q get network-modes.@vhost[$idx].domain)
[ -z "$domain" ] && break
json_add_object
json_add_string "domain" "$domain"
json_add_string "backend" "$(uci -q get network-modes.@vhost[$idx].backend)"
json_add_int "port" "$(uci -q get network-modes.@vhost[$idx].port || echo 80)"
json_add_boolean "ssl" "$(uci -q get network-modes.@vhost[$idx].ssl || echo 0)"
json_close_object
idx=$((idx + 1))
done
json_close_array
# Available protocols
json_add_array "available_wan_protocols"
json_add_string "" "dhcp"
json_add_string "" "static"
json_add_string "" "pppoe"
json_add_string "" "pptp"
json_add_string "" "l2tp"
json_close_array
json_dump
}
# Get travel router configuration
get_travel_config() {
json_init
local client_iface=$(uci -q get network-modes.travel.client_interface || echo "wlan1")
local hotspot_iface=$(uci -q get network-modes.travel.hotspot_interface || echo "wlan0")
local client_radio=$(uci -q get network-modes.travel.client_radio || echo "radio1")
local hotspot_radio=$(uci -q get network-modes.travel.hotspot_radio || echo "radio0")
local clone_mac=$(uci -q get network-modes.travel.clone_mac || echo "")
json_add_string "mode" "travel"
json_add_string "name" "$(uci -q get network-modes.travel.name || echo 'Travel Router')"
json_add_string "description" "$(uci -q get network-modes.travel.description || echo 'Portable WiFi router for hotels and coworking spaces')"
json_add_object "client"
json_add_string "interface" "$client_iface"
json_add_string "radio" "$client_radio"
json_add_string "ssid" "$(uci -q get network-modes.travel.ssid || echo '')"
json_add_string "encryption" "$(uci -q get network-modes.travel.encryption || echo 'sae-mixed')"
json_add_string "password" "$(uci -q get network-modes.travel.password || echo '')"
json_add_string "clone_mac" "$clone_mac"
json_add_boolean "mac_clone_enabled" "$([ -n "$clone_mac" ] && echo 1 || echo 0)"
json_close_object
json_add_object "hotspot"
json_add_string "interface" "$hotspot_iface"
json_add_string "radio" "$hotspot_radio"
json_add_string "ssid" "$(uci -q get network-modes.travel.hotspot_ssid || echo 'SecuBox-Travel')"
json_add_string "password" "$(uci -q get network-modes.travel.hotspot_password || echo 'TravelSafe123!')"
json_add_string "band" "$(uci -q get network-modes.travel.hotspot_band || echo 'dual')"
json_close_object
json_add_object "lan"
json_add_string "subnet" "$(uci -q get network-modes.travel.lan_subnet || echo '10.77.0.1')"
json_add_string "netmask" "$(uci -q get network-modes.travel.lan_netmask || echo '255.255.255.0')"
json_close_object
json_add_array "available_interfaces"
for iface in $(ls /sys/class/net/ 2>/dev/null | grep -E '^wl|^wlan' || true); do
json_add_string "" "$iface"
done
json_close_array
json_add_array "available_radios"
for radio in $(uci show wireless 2>/dev/null | grep "=wifi-device" | cut -d'.' -f2 | cut -d'=' -f1); do
json_add_string "" "$radio"
done
json_close_array
json_dump
}
# Scan nearby WiFi networks for travel mode
travel_scan_networks() {
json_init
json_add_array "networks"
local iface=$(uci -q get network-modes.travel.client_interface || echo "wlan1")
local scan_output
scan_output="$(iwinfo "$iface" scan 2>/dev/null || true)"
if [ -z "$scan_output" ]; then
json_close_array
json_dump
return
fi
local bssid=""
local ssid=""
local channel=""
local signal=""
local encryption=""
local quality=""
while IFS= read -r line; do
case "$line" in
Cell*)
if [ -n "$bssid" ]; then
json_add_object
json_add_string "ssid" "${ssid:-Unknown}"
json_add_string "bssid" "$bssid"
json_add_string "channel" "${channel:-?}"
json_add_string "signal" "${signal:-N/A}"
json_add_string "quality" "${quality:-N/A}"
json_add_string "encryption" "${encryption:-Unknown}"
json_close_object
fi
bssid=$(printf '%s\n' "$line" | awk '{print $5}')
ssid=""
channel=""
signal=""
encryption=""
quality=""
;;
*"ESSID:"*)
ssid=$(printf '%s\n' "$line" | sed -n 's/.*ESSID: "\(.*\)".*/\1/p')
;;
*"Channel:"*)
channel=$(printf '%s\n' "$line" | awk -F'Channel:' '{print $2}' | awk '{print $1}')
;;
*"Signal:"*)
signal=$(printf '%s\n' "$line" | awk -F'Signal:' '{print $2}' | awk '{print $1" "$2}')
if echo "$line" | grep -q 'Quality'; then
quality=$(printf '%s\n' "$line" | sed -n 's/.*Quality: \([0-9\/]*\).*/\1/p')
fi
;;
*"Encryption:"*)
encryption=$(printf '%s\n' "$line" | sed -n 's/.*Encryption: //p')
;;
esac
done <<EOF
$scan_output
EOF
if [ -n "$bssid" ]; then
json_add_object
json_add_string "ssid" "${ssid:-Unknown}"
json_add_string "bssid" "$bssid"
json_add_string "channel" "${channel:-?}"
json_add_string "signal" "${signal:-N/A}"
json_add_string "quality" "${quality:-N/A}"
json_add_string "encryption" "${encryption:-Unknown}"
json_close_object
fi
json_close_array
json_dump
}
# Apply mode change (with actual network reconfiguration and rollback timer)
apply_mode() {
json_init
local pending_mode=$(uci -q get network-modes.config.pending_mode || echo "")
if [ -z "$pending_mode" ]; then
json_add_boolean "success" 0
json_add_string "error" "Aucun mode en attente. Utilisez set_mode d'abord."
json_dump
return
fi
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
# Backup current config
mkdir -p "$BACKUP_DIR"
local backup_file="$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).tar.gz"
tar -czf "$backup_file" /etc/config/network /etc/config/wireless /etc/config/firewall /etc/config/dhcp 2>/dev/null
# Apply network configuration based on mode
case "$pending_mode" in
router)
# Router mode: NAT, DHCP server, firewall
# WAN interface
uci delete network.wan 2>/dev/null
uci set network.wan=interface
uci set network.wan.proto='dhcp'
uci set network.wan.device='eth1'
# LAN interface
uci set network.lan=interface
uci set network.lan.proto='static'
uci set network.lan.device='eth0'
uci set network.lan.ipaddr='192.168.1.1'
uci set network.lan.netmask='255.255.255.0'
# DHCP server
uci set dhcp.lan=dhcp
uci set dhcp.lan.interface='lan'
uci set dhcp.lan.start='100'
uci set dhcp.lan.limit='150'
uci set dhcp.lan.leasetime='12h'
# Firewall zones
uci set firewall.@zone[0]=zone
uci set firewall.@zone[0].name='lan'
uci set firewall.@zone[0].input='ACCEPT'
uci set firewall.@zone[0].output='ACCEPT'
uci set firewall.@zone[0].forward='ACCEPT'
uci set firewall.@zone[1]=zone
uci set firewall.@zone[1].name='wan'
uci set firewall.@zone[1].input='REJECT'
uci set firewall.@zone[1].output='ACCEPT'
uci set firewall.@zone[1].forward='REJECT'
uci set firewall.@zone[1].masq='1'
uci set firewall.@zone[1].mtu_fix='1'
generate_squid_config
generate_tinyproxy_config
generate_privoxy_config
configure_doh_proxy
apply_transparent_proxy_rules
deploy_nginx_vhosts
;;
travel)
local client_iface=$(uci -q get network-modes.travel.client_interface || echo "wlan1")
local client_radio=$(uci -q get network-modes.travel.client_radio || echo "radio1")
local hotspot_radio=$(uci -q get network-modes.travel.hotspot_radio || echo "radio0")
local travel_ssid=$(uci -q get network-modes.travel.ssid || echo "")
local travel_password=$(uci -q get network-modes.travel.password || echo "")
local travel_encryption=$(uci -q get network-modes.travel.encryption || echo "sae-mixed")
local hotspot_ssid=$(uci -q get network-modes.travel.hotspot_ssid || echo "SecuBox-Travel")
local hotspot_password=$(uci -q get network-modes.travel.hotspot_password || echo "TravelSafe123!")
local clone_mac=$(uci -q get network-modes.travel.clone_mac || echo "")
local lan_ip=$(uci -q get network-modes.travel.lan_subnet || echo "10.77.0.1")
local lan_netmask=$(uci -q get network-modes.travel.lan_netmask || echo "255.255.255.0")
uci delete network.wan 2>/dev/null
uci set network.wan=interface
uci set network.wan.proto='dhcp'
uci set network.wan.device="$client_iface"
uci set network.lan=interface
uci set network.lan.proto='static'
uci set network.lan.device='br-lan'
uci set network.lan.ipaddr="$lan_ip"
uci set network.lan.netmask="$lan_netmask"
uci set dhcp.lan=dhcp
uci set dhcp.lan.interface='lan'
uci set dhcp.lan.start='60'
uci set dhcp.lan.limit='80'
uci set dhcp.lan.leasetime='6h'
uci set firewall.@zone[0]=zone
uci set firewall.@zone[0].name='lan'
uci set firewall.@zone[0].input='ACCEPT'
uci set firewall.@zone[0].output='ACCEPT'
uci set firewall.@zone[0].forward='ACCEPT'
uci set firewall.@zone[1]=zone
uci set firewall.@zone[1].name='wan'
uci set firewall.@zone[1].network='wan'
uci set firewall.@zone[1].input='REJECT'
uci set firewall.@zone[1].output='ACCEPT'
uci set firewall.@zone[1].forward='REJECT'
uci set firewall.@zone[1].masq='1'
uci set firewall.@zone[1].mtu_fix='1'
uci delete wireless.travel_sta 2>/dev/null
uci set wireless.travel_sta=wifi-iface
uci set wireless.travel_sta.device="$client_radio"
uci set wireless.travel_sta.mode='sta'
uci set wireless.travel_sta.network='wan'
[ -n "$travel_ssid" ] && uci set wireless.travel_sta.ssid="$travel_ssid" || uci delete wireless.travel_sta.ssid 2>/dev/null
uci set wireless.travel_sta.encryption="$travel_encryption"
if [ -n "$travel_password" ]; then
uci set wireless.travel_sta.key="$travel_password"
else
uci delete wireless.travel_sta.key 2>/dev/null
fi
if [ -n "$clone_mac" ]; then
uci set wireless.travel_sta.macaddr="$clone_mac"
else
uci delete wireless.travel_sta.macaddr 2>/dev/null
fi
uci delete wireless.travel_ap 2>/dev/null
uci set wireless.travel_ap=wifi-iface
uci set wireless.travel_ap.device="$hotspot_radio"
uci set wireless.travel_ap.mode='ap'
uci set wireless.travel_ap.network='lan'
uci set wireless.travel_ap.ssid="$hotspot_ssid"
uci set wireless.travel_ap.encryption='sae-mixed'
uci set wireless.travel_ap.key="$hotspot_password"
uci set wireless.travel_ap.ieee80211w='1'
uci set wireless.travel_ap.hidden='0'
;;
accesspoint)
# Access Point mode: Bridge, no NAT, DHCP client
# Delete WAN
uci delete network.wan 2>/dev/null
# Bridge LAN
uci set network.lan=interface
uci set network.lan.proto='dhcp'
uci set network.lan.type='bridge'
uci set network.lan.ifname='eth0 eth1'
# Disable DHCP server
uci set dhcp.lan.ignore='1'
# Disable firewall
uci set firewall.@zone[0].input='ACCEPT'
uci set firewall.@zone[0].forward='ACCEPT'
uci delete firewall.@zone[1] 2>/dev/null
apply_accesspoint_features
;;
relay)
# Repeater mode: STA + AP relay
# Client interface (sta)
uci set network.wwan=interface
uci set network.wwan.proto='dhcp'
# AP interface
uci set network.lan=interface
uci set network.lan.proto='static'
uci set network.lan.ipaddr='192.168.2.1'
uci set network.lan.netmask='255.255.255.0'
# Relay with relayd
uci set network.stabridge=interface
uci set network.stabridge.proto='relay'
uci set network.stabridge.network='lan wwan'
apply_wireguard_config
apply_mtu_clamping
enable_tcp_bbr
;;
sniffer)
local ports=$(uci -q get network-modes.sniffer.bridge_ports || echo "eth0 eth1")
local bridge_iface=$(uci -q get network-modes.sniffer.bridge_interface || echo "br-lan")
local promisc=$(uci -q get network-modes.sniffer.promiscuous || echo 1)
uci delete network.wan 2>/dev/null
uci set network.lan=interface
uci set network.lan.proto='none'
uci set network.lan.type='bridge'
uci set network.lan.ifname="$ports"
uci set network.lan.delegate='0'
uci set network.lan.device="$bridge_iface"
uci set dhcp.lan=dhcp
uci set dhcp.lan.interface='lan'
uci set dhcp.lan.ignore='1'
uci set firewall.@zone[0].input='ACCEPT'
uci set firewall.@zone[0].output='ACCEPT'
uci set firewall.@zone[0].forward='ACCEPT'
uci delete firewall.@zone[1] 2>/dev/null
if [ "$promisc" = "1" ]; then
for port in $ports; do
ip link set "$port" promisc on 2>/dev/null || true
done
fi
;;
bridge)
# Pure L2 bridge: all interfaces bridged, DHCP client
uci delete network.wan 2>/dev/null
# Bridge all interfaces
uci set network.lan=interface
uci set network.lan.proto='dhcp'
uci set network.lan.type='bridge'
uci set network.lan.bridge_empty='1'
# Disable DHCP server
uci set dhcp.lan.ignore='1'
# Disable firewall
uci delete firewall.@zone[1] 2>/dev/null
;;
esac
if [ "$pending_mode" = "sniffer" ]; then
start_packet_capture
else
stop_packet_capture
fi
# Commit all changes
uci commit network
uci commit dhcp
uci commit firewall
# Update current mode
uci set network-modes.config.current_mode="$pending_mode"
uci set network-modes.config.last_change="$(date '+%Y-%m-%d %H:%M:%S')"
uci set network-modes.config.rollback_timer="120"
uci commit network-modes
# Start rollback timer (2 minutes) in background
(
for i in $(seq 120 -1 0); do
echo "$i" > /tmp/network-mode-rollback.remaining
sleep 1
done
# Timer expired, rollback
logger -t network-modes "Rollback timer expired, reverting to $current_mode"
cd /
tar -xzf "$backup_file" 2>/dev/null
/etc/init.d/network reload 2>&1
/etc/init.d/firewall reload 2>&1
/etc/init.d/dnsmasq reload 2>&1
uci set network-modes.config.current_mode="$current_mode"
uci delete network-modes.config.pending_mode 2>/dev/null
uci set network-modes.config.last_change="$(date '+%Y-%m-%d %H:%M:%S') (auto-rollback)"
uci commit network-modes
rm -f /tmp/network-mode-rollback.pid
rm -f /tmp/network-mode-rollback.remaining
) &
echo $! > /tmp/network-mode-rollback.pid
# Apply network changes NOW (async to not block RPC)
(
sleep 2
/etc/init.d/network reload 2>&1
/etc/init.d/firewall reload 2>&1
/etc/init.d/dnsmasq reload 2>&1
) &
json_add_boolean "success" 1
json_add_string "mode" "$pending_mode"
json_add_string "previous_mode" "$current_mode"
json_add_string "message" "Mode $pending_mode appliqué. Confirmez dans les 2 minutes ou rollback automatique."
json_add_string "backup" "$backup_file"
json_add_int "rollback_seconds" 120
json_dump
}
# Update mode settings
update_settings() {
read input
json_load "$input"
json_get_var mode mode
json_init
case "$mode" in
sniffer)
json_get_var bridge_interface bridge_interface
json_get_var capture_interface capture_interface
json_get_var netifyd_enabled netifyd_enabled
json_get_var promiscuous promiscuous
[ -n "$bridge_interface" ] && uci set network-modes.sniffer.bridge_interface="$bridge_interface"
[ -n "$capture_interface" ] && uci set network-modes.sniffer.capture_interface="$capture_interface"
[ -n "$netifyd_enabled" ] && uci set network-modes.sniffer.netifyd_enabled="$netifyd_enabled"
[ -n "$promiscuous" ] && uci set network-modes.sniffer.promiscuous="$promiscuous"
;;
accesspoint)
json_get_var wifi_channel wifi_channel
json_get_var wifi_htmode wifi_htmode
json_get_var wifi_txpower wifi_txpower
json_get_var roaming_enabled roaming_enabled
json_get_var band_steering band_steering
json_get_var rrm_enabled rrm_enabled
json_get_var wnm_enabled wnm_enabled
json_get_var airtime_fairness airtime_fairness
json_get_var beamforming beamforming
[ -n "$wifi_channel" ] && uci set network-modes.accesspoint.wifi_channel="$wifi_channel"
[ -n "$wifi_htmode" ] && uci set network-modes.accesspoint.wifi_htmode="$wifi_htmode"
[ -n "$wifi_txpower" ] && uci set network-modes.accesspoint.wifi_txpower="$wifi_txpower"
[ -n "$roaming_enabled" ] && uci set network-modes.accesspoint.roaming_enabled="$roaming_enabled"
[ -n "$band_steering" ] && uci set network-modes.accesspoint.band_steering="$band_steering"
[ -n "$rrm_enabled" ] && uci set network-modes.accesspoint.rrm_enabled="$rrm_enabled"
[ -n "$wnm_enabled" ] && uci set network-modes.accesspoint.wnm_enabled="$wnm_enabled"
[ -n "$airtime_fairness" ] && uci set network-modes.accesspoint.airtime_fairness="$airtime_fairness"
[ -n "$beamforming" ] && uci set network-modes.accesspoint.beamforming="$beamforming"
;;
relay)
json_get_var wireguard_enabled wireguard_enabled
json_get_var wireguard_interface wireguard_interface
json_get_var wireguard_mtu wireguard_mtu
json_get_var mtu_optimization mtu_optimization
json_get_var mss_clamping mss_clamping
json_get_var relay_interface relay_interface
json_get_var lan_interface lan_interface
json_get_var tcp_optimization tcp_optimization
json_get_var conntrack_max conntrack_max
[ -n "$wireguard_enabled" ] && uci set network-modes.relay.wireguard_enabled="$wireguard_enabled"
[ -n "$wireguard_interface" ] && uci set network-modes.relay.wireguard_interface="$wireguard_interface"
[ -n "$wireguard_mtu" ] && uci set network-modes.relay.wireguard_mtu="$wireguard_mtu"
[ -n "$relay_interface" ] && uci set network-modes.relay.relay_interface="$relay_interface"
[ -n "$lan_interface" ] && uci set network-modes.relay.lan_interface="$lan_interface"
[ -n "$mtu_optimization" ] && uci set network-modes.relay.mtu_optimization="$mtu_optimization"
[ -n "$mss_clamping" ] && uci set network-modes.relay.mss_clamping="$mss_clamping"
[ -n "$tcp_optimization" ] && uci set network-modes.relay.tcp_optimization="$tcp_optimization"
[ -n "$conntrack_max" ] && uci set network-modes.relay.conntrack_max="$conntrack_max"
;;
router)
json_get_var wan_interface wan_interface
json_get_var wan_protocol wan_protocol
json_get_var nat_enabled nat_enabled
json_get_var firewall_enabled firewall_enabled
json_get_var proxy_enabled proxy_enabled
json_get_var proxy_type proxy_type
json_get_var proxy_port proxy_port
json_get_var https_frontend https_frontend
json_get_var frontend_type frontend_type
json_get_var transparent_proxy transparent_proxy
json_get_var dns_over_https dns_over_https
json_get_var letsencrypt letsencrypt
[ -n "$wan_interface" ] && uci set network-modes.router.wan_interface="$wan_interface"
[ -n "$wan_protocol" ] && uci set network-modes.router.wan_protocol="$wan_protocol"
[ -n "$nat_enabled" ] && uci set network-modes.router.nat_enabled="$nat_enabled"
[ -n "$firewall_enabled" ] && uci set network-modes.router.firewall_enabled="$firewall_enabled"
[ -n "$proxy_enabled" ] && uci set network-modes.router.proxy_enabled="$proxy_enabled"
[ -n "$proxy_type" ] && uci set network-modes.router.proxy_type="$proxy_type"
[ -n "$proxy_port" ] && uci set network-modes.router.proxy_port="$proxy_port"
[ -n "$https_frontend" ] && uci set network-modes.router.https_frontend="$https_frontend"
[ -n "$frontend_type" ] && uci set network-modes.router.frontend_type="$frontend_type"
[ -n "$transparent_proxy" ] && uci set network-modes.router.proxy_transparent="$transparent_proxy"
[ -n "$dns_over_https" ] && uci set network-modes.router.dns_over_https="$dns_over_https"
[ -n "$letsencrypt" ] && uci set network-modes.router.letsencrypt="$letsencrypt"
;;
travel)
json_get_var client_interface client_interface
json_get_var client_radio client_radio
json_get_var hotspot_radio hotspot_radio
json_get_var ssid ssid
json_get_var password password
json_get_var encryption encryption
json_get_var hotspot_ssid hotspot_ssid
json_get_var hotspot_password hotspot_password
json_get_var clone_mac clone_mac
json_get_var lan_subnet lan_subnet
json_get_var lan_netmask lan_netmask
[ -n "$client_interface" ] && uci set network-modes.travel.client_interface="$client_interface"
[ -n "$client_radio" ] && uci set network-modes.travel.client_radio="$client_radio"
[ -n "$hotspot_radio" ] && uci set network-modes.travel.hotspot_radio="$hotspot_radio"
[ -n "$ssid" ] && uci set network-modes.travel.ssid="$ssid"
[ -n "$password" ] && uci set network-modes.travel.password="$password"
[ -n "$encryption" ] && uci set network-modes.travel.encryption="$encryption"
[ -n "$hotspot_ssid" ] && uci set network-modes.travel.hotspot_ssid="$hotspot_ssid"
[ -n "$hotspot_password" ] && uci set network-modes.travel.hotspot_password="$hotspot_password"
[ -n "$clone_mac" ] && uci set network-modes.travel.clone_mac="$clone_mac"
[ -n "$lan_subnet" ] && uci set network-modes.travel.lan_subnet="$lan_subnet"
[ -n "$lan_netmask" ] && uci set network-modes.travel.lan_netmask="$lan_netmask"
;;
doublenat)
json_get_var wan_interface wan_interface
json_get_var wan_protocol wan_protocol
json_get_var lan_interface lan_interface
json_get_var lan_ip lan_ip
json_get_var lan_netmask lan_netmask
json_get_var guest_network guest_network
json_get_var isolate_guest isolate_guest
json_get_var upnp_enabled upnp_enabled
json_get_var dmz_host dmz_host
[ -n "$wan_interface" ] && uci set network-modes.doublenat.wan_interface="$wan_interface"
[ -n "$wan_protocol" ] && uci set network-modes.doublenat.wan_protocol="$wan_protocol"
[ -n "$lan_interface" ] && uci set network-modes.doublenat.lan_interface="$lan_interface"
[ -n "$lan_ip" ] && uci set network-modes.doublenat.lan_ip="$lan_ip"
[ -n "$lan_netmask" ] && uci set network-modes.doublenat.lan_netmask="$lan_netmask"
[ -n "$guest_network" ] && uci set network-modes.doublenat.guest_network="$guest_network"
[ -n "$isolate_guest" ] && uci set network-modes.doublenat.isolate_guest="$isolate_guest"
[ -n "$upnp_enabled" ] && uci set network-modes.doublenat.upnp_enabled="$upnp_enabled"
[ -n "$dmz_host" ] && uci set network-modes.doublenat.dmz_host="$dmz_host"
;;
multiwan)
json_get_var wan_primary wan_primary
json_get_var wan_secondary wan_secondary
json_get_var policy policy
json_get_var tracking_host tracking_host
json_get_var tracking_interval tracking_interval
json_get_var failover_hold failover_hold
json_get_var load_balance load_balance
json_get_var use_mwan3 use_mwan3
[ -n "$wan_primary" ] && uci set network-modes.multiwan.wan_primary="$wan_primary"
[ -n "$wan_secondary" ] && uci set network-modes.multiwan.wan_secondary="$wan_secondary"
[ -n "$policy" ] && uci set network-modes.multiwan.policy="$policy"
[ -n "$tracking_host" ] && uci set network-modes.multiwan.tracking_host="$tracking_host"
[ -n "$tracking_interval" ] && uci set network-modes.multiwan.tracking_interval="$tracking_interval"
[ -n "$failover_hold" ] && uci set network-modes.multiwan.failover_hold="$failover_hold"
[ -n "$load_balance" ] && uci set network-modes.multiwan.load_balance="$load_balance"
[ -n "$use_mwan3" ] && uci set network-modes.multiwan.use_mwan3="$use_mwan3"
;;
vpnrelay)
json_get_var vpn_type vpn_type
json_get_var vpn_provider vpn_provider
json_get_var wg_interface wg_interface
json_get_var openvpn_profile openvpn_profile
json_get_var upstream_interface upstream_interface
json_get_var policy_routing policy_routing
json_get_var dns_override dns_override
json_get_var kill_switch kill_switch
json_get_var lan_bypass lan_bypass
[ -n "$vpn_type" ] && uci set network-modes.vpnrelay.vpn_type="$vpn_type"
[ -n "$vpn_provider" ] && uci set network-modes.vpnrelay.vpn_provider="$vpn_provider"
[ -n "$wg_interface" ] && uci set network-modes.vpnrelay.wg_interface="$wg_interface"
[ -n "$openvpn_profile" ] && uci set network-modes.vpnrelay.openvpn_profile="$openvpn_profile"
[ -n "$upstream_interface" ] && uci set network-modes.vpnrelay.upstream_interface="$upstream_interface"
[ -n "$policy_routing" ] && uci set network-modes.vpnrelay.policy_routing="$policy_routing"
[ -n "$dns_override" ] && uci set network-modes.vpnrelay.dns_override="$dns_override"
[ -n "$kill_switch" ] && uci set network-modes.vpnrelay.kill_switch="$kill_switch"
[ -n "$lan_bypass" ] && uci set network-modes.vpnrelay.lan_bypass="$lan_bypass"
;;
*)
json_add_boolean "success" 0
json_add_string "error" "Invalid mode"
json_dump
return
;;
esac
uci commit network-modes
[ "$mode" = "accesspoint" ] && apply_accesspoint_features
if [ "$mode" = "sniffer" ]; then
local current_mode=$(uci -q get network-modes.config.current_mode || echo "")
[ "$current_mode" = "sniffer" ] && start_packet_capture
fi
json_add_boolean "success" 1
json_add_string "message" "Settings updated for $mode mode"
json_dump
}
# Squid proxy configuration
generate_squid_config() {
local proxy_type=$(uci -q get network-modes.router.proxy_type || echo "none")
local enabled=$(uci -q get network-modes.router.proxy_enabled || echo 0)
[ "$proxy_type" = "squid" ] || return 0
[ "$enabled" = "1" ] || return 0
if ! command -v squid >/dev/null 2>&1; then
logger -t network-modes "squid binary not found"
return 1
fi
local proxy_port=$(uci -q get network-modes.router.proxy_port || echo 3128)
local cache_size=$(uci -q get network-modes.router.proxy_cache_size || echo 256)
mkdir -p /etc/squid /var/spool/squid /var/log/squid
cat > /etc/squid/squid.conf <<EOF
# SecuBox Network Modes - Squid Configuration
# Generated: $(date)
http_port $proxy_port transparent
cache_dir ufs /var/spool/squid $cache_size 16 256
cache_mem 64 MB
maximum_object_size 4096 KB
minimum_object_size 0 KB
acl localnet src 192.168.0.0/16
acl localnet src 10.0.0.0/8
acl localnet src fc00::/7
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
http_access allow localnet
http_access allow localhost
http_access deny all
access_log /var/log/squid/access.log squid
cache_log /var/log/squid/cache.log
dns_nameservers 1.1.1.1 8.8.8.8
forwarded_for on
via on
EOF
squid -z >/dev/null 2>&1 || true
/etc/init.d/squid restart >/dev/null 2>&1 || true
}
generate_tinyproxy_config() {
local proxy_type=$(uci -q get network-modes.router.proxy_type || echo "none")
local enabled=$(uci -q get network-modes.router.proxy_enabled || echo 0)
[ "$proxy_type" = "tinyproxy" ] || return 0
[ "$enabled" = "1" ] || return 0
if ! command -v tinyproxy >/dev/null 2>&1; then
logger -t network-modes "tinyproxy binary not found"
return 1
fi
local proxy_port=$(uci -q get network-modes.router.proxy_port || echo 8888)
mkdir -p /etc/tinyproxy
cat > /etc/tinyproxy/tinyproxy.conf <<EOF
User nobody
Group nogroup
Port $proxy_port
Listen 0.0.0.0
Timeout 600
DefaultErrorFile "/usr/share/tinyproxy/default.html"
Logfile "/var/log/tinyproxy/tinyproxy.log"
StatFile "/usr/share/tinyproxy/stats.html"
MaxClients 100
MinSpareServers 5
MaxSpareServers 20
StartServers 10
Allow 192.168.0.0/16
Allow 10.0.0.0/8
Allow 172.16.0.0/12
ViaProxyName "SecuBox"
EOF
/etc/init.d/tinyproxy restart >/dev/null 2>&1 || true
}
generate_privoxy_config() {
local proxy_type=$(uci -q get network-modes.router.proxy_type || echo "none")
local enabled=$(uci -q get network-modes.router.proxy_enabled || echo 0)
[ "$proxy_type" = "privoxy" ] || return 0
[ "$enabled" = "1" ] || return 0
if ! command -v privoxy >/dev/null 2>&1; then
logger -t network-modes "privoxy binary not found"
return 1
fi
local proxy_port=$(uci -q get network-modes.router.proxy_port || echo 8118)
mkdir -p /etc/privoxy
cat > /etc/privoxy/config <<EOF
listen-address 0.0.0.0:$proxy_port
toggle 1
enable-remote-toggle 0
enable-remote-http-toggle 0
accept-intercepted-requests 1
forward-socks5 / 127.0.0.1:9050 .
logfile /var/log/privoxy/logfile
EOF
/etc/init.d/privoxy restart >/dev/null 2>&1 || true
}
apply_transparent_proxy_rules() {
local proxy_enabled=$(uci -q get network-modes.router.proxy_enabled || echo 0)
local proxy_port=$(uci -q get network-modes.router.proxy_port || echo 3128)
local transparent=$(uci -q get network-modes.router.proxy_transparent || echo 0)
uci -q delete firewall.proxy_redirect
if [ "$proxy_enabled" = "1" ] && [ "$transparent" = "1" ]; then
uci set firewall.proxy_redirect=redirect
uci set firewall.proxy_redirect.name='Transparent Web Proxy'
uci set firewall.proxy_redirect.src='lan'
uci set firewall.proxy_redirect.proto='tcp'
uci set firewall.proxy_redirect.src_dport='80'
uci set firewall.proxy_redirect.dest_port="$proxy_port"
uci set firewall.proxy_redirect.target='DNAT'
fi
uci commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1 || true
}
configure_doh_proxy() {
local doh_enabled=$(uci -q get network-modes.router.dns_over_https || echo 0)
[ "$doh_enabled" = "1" ] || return 0
if ! command -v https_dns_proxy >/dev/null 2>&1; then
logger -t network-modes "https_dns_proxy not installed"
return 1
fi
local provider=$(uci -q get network-modes.router.doh_provider || echo "cloudflare")
local doh_url
case "$provider" in
cloudflare) doh_url="https://1.1.1.1/dns-query" ;;
google) doh_url="https://dns.google/dns-query" ;;
quad9) doh_url="https://dns.quad9.net/dns-query" ;;
*) doh_url="https://1.1.1.1/dns-query" ;;
esac
uci set https-dns-proxy.@https-dns-proxy[0]=https-dns-proxy
uci set https-dns-proxy.@https-dns-proxy[0].resolver_url="$doh_url"
uci set https-dns-proxy.@https-dns-proxy[0].listen_addr='127.0.0.1'
uci set https-dns-proxy.@https-dns-proxy[0].listen_port='5053'
uci commit https-dns-proxy
uci set dhcp.@dnsmasq[0].noresolv='1'
uci -q delete dhcp.@dnsmasq[0].server
uci add_list dhcp.@dnsmasq[0].server="127.0.0.1#5053"
uci commit dhcp
/etc/init.d/https-dns-proxy restart >/dev/null 2>&1 || true
/etc/init.d/dnsmasq restart >/dev/null 2>&1 || true
}
apply_accesspoint_features() {
local iface="wireless.@wifi-iface[0]"
uci -q show "$iface" >/dev/null 2>&1 || return 0
local changed=0
local ft_enabled=$(uci -q get network-modes.accesspoint.roaming_enabled || echo 0)
if [ "$ft_enabled" = "1" ]; then
local ssid=$(uci -q get $iface.ssid || echo "SecuBox")
local mobility_domain=$(echo -n "$ssid" | md5sum 2>/dev/null | cut -c1-4)
[ -z "$mobility_domain" ] && mobility_domain="a1b2"
uci set $iface.ieee80211r='1'
uci set $iface.mobility_domain="$mobility_domain"
uci set $iface.ft_over_ds='1'
uci set $iface.ft_psk_generate_local='1'
uci set $iface.reassociation_deadline='1000'
changed=1
else
uci set $iface.ieee80211r='0'
uci -q delete $iface.mobility_domain
fi
local rrm_enabled=$(uci -q get network-modes.accesspoint.rrm_enabled || echo 0)
if [ "$rrm_enabled" = "1" ]; then
uci set $iface.ieee80211k='1'
uci set $iface.rrm_neighbor_report='1'
uci set $iface.rrm_beacon_report='1'
changed=1
else
uci set $iface.ieee80211k='0'
uci -q delete $iface.rrm_neighbor_report
uci -q delete $iface.rrm_beacon_report
fi
local v_enabled=$(uci -q get network-modes.accesspoint.wnm_enabled || echo 0)
if [ "$v_enabled" = "1" ]; then
uci set $iface.ieee80211v='1'
uci set $iface.bss_transition='1'
uci set $iface.wnm_sleep_mode='1'
changed=1
else
uci set $iface.ieee80211v='0'
uci -q delete $iface.bss_transition
uci -q delete $iface.wnm_sleep_mode
fi
local band=$(uci -q get network-modes.accesspoint.band_steering || echo 0)
if [ "$band" = "1" ]; then
local radio_2g=$(uci show wireless 2>/dev/null | grep "band='2g'" | head -n1)
local radio_5g=$(uci show wireless 2>/dev/null | grep "band='5g'" | head -n1)
if [ -n "$radio_2g" ] && [ -n "$radio_5g" ]; then
uci set $iface.bss_load_update_period='60'
uci set $iface.chan_util_avg_period='600'
uci set $iface.disassoc_low_ack='1'
changed=1
else
logger -t network-modes "band steering requested but dual-band radios missing"
fi
else
uci -q delete $iface.bss_load_update_period
uci -q delete $iface.chan_util_avg_period
uci -q delete $iface.disassoc_low_ack
fi
if [ "$changed" = "1" ]; then
uci commit wireless
wifi reload >/dev/null 2>&1 || true
fi
}
start_packet_capture() {
local capture_enabled=$(uci -q get network-modes.sniffer.pcap_capture || echo 0)
if [ "$capture_enabled" != "1" ]; then
stop_packet_capture
return 0
fi
if ! command -v tcpdump >/dev/null 2>&1; then
logger -t network-modes "tcpdump not installed"
return 1
fi
local capture_interface=$(uci -q get network-modes.sniffer.capture_interface || echo "br-lan")
local capture_filter=$(uci -q get network-modes.sniffer.capture_filter || echo "")
local max_size=$(uci -q get network-modes.sniffer.pcap_max_size || echo "100")
local rotate_count=$(uci -q get network-modes.sniffer.pcap_rotate || echo "10")
mkdir -p "$PCAP_DIR"
stop_packet_capture
if [ -n "$capture_filter" ]; then
tcpdump -i "$capture_interface" -w "$PCAP_DIR/capture.pcap" -C "$max_size" -W "$rotate_count" -s 0 $capture_filter >/dev/null 2>&1 &
else
tcpdump -i "$capture_interface" -w "$PCAP_DIR/capture.pcap" -C "$max_size" -W "$rotate_count" -s 0 >/dev/null 2>&1 &
fi
local pid=$!
echo "$pid" > /var/run/tcpdump-sniffer.pid
}
stop_packet_capture() {
if [ -f /var/run/tcpdump-sniffer.pid ]; then
local pid=$(cat /var/run/tcpdump-sniffer.pid 2>/dev/null)
[ -n "$pid" ] && kill "$pid" 2>/dev/null || true
rm -f /var/run/tcpdump-sniffer.pid
fi
killall tcpdump >/dev/null 2>&1 || true
}
validate_pcap_filter() {
read input
json_load "$input"
json_get_var filter filter
if [ -z "$filter" ]; then
json_init
json_add_boolean "valid" 1
json_add_string "filter" "all"
json_dump
return
fi
if command -v tcpdump >/dev/null 2>&1 && tcpdump -i any -d "$filter" >/dev/null 2>&1; then
json_init
json_add_boolean "valid" 1
json_add_string "filter" "$filter"
json_dump
else
json_init
json_add_boolean "valid" 0
json_add_string "error" "Invalid BPF syntax"
json_dump
fi
}
cleanup_old_pcaps() {
local max_age=$(uci -q get network-modes.sniffer.pcap_retention_days || echo "7")
mkdir -p "$PCAP_DIR"
local deleted=0
find "$PCAP_DIR" -name "*.pcap*" -mtime +$max_age -type f 2>/dev/null | while read -r file; do
rm -f "$file"
deleted=$((deleted + 1))
done
local total_size=$(du -sm "$PCAP_DIR" 2>/dev/null | cut -f1 || echo "0")
json_init
json_add_boolean "success" 1
json_add_int "deleted" "$deleted"
json_add_int "total_size_mb" "$total_size"
json_dump
}
deploy_nginx_vhosts() {
local frontend_enabled=$(uci -q get network-modes.router.https_frontend || echo 0)
local frontend_type=$(uci -q get network-modes.router.frontend_type || echo nginx)
[ "$frontend_enabled" = "1" ] || return 0
[ "$frontend_type" = "nginx" ] || return 0
if ! command -v nginx >/dev/null 2>&1; then
logger -t network-modes "nginx not installed"
return 1
fi
rm -f /etc/nginx/conf.d/vhost-*.conf 2>/dev/null || true
mkdir -p /etc/nginx/conf.d /etc/acme
local idx=0
local deployed=0
while true; do
local domain=$(uci -q get network-modes.@vhost[$idx].domain)
[ -n "$domain" ] || break
local backend=$(uci -q get network-modes.@vhost[$idx].backend || echo "127.0.0.1")
local port=$(uci -q get network-modes.@vhost[$idx].port || echo 80)
local ssl=$(uci -q get network-modes.@vhost[$idx].ssl || echo 0)
cat > "/etc/nginx/conf.d/vhost-$idx.conf" <<EOF
server {
listen 80;
server_name $domain;
EOF
if [ "$ssl" = "1" ]; then
cat >> "/etc/nginx/conf.d/vhost-$idx.conf" <<EOF
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl http2;
server_name $domain;
ssl_certificate /etc/acme/$domain/fullchain.cer;
ssl_certificate_key /etc/acme/$domain/$domain.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://$backend:$port;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF
else
cat >> "/etc/nginx/conf.d/vhost-$idx.conf" <<EOF
location / {
proxy_pass http://$backend:$port;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF
fi
deployed=$((deployed + 1))
idx=$((idx + 1))
done
/etc/init.d/nginx reload >/dev/null 2>&1 || true
logger -t network-modes "Deployed $deployed nginx vhosts"
}
issue_letsencrypt_cert() {
local domain="$1"
[ -n "$domain" ] || return 1
local acme_email=$(uci -q get network-modes.router.acme_email)
[ -n "$acme_email" ] || acme_email="admin@$domain"
if ! command -v /usr/lib/acme/acme.sh >/dev/null 2>&1; then
opkg update >/dev/null 2>&1 || true
opkg install acme acme-dnsapi >/dev/null 2>&1 || true
fi
/usr/lib/acme/acme.sh --issue -d "$domain" --webroot /www --accountemail "$acme_email" --force >/tmp/acme-issue.log 2>&1
if [ $? -eq 0 ]; then
mkdir -p /etc/acme/$domain
/usr/lib/acme/acme.sh --install-cert \
-d "$domain" \
--cert-file /etc/acme/$domain/cert.cer \
--key-file /etc/acme/$domain/$domain.key \
--fullchain-file /etc/acme/$domain/fullchain.cer \
--reloadcmd "/etc/init.d/nginx reload" >/dev/null 2>&1
return 0
else
logger -t network-modes "ACME issue failed for $domain"
return 1
fi
}
validate_ssl_cert() {
local domain="$1"
local cert_file="/etc/acme/$domain/fullchain.cer"
[ -f "$cert_file" ] || return 1
command -v openssl >/dev/null 2>&1 || return 1
local expiry_date=$(openssl x509 -enddate -noout -in "$cert_file" | cut -d= -f2)
local expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null || echo 0)
local now_epoch=$(date +%s)
[ "$expiry_epoch" -gt 0 ] || return 1
local days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
logger -t network-modes "Cert $domain valid $days_left days"
[ "$days_left" -gt 0 ]
}
# Generate WireGuard key pair and store in UCI
generate_wireguard_keys() {
json_init
if ! command -v wg >/dev/null 2>&1; then
json_add_boolean "success" 0
json_add_string "error" "wireguard-tools not installed"
json_dump
return
fi
local privkey="$(wg genkey 2>/dev/null)"
if [ -z "$privkey" ]; then
json_add_boolean "success" 0
json_add_string "error" "failed to generate private key"
json_dump
return
fi
local pubkey
pubkey="$(printf '%s' "$privkey" | wg pubkey 2>/dev/null)"
if [ -z "$pubkey" ]; then
json_add_boolean "success" 0
json_add_string "error" "failed to derive public key"
json_dump
return
fi
uci set network-modes.relay.wg_private_key="$privkey"
uci set network-modes.relay.wg_public_key="$pubkey"
uci commit network-modes
json_add_boolean "success" 1
json_add_string "private_key" "$privkey"
json_add_string "public_key" "$pubkey"
json_dump
}
# Deploy WireGuard interface/peer config to /etc/config/network
apply_wireguard_config() {
local wg_enabled=$(uci -q get network-modes.relay.wireguard_enabled || echo "0")
[ "$wg_enabled" = "1" ] || return 0
local privkey=$(uci -q get network-modes.relay.wg_private_key)
local peer_pubkey=$(uci -q get network-modes.relay.wg_peer_pubkey)
local peer_endpoint=$(uci -q get network-modes.relay.wg_peer_endpoint)
local wg_port=$(uci -q get network-modes.relay.wg_port || echo "51820")
local wg_ip=$(uci -q get network-modes.relay.wg_ip || echo "10.200.200.2/24")
[ -n "$privkey" ] || return 1
[ -n "$peer_pubkey" ] || return 1
[ -n "$peer_endpoint" ] || return 1
uci -q delete network.wg0
uci -q delete network.wg0_peer
uci set network.wg0=interface
uci set network.wg0.proto='wireguard'
uci set network.wg0.private_key="$privkey"
uci set network.wg0.listen_port="$wg_port"
uci add_list network.wg0.addresses="$wg_ip"
uci set network.wg0_peer=wireguard_wg0
uci set network.wg0_peer.public_key="$peer_pubkey"
uci set network.wg0_peer.endpoint_host="$(echo "$peer_endpoint" | cut -d: -f1)"
uci set network.wg0_peer.endpoint_port="$(echo "$peer_endpoint" | cut -d: -f2-)"
uci set network.wg0_peer.persistent_keepalive='25'
uci add_list network.wg0_peer.allowed_ips='0.0.0.0/0'
uci commit network
/etc/init.d/network reload >/dev/null 2>&1
}
# Configure firewall MSS clamping based on configured MTU
apply_mtu_clamping() {
local mtu_optimization=$(uci -q get network-modes.relay.mtu_optimization || echo "0")
[ "$mtu_optimization" = "1" ] || return 0
local wg_mtu=$(uci -q get network-modes.relay.wg_mtu || echo "1420")
local mss_value=$((wg_mtu - 40))
if [ "$mss_value" -lt 500 ]; then
mss_value=500
fi
uci -q delete firewall.mss_clamping
uci set firewall.mss_clamping=rule
uci set firewall.mss_clamping.name='WireGuard MSS Clamping'
uci set firewall.mss_clamping.src='lan'
uci set firewall.mss_clamping.dest='wan'
uci set firewall.mss_clamping.proto='tcp'
uci set firewall.mss_clamping.tcp_flags='SYN'
uci set firewall.mss_clamping.target='TCPMSS'
uci set firewall.mss_clamping.set_mss="$mss_value"
uci commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1
}
# Enable TCP BBR congestion control if requested
enable_tcp_bbr() {
local tcp_optimize=$(uci -q get network-modes.relay.tcp_optimization || echo "0")
[ "$tcp_optimize" = "1" ] || return 0
if ! modprobe tcp_bbr 2>/dev/null; then
logger -t network-modes "tcp_bbr module unavailable"
return 1
fi
cat > /etc/sysctl.d/90-tcp-bbr.conf <<EOF
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
EOF
sysctl -p /etc/sysctl.d/90-tcp-bbr.conf >/dev/null 2>&1
}
# Add virtual host
add_vhost() {
read input
json_load "$input"
json_get_var domain domain
json_get_var backend backend
json_get_var port port
json_get_var ssl ssl
json_init
if [ -z "$domain" ] || [ -z "$backend" ]; then
json_add_boolean "success" 0
json_add_string "error" "Domain and backend are required"
json_dump
return
fi
uci add network-modes vhost
uci set network-modes.@vhost[-1].domain="$domain"
uci set network-modes.@vhost[-1].backend="$backend"
uci set network-modes.@vhost[-1].port="${port:-80}"
uci set network-modes.@vhost[-1].ssl="${ssl:-0}"
uci commit network-modes
json_add_boolean "success" 1
json_add_string "message" "Virtual host $domain added"
json_dump
}
# Generate config for current mode
generate_config() {
read input
json_load "$input"
json_get_var mode mode
json_init
local config=""
case "$mode" in
sniffer)
local bridge=$(uci -q get network-modes.sniffer.bridge_interface || echo "br-lan")
config="# Sniffer Mode Configuration
# /etc/config/network
config interface 'loopback'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'
option device 'lo'
config device
option name '$bridge'
option type 'bridge'
list ports 'eth0'
list ports 'eth1'
config interface 'lan'
option device '$bridge'
option proto 'none'
# Disable DHCP
# /etc/config/dhcp
config dhcp 'lan'
option ignore '1'
# Enable promiscuous mode
# Run: ip link set eth0 promisc on
# Run: ip link set eth1 promisc on"
;;
accesspoint)
local channel=$(uci -q get network-modes.accesspoint.wifi_channel || echo "auto")
local htmode=$(uci -q get network-modes.accesspoint.wifi_htmode || echo "VHT80")
local txpower=$(uci -q get network-modes.accesspoint.wifi_txpower || echo "20")
config="# Access Point Mode Configuration
# /etc/config/network
config interface 'lan'
option proto 'dhcp'
option device 'eth0'
# /etc/config/wireless
config wifi-device 'radio0'
option type 'mac80211'
option channel '$channel'
option htmode '$htmode'
option txpower '$txpower'
option country 'FR'
config wifi-iface 'default_radio0'
option device 'radio0'
option network 'lan'
option mode 'ap'
option ssid 'OpenWrt-AP'
option encryption 'sae-mixed'
option key 'your_password'
option ieee80211r '1'
option ft_over_ds '1'
option ieee80211k '1'
option ieee80211v '1'
option bss_transition '1'"
;;
relay)
local wg_iface=$(uci -q get network-modes.relay.wireguard_interface || echo "wg0")
config="# Relay Mode Configuration
# /etc/config/network
config interface 'lan'
option proto 'static'
option ipaddr '192.168.1.1'
option netmask '255.255.255.0'
option device 'br-lan'
config interface 'wwan'
option proto 'dhcp'
option device 'wlan0'
config interface '$wg_iface'
option proto 'wireguard'
option listen_port '51820'
list addresses '10.0.0.1/24'
# /etc/config/firewall - MSS clamping
config rule
option name 'MSS-Clamping'
option src '*'
option dest '*'
option proto 'tcp'
option extra '-m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu'"
;;
router)
local wan_proto=$(uci -q get network-modes.router.wan_protocol || echo "dhcp")
config="# Router Mode Configuration
# /etc/config/network
config interface 'wan'
option proto '$wan_proto'
option device 'eth1'
config interface 'lan'
option proto 'static'
option device 'br-lan'
option ipaddr '192.168.1.1'
option netmask '255.255.255.0'
# /etc/config/firewall
config defaults
option syn_flood '1'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'REJECT'
config zone
option name 'wan'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'
option mtu_fix '1'
list network 'wan'
config zone
option name 'lan'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'ACCEPT'
list network 'lan'
config forwarding
option src 'lan'
option dest 'wan'"
;;
travel)
local travel_ssid=$(uci -q get network-modes.travel.ssid || echo "HotelWiFi")
local hotspot_ssid=$(uci -q get network-modes.travel.hotspot_ssid || echo "SecuBox-Travel")
local lan_ip=$(uci -q get network-modes.travel.lan_subnet || echo "10.77.0.1")
config="# Travel Router Mode
# /etc/config/network
config interface 'wan'
option proto 'dhcp'
option device 'wlan-sta'
config interface 'lan'
option proto 'static'
option ipaddr '$lan_ip'
option netmask '255.255.255.0'
option device 'br-lan'
# /etc/config/wireless
config wifi-iface 'travel_sta'
option device 'radio1'
option mode 'sta'
option network 'wan'
option ssid '$travel_ssid'
option encryption 'sae-mixed'
option key 'hotel-password'
config wifi-iface 'travel_ap'
option device 'radio0'
option mode 'ap'
option network 'lan'
option ssid '$hotspot_ssid'
option encryption 'sae-mixed'
option key 'TravelSafe123!'
# /etc/config/firewall
config zone
option name 'wan'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'"
;;
doublenat)
local lan_ip=$(uci -q get network-modes.doublenat.lan_ip || echo "10.10.0.1")
local lan_netmask=$(uci -q get network-modes.doublenat.lan_netmask || echo "255.255.255.0")
local guest=$(uci -q get network-modes.doublenat.guest_network || echo "br-guest")
config="# Double NAT mode
# /etc/config/network
config interface 'wan'
option proto 'dhcp'
option device 'eth1'
config interface 'lan'
option proto 'static'
option ipaddr '$lan_ip'
option netmask '$lan_netmask'
option device 'br-lan'
config interface '$guest'
option proto 'static'
option ipaddr '172.16.0.1'
option netmask '255.255.255.0'
# /etc/config/firewall
config zone
option name 'lan'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'REJECT'
list network 'lan'
config zone
option name 'guest'
option input 'REJECT'
option output 'ACCEPT'
option forward 'REJECT'
list network '$guest'
config forwarding
option src 'lan'
option dest 'wan'"
;;
multiwan)
local primary=$(uci -q get network-modes.multiwan.wan_primary || echo "eth1")
local secondary=$(uci -q get network-modes.multiwan.wan_secondary || echo "wwan0")
local host=$(uci -q get network-modes.multiwan.tracking_host || echo "8.8.8.8")
config="# Multi-WAN (mwan3) snippet
# /etc/config/mwan3
config interface '$primary'
option enabled '1'
option reliability '1'
option initial_state 'online'
option track_method 'ping'
list track_ip '$host'
config interface '$secondary'
option enabled '1'
option reliability '1'
option initial_state 'online'
option timeout '5'
option track_method 'ping'
list track_ip '$host'
config member 'member_primary'
option interface '$primary'
option metric '1'
option weight '3'
config member 'member_secondary'
option interface '$secondary'
option metric '2'
option weight '1'
config policy 'balanced'
list use_member 'member_primary'
list use_member 'member_secondary'
config policy 'failover'
list use_member 'member_primary'
list use_member 'member_secondary'
option last_resort 'unreachable'"
;;
vpnrelay)
local vpn_type=$(uci -q get network-modes.vpnrelay.vpn_type || echo "wireguard")
local wg_iface=$(uci -q get network-modes.vpnrelay.wg_interface || echo "wg0")
config="# VPN Relay example
# /etc/config/network
config interface '$wg_iface'
option proto 'wireguard'
option private_key '<private>'
list addresses '10.8.0.2/32'
config wireguard_$wg_iface
option public_key '<server_pub>'
option endpoint_host 'vpn.example.com'
option endpoint_port '51820'
option persistent_keepalive '25'
list allowed_ips '0.0.0.0/0'
# /etc/config/firewall
config zone
option name 'vpn'
option input 'ACCEPT'
option output 'ACCEPT'
option forward 'REJECT'
option masq '1'
list network '$wg_iface'
config forwarding
option src 'lan'
option dest 'vpn'
config rule
option name 'Kill-Switch'
option src 'lan'
option dest 'wan'
option proto 'all'
option target 'REJECT'
option enabled '1'
# Force DNS over VPN
uci set dhcp.@dnsmasq[0].server='10.8.0.1'
uci commit dhcp && /etc/init.d/dnsmasq restart"
;;
esac
json_add_string "config" "$config"
json_add_string "mode" "$mode"
json_dump
}
# Get current mode details
get_current_mode() {
json_init
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
local last_change=$(uci -q get network-modes.config.last_change || echo "Never")
local pending_mode=$(uci -q get network-modes.config.pending_mode || echo "")
local rollback_timer=$(uci -q get network-modes.config.rollback_timer || echo "0")
json_add_string "current_mode" "$current_mode"
json_add_string "mode_name" "$(uci -q get network-modes.$current_mode.name || echo "$current_mode")"
json_add_string "description" "$(uci -q get network-modes.$current_mode.description || echo "")"
json_add_string "last_change" "$last_change"
json_add_string "pending_mode" "$pending_mode"
json_add_int "rollback_timer" "$rollback_timer"
# Check if rollback is active
if [ -f "/tmp/network-mode-rollback.pid" ]; then
json_add_boolean "rollback_active" 1
local remaining=$(cat /tmp/network-mode-rollback.remaining 2>/dev/null || echo "0")
json_add_int "rollback_remaining" "$remaining"
else
json_add_boolean "rollback_active" 0
json_add_int "rollback_remaining" 0
fi
json_dump
}
# Get available modes
get_available_modes() {
json_init
json_add_array "modes"
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
add_mode() {
local id="$1"; shift
local name="$1"; shift
local icon="$1"; shift
local desc="$1"; shift
json_add_object
json_add_string "id" "$id"
json_add_string "name" "$name"
json_add_string "description" "$desc"
json_add_string "icon" "$icon"
json_add_boolean "current" "$([ "$current_mode" = "$id" ] && echo 1 || echo 0)"
json_add_array "features"
for feature in "$@"; do
json_add_string "" "$feature"
done
json_close_array
json_close_object
}
add_mode "router" "Router" "🌐" "Mode routeur complet avec NAT, DHCP et firewall" \
"NAT activé" "Serveur DHCP" "Firewall (zones WAN/LAN)" "Proxy optionnel"
add_mode "doublenat" "Double NAT" "🔁" "Derrière box opérateur avec réseau isolé" \
"WAN DHCP client" "Deuxième LAN privé" "Isolation invitée" "UPnP désactivé"
add_mode "multiwan" "Multi-WAN" "⚡" "Agrégation et bascule multi-liens WAN" \
"Double WAN" "Suivi de santé" "Équilibrage de charge" "Failover automatique"
add_mode "accesspoint" "Access Point" "📶" "Point d'accès WiFi en mode bridge" \
"Bridge WAN+LAN" "Pas de DHCP" "Pas de firewall" "Optimisations WiFi"
add_mode "relay" "Repeater" "🔄" "Client WiFi + Répéteur AP" \
"Client WiFi (sta0)" "AP répéteur (ap0)" "WireGuard optionnel" "Optimisations MTU/MSS"
add_mode "vpnrelay" "VPN Relay" "🛡️" "Tunnels WireGuard/OpenVPN avec policy routing" \
"Policy routing" "Kill-switch" "DNS sécurisé" "Split tunnel"
add_mode "travel" "Travel Router" "✈️" "Hotspot personnel depuis WiFi/ethernet hôtel" \
"Client WiFi + scan" "Clone MAC WAN" "Hotspot WPA3" "NAT + DHCP isolé"
add_mode "bridge" "Bridge" "🔗" "Bridge Layer 2 pur, DHCP client" \
"Bridge transparent L2" "Toutes interfaces bridgées" "DHCP client" "Pas de firewall"
json_close_array
json_dump
}
# Set mode (prepare for switch)
set_mode() {
read input
json_load "$input"
json_get_var target_mode mode
json_init
# Validate mode
case "$target_mode" in
router|multiwan|doublenat|accesspoint|relay|vpnrelay|bridge|sniffer|travel)
;;
*)
json_add_boolean "success" 0
json_add_string "error" "Mode invalide: $target_mode"
json_dump
return
;;
esac
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
if [ "$current_mode" = "$target_mode" ]; then
json_add_boolean "success" 0
json_add_string "error" "Déjà en mode $target_mode"
json_dump
return
fi
# Store pending mode
uci set network-modes.config.pending_mode="$target_mode"
uci set network-modes.config.pending_since="$(date '+%Y-%m-%d %H:%M:%S')"
uci commit network-modes
json_add_boolean "success" 1
json_add_string "current_mode" "$current_mode"
json_add_string "target_mode" "$target_mode"
json_add_string "message" "Mode $target_mode préparé. Utilisez preview_changes puis apply_mode."
json_dump
}
# Preview changes before applying
preview_changes() {
json_init
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
local pending_mode=$(uci -q get network-modes.config.pending_mode || echo "")
if [ -z "$pending_mode" ]; then
json_add_boolean "success" 0
json_add_string "error" "Aucun changement de mode en attente"
json_dump
return
fi
json_add_boolean "success" 1
json_add_string "current_mode" "$current_mode"
json_add_string "target_mode" "$pending_mode"
# Changes array
json_add_array "changes"
case "$pending_mode" in
router)
json_add_object
json_add_string "file" "/etc/config/network"
json_add_string "change" "WAN: proto dhcp, NAT enabled"
json_close_object
json_add_object
json_add_string "file" "/etc/config/network"
json_add_string "change" "LAN: static IP, DHCP server enabled"
json_close_object
json_add_object
json_add_string "file" "/etc/config/firewall"
json_add_string "change" "Zones: WAN, LAN, forwarding rules"
json_close_object
;;
accesspoint)
json_add_object
json_add_string "file" "/etc/config/network"
json_add_string "change" "Bridge: br-lan (WAN+LAN)"
json_close_object
json_add_object
json_add_string "file" "/etc/config/network"
json_add_string "change" "DHCP: client mode"
json_close_object
json_add_object
json_add_string "file" "/etc/config/firewall"
json_add_string "change" "Firewall: disabled"
json_close_object
;;
relay)
json_add_object
json_add_string "file" "/etc/config/wireless"
json_add_string "change" "WiFi STA: client mode on wlan0"
json_close_object
json_add_object
json_add_string "file" "/etc/config/wireless"
json_add_string "change" "WiFi AP: repeater mode on wlan1"
json_close_object
json_add_object
json_add_string "file" "/etc/config/network"
json_add_string "change" "Relay: relayd between sta0 and ap0"
json_close_object
;;
bridge)
json_add_object
json_add_string "file" "/etc/config/network"
json_add_string "change" "Bridge: all interfaces to br-lan"
json_close_object
json_add_object
json_add_string "file" "/etc/config/network"
json_add_string "change" "DHCP: client only"
json_close_object
json_add_object
json_add_string "file" "/etc/config/firewall"
json_add_string "change" "Firewall: disabled"
json_close_object
;;
esac
json_close_array
# Warnings
json_add_array "warnings"
json_add_string "" "La connexion réseau sera interrompue pendant la reconfiguration"
json_add_string "" "Vous devrez peut-être reconnecter via la nouvelle IP"
json_add_string "" "Un rollback automatique de 2 minutes sera activé"
json_add_string "" "Vous devez confirmer le changement avant expiration"
json_close_array
json_dump
}
# Multi-WAN configuration
get_multiwan_config() {
json_init
json_add_string "mode" "multiwan"
json_add_string "name" "$(uci -q get network-modes.multiwan.name || echo 'Multi-WAN Controller')"
json_add_string "description" "$(uci -q get network-modes.multiwan.description || echo 'Dual-WAN bonding with health tracking')"
json_add_object "policy"
json_add_string "profile" "$(uci -q get network-modes.multiwan.policy || echo 'balanced')"
json_add_boolean "load_balance" "$(uci -q get network-modes.multiwan.load_balance || echo 1)"
json_add_string "tracking_host" "$(uci -q get network-modes.multiwan.tracking_host || echo '8.8.8.8')"
json_add_int "tracking_interval" "$(uci -q get network-modes.multiwan.tracking_interval || echo 30)"
json_add_int "failover_hold" "$(uci -q get network-modes.multiwan.failover_hold || echo 45)"
json_close_object
json_add_object "links"
json_add_string "primary" "$(uci -q get network-modes.multiwan.wan_primary || echo 'eth1')"
json_add_string "secondary" "$(uci -q get network-modes.multiwan.wan_secondary || echo 'wwan0')"
json_add_boolean "mwan3_enabled" "$(uci -q get network-modes.multiwan.use_mwan3 || echo 0)"
json_close_object
json_add_array "wan_candidates"
for iface in $(ls /sys/class/net 2>/dev/null); do
[ "$iface" = "lo" ] && continue
case "$iface" in
br-*|ifb*|gre*|ip6*|mon*) continue ;;
esac
local operstate="$(cat /sys/class/net/$iface/operstate 2>/dev/null)"
local mac="$(cat /sys/class/net/$iface/address 2>/dev/null)"
json_add_object
json_add_string "name" "$iface"
json_add_boolean "up" "$([ "$operstate" = "up" ] && echo 1 || echo 0)"
json_add_string "mac" "$mac"
json_close_object
done
json_close_array
json_dump
}
# VPN relay configuration
get_vpnrelay_config() {
json_init
json_add_string "mode" "vpnrelay"
json_add_string "name" "$(uci -q get network-modes.vpnrelay.name || echo 'VPN Relay')"
json_add_string "description" "$(uci -q get network-modes.vpnrelay.description || echo 'Route LAN traffic through VPN tunnels with policy routing')"
json_add_object "vpn"
json_add_string "type" "$(uci -q get network-modes.vpnrelay.vpn_type || echo 'wireguard')"
json_add_string "provider" "$(uci -q get network-modes.vpnrelay.vpn_provider || echo 'Mullvad')"
json_add_string "wg_interface" "$(uci -q get network-modes.vpnrelay.wg_interface || echo 'wg0')"
json_add_string "openvpn_profile" "$(uci -q get network-modes.vpnrelay.openvpn_profile || echo '')"
json_add_string "upstream_interface" "$(uci -q get network-modes.vpnrelay.upstream_interface || echo 'wan')"
json_close_object
json_add_object "policy"
json_add_boolean "policy_routing" "$(uci -q get network-modes.vpnrelay.policy_routing || echo 1)"
json_add_boolean "dns_override" "$(uci -q get network-modes.vpnrelay.dns_override || echo 1)"
json_add_boolean "kill_switch" "$(uci -q get network-modes.vpnrelay.kill_switch || echo 1)"
json_add_boolean "lan_bypass" "$(uci -q get network-modes.vpnrelay.lan_bypass || echo 0)"
json_close_object
json_add_array "wireguard_interfaces"
if command -v wg >/dev/null 2>&1; then
for iface in $(wg show interfaces 2>/dev/null); do
json_add_string "" "$iface"
done
fi
json_close_array
json_add_array "openvpn_profiles"
for profile in /etc/openvpn/*.ovpn /etc/openvpn/*.conf; do
[ -f "$profile" ] || continue
json_add_string "" "$(basename "$profile")"
done
json_close_array
json_add_array "available_interfaces"
for iface in $(ls /sys/class/net 2>/dev/null); do
[ "$iface" = "lo" ] && continue
json_add_string "" "$iface"
done
json_close_array
json_dump
}
# Double NAT configuration
get_doublenat_config() {
json_init
local wan_iface=$(uci -q get network-modes.doublenat.wan_interface || echo "eth1")
local wan_proto=$(uci -q get network-modes.doublenat.wan_protocol || echo "dhcp")
local lan_iface=$(uci -q get network-modes.doublenat.lan_interface || echo "br-lan")
local lan_ip=$(uci -q get network-modes.doublenat.lan_ip || echo "10.10.0.1")
local lan_netmask=$(uci -q get network-modes.doublenat.lan_netmask || echo "255.255.255.0")
json_add_string "mode" "doublenat"
json_add_string "name" "$(uci -q get network-modes.doublenat.name || echo 'Double NAT')"
json_add_string "description" "$(uci -q get network-modes.doublenat.description || echo 'Operate behind an ISP router with isolated LAN and guest network')"
json_add_object "wan"
json_add_string "interface" "$wan_iface"
json_add_string "protocol" "$wan_proto"
json_close_object
json_add_object "lan"
json_add_string "interface" "$lan_iface"
json_add_string "ipaddr" "$lan_ip"
json_add_string "netmask" "$lan_netmask"
json_close_object
json_add_boolean "upnp_enabled" "$(uci -q get network-modes.doublenat.upnp_enabled || echo 0)"
json_add_string "guest_network" "$(uci -q get network-modes.doublenat.guest_network || echo 'br-guest')"
json_add_boolean "isolate_guest" "$(uci -q get network-modes.doublenat.isolate_guest || echo 1)"
json_add_string "dmz_host" "$(uci -q get network-modes.doublenat.dmz_host || echo '')"
json_dump
}
# Confirm mode change (cancel rollback timer)
confirm_mode() {
json_init
local current_mode=$(uci -q get network-modes.config.current_mode || echo "router")
# Stop rollback timer
if [ -f "/tmp/network-mode-rollback.pid" ]; then
local pid=$(cat /tmp/network-mode-rollback.pid)
kill $pid 2>/dev/null
rm -f /tmp/network-mode-rollback.pid
rm -f /tmp/network-mode-rollback.remaining
# Clear pending mode
uci delete network-modes.config.pending_mode 2>/dev/null
uci delete network-modes.config.pending_since 2>/dev/null
uci set network-modes.config.rollback_timer="0"
uci commit network-modes
json_add_boolean "success" 1
json_add_string "message" "Mode $current_mode confirmé, rollback annulé"
json_add_string "mode" "$current_mode"
else
json_add_boolean "success" 0
json_add_string "error" "Aucun rollback actif"
fi
json_dump
}
# Rollback to previous mode
rollback() {
json_init
# Stop any active rollback timer
if [ -f "/tmp/network-mode-rollback.pid" ]; then
local pid=$(cat /tmp/network-mode-rollback.pid)
kill $pid 2>/dev/null
rm -f /tmp/network-mode-rollback.pid
rm -f /tmp/network-mode-rollback.remaining
fi
# Get backup file
local latest_backup=$(ls -t "$BACKUP_DIR"/backup_*.tar.gz 2>/dev/null | head -1)
if [ -z "$latest_backup" ]; then
json_add_boolean "success" 0
json_add_string "error" "Aucune sauvegarde disponible"
json_dump
return
fi
# Restore backup
cd /
tar -xzf "$latest_backup" 2>/dev/null
# Reload network services
/etc/init.d/network reload 2>&1
/etc/init.d/firewall reload 2>&1
/etc/init.d/dnsmasq reload 2>&1
# Update UCI
local previous_mode=$(uci -q get network-modes.config.current_mode || echo "router")
uci delete network-modes.config.pending_mode 2>/dev/null
uci set network-modes.config.last_change="$(date '+%Y-%m-%d %H:%M:%S') (rollback)"
uci commit network-modes
json_add_boolean "success" 1
json_add_string "message" "Configuration restaurée depuis la sauvegarde"
json_add_string "mode" "$previous_mode"
json_add_string "backup_file" "$latest_backup"
json_dump
}
# Main dispatcher
case "$1" in
list)
echo '{"status":{},"modes":{},"get_current_mode":{},"get_available_modes":{},"set_mode":{"mode":"str"},"preview_changes":{},"apply_mode":{},"confirm_mode":{},"rollback":{},"sniffer_config":{},"ap_config":{},"relay_config":{},"router_config":{},"travel_config":{},"doublenat_config":{},"multiwan_config":{},"vpnrelay_config":{},"travel_scan_networks":{},"update_settings":{"mode":"str"},"generate_wireguard_keys":{},"apply_wireguard_config":{},"apply_mtu_clamping":{},"enable_tcp_bbr":{},"add_vhost":{"domain":"str","backend":"str","port":"int","ssl":"bool"},"generate_config":{"mode":"str"},"validate_pcap_filter":{"filter":"str"},"cleanup_old_pcaps":{}}'
;;
call)
case "$2" in
status)
get_status
;;
modes)
get_modes
;;
get_current_mode)
get_current_mode
;;
get_available_modes)
get_available_modes
;;
set_mode)
set_mode
;;
preview_changes)
preview_changes
;;
apply_mode)
apply_mode
;;
confirm_mode)
confirm_mode
;;
rollback)
rollback
;;
sniffer_config)
get_sniffer_config
;;
ap_config)
get_ap_config
;;
relay_config)
get_relay_config
;;
router_config)
get_router_config
;;
travel_config)
get_travel_config
;;
doublenat_config)
get_doublenat_config
;;
multiwan_config)
get_multiwan_config
;;
vpnrelay_config)
get_vpnrelay_config
;;
travel_scan_networks)
travel_scan_networks
;;
update_settings)
update_settings
;;
generate_wireguard_keys)
generate_wireguard_keys
;;
apply_wireguard_config)
if apply_wireguard_config; then
json_init; json_add_boolean "success" 1; json_dump
else
json_init; json_add_boolean "success" 0; json_add_string "error" "WireGuard deployment failed"; json_dump
fi
;;
apply_mtu_clamping)
if apply_mtu_clamping; then
json_init; json_add_boolean "success" 1; json_dump
else
json_init; json_add_boolean "success" 0; json_add_string "error" "Unable to update firewall rule"; json_dump
fi
;;
enable_tcp_bbr)
if enable_tcp_bbr; then
json_init; json_add_boolean "success" 1; json_dump
else
json_init; json_add_boolean "success" 0; json_add_string "error" "TCP BBR not applied"; json_dump
fi
;;
add_vhost)
add_vhost
;;
generate_config)
generate_config
;;
validate_pcap_filter)
validate_pcap_filter
;;
cleanup_old_pcaps)
cleanup_old_pcaps
;;
*)
echo '{"error": "Unknown method"}'
;;
esac
;;
esac