feat(dpi): Implement Phase 1 of Dual-Stream DPI architecture
- secubox-dpi-dual package with parallel MITM + Passive TAP analysis - TAP stream: tc mirred port mirroring to dummy interface for netifyd - Flow collector: Stats aggregation from netifyd, cleanup, JSON output - Correlation engine: Matches MITM WAF events with TAP flow data - Watches CrowdSec decisions and WAF alerts for threat enrichment - CLI: dpi-dualctl with start/stop/status/flows/threats/mirror commands - Procd service: manages flow-collector + correlator instances - MITM double buffer: dpi_buffer.py mitmproxy addon (Phase 2 prep) - UCI config: dual/mitm-only/tap-only mode selection Architecture: package/secubox/DUAL-STREAM-DPI.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fccac11148
commit
58a51eb271
@ -5187,3 +5187,18 @@ git checkout HEAD -- index.html
|
|||||||
- ↻ Auto-rotate button: continuous rotation animation
|
- ↻ Auto-rotate button: continuous rotation animation
|
||||||
- **Default Colorset**: RGB (simple red/green/blue)
|
- **Default Colorset**: RGB (simple red/green/blue)
|
||||||
- **Deployed**: https://wall.maegia.tv/
|
- **Deployed**: https://wall.maegia.tv/
|
||||||
|
|
||||||
|
### 2026-03-15
|
||||||
|
|
||||||
|
- **Dual-Stream DPI Architecture (Phase 1 Complete)**
|
||||||
|
- New `secubox-dpi-dual` package: parallel MITM + Passive TAP deep packet inspection
|
||||||
|
- Architecture: DUAL-STREAM-DPI.md comprehensive spec document
|
||||||
|
- TAP Stream: tc mirred port mirroring → dummy interface → netifyd/nDPI analysis
|
||||||
|
- Flow Collector: Stats aggregation from netifyd, writes `/tmp/secubox/dpi-flows.json`
|
||||||
|
- Correlation Engine: Matches MITM WAF events + TAP flow data, CrowdSec integration
|
||||||
|
- CLI: `dpi-dualctl` start/stop/status/flows/threats/mirror
|
||||||
|
- Procd service: manages flow-collector + correlator instances
|
||||||
|
- MITM Double Buffer: `dpi_buffer.py` mitmproxy addon (Phase 2 prep)
|
||||||
|
- UCI config: `/etc/config/dpi-dual` with dual/mitm-only/tap-only modes
|
||||||
|
- Files: mirror-setup.sh, dpi-flow-collector, dpi-correlator, dpi-dualctl, init.d/dpi-dual, dpi_buffer.py
|
||||||
|
|
||||||
|
|||||||
@ -579,6 +579,38 @@ _Last updated: 2026-03-15 (Wall Colorsets)_
|
|||||||
|
|
||||||
(No active tasks)
|
(No active tasks)
|
||||||
|
|
||||||
|
### 2026-03-15
|
||||||
|
|
||||||
|
- **Dual-Stream DPI Architecture (Phase 1 Complete)**
|
||||||
|
- New `secubox-dpi-dual` package implementing parallel MITM + Passive TAP DPI
|
||||||
|
- Architecture doc: `package/secubox/DUAL-STREAM-DPI.md`
|
||||||
|
- **TAP Stream (Passive)**:
|
||||||
|
- `mirror-setup.sh`: tc mirred port mirroring (ingress + egress)
|
||||||
|
- Creates dummy TAP interface for netifyd analysis
|
||||||
|
- Software and hardware TAP mode support
|
||||||
|
- **Flow Collector**:
|
||||||
|
- `dpi-flow-collector`: Aggregates netifyd flow statistics
|
||||||
|
- Writes stats to `/tmp/secubox/dpi-flows.json`
|
||||||
|
- Interface stats from /sys/class/net
|
||||||
|
- Configurable flow retention cleanup
|
||||||
|
- **Correlation Engine**:
|
||||||
|
- `dpi-correlator`: Matches MITM + TAP stream events
|
||||||
|
- Watches CrowdSec decisions and WAF alerts
|
||||||
|
- Enriches threats with context from both streams
|
||||||
|
- Output: `/tmp/secubox/correlated-threats.json`
|
||||||
|
- **CLI Tool**:
|
||||||
|
- `dpi-dualctl`: start/stop/restart/status/flows/threats/mirror
|
||||||
|
- Shows unified status of both streams
|
||||||
|
- **Procd Service**:
|
||||||
|
- `init.d/dpi-dual`: Manages flow-collector and correlator instances
|
||||||
|
- Auto-starts based on UCI mode setting (dual/mitm-only/tap-only)
|
||||||
|
- **MITM Double Buffer (Phase 2 prep)**:
|
||||||
|
- `dpi_buffer.py`: mitmproxy addon for async analysis
|
||||||
|
- Ring buffer with configurable size (1000 requests default)
|
||||||
|
- Heuristic threat scoring (path traversal, XSS, SQLi, LFI patterns)
|
||||||
|
- Writes threats to `/tmp/secubox/waf-alerts.json`
|
||||||
|
- **UCI Config**: `/etc/config/dpi-dual` with global, mitm, tap, correlation sections
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Next Up
|
## Next Up
|
||||||
|
|||||||
51
package/secubox/secubox-dpi-dual/Makefile
Normal file
51
package/secubox/secubox-dpi-dual/Makefile
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=secubox-dpi-dual
|
||||||
|
PKG_VERSION:=1.0.0
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_MAINTAINER:=SecuBox <secubox@gk2.net>
|
||||||
|
PKG_LICENSE:=GPL-3.0
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define Package/secubox-dpi-dual
|
||||||
|
SECTION:=secubox
|
||||||
|
CATEGORY:=SecuBox
|
||||||
|
SUBMENU:=Security
|
||||||
|
TITLE:=Dual-Stream DPI (MITM + Passive TAP)
|
||||||
|
DEPENDS:=+netifyd +iproute2-tc +jsonfilter +coreutils-stat
|
||||||
|
PKGARCH:=all
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/secubox-dpi-dual/description
|
||||||
|
Dual-stream Deep Packet Inspection architecture:
|
||||||
|
- Stream 1 (MITM): HAProxy + mitmproxy with double buffer
|
||||||
|
- Stream 2 (TAP): tc mirred + netifyd passive analysis
|
||||||
|
- Correlation engine for unified threat analytics
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/secubox-dpi-dual/conffiles
|
||||||
|
/etc/config/dpi-dual
|
||||||
|
endef
|
||||||
|
|
||||||
|
define Package/secubox-dpi-dual/install
|
||||||
|
$(INSTALL_DIR) $(1)/etc/config
|
||||||
|
$(INSTALL_CONF) ./files/etc/config/dpi-dual $(1)/etc/config/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
|
$(INSTALL_BIN) ./files/etc/init.d/dpi-dual $(1)/etc/init.d/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/sbin
|
||||||
|
$(INSTALL_BIN) ./files/usr/sbin/dpi-dualctl $(1)/usr/sbin/
|
||||||
|
$(INSTALL_BIN) ./files/usr/sbin/dpi-flow-collector $(1)/usr/sbin/
|
||||||
|
$(INSTALL_BIN) ./files/usr/sbin/dpi-correlator $(1)/usr/sbin/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/usr/lib/dpi-dual
|
||||||
|
$(INSTALL_BIN) ./files/usr/lib/dpi-dual/mirror-setup.sh $(1)/usr/lib/dpi-dual/
|
||||||
|
|
||||||
|
$(INSTALL_DIR) $(1)/srv/mitmproxy/addons
|
||||||
|
$(INSTALL_DATA) ./files/srv/mitmproxy/addons/dpi_buffer.py $(1)/srv/mitmproxy/addons/
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call BuildPackage,secubox-dpi-dual))
|
||||||
27
package/secubox/secubox-dpi-dual/files/etc/config/dpi-dual
Normal file
27
package/secubox/secubox-dpi-dual/files/etc/config/dpi-dual
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
config global 'settings'
|
||||||
|
option enabled '1'
|
||||||
|
option mode 'dual'
|
||||||
|
option correlation '1'
|
||||||
|
option stats_dir '/tmp/secubox'
|
||||||
|
option flow_dir '/tmp/dpi-flows'
|
||||||
|
|
||||||
|
config mitm 'mitm'
|
||||||
|
option enabled '1'
|
||||||
|
option buffer_size '1000'
|
||||||
|
option async_analysis '1'
|
||||||
|
option replay_on_alert '1'
|
||||||
|
option buffer_dir '/tmp/dpi-buffer'
|
||||||
|
|
||||||
|
config tap 'tap'
|
||||||
|
option enabled '1'
|
||||||
|
option interface 'tap0'
|
||||||
|
option mirror_source 'eth0'
|
||||||
|
option mirror_mode 'software'
|
||||||
|
option flow_retention '300'
|
||||||
|
option netifyd_instance 'tap'
|
||||||
|
|
||||||
|
config correlation 'correlation'
|
||||||
|
option enabled '1'
|
||||||
|
option window '60'
|
||||||
|
option output '/tmp/secubox/correlated-threats.json'
|
||||||
|
option watch_crowdsec '1'
|
||||||
91
package/secubox/secubox-dpi-dual/files/etc/init.d/dpi-dual
Normal file
91
package/secubox/secubox-dpi-dual/files/etc/init.d/dpi-dual
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#!/bin/sh /etc/rc.common
|
||||||
|
# DPI Dual-Stream procd service
|
||||||
|
# Part of secubox-dpi-dual package
|
||||||
|
|
||||||
|
START=95
|
||||||
|
STOP=10
|
||||||
|
USE_PROCD=1
|
||||||
|
|
||||||
|
NAME="dpi-dual"
|
||||||
|
PROG="/usr/sbin/dpi-dualctl"
|
||||||
|
|
||||||
|
validate_section() {
|
||||||
|
uci_load_validate dpi-dual global "$1" "$2" \
|
||||||
|
'enabled:bool:1' \
|
||||||
|
'mode:string:dual' \
|
||||||
|
'correlation:bool:1' \
|
||||||
|
'stats_dir:string:/tmp/secubox' \
|
||||||
|
'flow_dir:string:/tmp/dpi-flows'
|
||||||
|
}
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
local enabled mode
|
||||||
|
|
||||||
|
config_load dpi-dual
|
||||||
|
config_get enabled settings enabled "1"
|
||||||
|
config_get mode settings mode "dual"
|
||||||
|
|
||||||
|
[ "$enabled" != "1" ] && {
|
||||||
|
echo "DPI Dual-Stream is disabled"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Starting DPI Dual-Stream (mode: $mode)..."
|
||||||
|
|
||||||
|
# Create directories
|
||||||
|
local stats_dir flow_dir
|
||||||
|
config_get stats_dir settings stats_dir "/tmp/secubox"
|
||||||
|
config_get flow_dir settings flow_dir "/tmp/dpi-flows"
|
||||||
|
mkdir -p "$stats_dir" "$flow_dir"
|
||||||
|
|
||||||
|
# Start TAP stream if enabled
|
||||||
|
case "$mode" in
|
||||||
|
dual|tap-only)
|
||||||
|
/usr/lib/dpi-dual/mirror-setup.sh start
|
||||||
|
|
||||||
|
# Start flow collector as procd service
|
||||||
|
procd_open_instance flow-collector
|
||||||
|
procd_set_param command /usr/sbin/dpi-flow-collector start
|
||||||
|
procd_set_param respawn
|
||||||
|
procd_set_param stdout 1
|
||||||
|
procd_set_param stderr 1
|
||||||
|
procd_close_instance
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Start correlator if enabled
|
||||||
|
local correlation
|
||||||
|
config_get correlation settings correlation "1"
|
||||||
|
if [ "$correlation" = "1" ]; then
|
||||||
|
procd_open_instance correlator
|
||||||
|
procd_set_param command /usr/sbin/dpi-correlator start
|
||||||
|
procd_set_param respawn
|
||||||
|
procd_set_param stdout 1
|
||||||
|
procd_set_param stderr 1
|
||||||
|
procd_close_instance
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "DPI Dual-Stream started"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_service() {
|
||||||
|
echo "Stopping DPI Dual-Stream..."
|
||||||
|
|
||||||
|
# Stop mirror
|
||||||
|
/usr/lib/dpi-dual/mirror-setup.sh stop 2>/dev/null
|
||||||
|
|
||||||
|
echo "DPI Dual-Stream stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_service() {
|
||||||
|
stop_service
|
||||||
|
start_service
|
||||||
|
}
|
||||||
|
|
||||||
|
service_triggers() {
|
||||||
|
procd_add_reload_trigger "dpi-dual"
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
/usr/sbin/dpi-dualctl status
|
||||||
|
}
|
||||||
@ -0,0 +1,233 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
DPI Double Buffer Addon for mitmproxy
|
||||||
|
Part of secubox-dpi-dual package
|
||||||
|
|
||||||
|
Implements the double-buffer pattern:
|
||||||
|
- Buffer A: Live path, minimal latency (default mitmproxy behavior)
|
||||||
|
- Buffer B: Copy for deep analysis, async processing
|
||||||
|
|
||||||
|
This addon queues requests for asynchronous analysis without
|
||||||
|
blocking the live traffic path.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
import asyncio
|
||||||
|
from pathlib import Path
|
||||||
|
from collections import deque
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from mitmproxy import http, ctx
|
||||||
|
|
||||||
|
|
||||||
|
class DPIBuffer:
|
||||||
|
"""Double-buffer for request analysis without blocking live traffic."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.buffer_size = 1000
|
||||||
|
self.buffer: deque = deque(maxlen=self.buffer_size)
|
||||||
|
self.buffer_dir = Path("/tmp/dpi-buffer")
|
||||||
|
self.stats_file = Path("/tmp/secubox/dpi-buffer.json")
|
||||||
|
self.analysis_enabled = True
|
||||||
|
self.request_count = 0
|
||||||
|
self.threat_count = 0
|
||||||
|
|
||||||
|
# Ensure directories exist
|
||||||
|
self.buffer_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
self.stats_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def load(self, loader):
|
||||||
|
"""Load configuration from mitmproxy options."""
|
||||||
|
loader.add_option(
|
||||||
|
name="dpi_buffer_size",
|
||||||
|
typespec=int,
|
||||||
|
default=1000,
|
||||||
|
help="Size of the request buffer for async analysis",
|
||||||
|
)
|
||||||
|
loader.add_option(
|
||||||
|
name="dpi_async_analysis",
|
||||||
|
typespec=bool,
|
||||||
|
default=True,
|
||||||
|
help="Enable asynchronous request analysis",
|
||||||
|
)
|
||||||
|
|
||||||
|
def configure(self, updated):
|
||||||
|
"""Apply configuration updates."""
|
||||||
|
if "dpi_buffer_size" in updated:
|
||||||
|
self.buffer_size = ctx.options.dpi_buffer_size
|
||||||
|
# Resize buffer
|
||||||
|
new_buffer = deque(self.buffer, maxlen=self.buffer_size)
|
||||||
|
self.buffer = new_buffer
|
||||||
|
|
||||||
|
if "dpi_async_analysis" in updated:
|
||||||
|
self.analysis_enabled = ctx.options.dpi_async_analysis
|
||||||
|
|
||||||
|
def request(self, flow: http.HTTPFlow):
|
||||||
|
"""
|
||||||
|
Handle incoming request.
|
||||||
|
Buffer A: Forward immediately (default mitmproxy behavior)
|
||||||
|
Buffer B: Queue for async analysis
|
||||||
|
"""
|
||||||
|
self.request_count += 1
|
||||||
|
|
||||||
|
# Build entry for Buffer B (async analysis)
|
||||||
|
entry = self._build_entry(flow)
|
||||||
|
self.buffer.append(entry)
|
||||||
|
|
||||||
|
# Queue for async analysis if enabled
|
||||||
|
if self.analysis_enabled:
|
||||||
|
asyncio.create_task(self._async_analyze(entry))
|
||||||
|
|
||||||
|
# Update stats periodically (every 10 requests)
|
||||||
|
if self.request_count % 10 == 0:
|
||||||
|
self._write_stats()
|
||||||
|
|
||||||
|
def response(self, flow: http.HTTPFlow):
|
||||||
|
"""Handle response - update buffer entry with response info."""
|
||||||
|
if not flow.request.timestamp_start:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find and update the corresponding entry
|
||||||
|
req_hash = self._request_hash(flow)
|
||||||
|
for entry in self.buffer:
|
||||||
|
if entry.get("req_hash") == req_hash:
|
||||||
|
entry["response"] = {
|
||||||
|
"status": flow.response.status_code if flow.response else None,
|
||||||
|
"content_length": len(flow.response.content) if flow.response and flow.response.content else 0,
|
||||||
|
"content_type": flow.response.headers.get("content-type", "") if flow.response else "",
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
def _build_entry(self, flow: http.HTTPFlow) -> Dict[str, Any]:
|
||||||
|
"""Build a buffer entry from a flow."""
|
||||||
|
content_hash = None
|
||||||
|
if flow.request.content:
|
||||||
|
content_hash = hashlib.md5(flow.request.content).hexdigest()
|
||||||
|
|
||||||
|
client_ip = "unknown"
|
||||||
|
if flow.client_conn and flow.client_conn.peername:
|
||||||
|
client_ip = flow.client_conn.peername[0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ts": flow.request.timestamp_start,
|
||||||
|
"req_hash": self._request_hash(flow),
|
||||||
|
"method": flow.request.method,
|
||||||
|
"host": flow.request.host,
|
||||||
|
"port": flow.request.port,
|
||||||
|
"path": flow.request.path,
|
||||||
|
"headers": dict(flow.request.headers),
|
||||||
|
"content_hash": content_hash,
|
||||||
|
"content_length": len(flow.request.content) if flow.request.content else 0,
|
||||||
|
"client_ip": client_ip,
|
||||||
|
"analyzed": False,
|
||||||
|
"threat_score": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _request_hash(self, flow: http.HTTPFlow) -> str:
|
||||||
|
"""Generate a unique hash for a request."""
|
||||||
|
key = f"{flow.request.timestamp_start}:{flow.request.host}:{flow.request.path}"
|
||||||
|
return hashlib.md5(key.encode()).hexdigest()[:16]
|
||||||
|
|
||||||
|
async def _async_analyze(self, entry: Dict[str, Any]):
|
||||||
|
"""
|
||||||
|
Async analysis pipeline - runs without blocking live traffic.
|
||||||
|
|
||||||
|
Analysis steps:
|
||||||
|
1. Pattern matching against known threat signatures
|
||||||
|
2. Anomaly scoring based on request characteristics
|
||||||
|
3. Rate limiting detection
|
||||||
|
4. Write results to analysis log
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
threat_score = 0
|
||||||
|
|
||||||
|
# Simple heuristic analysis (placeholder for more sophisticated detection)
|
||||||
|
# Check for common attack patterns in path
|
||||||
|
suspicious_patterns = [
|
||||||
|
"../", "..\\", # Path traversal
|
||||||
|
"<script", "javascript:", # XSS
|
||||||
|
"SELECT ", "UNION ", "INSERT ", # SQL injection
|
||||||
|
"/etc/passwd", "/etc/shadow", # LFI
|
||||||
|
"cmd=", "exec=", "system(", # Command injection
|
||||||
|
]
|
||||||
|
|
||||||
|
path_lower = entry.get("path", "").lower()
|
||||||
|
for pattern in suspicious_patterns:
|
||||||
|
if pattern.lower() in path_lower:
|
||||||
|
threat_score += 20
|
||||||
|
|
||||||
|
# Check for unusual content types in requests
|
||||||
|
content_type = entry.get("headers", {}).get("content-type", "")
|
||||||
|
if "multipart/form-data" in content_type and entry.get("content_length", 0) > 1000000:
|
||||||
|
threat_score += 10 # Large file upload
|
||||||
|
|
||||||
|
# Update entry with analysis results
|
||||||
|
entry["analyzed"] = True
|
||||||
|
entry["threat_score"] = min(threat_score, 100)
|
||||||
|
|
||||||
|
# Track threats
|
||||||
|
if threat_score > 30:
|
||||||
|
self.threat_count += 1
|
||||||
|
await self._log_threat(entry)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
ctx.log.error(f"DPI Buffer analysis error: {e}")
|
||||||
|
|
||||||
|
async def _log_threat(self, entry: Dict[str, Any]):
|
||||||
|
"""Log a detected threat to the alerts file."""
|
||||||
|
alert_file = Path("/tmp/secubox/waf-alerts.json")
|
||||||
|
try:
|
||||||
|
alerts = []
|
||||||
|
if alert_file.exists():
|
||||||
|
alerts = json.loads(alert_file.read_text())
|
||||||
|
|
||||||
|
alert_id = len(alerts) + 1
|
||||||
|
alerts.append({
|
||||||
|
"id": alert_id,
|
||||||
|
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||||
|
"client_ip": entry.get("client_ip"),
|
||||||
|
"host": entry.get("host"),
|
||||||
|
"path": entry.get("path"),
|
||||||
|
"method": entry.get("method"),
|
||||||
|
"threat_score": entry.get("threat_score"),
|
||||||
|
"rule": "dpi_buffer_analysis",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Keep last 1000 alerts
|
||||||
|
alerts = alerts[-1000:]
|
||||||
|
alert_file.write_text(json.dumps(alerts, indent=2))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
ctx.log.error(f"Failed to log threat: {e}")
|
||||||
|
|
||||||
|
def _write_stats(self):
|
||||||
|
"""Write buffer statistics to stats file."""
|
||||||
|
try:
|
||||||
|
stats = {
|
||||||
|
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||||
|
"entries": len(self.buffer),
|
||||||
|
"max_size": self.buffer_size,
|
||||||
|
"requests_total": self.request_count,
|
||||||
|
"threats_detected": self.threat_count,
|
||||||
|
"analysis_enabled": self.analysis_enabled,
|
||||||
|
}
|
||||||
|
self.stats_file.write_text(json.dumps(stats, indent=2))
|
||||||
|
except Exception as e:
|
||||||
|
ctx.log.error(f"Failed to write stats: {e}")
|
||||||
|
|
||||||
|
def get_context(self, client_ip: str, window_sec: int = 60) -> list:
|
||||||
|
"""
|
||||||
|
Get recent requests from the same IP for context on alerts.
|
||||||
|
Used by the correlation engine to gather context around threat events.
|
||||||
|
"""
|
||||||
|
now = time.time()
|
||||||
|
return [
|
||||||
|
e for e in self.buffer
|
||||||
|
if e.get("client_ip") == client_ip
|
||||||
|
and now - e.get("ts", 0) < window_sec
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Mitmproxy addon instance
|
||||||
|
addons = [DPIBuffer()]
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Mirror setup for passive DPI using tc mirred
|
||||||
|
# Creates a TAP interface and mirrors traffic from source interface
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
|
||||||
|
config_load dpi-dual
|
||||||
|
|
||||||
|
TAP_IF=""
|
||||||
|
MIRROR_SRC=""
|
||||||
|
MIRROR_MODE=""
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
config_get TAP_IF tap interface "tap0"
|
||||||
|
config_get MIRROR_SRC tap mirror_source "eth0"
|
||||||
|
config_get MIRROR_MODE tap mirror_mode "software"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_tap_interface() {
|
||||||
|
echo "Creating TAP interface: $TAP_IF"
|
||||||
|
|
||||||
|
# Remove if exists
|
||||||
|
ip link show "$TAP_IF" >/dev/null 2>&1 && ip link del "$TAP_IF"
|
||||||
|
|
||||||
|
# Create dummy interface for receiving mirrored packets
|
||||||
|
ip link add name "$TAP_IF" type dummy
|
||||||
|
ip link set "$TAP_IF" up
|
||||||
|
|
||||||
|
# Set promiscuous mode
|
||||||
|
ip link set "$TAP_IF" promisc on
|
||||||
|
|
||||||
|
echo "TAP interface $TAP_IF created"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_mirror_ingress() {
|
||||||
|
echo "Setting up ingress mirror: $MIRROR_SRC -> $TAP_IF"
|
||||||
|
|
||||||
|
# Add ingress qdisc if not exists
|
||||||
|
tc qdisc show dev "$MIRROR_SRC" | grep -q "ingress" || \
|
||||||
|
tc qdisc add dev "$MIRROR_SRC" handle ffff: ingress
|
||||||
|
|
||||||
|
# Mirror all ingress traffic to TAP
|
||||||
|
tc filter add dev "$MIRROR_SRC" parent ffff: protocol all prio 1 \
|
||||||
|
u32 match u32 0 0 \
|
||||||
|
action mirred egress mirror dev "$TAP_IF"
|
||||||
|
|
||||||
|
echo "Ingress mirror configured"
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_mirror_egress() {
|
||||||
|
echo "Setting up egress mirror: $MIRROR_SRC -> $TAP_IF"
|
||||||
|
|
||||||
|
# Add root qdisc for egress if not exists
|
||||||
|
tc qdisc show dev "$MIRROR_SRC" | grep -q "prio" || \
|
||||||
|
tc qdisc add dev "$MIRROR_SRC" handle 1: root prio
|
||||||
|
|
||||||
|
# Mirror all egress traffic to TAP
|
||||||
|
tc filter add dev "$MIRROR_SRC" parent 1: protocol all prio 1 \
|
||||||
|
u32 match u32 0 0 \
|
||||||
|
action mirred egress mirror dev "$TAP_IF"
|
||||||
|
|
||||||
|
echo "Egress mirror configured"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_mirror() {
|
||||||
|
echo "Cleaning up mirror configuration for $MIRROR_SRC"
|
||||||
|
|
||||||
|
# Remove ingress qdisc
|
||||||
|
tc qdisc del dev "$MIRROR_SRC" handle ffff: ingress 2>/dev/null
|
||||||
|
|
||||||
|
# Remove root qdisc (careful - this removes all tc config)
|
||||||
|
tc qdisc del dev "$MIRROR_SRC" root 2>/dev/null
|
||||||
|
|
||||||
|
echo "Mirror cleanup done"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_tap_interface() {
|
||||||
|
echo "Removing TAP interface: $TAP_IF"
|
||||||
|
ip link del "$TAP_IF" 2>/dev/null
|
||||||
|
echo "TAP interface removed"
|
||||||
|
}
|
||||||
|
|
||||||
|
status() {
|
||||||
|
echo "=== TAP Interface ==="
|
||||||
|
if ip link show "$TAP_IF" >/dev/null 2>&1; then
|
||||||
|
ip -s link show "$TAP_IF"
|
||||||
|
else
|
||||||
|
echo "TAP interface $TAP_IF not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Mirror Source: $MIRROR_SRC ==="
|
||||||
|
echo "Ingress qdisc:"
|
||||||
|
tc qdisc show dev "$MIRROR_SRC" | grep ingress || echo " (none)"
|
||||||
|
echo "Ingress filters:"
|
||||||
|
tc filter show dev "$MIRROR_SRC" parent ffff: 2>/dev/null || echo " (none)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Egress qdisc:"
|
||||||
|
tc qdisc show dev "$MIRROR_SRC" | grep -v ingress || echo " (none)"
|
||||||
|
echo "Egress filters:"
|
||||||
|
tc filter show dev "$MIRROR_SRC" parent 1: 2>/dev/null || echo " (none)"
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
load_config
|
||||||
|
|
||||||
|
if [ "$MIRROR_MODE" = "hardware" ]; then
|
||||||
|
echo "Hardware TAP mode - configure switch port mirroring manually"
|
||||||
|
echo "TAP interface: $TAP_IF"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Software mirroring with tc mirred
|
||||||
|
create_tap_interface
|
||||||
|
setup_mirror_ingress
|
||||||
|
setup_mirror_egress
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Mirror setup complete:"
|
||||||
|
echo " Source: $MIRROR_SRC"
|
||||||
|
echo " TAP: $TAP_IF"
|
||||||
|
echo " Mode: $MIRROR_MODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
load_config
|
||||||
|
|
||||||
|
cleanup_mirror
|
||||||
|
remove_tap_interface
|
||||||
|
|
||||||
|
echo "Mirror stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
stop
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
stop
|
||||||
|
sleep 1
|
||||||
|
start
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
load_config
|
||||||
|
status
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
184
package/secubox/secubox-dpi-dual/files/usr/sbin/dpi-correlator
Normal file
184
package/secubox/secubox-dpi-dual/files/usr/sbin/dpi-correlator
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# DPI Correlator - Matches events from MITM and TAP streams
|
||||||
|
# Part of secubox-dpi-dual package
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
|
||||||
|
config_load dpi-dual
|
||||||
|
|
||||||
|
MITM_LOG=""
|
||||||
|
DPI_FLOWS=""
|
||||||
|
CORRELATED=""
|
||||||
|
WINDOW=""
|
||||||
|
WATCH_CROWDSEC=""
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
config_get MITM_LOG mitm log_file "/var/log/mitmproxy/access.log"
|
||||||
|
config_get DPI_FLOWS settings flow_dir "/tmp/dpi-flows"
|
||||||
|
config_get CORRELATED correlation output "/tmp/secubox/correlated-threats.json"
|
||||||
|
config_get WINDOW correlation window "60"
|
||||||
|
config_get WATCH_CROWDSEC correlation watch_crowdsec "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
init_files() {
|
||||||
|
local corr_dir
|
||||||
|
corr_dir=$(dirname "$CORRELATED")
|
||||||
|
mkdir -p "$corr_dir"
|
||||||
|
[ ! -f "$CORRELATED" ] && echo "[]" > "$CORRELATED"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get recent MITM requests from an IP
|
||||||
|
get_mitm_context() {
|
||||||
|
local ip="$1"
|
||||||
|
local count="${2:-10}"
|
||||||
|
|
||||||
|
if [ -f "$MITM_LOG" ]; then
|
||||||
|
grep "$ip" "$MITM_LOG" 2>/dev/null | tail -"$count" | \
|
||||||
|
awk -F'\t' '{printf "{\"ts\":\"%s\",\"method\":\"%s\",\"host\":\"%s\",\"path\":\"%s\"},", $1, $2, $3, $4}' | \
|
||||||
|
sed 's/,$//'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get DPI flow info for an IP
|
||||||
|
get_dpi_context() {
|
||||||
|
local ip="$1"
|
||||||
|
|
||||||
|
if [ -d "$DPI_FLOWS" ]; then
|
||||||
|
find "$DPI_FLOWS" -name "*.json" -exec grep -l "$ip" {} \; 2>/dev/null | \
|
||||||
|
head -5 | xargs cat 2>/dev/null | \
|
||||||
|
jsonfilter -e '@' 2>/dev/null | \
|
||||||
|
tr '\n' ',' | sed 's/,$//'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Correlate a threat event
|
||||||
|
correlate_threat() {
|
||||||
|
local ip="$1"
|
||||||
|
local timestamp="$2"
|
||||||
|
local reason="${3:-unknown}"
|
||||||
|
|
||||||
|
local mitm_ctx dpi_ctx
|
||||||
|
mitm_ctx=$(get_mitm_context "$ip")
|
||||||
|
dpi_ctx=$(get_dpi_context "$ip")
|
||||||
|
|
||||||
|
# Build correlation entry
|
||||||
|
local entry
|
||||||
|
entry=$(cat << EOF
|
||||||
|
{
|
||||||
|
"ip": "$ip",
|
||||||
|
"timestamp": "$timestamp",
|
||||||
|
"reason": "$reason",
|
||||||
|
"mitm_context": [$mitm_ctx],
|
||||||
|
"dpi_context": [$dpi_ctx],
|
||||||
|
"correlated_at": "$(date -Iseconds)"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# Append to correlated file (keep last 1000 entries)
|
||||||
|
local tmp_file="/tmp/correlated_$$.json"
|
||||||
|
if [ -f "$CORRELATED" ]; then
|
||||||
|
# Read existing, add new, keep last 1000
|
||||||
|
(cat "$CORRELATED" 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null; echo "$entry") | \
|
||||||
|
tail -1000 | \
|
||||||
|
awk 'BEGIN{print "["} {if(NR>1)print ","; print} END{print "]"}' > "$tmp_file"
|
||||||
|
mv "$tmp_file" "$CORRELATED"
|
||||||
|
else
|
||||||
|
echo "[$entry]" > "$CORRELATED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Correlated threat: $ip ($reason)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Watch CrowdSec for new decisions
|
||||||
|
watch_crowdsec_decisions() {
|
||||||
|
local cs_db="/var/lib/crowdsec/data/crowdsec.db"
|
||||||
|
local last_check="/tmp/dpi-correlator-lastcheck"
|
||||||
|
|
||||||
|
[ ! -f "$cs_db" ] && return
|
||||||
|
|
||||||
|
# Get timestamp of last check
|
||||||
|
local last_ts=0
|
||||||
|
[ -f "$last_check" ] && last_ts=$(cat "$last_check")
|
||||||
|
|
||||||
|
# Query new decisions (simplified - just check recent bans)
|
||||||
|
if command -v cscli >/dev/null 2>&1; then
|
||||||
|
cscli decisions list -o json 2>/dev/null | \
|
||||||
|
jsonfilter -e '@[*].value' 2>/dev/null | \
|
||||||
|
while read -r ip; do
|
||||||
|
[ -n "$ip" ] && correlate_threat "$ip" "$(date -Iseconds)" "crowdsec_ban"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update last check timestamp
|
||||||
|
date +%s > "$last_check"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Watch for WAF alerts from mitmproxy
|
||||||
|
watch_waf_alerts() {
|
||||||
|
local waf_alerts="/tmp/secubox/waf-alerts.json"
|
||||||
|
local last_alert="/tmp/dpi-correlator-lastalert"
|
||||||
|
|
||||||
|
[ ! -f "$waf_alerts" ] && return
|
||||||
|
|
||||||
|
local last_id=0
|
||||||
|
[ -f "$last_alert" ] && last_id=$(cat "$last_alert")
|
||||||
|
|
||||||
|
# Process new alerts
|
||||||
|
local current_id
|
||||||
|
current_id=$(jsonfilter -i "$waf_alerts" -e '@[-1].id' 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
if [ "$current_id" -gt "$last_id" ]; then
|
||||||
|
# Get new alerts
|
||||||
|
jsonfilter -i "$waf_alerts" -e '@[*]' 2>/dev/null | while read -r alert; do
|
||||||
|
local alert_id ip reason
|
||||||
|
alert_id=$(echo "$alert" | jsonfilter -e '@.id' 2>/dev/null)
|
||||||
|
[ "$alert_id" -le "$last_id" ] && continue
|
||||||
|
|
||||||
|
ip=$(echo "$alert" | jsonfilter -e '@.client_ip' 2>/dev/null)
|
||||||
|
reason=$(echo "$alert" | jsonfilter -e '@.rule' 2>/dev/null || echo "waf_alert")
|
||||||
|
|
||||||
|
[ -n "$ip" ] && correlate_threat "$ip" "$(date -Iseconds)" "$reason"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$current_id" > "$last_alert"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_correlator() {
|
||||||
|
load_config
|
||||||
|
init_files
|
||||||
|
|
||||||
|
echo "DPI Correlator started"
|
||||||
|
echo " Window: ${WINDOW}s"
|
||||||
|
echo " Output: $CORRELATED"
|
||||||
|
echo " Watch CrowdSec: $WATCH_CROWDSEC"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
[ "$WATCH_CROWDSEC" = "1" ] && watch_crowdsec_decisions
|
||||||
|
watch_waf_alerts
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
run_correlator
|
||||||
|
;;
|
||||||
|
correlate)
|
||||||
|
# Manual correlation: dpi-correlator correlate <ip> [reason]
|
||||||
|
load_config
|
||||||
|
init_files
|
||||||
|
[ -n "$2" ] && correlate_threat "$2" "$(date -Iseconds)" "${3:-manual}"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
load_config
|
||||||
|
echo "Correlated threats: $(wc -l < "$CORRELATED" 2>/dev/null || echo 0)"
|
||||||
|
echo "Last 5 correlations:"
|
||||||
|
tail -5 "$CORRELATED" 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null || echo " (none)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|correlate <ip> [reason]|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
235
package/secubox/secubox-dpi-dual/files/usr/sbin/dpi-dualctl
Normal file
235
package/secubox/secubox-dpi-dual/files/usr/sbin/dpi-dualctl
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# DPI Dual Control - CLI for dual-stream DPI management
|
||||||
|
# Part of secubox-dpi-dual package
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
|
||||||
|
config_load dpi-dual
|
||||||
|
|
||||||
|
STATS_DIR=""
|
||||||
|
FLOW_DIR=""
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
config_get STATS_DIR settings stats_dir "/tmp/secubox"
|
||||||
|
config_get FLOW_DIR settings flow_dir "/tmp/dpi-flows"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_start() {
|
||||||
|
echo "Starting DPI Dual-Stream..."
|
||||||
|
|
||||||
|
# Check mode
|
||||||
|
local mode
|
||||||
|
config_get mode settings mode "dual"
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
dual|tap-only)
|
||||||
|
echo "Starting TAP stream..."
|
||||||
|
/usr/lib/dpi-dual/mirror-setup.sh start
|
||||||
|
|
||||||
|
# Restart netifyd to pick up TAP interface
|
||||||
|
if /etc/init.d/netifyd enabled 2>/dev/null; then
|
||||||
|
/etc/init.d/netifyd restart
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Start flow collector
|
||||||
|
start-stop-daemon -S -b -x /usr/sbin/dpi-flow-collector -- start
|
||||||
|
echo "TAP stream started"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
dual|mitm-only)
|
||||||
|
echo "MITM stream managed by mitmproxy service"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Start correlator if enabled
|
||||||
|
local correlation
|
||||||
|
config_get correlation settings correlation "1"
|
||||||
|
if [ "$correlation" = "1" ]; then
|
||||||
|
echo "Starting correlator..."
|
||||||
|
start-stop-daemon -S -b -x /usr/sbin/dpi-correlator -- start
|
||||||
|
echo "Correlator started"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "DPI Dual-Stream started (mode: $mode)"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_stop() {
|
||||||
|
echo "Stopping DPI Dual-Stream..."
|
||||||
|
|
||||||
|
# Stop correlator
|
||||||
|
killall dpi-correlator 2>/dev/null
|
||||||
|
|
||||||
|
# Stop flow collector
|
||||||
|
killall dpi-flow-collector 2>/dev/null
|
||||||
|
|
||||||
|
# Stop mirror
|
||||||
|
/usr/lib/dpi-dual/mirror-setup.sh stop
|
||||||
|
|
||||||
|
echo "DPI Dual-Stream stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
load_config
|
||||||
|
|
||||||
|
local mode
|
||||||
|
config_get mode settings mode "dual"
|
||||||
|
|
||||||
|
echo "=== DPI Dual-Stream Status ==="
|
||||||
|
echo "Mode: $mode"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== MITM Stream ==="
|
||||||
|
if pgrep mitmproxy >/dev/null 2>&1; then
|
||||||
|
echo "Status: RUNNING"
|
||||||
|
pgrep -a mitmproxy | head -1
|
||||||
|
else
|
||||||
|
echo "Status: STOPPED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local buffer_file="$STATS_DIR/dpi-buffer.json"
|
||||||
|
if [ -f "$buffer_file" ]; then
|
||||||
|
local entries
|
||||||
|
entries=$(jsonfilter -i "$buffer_file" -e '@.entries' 2>/dev/null || echo 0)
|
||||||
|
echo "Buffer entries: $entries"
|
||||||
|
else
|
||||||
|
echo "Buffer: not available"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== TAP Stream ==="
|
||||||
|
local tap_if
|
||||||
|
config_get tap_if tap interface "tap0"
|
||||||
|
|
||||||
|
if ip link show "$tap_if" >/dev/null 2>&1; then
|
||||||
|
echo "TAP Interface: $tap_if (UP)"
|
||||||
|
ip -s link show "$tap_if" 2>/dev/null | grep -E "RX:|TX:" | head -2
|
||||||
|
else
|
||||||
|
echo "TAP Interface: $tap_if (DOWN)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if pgrep netifyd >/dev/null 2>&1; then
|
||||||
|
echo "netifyd: RUNNING"
|
||||||
|
else
|
||||||
|
echo "netifyd: STOPPED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if pgrep dpi-flow-collector >/dev/null 2>&1; then
|
||||||
|
echo "Flow Collector: RUNNING"
|
||||||
|
else
|
||||||
|
echo "Flow Collector: STOPPED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local flows_file="$STATS_DIR/dpi-flows.json"
|
||||||
|
if [ -f "$flows_file" ]; then
|
||||||
|
local flows_1min
|
||||||
|
flows_1min=$(jsonfilter -i "$flows_file" -e '@.flows_1min' 2>/dev/null || echo 0)
|
||||||
|
echo "Flows (1min): $flows_1min"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "=== Correlation Engine ==="
|
||||||
|
if pgrep dpi-correlator >/dev/null 2>&1; then
|
||||||
|
echo "Status: RUNNING"
|
||||||
|
else
|
||||||
|
echo "Status: STOPPED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local corr_file
|
||||||
|
config_get corr_file correlation output "/tmp/secubox/correlated-threats.json"
|
||||||
|
if [ -f "$corr_file" ]; then
|
||||||
|
local threats
|
||||||
|
threats=$(wc -l < "$corr_file" 2>/dev/null || echo 0)
|
||||||
|
echo "Threats correlated: $threats"
|
||||||
|
else
|
||||||
|
echo "Threats correlated: 0"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_flows() {
|
||||||
|
load_config
|
||||||
|
local flows_file="$STATS_DIR/dpi-flows.json"
|
||||||
|
|
||||||
|
if [ -f "$flows_file" ]; then
|
||||||
|
cat "$flows_file"
|
||||||
|
else
|
||||||
|
echo '{"error":"No flow data available"}'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_threats() {
|
||||||
|
local count="${1:-20}"
|
||||||
|
local corr_file
|
||||||
|
config_get corr_file correlation output "/tmp/secubox/correlated-threats.json"
|
||||||
|
|
||||||
|
if [ -f "$corr_file" ]; then
|
||||||
|
tail -"$count" "$corr_file"
|
||||||
|
else
|
||||||
|
echo '[]'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_mirror() {
|
||||||
|
/usr/lib/dpi-dual/mirror-setup.sh "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_help() {
|
||||||
|
cat << EOF
|
||||||
|
DPI Dual-Stream Control
|
||||||
|
|
||||||
|
Usage: $0 <command> [args]
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
start Start all DPI streams (according to mode)
|
||||||
|
stop Stop all DPI streams
|
||||||
|
restart Restart all DPI streams
|
||||||
|
status Show status of all streams
|
||||||
|
flows Show current flow statistics (JSON)
|
||||||
|
threats [N] Show last N correlated threats (default: 20)
|
||||||
|
mirror <cmd> Control mirror setup (start|stop|status)
|
||||||
|
help Show this help
|
||||||
|
|
||||||
|
Configuration: /etc/config/dpi-dual
|
||||||
|
|
||||||
|
Modes:
|
||||||
|
dual - Both MITM and TAP streams active
|
||||||
|
mitm-only - Only MITM stream (HAProxy + mitmproxy)
|
||||||
|
tap-only - Only passive TAP stream (netifyd)
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
cmd_start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
cmd_stop
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
cmd_stop
|
||||||
|
sleep 1
|
||||||
|
cmd_start
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
cmd_status
|
||||||
|
;;
|
||||||
|
flows)
|
||||||
|
cmd_flows
|
||||||
|
;;
|
||||||
|
threats)
|
||||||
|
cmd_threats "$2"
|
||||||
|
;;
|
||||||
|
mirror)
|
||||||
|
shift
|
||||||
|
cmd_mirror "$@"
|
||||||
|
;;
|
||||||
|
help|--help|-h)
|
||||||
|
cmd_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cmd_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# DPI Flow Collector - Aggregates netifyd flow statistics
|
||||||
|
# Part of secubox-dpi-dual package
|
||||||
|
|
||||||
|
. /lib/functions.sh
|
||||||
|
|
||||||
|
config_load dpi-dual
|
||||||
|
|
||||||
|
FLOW_DIR=""
|
||||||
|
STATS_DIR=""
|
||||||
|
RETENTION=""
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
config_get FLOW_DIR settings flow_dir "/tmp/dpi-flows"
|
||||||
|
config_get STATS_DIR settings stats_dir "/tmp/secubox"
|
||||||
|
config_get RETENTION tap flow_retention "300"
|
||||||
|
}
|
||||||
|
|
||||||
|
init_dirs() {
|
||||||
|
mkdir -p "$FLOW_DIR"
|
||||||
|
mkdir -p "$STATS_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_flows() {
|
||||||
|
local stats_file="$STATS_DIR/dpi-flows.json"
|
||||||
|
local netifyd_socket="/var/run/netifyd/netifyd.sock"
|
||||||
|
|
||||||
|
# Count recent flow files
|
||||||
|
local total_flows=0
|
||||||
|
if [ -d "$FLOW_DIR" ]; then
|
||||||
|
total_flows=$(find "$FLOW_DIR" -name "*.json" -mmin -1 2>/dev/null | wc -l)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get protocol distribution from netifyd status
|
||||||
|
local protocols="{}"
|
||||||
|
if [ -S "$netifyd_socket" ]; then
|
||||||
|
# Try to get stats from netifyd socket
|
||||||
|
local proto_data
|
||||||
|
proto_data=$(echo '{"type":"get_stats"}' | nc -U "$netifyd_socket" 2>/dev/null | head -1)
|
||||||
|
if [ -n "$proto_data" ]; then
|
||||||
|
protocols=$(echo "$proto_data" | jsonfilter -e '@.protocols' 2>/dev/null || echo '{}')
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get interface stats from /proc
|
||||||
|
local tap_if
|
||||||
|
config_get tap_if tap interface "tap0"
|
||||||
|
|
||||||
|
local rx_bytes=0 tx_bytes=0 rx_packets=0 tx_packets=0
|
||||||
|
if [ -d "/sys/class/net/$tap_if/statistics" ]; then
|
||||||
|
rx_bytes=$(cat "/sys/class/net/$tap_if/statistics/rx_bytes" 2>/dev/null || echo 0)
|
||||||
|
tx_bytes=$(cat "/sys/class/net/$tap_if/statistics/tx_bytes" 2>/dev/null || echo 0)
|
||||||
|
rx_packets=$(cat "/sys/class/net/$tap_if/statistics/rx_packets" 2>/dev/null || echo 0)
|
||||||
|
tx_packets=$(cat "/sys/class/net/$tap_if/statistics/tx_packets" 2>/dev/null || echo 0)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Write stats JSON
|
||||||
|
cat > "$stats_file" << EOF
|
||||||
|
{
|
||||||
|
"timestamp": "$(date -Iseconds)",
|
||||||
|
"flows_1min": $total_flows,
|
||||||
|
"tap_interface": "$tap_if",
|
||||||
|
"rx_bytes": $rx_bytes,
|
||||||
|
"tx_bytes": $tx_bytes,
|
||||||
|
"rx_packets": $rx_packets,
|
||||||
|
"tx_packets": $tx_packets,
|
||||||
|
"protocols": $protocols,
|
||||||
|
"mode": "passive_tap"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup_old_flows() {
|
||||||
|
# Remove flow files older than retention period
|
||||||
|
local retention_min=$((RETENTION / 60))
|
||||||
|
[ "$retention_min" -lt 1 ] && retention_min=1
|
||||||
|
|
||||||
|
find "$FLOW_DIR" -name "*.json" -mmin +"$retention_min" -delete 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
run_collector() {
|
||||||
|
load_config
|
||||||
|
init_dirs
|
||||||
|
|
||||||
|
echo "DPI Flow Collector started"
|
||||||
|
echo " Flow dir: $FLOW_DIR"
|
||||||
|
echo " Stats dir: $STATS_DIR"
|
||||||
|
echo " Retention: ${RETENTION}s"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
collect_flows
|
||||||
|
cleanup_old_flows
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
run_collector
|
||||||
|
;;
|
||||||
|
once)
|
||||||
|
load_config
|
||||||
|
init_dirs
|
||||||
|
collect_flows
|
||||||
|
cleanup_old_flows
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|once}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Loading…
Reference in New Issue
Block a user