#!/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 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 "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 </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" ;; *) 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 </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'" ;; 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") # Router mode json_add_object json_add_string "id" "router" json_add_string "name" "Router" json_add_string "description" "Mode routeur complet avec NAT, DHCP et firewall" json_add_string "icon" "🌐" json_add_boolean "current" "$([ "$current_mode" = "router" ] && echo 1 || echo 0)" json_add_array "features" json_add_string "" "NAT activé" json_add_string "" "Serveur DHCP" json_add_string "" "Firewall (zones WAN/LAN)" json_add_string "" "Proxy optionnel" json_close_array json_close_object # Access Point mode json_add_object json_add_string "id" "accesspoint" json_add_string "name" "Access Point" json_add_string "description" "Point d'accès WiFi en mode bridge, pas de NAT" json_add_string "icon" "📶" json_add_boolean "current" "$([ "$current_mode" = "accesspoint" ] && echo 1 || echo 0)" json_add_array "features" json_add_string "" "Bridge WAN+LAN" json_add_string "" "Pas de DHCP (mode client)" json_add_string "" "Pas de firewall" json_add_string "" "Optimisations WiFi 802.11r/k/v" json_close_array json_close_object # Repeater/Relay mode json_add_object json_add_string "id" "relay" json_add_string "name" "Repeater" json_add_string "description" "Client WiFi + Répéteur AP avec optimisations" json_add_string "icon" "🔄" json_add_boolean "current" "$([ "$current_mode" = "relay" ] && echo 1 || echo 0)" json_add_array "features" json_add_string "" "Client WiFi (sta0)" json_add_string "" "AP répéteur (ap0)" json_add_string "" "WireGuard optionnel" json_add_string "" "Optimisations MTU/MSS" json_close_array json_close_object # Travel router mode json_add_object json_add_string "id" "travel" json_add_string "name" "Travel Router" json_add_string "description" "Portable hotspot WiFi depuis l'ethernet ou WiFi hôtel" json_add_string "icon" "✈️" json_add_boolean "current" "$([ "$current_mode" = "travel" ] && echo 1 || echo 0)" json_add_array "features" json_add_string "" "Client WiFi + scan SSID" json_add_string "" "Clone MAC WAN" json_add_string "" "Hotspot privé WPA3" json_add_string "" "NAT + DHCP isolé" json_close_array json_close_object # Bridge mode json_add_object json_add_string "id" "bridge" json_add_string "name" "Bridge" json_add_string "description" "Bridge Layer 2 pur, DHCP client" json_add_string "icon" "🔗" json_add_boolean "current" "$([ "$current_mode" = "bridge" ] && echo 1 || echo 0)" json_add_array "features" json_add_string "" "Bridge transparent L2" json_add_string "" "Toutes interfaces bridgées" json_add_string "" "DHCP client" json_add_string "" "Pas de firewall" json_close_array json_close_object 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|accesspoint|relay|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 } # 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":{},"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 ;; 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