secubox-openwrt/package/secubox/luci-app-ndpid/root/usr/libexec/rpcd/luci.ndpid
CyberMind-FR 78f4fe4962 feat: Major updates - CDN cache with Squid, network modes UI rework, bugfixes
CDN Cache:
- Migrate from nginx to Squid proxy for better caching
- Add aggressive caching rules for Windows Update, Linux repos, Steam, Apple
- Proper firewall integration via UCI (transparent proxy)
- Real-time stats from Squid access logs

Network Modes:
- Complete UI rework with MirrorBox dark theme
- 9 network modes with emojis and descriptions
- Dynamic CSS animations and modern styling

Fixes:
- Fix jshn boolean handling in secubox-recovery (1/0 vs true/false)
- Fix nDPId RPCD to use netifyd as fallback DPI provider
- Update media-flow and security-threats dashboards

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 19:46:28 +01:00

768 lines
18 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: MIT
# SecuBox nDPId - RPCD Backend
# Complete interface for nDPId DPI daemon
# Copyright (C) 2025 CyberMind.fr
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
# Paths
CONFIG_FILE="/etc/config/ndpid"
STATUS_FILE="/var/run/netifyd/status.json"
DISTRIBUTOR_SOCK="/var/run/ndpid/distributor.sock"
COLLECTOR_SOCK="/var/run/ndpid/collector.sock"
FLOWS_CACHE="/tmp/ndpid-flows.json"
APPS_CACHE="/tmp/ndpid-apps.json"
STATS_CACHE="/tmp/ndpid-stats.json"
LOG_FILE="/var/log/ndpid.log"
# Logging
log_msg() {
local level="$1"
shift
logger -t luci.ndpid "[$level] $*"
}
# Check if nDPId is running
check_ndpid_running() {
pidof ndpid >/dev/null 2>&1
}
# Check if nDPIsrvd is running
check_ndpisrvd_running() {
pidof ndpisrvd >/dev/null 2>&1
}
# Check if netifyd is running
check_netifyd_running() {
pidof netifyd >/dev/null 2>&1
}
# Get service status
get_service_status() {
json_init
local running=0
local distributor_running=0
local pid=""
local uptime=0
local version=""
local netifyd_running=0
local netifyd_pid=""
local netifyd_uptime=0
local netifyd_version=""
local flow_count=0
# Check nDPId
if check_ndpid_running; then
running=1
pid=$(pidof ndpid | awk '{print $1}')
# Get uptime from /proc
if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
local start_time=$(stat -c %Y /proc/$pid 2>/dev/null)
local now=$(date +%s)
[ -n "$start_time" ] && uptime=$((now - start_time))
fi
# Get version
version=$(ndpid -v 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+' || echo "1.7")
fi
check_ndpisrvd_running && distributor_running=1
# Check netifyd (fallback DPI)
if check_netifyd_running; then
netifyd_running=1
netifyd_pid=$(pidof netifyd | awk '{print $1}')
# Get uptime from status file
if [ -f "$STATUS_FILE" ]; then
netifyd_uptime=$(cat "$STATUS_FILE" 2>/dev/null | jsonfilter -e '@.uptime' 2>/dev/null || echo 0)
flow_count=$(cat "$STATUS_FILE" 2>/dev/null | jsonfilter -e '@.flow_count' 2>/dev/null || echo 0)
fi
# Get version
netifyd_version=$(netifyd -v 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "4.4.7")
fi
json_add_boolean "running" "$running"
json_add_boolean "distributor_running" "$distributor_running"
json_add_string "pid" "$pid"
json_add_int "uptime" "$uptime"
json_add_string "version" "$version"
json_add_string "collector_socket" "$COLLECTOR_SOCK"
json_add_string "distributor_socket" "$DISTRIBUTOR_SOCK"
# Netifyd status
json_add_boolean "netifyd_running" "$netifyd_running"
json_add_string "netifyd_pid" "$netifyd_pid"
json_add_int "netifyd_uptime" "$netifyd_uptime"
json_add_string "netifyd_version" "$netifyd_version"
json_add_int "flow_count" "$flow_count"
# Check if compat layer is running
local compat_running=0
pidof ndpid-compat >/dev/null 2>&1 && compat_running=1
json_add_boolean "compat_running" "$compat_running"
# DPI available if either ndpid or netifyd is running
local dpi_available=0
[ "$running" = "1" ] || [ "$netifyd_running" = "1" ] && dpi_available=1
json_add_boolean "dpi_available" "$dpi_available"
json_dump
}
# Get real-time flow statistics from status file
get_realtime_flows() {
json_init
if [ -f "$STATUS_FILE" ]; then
local status_json=$(cat "$STATUS_FILE" 2>/dev/null)
if [ -n "$status_json" ]; then
# Parse values
local flow_count=$(echo "$status_json" | jsonfilter -e '@.flow_count' 2>/dev/null || echo 0)
local flows_active=$(echo "$status_json" | jsonfilter -e '@.flows_active' 2>/dev/null || echo 0)
local uptime=$(echo "$status_json" | jsonfilter -e '@.uptime' 2>/dev/null || echo 0)
json_add_int "flow_count" "$flow_count"
json_add_int "flows_active" "$flows_active"
json_add_int "uptime" "$uptime"
json_add_boolean "available" 1
else
json_add_boolean "available" 0
fi
else
json_add_boolean "available" 0
json_add_int "flow_count" 0
json_add_int "flows_active" 0
fi
json_dump
}
# Get interface statistics
get_interface_stats() {
json_init
json_add_array "interfaces"
if [ -f "$STATUS_FILE" ]; then
local stats=$(cat "$STATUS_FILE" | jsonfilter -e '@.stats' 2>/dev/null)
if [ -n "$stats" ] && command -v jq >/dev/null 2>&1; then
# Parse each interface
for iface in $(echo "$stats" | jq -r 'keys[]' 2>/dev/null); do
json_add_object
json_add_string "name" "$iface"
local ip_bytes=$(echo "$stats" | jq -r ".\"$iface\".ip_bytes // 0")
local wire_bytes=$(echo "$stats" | jq -r ".\"$iface\".wire_bytes // 0")
local tcp=$(echo "$stats" | jq -r ".\"$iface\".tcp // 0")
local udp=$(echo "$stats" | jq -r ".\"$iface\".udp // 0")
local icmp=$(echo "$stats" | jq -r ".\"$iface\".icmp // 0")
json_add_int "ip_bytes" "$ip_bytes"
json_add_int "wire_bytes" "$wire_bytes"
json_add_int "tcp" "$tcp"
json_add_int "udp" "$udp"
json_add_int "icmp" "$icmp"
json_close_object
done
fi
fi
json_close_array
json_dump
}
# Get top applications (from aggregated apps file)
get_top_applications() {
json_init
json_add_array "applications"
local app_data=""
# Try apps cache first (pre-aggregated by ndpid-compat)
if [ -f "$APPS_CACHE" ] && command -v jq >/dev/null 2>&1; then
app_data=$(jq -r '.[0:15][] | "\(.name // "Unknown")|\(.category // "Unknown")|\(.flows // 0)|\(.bytes // 0)"' \
"$APPS_CACHE" 2>/dev/null)
# Fallback to flows cache
elif [ -f "$FLOWS_CACHE" ] && command -v jq >/dev/null 2>&1; then
app_data=$(jq -r '
group_by(.app) |
map({
name: (.[0].app // "Unknown"),
category: (.[0].category // "Unknown"),
flows: length,
bytes: ([.[].bytes_rx, .[].bytes_tx] | add // 0)
}) |
sort_by(-.bytes) |
.[0:15][] |
"\(.name)|\(.category)|\(.flows)|\(.bytes)"
' "$FLOWS_CACHE" 2>/dev/null)
fi
if [ -n "$app_data" ]; then
while IFS='|' read -r name category flows bytes; do
[ -z "$name" ] && continue
json_add_object
json_add_string "name" "${name:-Unknown}"
json_add_string "category" "${category:-Unknown}"
json_add_int "flows" "${flows:-0}"
json_add_int "bytes" "${bytes:-0}"
json_close_object
done <<EOF
$app_data
EOF
fi
json_close_array
json_dump
}
# Get detailed flows with detection info
get_detailed_flows() {
json_init
json_add_array "flows"
if [ -f "$FLOWS_CACHE" ] && command -v jq >/dev/null 2>&1; then
# Get recent active flows sorted by bytes - use heredoc to avoid subshell
local flow_data
flow_data=$(jq -r '
sort_by(-(.bytes_rx + .bytes_tx)) |
.[0:100][] |
"\(.id // 0)|\(.src_ip // "")|\(.src_port // 0)|\(.dst_ip // "")|\(.dst_port // 0)|\(.proto // "")|\(.app // "Unknown")|\(.category // "Unknown")|\(.hostname // "")|\(.confidence // "")|\(.bytes_rx // 0)|\(.bytes_tx // 0)|\(.packets // 0)|\(.state // "unknown")|\(.iface // "")"
' "$FLOWS_CACHE" 2>/dev/null)
while IFS='|' read -r id src_ip src_port dst_ip dst_port proto app category hostname confidence bytes_rx bytes_tx packets state iface; do
[ -z "$id" ] && continue
json_add_object
json_add_int "id" "${id:-0}"
json_add_string "src_ip" "${src_ip}"
json_add_int "src_port" "${src_port:-0}"
json_add_string "dst_ip" "${dst_ip}"
json_add_int "dst_port" "${dst_port:-0}"
json_add_string "proto" "${proto}"
json_add_string "app" "${app:-Unknown}"
json_add_string "category" "${category:-Unknown}"
json_add_string "hostname" "${hostname}"
json_add_string "confidence" "${confidence}"
json_add_int "bytes_rx" "${bytes_rx:-0}"
json_add_int "bytes_tx" "${bytes_tx:-0}"
json_add_int "packets" "${packets:-0}"
json_add_string "state" "${state:-unknown}"
json_add_string "iface" "${iface}"
json_close_object
done <<EOF
$flow_data
EOF
fi
json_close_array
json_dump
}
# Get category breakdown
get_categories() {
json_init
json_add_array "categories"
if [ -f "$APPS_CACHE" ] && command -v jq >/dev/null 2>&1; then
local cat_data
cat_data=$(jq -r '
group_by(.category) |
map({
name: (.[0].category // "Unknown"),
apps: length,
flows: ([.[].flows] | add),
bytes: ([.[].bytes] | add)
}) |
sort_by(-.bytes) |
.[] |
"\(.name)|\(.apps)|\(.flows)|\(.bytes)"
' "$APPS_CACHE" 2>/dev/null)
while IFS='|' read -r name apps flows bytes; do
[ -z "$name" ] && continue
json_add_object
json_add_string "name" "${name:-Unknown}"
json_add_int "apps" "${apps:-0}"
json_add_int "flows" "${flows:-0}"
json_add_int "bytes" "${bytes:-0}"
json_close_object
done <<EOF
$cat_data
EOF
fi
json_close_array
json_dump
}
# Get top protocols
get_top_protocols() {
json_init
json_add_array "protocols"
if [ -f "$STATUS_FILE" ]; then
local stats=$(cat "$STATUS_FILE" | jsonfilter -e '@.stats' 2>/dev/null)
if [ -n "$stats" ] && command -v jq >/dev/null 2>&1; then
# Aggregate protocol counts across interfaces
local tcp=$(echo "$stats" | jq '[.[].tcp] | add // 0')
local udp=$(echo "$stats" | jq '[.[].udp] | add // 0')
local icmp=$(echo "$stats" | jq '[.[].icmp] | add // 0')
json_add_object
json_add_string "name" "TCP"
json_add_int "count" "$tcp"
json_close_object
json_add_object
json_add_string "name" "UDP"
json_add_int "count" "$udp"
json_close_object
json_add_object
json_add_string "name" "ICMP"
json_add_int "count" "$icmp"
json_close_object
fi
fi
json_close_array
json_dump
}
# Get configuration
get_config() {
json_init
config_load ndpid
# Main settings
local enabled interfaces collector_socket max_flows
config_get_bool enabled main enabled 0
config_get interfaces main interface ""
config_get collector_socket main collector_socket "/var/run/ndpid/collector.sock"
config_get max_flows main max_flows 100000
json_add_boolean "enabled" "$enabled"
json_add_string "interfaces" "$interfaces"
json_add_string "collector_socket" "$collector_socket"
json_add_int "max_flows" "$max_flows"
# Distributor settings
local dist_enabled tcp_port tcp_address
config_get_bool dist_enabled distributor enabled 1
config_get tcp_port distributor tcp_port 7000
config_get tcp_address distributor tcp_address "127.0.0.1"
json_add_object "distributor"
json_add_boolean "enabled" "$dist_enabled"
json_add_int "tcp_port" "$tcp_port"
json_add_string "tcp_address" "$tcp_address"
json_close_object
# Compat settings
local compat_enabled
config_get_bool compat_enabled compat enabled 1
json_add_object "compat"
json_add_boolean "enabled" "$compat_enabled"
json_close_object
# Actions settings
local actions_enabled
config_get_bool actions_enabled actions enabled 0
json_add_object "actions"
json_add_boolean "enabled" "$actions_enabled"
json_close_object
json_dump
}
# Get dashboard summary
get_dashboard() {
json_init
# Service status
json_add_object "service"
local running=0
check_ndpid_running && running=1
json_add_boolean "running" "$running"
local distributor_running=0
check_ndpisrvd_running && distributor_running=1
json_add_boolean "distributor_running" "$distributor_running"
local compat_running=0
pidof ndpid-compat >/dev/null 2>&1 && compat_running=1
json_add_boolean "compat_running" "$compat_running"
json_add_string "version" "$(ndpid -v 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+' || echo '1.7')"
json_close_object
# Flow stats
json_add_object "flows"
if [ -f "$STATUS_FILE" ]; then
local flow_count=$(cat "$STATUS_FILE" | jsonfilter -e '@.flow_count' 2>/dev/null || echo 0)
local flows_active=$(cat "$STATUS_FILE" | jsonfilter -e '@.flows_active' 2>/dev/null || echo 0)
json_add_int "total" "$flow_count"
json_add_int "active" "$flows_active"
else
json_add_int "total" 0
json_add_int "active" 0
fi
json_close_object
# System stats
json_add_object "system"
local pid=$(pidof ndpid | awk '{print $1}')
if [ -n "$pid" ] && [ -d "/proc/$pid" ]; then
# Memory usage
local mem_kb=$(awk '/VmRSS/{print $2}' /proc/$pid/status 2>/dev/null || echo 0)
json_add_int "memory_kb" "$mem_kb"
# CPU (simplified)
json_add_string "cpu" "0%"
else
json_add_int "memory_kb" 0
json_add_string "cpu" "0%"
fi
json_close_object
# Interfaces
json_add_array "interfaces"
config_load ndpid
config_get interfaces main interface ""
for iface in $interfaces; do
json_add_string "" "$iface"
done
json_close_array
json_dump
}
# Service control: start
service_start() {
json_init
/etc/init.d/ndpisrvd start 2>&1
sleep 1
/etc/init.d/ndpid start 2>&1
# Start compat layer
local compat_enabled
config_load ndpid
config_get_bool compat_enabled compat enabled 1
if [ "$compat_enabled" -eq 1 ]; then
/usr/bin/ndpid-compat -d 2>&1
fi
sleep 2
if check_ndpid_running; then
json_add_boolean "success" 1
json_add_string "message" "nDPId started successfully"
log_msg "INFO" "Service started"
else
json_add_boolean "success" 0
json_add_string "message" "Failed to start nDPId"
log_msg "ERROR" "Service failed to start"
fi
json_dump
}
# Service control: stop
service_stop() {
json_init
# Stop compat layer
if [ -f /var/run/ndpid-compat.pid ]; then
kill $(cat /var/run/ndpid-compat.pid) 2>/dev/null
rm -f /var/run/ndpid-compat.pid
fi
/etc/init.d/ndpid stop 2>&1
/etc/init.d/ndpisrvd stop 2>&1
sleep 1
if ! check_ndpid_running; then
json_add_boolean "success" 1
json_add_string "message" "nDPId stopped successfully"
log_msg "INFO" "Service stopped"
else
json_add_boolean "success" 0
json_add_string "message" "Failed to stop nDPId"
log_msg "ERROR" "Service failed to stop"
fi
json_dump
}
# Service control: restart
service_restart() {
json_init
service_stop >/dev/null 2>&1
sleep 2
service_start >/dev/null 2>&1
if check_ndpid_running; then
json_add_boolean "success" 1
json_add_string "message" "nDPId restarted successfully"
else
json_add_boolean "success" 0
json_add_string "message" "Failed to restart nDPId"
fi
json_dump
}
# Service control: enable
service_enable() {
json_init
uci set ndpid.main.enabled=1
uci commit ndpid
/etc/init.d/ndpid enable
/etc/init.d/ndpisrvd enable
json_add_boolean "success" 1
json_add_string "message" "nDPId enabled"
json_dump
}
# Service control: disable
service_disable() {
json_init
uci set ndpid.main.enabled=0
uci commit ndpid
/etc/init.d/ndpid disable
/etc/init.d/ndpisrvd disable
json_add_boolean "success" 1
json_add_string "message" "nDPId disabled"
json_dump
}
# Update configuration
update_config() {
local data="$1"
json_init
if [ -z "$data" ]; then
json_add_boolean "success" 0
json_add_string "error" "No configuration data provided"
json_dump
return
fi
# Parse and apply configuration
local enabled=$(echo "$data" | jsonfilter -e '@.enabled' 2>/dev/null)
local interfaces=$(echo "$data" | jsonfilter -e '@.interfaces' 2>/dev/null)
local max_flows=$(echo "$data" | jsonfilter -e '@.max_flows' 2>/dev/null)
[ -n "$enabled" ] && uci set ndpid.main.enabled="$enabled"
[ -n "$max_flows" ] && uci set ndpid.main.max_flows="$max_flows"
# Handle interfaces (clear and re-add)
if [ -n "$interfaces" ]; then
uci -q delete ndpid.main.interface
for iface in $interfaces; do
uci add_list ndpid.main.interface="$iface"
done
fi
uci commit ndpid
json_add_boolean "success" 1
json_add_string "message" "Configuration updated"
log_msg "INFO" "Configuration updated"
json_dump
}
# Clear statistics cache
clear_cache() {
json_init
rm -f "$FLOWS_CACHE" "$STATS_CACHE"
rm -rf /tmp/ndpid-state
json_add_boolean "success" 1
json_add_string "message" "Cache cleared"
json_dump
}
# Get monitored interfaces list
get_interfaces() {
json_init
json_add_array "interfaces"
# Get configured interfaces
config_load ndpid
config_get interfaces main interface ""
for iface in $interfaces; do
json_add_object
json_add_string "name" "$iface"
# Check if interface exists
if [ -d "/sys/class/net/$iface" ]; then
json_add_boolean "exists" 1
# Get interface state
local state=$(cat /sys/class/net/$iface/operstate 2>/dev/null || echo "unknown")
json_add_string "state" "$state"
# Get MAC address
local mac=$(cat /sys/class/net/$iface/address 2>/dev/null || echo "")
json_add_string "mac" "$mac"
else
json_add_boolean "exists" 0
fi
json_close_object
done
json_close_array
# Scan available interfaces from network config and system
json_add_array "available"
# Get bridges and network interfaces from UCI
local net_ifaces=""
config_load network
# Get all defined interfaces (br-lan, br-wan, etc)
for iface in $(ls /sys/class/net/ 2>/dev/null); do
case "$iface" in
lo|sit*|ip6*|gre*|ifb*|teql*)
# Skip loopback and virtual tunnel interfaces
;;
br-*|eth*|wlan*|lan*|wan*)
# Include bridges, ethernet, wifi, and named interfaces
local state=$(cat /sys/class/net/$iface/operstate 2>/dev/null || echo "unknown")
local type=$(cat /sys/class/net/$iface/type 2>/dev/null || echo "0")
# Only include if it's a real interface (type 1 = ethernet)
if [ "$type" = "1" ] || [ -d "/sys/class/net/$iface/bridge" ]; then
json_add_object
json_add_string "name" "$iface"
json_add_string "state" "$state"
# Check if it's a bridge
if [ -d "/sys/class/net/$iface/bridge" ]; then
json_add_string "type" "bridge"
else
json_add_string "type" "interface"
fi
json_close_object
fi
;;
esac
done
json_close_array
json_dump
}
# RPC method dispatcher
case "$1" in
list)
cat << 'EOF'
{
"get_service_status": {},
"get_realtime_flows": {},
"get_detailed_flows": {},
"get_interface_stats": {},
"get_top_applications": {},
"get_top_protocols": {},
"get_categories": {},
"get_config": {},
"get_dashboard": {},
"get_interfaces": {},
"service_start": {},
"service_stop": {},
"service_restart": {},
"service_enable": {},
"service_disable": {},
"update_config": { "data": "object" },
"clear_cache": {}
}
EOF
;;
call)
case "$2" in
get_service_status)
get_service_status
;;
get_realtime_flows)
get_realtime_flows
;;
get_detailed_flows)
get_detailed_flows
;;
get_interface_stats)
get_interface_stats
;;
get_top_applications)
get_top_applications
;;
get_top_protocols)
get_top_protocols
;;
get_categories)
get_categories
;;
get_config)
get_config
;;
get_dashboard)
get_dashboard
;;
get_interfaces)
get_interfaces
;;
service_start)
service_start
;;
service_stop)
service_stop
;;
service_restart)
service_restart
;;
service_enable)
service_enable
;;
service_disable)
service_disable
;;
update_config)
read -r input
data=$(echo "$input" | jsonfilter -e '@.data' 2>/dev/null)
update_config "$data"
;;
clear_cache)
clear_cache
;;
*)
echo '{"error": "Unknown method"}'
;;
esac
;;
*)
echo '{"error": "Invalid action"}'
;;
esac