#!/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" is_module_enabled() { local enabled enabled=$(uci -q get secubox.network_modes.enabled) [ -z "$enabled" ] && return 0 [ "$enabled" = "1" ] && return 0 return 1 } # 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_dmz_config() { json_init json_add_string "mode" "dmz" json_add_string "name" "Router + DMZ" json_add_string "description" "LAN avec NAT + segment DMZ isolé" local wan_iface=$(uci -q get network-modes.dmz.wan_interface || echo "eth1") local wan_proto=$(uci -q get network-modes.dmz.wan_protocol || echo "dhcp") local lan_iface=$(uci -q get network-modes.dmz.lan_interface || echo "br-lan") local lan_ip=$(uci -q get network-modes.dmz.lan_ip || echo "192.168.1.1") local lan_netmask=$(uci -q get network-modes.dmz.lan_netmask || echo "255.255.255.0") local dmz_iface=$(uci -q get network-modes.dmz.dmz_interface || echo "eth2") local dmz_ip=$(uci -q get network-modes.dmz.dmz_ip || echo "192.168.50.1") local dmz_netmask=$(uci -q get network-modes.dmz.dmz_netmask || echo "255.255.255.0") local dmz_start=$(uci -q get network-modes.dmz.dmz_dhcp_start || echo "50") local dmz_limit=$(uci -q get network-modes.dmz.dmz_dhcp_limit || echo "80") local dmz_isolated=$(uci -q get network-modes.dmz.dmz_isolated || echo "1") json_add_object "wan" json_add_string "interface" "$wan_iface" json_add_string "protocol" "$wan_proto" json_add_array "protocols" for proto in dhcp static pppoe; do json_add_string "" "$proto" done json_close_array json_close_object json_add_object "lan" json_add_string "interface" "$lan_iface" json_add_string "ip_address" "$lan_ip" json_add_string "netmask" "$lan_netmask" json_close_object json_add_object "dmz" json_add_string "interface" "$dmz_iface" json_add_string "ip_address" "$dmz_ip" json_add_string "netmask" "$dmz_netmask" json_add_int "dhcp_start" "$dmz_start" json_add_int "dhcp_limit" "$dmz_limit" json_add_boolean "isolated" "$([ "$dmz_isolated" = "1" ] && echo 1 || echo 0)" json_close_object json_add_array "available_interfaces" for iface in $(ls /sys/class/net/ | grep -v "^lo$"); do json_add_string "" "$iface" done 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 </dev/null uci set network-modes.config.last_backup="$backup_file" uci set network-modes.config.last_backup_time="$(date '+%Y-%m-%d %H:%M:%S')" # 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 ;; dmz) local dmz_wan=$(uci -q get network-modes.dmz.wan_interface || echo "eth1") local dmz_wan_proto=$(uci -q get network-modes.dmz.wan_protocol || echo "dhcp") local dmz_lan_iface=$(uci -q get network-modes.dmz.lan_interface || echo "br-lan") local dmz_lan_ip=$(uci -q get network-modes.dmz.lan_ip || echo "192.168.1.1") local dmz_lan_netmask=$(uci -q get network-modes.dmz.lan_netmask || echo "255.255.255.0") local dmz_iface=$(uci -q get network-modes.dmz.dmz_interface || echo "eth2") local dmz_ip=$(uci -q get network-modes.dmz.dmz_ip || echo "192.168.50.1") local dmz_netmask=$(uci -q get network-modes.dmz.dmz_netmask || echo "255.255.255.0") local dmz_start=$(uci -q get network-modes.dmz.dmz_dhcp_start || echo "50") local dmz_limit=$(uci -q get network-modes.dmz.dmz_dhcp_limit || echo "80") local dmz_isolated_flag=$(uci -q get network-modes.dmz.dmz_isolated || echo "1") uci delete network.wan 2>/dev/null uci set network.wan=interface uci set network.wan.device="$dmz_wan" uci set network.wan.proto="$dmz_wan_proto" uci set network.lan=interface uci set network.lan.proto='static' uci set network.lan.device="$dmz_lan_iface" uci set network.lan.ipaddr="$dmz_lan_ip" uci set network.lan.netmask="$dmz_lan_netmask" uci delete network.dmz 2>/dev/null uci set network.dmz=interface uci set network.dmz.proto='static' uci set network.dmz.device="$dmz_iface" uci set network.dmz.ipaddr="$dmz_ip" uci set network.dmz.netmask="$dmz_netmask" 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' uci delete dhcp.dmz 2>/dev/null uci set dhcp.dmz=dhcp uci set dhcp.dmz.interface='dmz' uci set dhcp.dmz.start="$dmz_start" uci set dhcp.dmz.limit="$dmz_limit" uci set dhcp.dmz.leasetime='12h' uci -q delete firewall.lan uci set firewall.lan=zone uci set firewall.lan.name='lan' uci set firewall.lan.network='lan' uci set firewall.lan.input='ACCEPT' uci set firewall.lan.output='ACCEPT' uci set firewall.lan.forward='ACCEPT' uci -q delete firewall.wan uci set firewall.wan=zone uci set firewall.wan.name='wan' uci set firewall.wan.network='wan' uci set firewall.wan.input='REJECT' uci set firewall.wan.output='ACCEPT' uci set firewall.wan.forward='REJECT' uci set firewall.wan.masq='1' uci set firewall.wan.mtu_fix='1' uci -q delete firewall.dmz uci set firewall.dmz=zone uci set firewall.dmz.name='dmz' uci set firewall.dmz.network='dmz' uci set firewall.dmz.input='REJECT' uci set firewall.dmz.output='ACCEPT' uci set firewall.dmz.forward='REJECT' uci -q delete firewall.dmz_wan uci set firewall.dmz_wan=forwarding uci set firewall.dmz_wan.src='dmz' uci set firewall.dmz_wan.dest='wan' uci -q delete firewall.dmz_lan 2>/dev/null if [ "$dmz_isolated_flag" != "1" ]; then uci set firewall.dmz_lan=forwarding uci set firewall.dmz_lan.src='dmz' uci set firewall.dmz_lan.dest='lan' fi ;; 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" ;; dmz) 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 dmz_interface dmz_interface json_get_var dmz_ip dmz_ip json_get_var dmz_netmask dmz_netmask json_get_var dmz_dhcp_start dmz_dhcp_start json_get_var dmz_dhcp_limit dmz_dhcp_limit json_get_var dmz_isolated dmz_isolated [ -n "$wan_interface" ] && uci set network-modes.dmz.wan_interface="$wan_interface" [ -n "$wan_protocol" ] && uci set network-modes.dmz.wan_protocol="$wan_protocol" [ -n "$lan_interface" ] && uci set network-modes.dmz.lan_interface="$lan_interface" [ -n "$lan_ip" ] && uci set network-modes.dmz.lan_ip="$lan_ip" [ -n "$lan_netmask" ] && uci set network-modes.dmz.lan_netmask="$lan_netmask" [ -n "$dmz_interface" ] && uci set network-modes.dmz.dmz_interface="$dmz_interface" [ -n "$dmz_ip" ] && uci set network-modes.dmz.dmz_ip="$dmz_ip" [ -n "$dmz_netmask" ] && uci set network-modes.dmz.dmz_netmask="$dmz_netmask" [ -n "$dmz_dhcp_start" ] && uci set network-modes.dmz.dmz_dhcp_start="$dmz_dhcp_start" [ -n "$dmz_dhcp_limit" ] && uci set network-modes.dmz.dmz_dhcp_limit="$dmz_dhcp_limit" [ -n "$dmz_isolated" ] && uci set network-modes.dmz.dmz_isolated="$dmz_isolated" ;; 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" ;; bridge) if ! uci -q get network-modes.bridge >/dev/null 2>&1; then uci set network-modes.bridge=mode uci set network-modes.bridge.name='Bridge' uci set network-modes.bridge.description='Layer 2 bridge template' fi json_get_var bridge_interface bridge_interface json_get_var bridge_ports bridge_ports json_get_var dhcp_client dhcp_client json_get_var firewall_mode firewall_mode [ -n "$bridge_interface" ] && uci set network-modes.bridge.bridge_interface="$bridge_interface" [ -n "$bridge_ports" ] && uci set network-modes.bridge.bridge_ports="$bridge_ports" [ -n "$dhcp_client" ] && uci set network-modes.bridge.dhcp_client="$dhcp_client" [ -n "$firewall_mode" ] && uci set network-modes.bridge.firewall_mode="$firewall_mode" ;; *) json_add_boolean "success" 0 json_add_string "error" "Invalid mode" json_dump return ;; esac uci commit network-modes local current_mode=$(uci -q get network-modes.config.current_mode || echo "") local module_active=1 if ! is_module_enabled; then module_active=0 fi if [ "$module_active" = "1" ] && [ "$mode" = "accesspoint" ] && [ "$current_mode" = "accesspoint" ]; then apply_accesspoint_features fi if [ "$module_active" = "1" ] && [ "$mode" = "sniffer" ] && [ "$current_mode" = "sniffer" ]; then start_packet_capture elif [ "$mode" = "sniffer" ]; then stop_packet_capture fi json_add_boolean "success" 1 if [ "$module_active" != "1" ]; then json_add_string "message" "Module disabled in SecuBox. Preferences saved for $mode template." elif [ "$mode" = "$current_mode" ]; then json_add_string "message" "Settings updated for $mode mode" else json_add_string "message" "Settings saved for $mode template (current mode: $current_mode)" fi 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 </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 </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 </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" <> "/etc/nginx/conf.d/vhost-$idx.conf" <> "/etc/nginx/conf.d/vhost-$idx.conf" </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 </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 '' list addresses '10.8.0.2/32' config wireguard_$wg_iface option public_key '' 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" if [ -n "$pending_mode" ]; then json_add_string "pending_mode_name" "$(uci -q get network-modes.$pending_mode.name || echo "$pending_mode")" fi json_add_int "rollback_timer" "$rollback_timer" json_add_string "last_backup" "$(uci -q get network-modes.config.last_backup || echo '')" json_add_string "last_backup_time" "$(uci -q get network-modes.config.last_backup_time || echo '')" # 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 "dmz" "Router + DMZ" "🛡️" "Mode routeur avec réseau DMZ isolé et firewall segmenté" \ "Réseau DMZ séparé" "DHCP optionnel pour DMZ" "Isolation LAN/DMZ" "Forwarding WAN contrôlé" 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|dmz|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" local dmz_ip_preview dmz_ip_preview=$(uci -q get network-modes.dmz.dmz_ip || echo "192.168.50.1") 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 ;; dmz) json_add_object json_add_string "file" "/etc/config/network" json_add_string "change" "WAN interface + LAN bridge reconfiguré" json_close_object json_add_object json_add_string "file" "/etc/config/network" json_add_string "change" "Nouveau segment DMZ (${dmz_ip_preview}/24)" json_close_object json_add_object json_add_string "file" "/etc/config/dhcp" json_add_string "change" "Serveur DHCP dédié pour DMZ" json_close_object json_add_object json_add_string "file" "/etc/config/firewall" json_add_string "change" "Zone DMZ isolée (forwarding uniquement vers WAN)" 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":{},"dmz_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 ;; dmz_config) get_dmz_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 dmz) local wan_if=$(uci -q get network-modes.dmz.wan_interface || echo "eth1") local wan_proto=$(uci -q get network-modes.dmz.wan_protocol || echo "dhcp") local lan_iface=$(uci -q get network-modes.dmz.lan_interface || echo "br-lan") local lan_ip=$(uci -q get network-modes.dmz.lan_ip || echo "192.168.1.1") local lan_mask=$(uci -q get network-modes.dmz.lan_netmask || echo "255.255.255.0") local dmz_iface=$(uci -q get network-modes.dmz.dmz_interface || echo "eth2") local dmz_ip=$(uci -q get network-modes.dmz.dmz_ip || echo "192.168.50.1") local dmz_mask=$(uci -q get network-modes.dmz.dmz_netmask || echo "255.255.255.0") config="# DMZ Mode Configuration # /etc/config/network config interface 'wan' option device '$wan_if' option proto '$wan_proto' config interface 'lan' option device '$lan_iface' option proto 'static' option ipaddr '$lan_ip' option netmask '$lan_mask' config interface 'dmz' option device '$dmz_iface' option proto 'static' option ipaddr '$dmz_ip' option netmask '$dmz_mask' # /etc/config/firewall config zone option name 'lan' option input 'ACCEPT' option output 'ACCEPT' option forward 'ACCEPT' list network 'lan' config zone option name 'dmz' option input 'REJECT' option output 'ACCEPT' option forward 'REJECT' list network 'dmz' 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 forwarding option src 'lan' option dest 'wan' config forwarding option src 'dmz' option dest 'wan'" ;;