#!/bin/sh # RPCD handler for DPI Dual-Stream dashboard # Part of luci-app-dpi-dual . /lib/functions.sh . /usr/share/libubox/jshn.sh STATS_DIR="/tmp/secubox" FLOW_DIR="/tmp/dpi-flows" BUFFER_FILE="$STATS_DIR/dpi-buffer.json" FLOWS_FILE="$STATS_DIR/dpi-flows.json" THREATS_FILE="$STATS_DIR/correlated-threats.json" ALERTS_FILE="$STATS_DIR/waf-alerts.json" read_json_file() { file="$1" if [ -f "$file" ]; then cat "$file" else echo '{}' fi } case "$1" in list) cat << 'EOF' { "status": {}, "get_flows": {}, "get_buffer": {"limit": 100}, "get_threats": {"limit": 50}, "get_correlation": {"limit": 20}, "get_correlation_stats": {}, "get_ip_context": {"ip": "string"}, "get_ip_reputation": {"ip": "string"}, "get_timeline": {"limit": 50}, "get_mirror_status": {}, "search_correlations": {"ip": "string", "limit": 50}, "start": {}, "stop": {}, "restart": {}, "replay_request": {"req_hash": "string"}, "correlate_ip": {"ip": "string"}, "ban_ip": {"ip": "string", "duration": "string"}, "set_auto_ban": {"enabled": true}, "get_tuning": {}, "set_tuning": {"param": "string", "value": "string"}, "whitelist_add": {"ip": "string"}, "whitelist_remove": {"ip": "string"}, "whitelist_list": {}, "reset_reputation": {"ip": "string"}, "get_lan_status": {}, "get_lan_clients": {}, "get_lan_destinations": {"limit": 100}, "get_lan_protocols": {} } EOF ;; call) case "$2" in status) # Get unified status of both streams config_load dpi-dual enabled="" mode="" correlation="" config_get enabled settings enabled "0" config_get mode settings mode "dual" config_get correlation settings correlation "0" # Check processes (use partial match for truncated process names) mitm_running=0 tap_running=0 collector_running=0 correlator_running=0 lan_collector_running=0 pgrep -f mitmproxy-in >/dev/null 2>&1 && mitm_running=1 pgrep netifyd >/dev/null 2>&1 && tap_running=1 pgrep -f dpi-flow-collect >/dev/null 2>&1 && collector_running=1 pgrep -f dpi-correlator >/dev/null 2>&1 && correlator_running=1 pgrep -f dpi-lan-collect >/dev/null 2>&1 && lan_collector_running=1 # Get TAP interface status tap_if="" tap_up=0 tap_rx=0 tap_tx=0 config_get tap_if tap interface "tap0" if ip link show "$tap_if" >/dev/null 2>&1; then tap_up=1 tap_rx=$(cat "/sys/class/net/$tap_if/statistics/rx_bytes" 2>/dev/null || echo 0) tap_tx=$(cat "/sys/class/net/$tap_if/statistics/tx_bytes" 2>/dev/null || echo 0) fi # Get buffer stats buffer_entries=0 buffer_threats=0 buffer_blocked=0 if [ -f "$BUFFER_FILE" ]; then buffer_entries=$(jsonfilter -i "$BUFFER_FILE" -e '@.entries' 2>/dev/null || echo 0) buffer_threats=$(jsonfilter -i "$BUFFER_FILE" -e '@.threats_detected' 2>/dev/null || echo 0) buffer_blocked=$(jsonfilter -i "$BUFFER_FILE" -e '@.blocked_count' 2>/dev/null || echo 0) fi # Get flow stats flows_1min=0 if [ -f "$FLOWS_FILE" ]; then flows_1min=$(jsonfilter -i "$FLOWS_FILE" -e '@.flows_1min' 2>/dev/null || echo 0) fi # Get correlation stats correlated_threats=0 if [ -f "$THREATS_FILE" ]; then correlated_threats=$(wc -l < "$THREATS_FILE" 2>/dev/null || echo 0) fi # Get LAN passive analysis stats lan_enabled="" lan_if="" config_get lan_enabled lan enabled "0" config_get lan_if lan interface "br-lan" lan_clients=0 lan_dests=0 lan_protos=0 lan_file="$STATS_DIR/lan-flows.json" if [ -f "$lan_file" ]; then lan_clients=$(jsonfilter -i "$lan_file" -e '@.active_clients' 2>/dev/null || echo 0) lan_dests=$(jsonfilter -i "$lan_file" -e '@.unique_destinations' 2>/dev/null || echo 0) lan_protos=$(jsonfilter -i "$lan_file" -e '@.detected_protocols' 2>/dev/null || echo 0) fi cat << EOF { "enabled": $enabled, "mode": "$mode", "correlation_enabled": $correlation, "mitm_stream": { "running": $mitm_running, "buffer_entries": $buffer_entries, "threats_detected": $buffer_threats, "blocked_count": $buffer_blocked }, "tap_stream": { "running": $tap_running, "interface": "$tap_if", "interface_up": $tap_up, "rx_bytes": $tap_rx, "tx_bytes": $tap_tx, "flows_1min": $flows_1min, "collector_running": $collector_running }, "correlation": { "running": $correlator_running, "threats_correlated": $correlated_threats }, "lan_passive": { "enabled": $lan_enabled, "running": $lan_collector_running, "interface": "$lan_if", "active_clients": $lan_clients, "unique_destinations": $lan_dests, "detected_protocols": $lan_protos } } EOF ;; get_flows) read_json_file "$FLOWS_FILE" ;; get_buffer) read "$3" json_load "$REPLY" json_get_var limit limit 100 if [ -f "$BUFFER_FILE" ]; then cat "$BUFFER_FILE" else echo '{"entries": 0, "requests": []}' fi ;; get_threats) read "$3" json_load "$REPLY" json_get_var limit limit 50 if [ -f "$ALERTS_FILE" ]; then # Return last N alerts total=$(jsonfilter -i "$ALERTS_FILE" -e '@[*]' 2>/dev/null | wc -l) cat << EOF { "total": $total, "alerts": $(tail -c 50000 "$ALERTS_FILE" 2>/dev/null || echo '[]') } EOF else echo '{"total": 0, "alerts": []}' fi ;; get_correlation) read "$3" json_load "$REPLY" json_get_var limit limit 20 if [ -f "$THREATS_FILE" ]; then total=$(wc -l < "$THREATS_FILE" 2>/dev/null || echo 0) cat << EOF { "total": $total, "correlated": $(tail -"$limit" "$THREATS_FILE" 2>/dev/null | tr '\n' ',' | sed 's/,$//' | awk '{print "["$0"]"}') } EOF else echo '{"total": 0, "correlated": []}' fi ;; get_mirror_status) /usr/lib/dpi-dual/mirror-setup.sh status 2>&1 | \ awk 'BEGIN{print "{"} /TAP Interface/ {tap=1} /not found/ {up=0} /UP/ {up=1} /RX:/ {rx=$2} /TX:/ {tx=$2} /ingress/ {ing=1} END{ printf "\"tap_found\": %s, \"tap_up\": %s, \"ingress_configured\": %s", (tap?1:0), (up?1:0), (ing?1:0); print "}" }' ;; start) /usr/sbin/dpi-dualctl start >/dev/null 2>&1 echo '{"success": true}' ;; stop) /usr/sbin/dpi-dualctl stop >/dev/null 2>&1 echo '{"success": true}' ;; restart) /usr/sbin/dpi-dualctl restart >/dev/null 2>&1 echo '{"success": true}' ;; replay_request) read "$3" json_load "$REPLY" json_get_var req_hash req_hash "" if [ -z "$req_hash" ]; then echo '{"success": false, "error": "req_hash required"}' else # Add to replay queue (read by mitmproxy addon) queue_file="/tmp/dpi-buffer/replay-queue.json" mkdir -p /tmp/dpi-buffer if [ ! -f "$queue_file" ]; then echo "[]" > "$queue_file" fi entry="{\"req_hash\":\"$req_hash\",\"queued_at\":\"$(date -Iseconds)\",\"status\":\"pending\"}" # Append to queue (keep last 100) (cat "$queue_file" | jsonfilter -e '@[*]' 2>/dev/null; echo "$entry") | \ tail -100 | \ awk 'BEGIN{print "["} {if(NR>1)print ","; print} END{print "]"}' > "$queue_file.tmp" mv "$queue_file.tmp" "$queue_file" echo '{"success": true, "message": "Request queued for replay"}' fi ;; correlate_ip) read "$3" json_load "$REPLY" json_get_var ip ip "" if [ -z "$ip" ]; then echo '{"success": false, "error": "IP required"}' else /usr/sbin/dpi-correlator correlate "$ip" "manual_request" >/dev/null 2>&1 echo '{"success": true, "message": "Correlation triggered for '"$ip"'"}' fi ;; get_correlation_stats) /usr/sbin/dpi-correlator stats 2>/dev/null || echo '{"total_correlations": 0}' ;; get_ip_context) read "$3" json_load "$REPLY" json_get_var ip ip "" if [ -z "$ip" ]; then echo '{"error": "IP required"}' else /usr/sbin/dpi-correlator context "$ip" 2>/dev/null || echo '{"error": "Context not available"}' fi ;; get_ip_reputation) read "$3" json_load "$REPLY" json_get_var ip ip "" if [ -z "$ip" ]; then echo '{"error": "IP required"}' else . /usr/lib/dpi-dual/correlation-lib.sh init_reputation_db score=$(get_ip_reputation "$ip") echo "{\"ip\": \"$ip\", \"reputation_score\": $score}" fi ;; get_timeline) read "$3" json_load "$REPLY" json_get_var limit limit 50 log_file="/tmp/secubox/correlated-threats.json" if [ -f "$log_file" ]; then total=$(wc -l < "$log_file" 2>/dev/null || echo 0) # Get last N entries as JSON array entries=$(tail -"$limit" "$log_file" 2>/dev/null | \ awk 'BEGIN { printf "[" } { if (NR > 1) printf ","; print } END { printf "]" }') cat << EOF { "total": $total, "limit": $limit, "entries": $entries } EOF else echo '{"total": 0, "limit": '$limit', "entries": []}' fi ;; search_correlations) read "$3" json_load "$REPLY" json_get_var ip ip "" json_get_var limit limit 50 results=$(/usr/sbin/dpi-correlator search "$ip" "$limit" 2>/dev/null | \ awk 'BEGIN { printf "[" } { if (NR > 1) printf ","; print } END { printf "]" }') echo "{\"results\": $results}" ;; ban_ip) read "$3" json_load "$REPLY" json_get_var ip ip "" json_get_var duration duration "4h" if [ -z "$ip" ]; then echo '{"success": false, "error": "IP required"}' else if command -v cscli >/dev/null 2>&1; then cscli decisions add -i "$ip" -d "$duration" -r "dpi-dual-manual" -t ban >/dev/null 2>&1 echo '{"success": true, "message": "IP '"$ip"' banned for '"$duration"'"}' else echo '{"success": false, "error": "CrowdSec not available"}' fi fi ;; set_auto_ban) read "$3" json_load "$REPLY" json_get_var enabled enabled "0" val="0" [ "$enabled" = "true" ] && val="1" uci set dpi-dual.correlation.auto_ban="$val" uci commit dpi-dual echo '{"success": true, "auto_ban": '$val'}' ;; get_tuning) config_load dpi-dual # Load correlation settings sensitivity="" threshold="" duration="" notifications="" notif_threshold="" decay="" decay_interval="" config_get sensitivity correlation sensitivity "medium" config_get threshold correlation auto_ban_threshold "80" config_get duration correlation auto_ban_duration "4h" config_get notifications correlation notifications "1" config_get notif_threshold correlation notification_threshold "70" config_get decay correlation reputation_decay "5" config_get decay_interval correlation decay_interval "3600" # Load scoring weights waf_block="" waf_alert="" cs_ban="" dpi_threat="" scanner="" brute_force="" default_score="" config_get waf_block scoring waf_block "25" config_get waf_alert scoring waf_alert "15" config_get cs_ban scoring crowdsec_ban "30" config_get dpi_threat scoring dpi_threat "20" config_get scanner scoring scanner "35" config_get brute_force scoring brute_force "40" config_get default_score scoring default "10" # Calculate sensitivity multiplier case "$sensitivity" in low) mult=70 ;; medium) mult=100 ;; high) mult=130 ;; *) mult=100 ;; esac cat << EOF { "sensitivity": "$sensitivity", "sensitivity_multiplier": $mult, "auto_ban_threshold": $threshold, "auto_ban_duration": "$duration", "notifications": $notifications, "notification_threshold": $notif_threshold, "reputation_decay": $decay, "decay_interval": $decay_interval, "scoring": { "waf_block": $waf_block, "waf_alert": $waf_alert, "crowdsec_ban": $cs_ban, "dpi_threat": $dpi_threat, "scanner": $scanner, "brute_force": $brute_force, "default": $default_score } } EOF ;; set_tuning) read "$3" json_load "$REPLY" json_get_var param param "" json_get_var value value "" if [ -z "$param" ] || [ -z "$value" ]; then echo '{"success": false, "error": "param and value required"}' else case "$param" in sensitivity) case "$value" in low|medium|high|custom) uci set dpi-dual.correlation.sensitivity="$value" uci commit dpi-dual echo '{"success": true, "param": "sensitivity", "value": "'"$value"'"}' ;; *) echo '{"success": false, "error": "Invalid sensitivity: use low, medium, high, or custom"}' ;; esac ;; threshold) if [ "$value" -ge 0 ] && [ "$value" -le 100 ] 2>/dev/null; then uci set dpi-dual.correlation.auto_ban_threshold="$value" uci commit dpi-dual echo '{"success": true, "param": "threshold", "value": '$value'}' else echo '{"success": false, "error": "Threshold must be 0-100"}' fi ;; duration) uci set dpi-dual.correlation.auto_ban_duration="$value" uci commit dpi-dual echo '{"success": true, "param": "duration", "value": "'"$value"'"}' ;; decay) if [ "$value" -ge 0 ] && [ "$value" -le 50 ] 2>/dev/null; then uci set dpi-dual.correlation.reputation_decay="$value" uci commit dpi-dual echo '{"success": true, "param": "decay", "value": '$value'}' else echo '{"success": false, "error": "Decay must be 0-50"}' fi ;; notification_threshold) if [ "$value" -ge 0 ] && [ "$value" -le 100 ] 2>/dev/null; then uci set dpi-dual.correlation.notification_threshold="$value" uci commit dpi-dual echo '{"success": true, "param": "notification_threshold", "value": '$value'}' else echo '{"success": false, "error": "Notification threshold must be 0-100"}' fi ;; waf_block|waf_alert|crowdsec_ban|dpi_threat|scanner|brute_force|default) if [ "$value" -ge 0 ] && [ "$value" -le 100 ] 2>/dev/null; then uci set dpi-dual.scoring."$param"="$value" uci commit dpi-dual echo '{"success": true, "param": "'"$param"'", "value": '$value'}' else echo '{"success": false, "error": "Score weight must be 0-100"}' fi ;; *) echo '{"success": false, "error": "Unknown param: '"$param"'"}' ;; esac fi ;; whitelist_add) read "$3" json_load "$REPLY" json_get_var ip ip "" if [ -z "$ip" ]; then echo '{"success": false, "error": "IP required"}' else uci add_list dpi-dual.whitelist.ip="$ip" uci commit dpi-dual echo '{"success": true, "message": "Added '"$ip"' to whitelist"}' fi ;; whitelist_remove) read "$3" json_load "$REPLY" json_get_var ip ip "" if [ -z "$ip" ]; then echo '{"success": false, "error": "IP required"}' else uci del_list dpi-dual.whitelist.ip="$ip" uci commit dpi-dual echo '{"success": true, "message": "Removed '"$ip"' from whitelist"}' fi ;; whitelist_list) config_load dpi-dual # Collect whitelist IPs wl_ips="" append_wl_ip() { [ -n "$wl_ips" ] && wl_ips="$wl_ips," wl_ips="$wl_ips\"$1\"" } config_list_foreach whitelist ip append_wl_ip echo "{\"whitelist\": [$wl_ips]}" ;; reset_reputation) read "$3" json_load "$REPLY" json_get_var ip ip "" if [ -z "$ip" ]; then echo '{"success": false, "error": "IP required"}' else . /usr/lib/dpi-dual/correlation-lib.sh reset_ip_reputation "$ip" echo '{"success": true, "message": "Reset reputation for '"$ip"'"}' fi ;; get_lan_status) # LAN passive flow analysis status config_load dpi-dual lan_enabled="" lan_if="" config_get lan_enabled lan enabled "0" config_get lan_if lan interface "br-lan" collector_running=0 pgrep -f dpi-lan-collect >/dev/null 2>&1 && collector_running=1 lan_file="$STATS_DIR/lan-flows.json" active_clients=0 unique_dests=0 detected_protos=0 rx_bytes=0 tx_bytes=0 if [ -f "$lan_file" ]; then active_clients=$(jsonfilter -i "$lan_file" -e '@.active_clients' 2>/dev/null || echo 0) unique_dests=$(jsonfilter -i "$lan_file" -e '@.unique_destinations' 2>/dev/null || echo 0) detected_protos=$(jsonfilter -i "$lan_file" -e '@.detected_protocols' 2>/dev/null || echo 0) rx_bytes=$(jsonfilter -i "$lan_file" -e '@.rx_bytes' 2>/dev/null || echo 0) tx_bytes=$(jsonfilter -i "$lan_file" -e '@.tx_bytes' 2>/dev/null || echo 0) fi cat << EOF { "enabled": $lan_enabled, "interface": "$lan_if", "collector_running": $collector_running, "active_clients": $active_clients, "unique_destinations": $unique_dests, "detected_protocols": $detected_protos, "rx_bytes": $rx_bytes, "tx_bytes": $tx_bytes } EOF ;; get_lan_clients) clients_file="$STATS_DIR/lan-clients.json" if [ -f "$clients_file" ]; then cat "$clients_file" else echo '{"timestamp":"","clients":[]}' fi ;; get_lan_destinations) read "$3" json_load "$REPLY" json_get_var limit limit 100 dests_file="$STATS_DIR/lan-destinations.json" if [ -f "$dests_file" ]; then cat "$dests_file" else echo '{"timestamp":"","destinations":[]}' fi ;; get_lan_protocols) protos_file="$STATS_DIR/lan-protocols.json" if [ -f "$protos_file" ]; then cat "$protos_file" else echo '{"timestamp":"","protocols":[]}' fi ;; *) echo '{"error": "Unknown method"}' ;; esac ;; esac