secubox-openwrt/luci-app-wireguard-dashboard/root/usr/libexec/rpcd/luci.wireguard-dashboard
CyberMind-FR cf39eb6e1d fix: resolve validation issues across all modules
- Fixed minified RPC declaration in secubox/modules.js that caused false positive in validation
- Added 30 missing menu entries across 10 modules:
  * bandwidth-manager: clients, schedules
  * client-guardian: zones, portal, logs, alerts, parental
  * crowdsec-dashboard: metrics
  * netdata-dashboard: system, processes, realtime, network
  * netifyd-dashboard: talkers, risks, devices
  * network-modes: router, accesspoint, relay, sniffer
  * secubox: settings
  * system-hub: components, diagnostics, health, remote, settings
  * vhost-manager: internal, ssl, redirects
  * wireguard-dashboard: traffic, config
- All modules now pass comprehensive validation (0 errors, 0 warnings)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-25 09:01:06 +01:00

780 lines
20 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# WireGuard Dashboard RPCD backend
# Copyright (C) 2024 CyberMind.fr - Gandalf
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
WG_CMD="/usr/bin/wg"
# Check if wireguard-tools is available
check_wg() {
if [ ! -x "$WG_CMD" ]; then
echo '{"error": "wireguard-tools not installed"}'
exit 0
fi
}
# Get overall status
get_status() {
json_init
# Check if wg command exists
if [ ! -x "$WG_CMD" ]; then
json_add_boolean "installed" 0
json_add_string "error" "wireguard-tools not installed"
json_dump
return
fi
json_add_boolean "installed" 1
# Get all interfaces
local interfaces=$($WG_CMD show interfaces 2>/dev/null)
local iface_count=0
local peer_count=0
local total_rx=0
local total_tx=0
local active_peers=0
json_add_array "interfaces"
for iface in $interfaces; do
iface_count=$((iface_count + 1))
# Get interface details
local pubkey=$($WG_CMD show $iface public-key 2>/dev/null)
local port=$($WG_CMD show $iface listen-port 2>/dev/null)
local fwmark=$($WG_CMD show $iface fwmark 2>/dev/null)
# Get interface IP from network config
local ip=$(ip -4 addr show $iface 2>/dev/null | grep -oP 'inet \K[\d.]+')
# Check if interface is up
local state="down"
if ip link show $iface 2>/dev/null | grep -q "UP"; then
state="up"
fi
json_add_object
json_add_string "name" "$iface"
json_add_string "public_key" "$pubkey"
json_add_int "listen_port" "${port:-0}"
json_add_string "ip_address" "${ip:-N/A}"
json_add_string "state" "$state"
json_add_string "fwmark" "${fwmark:-off}"
# Count peers for this interface
local iface_peers=$($WG_CMD show $iface peers 2>/dev/null | wc -l)
json_add_int "peer_count" "$iface_peers"
peer_count=$((peer_count + iface_peers))
# Get transfer totals for interface
local transfer=$($WG_CMD show $iface transfer 2>/dev/null)
local iface_rx=0
local iface_tx=0
echo "$transfer" | while read peer rx tx; do
iface_rx=$((iface_rx + rx))
iface_tx=$((iface_tx + tx))
done
json_close_object
done
json_close_array
# Calculate totals from all interfaces
for iface in $interfaces; do
local transfer=$($WG_CMD show $iface transfer 2>/dev/null)
while read peer rx tx; do
[ -n "$rx" ] && total_rx=$((total_rx + rx))
[ -n "$tx" ] && total_tx=$((total_tx + tx))
done << EOF
$transfer
EOF
# Check for recent handshakes (within last 3 minutes = 180 seconds)
local handshakes=$($WG_CMD show $iface latest-handshakes 2>/dev/null)
local now=$(date +%s)
while read peer hs; do
if [ -n "$hs" ] && [ "$hs" != "0" ]; then
local age=$((now - hs))
if [ $age -lt 180 ]; then
active_peers=$((active_peers + 1))
fi
fi
done << EOF
$handshakes
EOF
done
json_add_int "interface_count" "$iface_count"
json_add_int "total_peers" "$peer_count"
json_add_int "active_peers" "$active_peers"
json_add_int "total_rx" "$total_rx"
json_add_int "total_tx" "$total_tx"
# Uptime - get from first interface creation
local uptime=0
for iface in $interfaces; do
if [ -d "/sys/class/net/$iface" ]; then
# Approximate from interface stats
uptime=$(cat /proc/uptime | cut -d. -f1)
break
fi
done
json_add_int "uptime" "$uptime"
json_dump
}
# Get all interfaces with details
get_interfaces() {
json_init
json_add_array "interfaces"
local interfaces=$($WG_CMD show interfaces 2>/dev/null)
for iface in $interfaces; do
local pubkey=$($WG_CMD show $iface public-key 2>/dev/null)
local privkey_exists="yes"
local port=$($WG_CMD show $iface listen-port 2>/dev/null)
local fwmark=$($WG_CMD show $iface fwmark 2>/dev/null)
# Get IP addresses
local ipv4=$(ip -4 addr show $iface 2>/dev/null | grep -oP 'inet \K[\d./]+')
local ipv6=$(ip -6 addr show $iface 2>/dev/null | grep -oP 'inet6 \K[a-f0-9:/]+' | head -1)
# Interface state
local state="down"
local mtu=1420
if ip link show $iface 2>/dev/null | grep -q "UP"; then
state="up"
mtu=$(cat /sys/class/net/$iface/mtu 2>/dev/null || echo 1420)
fi
# Get RX/TX bytes from /sys
local rx_bytes=$(cat /sys/class/net/$iface/statistics/rx_bytes 2>/dev/null || echo 0)
local tx_bytes=$(cat /sys/class/net/$iface/statistics/tx_bytes 2>/dev/null || echo 0)
json_add_object
json_add_string "name" "$iface"
json_add_string "public_key" "$pubkey"
json_add_boolean "private_key_set" 1
json_add_int "listen_port" "${port:-0}"
json_add_string "fwmark" "${fwmark:-off}"
json_add_string "ipv4_address" "${ipv4:-N/A}"
json_add_string "ipv6_address" "${ipv6:-N/A}"
json_add_string "state" "$state"
json_add_int "mtu" "$mtu"
json_add_int "rx_bytes" "$rx_bytes"
json_add_int "tx_bytes" "$tx_bytes"
json_close_object
done
json_close_array
json_dump
}
# Get all peers with details
get_peers() {
json_init
json_add_array "peers"
local interfaces=$($WG_CMD show interfaces 2>/dev/null)
for iface in $interfaces; do
local peers=$($WG_CMD show $iface peers 2>/dev/null)
local endpoints=$($WG_CMD show $iface endpoints 2>/dev/null)
local allowed_ips=$($WG_CMD show $iface allowed-ips 2>/dev/null)
local handshakes=$($WG_CMD show $iface latest-handshakes 2>/dev/null)
local transfer=$($WG_CMD show $iface transfer 2>/dev/null)
local psk=$($WG_CMD show $iface preshared-keys 2>/dev/null)
local keepalive=$($WG_CMD show $iface persistent-keepalive 2>/dev/null)
local now=$(date +%s)
for peer in $peers; do
# Get endpoint
local endpoint=$(echo "$endpoints" | grep "^$peer" | awk '{print $2}')
# Get allowed IPs
local ips=$(echo "$allowed_ips" | grep "^$peer" | cut -f2-)
# Get latest handshake
local hs=$(echo "$handshakes" | grep "^$peer" | awk '{print $2}')
local hs_ago=0
local status="inactive"
if [ -n "$hs" ] && [ "$hs" != "0" ]; then
hs_ago=$((now - hs))
if [ $hs_ago -lt 180 ]; then
status="active"
elif [ $hs_ago -lt 600 ]; then
status="idle"
fi
fi
# Get transfer
local rx=0
local tx=0
local tr=$(echo "$transfer" | grep "^$peer")
if [ -n "$tr" ]; then
rx=$(echo "$tr" | awk '{print $2}')
tx=$(echo "$tr" | awk '{print $3}')
fi
# Get preshared key status
local has_psk=0
if echo "$psk" | grep -q "^$peer.*[^(none)]"; then
has_psk=1
fi
# Get keepalive
local ka=$(echo "$keepalive" | grep "^$peer" | awk '{print $2}')
# Truncate public key for display name
local short_key=$(echo "$peer" | cut -c1-8)
json_add_object
json_add_string "interface" "$iface"
json_add_string "public_key" "$peer"
json_add_string "short_key" "$short_key"
json_add_string "endpoint" "${endpoint:-(none)}"
json_add_string "allowed_ips" "${ips:-N/A}"
json_add_int "latest_handshake" "${hs:-0}"
json_add_int "handshake_ago" "$hs_ago"
json_add_string "status" "$status"
json_add_int "rx_bytes" "${rx:-0}"
json_add_int "tx_bytes" "${tx:-0}"
json_add_boolean "preshared_key" "$has_psk"
json_add_int "keepalive" "${ka:-0}"
json_close_object
done
done
json_close_array
json_dump
}
# Get traffic statistics
get_traffic() {
json_init
local interfaces=$($WG_CMD show interfaces 2>/dev/null)
local total_rx=0
local total_tx=0
json_add_array "interfaces"
for iface in $interfaces; do
local iface_rx=0
local iface_tx=0
# Get per-peer transfer
local transfer=$($WG_CMD show $iface transfer 2>/dev/null)
json_add_object
json_add_string "name" "$iface"
json_add_array "peers"
while read peer rx tx; do
if [ -n "$peer" ]; then
json_add_object
json_add_string "public_key" "$peer"
json_add_string "short_key" "$(echo $peer | cut -c1-8)"
json_add_int "rx_bytes" "${rx:-0}"
json_add_int "tx_bytes" "${tx:-0}"
json_close_object
iface_rx=$((iface_rx + ${rx:-0}))
iface_tx=$((iface_tx + ${tx:-0}))
fi
done << EOF
$transfer
EOF
json_close_array
json_add_int "total_rx" "$iface_rx"
json_add_int "total_tx" "$iface_tx"
json_close_object
total_rx=$((total_rx + iface_rx))
total_tx=$((total_tx + iface_tx))
done
json_close_array
json_add_int "total_rx" "$total_rx"
json_add_int "total_tx" "$total_tx"
json_dump
}
# Get configuration info (sanitized - no private keys)
get_config() {
json_init
json_add_array "interfaces"
local interfaces=$($WG_CMD show interfaces 2>/dev/null)
for iface in $interfaces; do
json_add_object
json_add_string "name" "$iface"
json_add_string "public_key" "$($WG_CMD show $iface public-key 2>/dev/null)"
json_add_int "listen_port" "$($WG_CMD show $iface listen-port 2>/dev/null || echo 0)"
# Get UCI config if available
local uci_proto=$(uci -q get network.$iface.proto)
local uci_addr=$(uci -q get network.$iface.addresses)
json_add_string "uci_proto" "${uci_proto:-N/A}"
json_add_string "uci_addresses" "${uci_addr:-N/A}"
# Peers config
json_add_array "peers"
local peers=$($WG_CMD show $iface peers 2>/dev/null)
local allowed=$($WG_CMD show $iface allowed-ips 2>/dev/null)
local endpoints=$($WG_CMD show $iface endpoints 2>/dev/null)
local keepalive=$($WG_CMD show $iface persistent-keepalive 2>/dev/null)
for peer in $peers; do
local ips=$(echo "$allowed" | grep "^$peer" | cut -f2-)
local ep=$(echo "$endpoints" | grep "^$peer" | awk '{print $2}')
local ka=$(echo "$keepalive" | grep "^$peer" | awk '{print $2}')
json_add_object
json_add_string "public_key" "$peer"
json_add_string "allowed_ips" "${ips:-N/A}"
json_add_string "endpoint" "${ep:-(none)}"
json_add_int "keepalive" "${ka:-0}"
json_close_object
done
json_close_array
json_close_object
done
json_close_array
json_dump
}
# Generate keys for new peer
generate_keys() {
json_init
if [ ! -x "$WG_CMD" ]; then
json_add_string "error" "wireguard-tools not installed"
json_dump
return
fi
# Generate private key
local privkey=$(wg genkey)
# Generate public key from private key
local pubkey=$(echo "$privkey" | wg pubkey)
# Generate preshared key
local psk=$(wg genpsk)
json_add_string "private_key" "$privkey"
json_add_string "public_key" "$pubkey"
json_add_string "preshared_key" "$psk"
json_dump
}
# Add a new peer to interface
add_peer() {
read input
json_load "$input"
json_get_var iface interface
json_get_var name name
json_get_var allowed_ips allowed_ips
json_get_var public_key public_key
json_get_var psk preshared_key
json_get_var endpoint endpoint
json_get_var keepalive persistent_keepalive
json_init
# Validate inputs
if [ -z "$iface" ] || [ -z "$public_key" ]; then
json_add_boolean "success" 0
json_add_string "error" "Missing required fields: interface and public_key"
json_dump
return
fi
# Default values
[ -z "$allowed_ips" ] && allowed_ips="10.0.0.2/32"
[ -z "$keepalive" ] && keepalive="25"
[ -z "$name" ] && name="peer_$(echo $public_key | cut -c1-8)"
# Create UCI section for peer
local section_name="wgpeer_$(echo $public_key | cut -c1-8 | tr 'A-Z+/=' 'a-z___')"
# Add peer to UCI network config
uci -q delete network.$section_name
uci set network.$section_name=wireguard_$iface
uci set network.$section_name.public_key="$public_key"
uci set network.$section_name.description="$name"
# Set allowed IPs (can be multiple)
uci delete network.$section_name.allowed_ips 2>/dev/null
for ip in $allowed_ips; do
uci add_list network.$section_name.allowed_ips="$ip"
done
# Optional: preshared key
if [ -n "$psk" ] && [ "$psk" != "none" ]; then
uci set network.$section_name.preshared_key="$psk"
fi
# Optional: endpoint
if [ -n "$endpoint" ] && [ "$endpoint" != "none" ]; then
local ep_host=$(echo "$endpoint" | cut -d: -f1)
local ep_port=$(echo "$endpoint" | cut -d: -f2)
uci set network.$section_name.endpoint_host="$ep_host"
uci set network.$section_name.endpoint_port="${ep_port:-51820}"
fi
# Persistent keepalive
if [ -n "$keepalive" ] && [ "$keepalive" != "0" ]; then
uci set network.$section_name.persistent_keepalive="$keepalive"
fi
# Route allowed IPs
uci set network.$section_name.route_allowed_ips="1"
# Commit changes
uci commit network
# Reload interface to apply
ifup "$iface" 2>/dev/null
json_add_boolean "success" 1
json_add_string "message" "Peer added successfully"
json_add_string "section" "$section_name"
json_add_string "peer_name" "$name"
json_dump
}
# Remove a peer from interface
remove_peer() {
read input
json_load "$input"
json_get_var iface interface
json_get_var public_key public_key
json_init
if [ -z "$iface" ] || [ -z "$public_key" ]; then
json_add_boolean "success" 0
json_add_string "error" "Missing interface or public_key"
json_dump
return
fi
# Find and remove UCI section with this public key
local found=0
local section=""
config_load network
config_cb() {
local type="$1"
local name="$2"
if [ "$type" = "wireguard_$iface" ]; then
local key=$(uci -q get network.$name.public_key)
if [ "$key" = "$public_key" ]; then
section="$name"
found=1
fi
fi
}
config_load network
if [ "$found" = "1" ] && [ -n "$section" ]; then
uci delete network.$section
uci commit network
ifup "$iface" 2>/dev/null
json_add_boolean "success" 1
json_add_string "message" "Peer removed successfully"
else
json_add_boolean "success" 0
json_add_string "error" "Peer not found in configuration"
fi
json_dump
}
# Generate client configuration file
generate_config() {
read input
json_load "$input"
json_get_var iface interface
json_get_var peer_key peer
json_get_var peer_privkey private_key
json_get_var server_endpoint endpoint
json_init
# Get interface details
local server_pubkey=$($WG_CMD show $iface public-key 2>/dev/null)
local server_port=$($WG_CMD show $iface listen-port 2>/dev/null)
# Get peer allowed IPs
local allowed=$($WG_CMD show $iface allowed-ips 2>/dev/null | grep "^$peer_key" | cut -f2-)
# Get preshared key if exists
local psk=$($WG_CMD show $iface preshared-keys 2>/dev/null | grep "^$peer_key" | awk '{print $2}')
# Get DNS servers from interface
local dns=$(uci -q get network.$iface.dns || echo "1.1.1.1, 1.0.0.1")
# Build configuration file content
local config="[Interface]
PrivateKey = ${peer_privkey}
Address = ${allowed}
DNS = ${dns}
[Peer]
PublicKey = ${server_pubkey}
Endpoint = ${server_endpoint}:${server_port}
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25"
# Add preshared key if exists
if [ -n "$psk" ] && [ "$psk" != "(none)" ]; then
config="${config}
PresharedKey = ${psk}"
fi
json_add_string "config" "$config"
json_add_string "filename" "wireguard_${iface}_client.conf"
json_dump
}
# Generate QR code PNG (base64) for peer config
generate_qr() {
read input
json_load "$input"
json_get_var iface interface
json_get_var peer_key peer
json_get_var peer_privkey private_key
json_get_var server_endpoint endpoint
json_init
if [ ! -x "/usr/bin/qrencode" ]; then
json_add_string "error" "qrencode not installed (opkg install qrencode)"
json_dump
return
fi
# Get interface details
local server_pubkey=$($WG_CMD show $iface public-key 2>/dev/null)
local server_port=$($WG_CMD show $iface listen-port 2>/dev/null)
# Get peer allowed IPs
local allowed=$($WG_CMD show $iface allowed-ips 2>/dev/null | grep "^$peer_key" | cut -f2-)
# Get preshared key if exists
local psk=$($WG_CMD show $iface preshared-keys 2>/dev/null | grep "^$peer_key" | awk '{print $2}')
# Get DNS servers
local dns=$(uci -q get network.$iface.dns || echo "1.1.1.1, 1.0.0.1")
# Build configuration
local config="[Interface]
PrivateKey = ${peer_privkey}
Address = ${allowed}
DNS = ${dns}
[Peer]
PublicKey = ${server_pubkey}
Endpoint = ${server_endpoint}:${server_port}
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25"
if [ -n "$psk" ] && [ "$psk" != "(none)" ]; then
config="${config}
PresharedKey = ${psk}"
fi
# Generate QR code PNG and convert to base64
local qr_base64=$(echo "$config" | qrencode -t PNG -o - | base64 -w 0)
json_add_string "qrcode" "data:image/png;base64,$qr_base64"
json_add_string "config" "$config"
json_dump
}
# Get bandwidth history (if vnstat available)
get_bandwidth_history() {
json_init
local interfaces=$($WG_CMD show interfaces 2>/dev/null)
json_add_array "history"
for iface in $interfaces; do
if [ -x "/usr/bin/vnstat" ]; then
# Get hourly data from vnstat if available
local vnstat_data=$(vnstat -i $iface --json h 2>/dev/null)
if [ -n "$vnstat_data" ]; then
json_add_object
json_add_string "interface" "$iface"
json_add_string "source" "vnstat"
# Parse vnstat JSON here
json_close_object
fi
fi
# Fallback: get current stats from /sys
local rx=$(cat /sys/class/net/$iface/statistics/rx_bytes 2>/dev/null || echo 0)
local tx=$(cat /sys/class/net/$iface/statistics/tx_bytes 2>/dev/null || echo 0)
json_add_object
json_add_string "interface" "$iface"
json_add_string "source" "sysfs"
json_add_int "rx_bytes" "$rx"
json_add_int "tx_bytes" "$tx"
json_add_int "timestamp" "$(date +%s)"
json_close_object
done
json_close_array
json_dump
}
# Get endpoint geolocation (simplified, based on IP ranges)
get_endpoint_info() {
read input
json_load "$input"
json_get_var endpoint endpoint
json_init
# Extract IP from endpoint
local ip=$(echo "$endpoint" | cut -d: -f1)
if [ -z "$ip" ] || [ "$ip" = "(none)" ]; then
json_add_string "error" "No endpoint"
json_dump
return
fi
json_add_string "ip" "$ip"
# Check if private IP
case "$ip" in
10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.2[0-9].*|172.3[01].*|192.168.*)
json_add_string "type" "private"
json_add_string "location" "Local Network"
;;
127.*)
json_add_string "type" "loopback"
json_add_string "location" "Localhost"
;;
*)
json_add_string "type" "public"
json_add_string "location" "Internet"
# Could integrate with GeoIP service here
;;
esac
# Reverse DNS lookup
local hostname=$(nslookup "$ip" 2>/dev/null | grep "name = " | awk '{print $NF}' | sed 's/\.$//')
json_add_string "hostname" "${hostname:-N/A}"
json_dump
}
# Check connectivity to peer
ping_peer() {
read input
json_load "$input"
json_get_var peer_ip ip
json_init
if [ -z "$peer_ip" ]; then
json_add_string "error" "No IP provided"
json_dump
return
fi
# Single ping with 2 second timeout
local result=$(ping -c 1 -W 2 "$peer_ip" 2>/dev/null)
local rc=$?
if [ $rc -eq 0 ]; then
local rtt=$(echo "$result" | grep "time=" | sed 's/.*time=\([0-9.]*\).*/\1/')
json_add_boolean "reachable" 1
json_add_string "rtt_ms" "$rtt"
else
json_add_boolean "reachable" 0
json_add_string "rtt_ms" "N/A"
fi
json_add_string "target" "$peer_ip"
json_dump
}
# Main dispatcher
case "$1" in
list)
echo '{"status":{},"interfaces":{},"peers":{},"traffic":{},"config":{},"generate_keys":{},"add_peer":{"interface":"str","name":"str","allowed_ips":"str","public_key":"str","preshared_key":"str","endpoint":"str","persistent_keepalive":"str"},"remove_peer":{"interface":"str","public_key":"str"},"generate_config":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"generate_qr":{"interface":"str","peer":"str","private_key":"str","endpoint":"str"},"bandwidth_history":{},"endpoint_info":{"endpoint":"str"},"ping_peer":{"ip":"str"}}'
;;
call)
case "$2" in
status)
get_status
;;
interfaces)
get_interfaces
;;
peers)
get_peers
;;
traffic)
get_traffic
;;
config)
get_config
;;
generate_keys)
generate_keys
;;
add_peer)
add_peer
;;
remove_peer)
remove_peer
;;
generate_config)
generate_config
;;
generate_qr)
generate_qr
;;
bandwidth_history)
get_bandwidth_history
;;
endpoint_info)
get_endpoint_info
;;
ping_peer)
ping_peer
;;
*)
echo '{"error": "Unknown method"}'
;;
esac
;;
esac