Add complete French (fr) and Chinese (zh) translations for all documentation: - Root files: README, CHANGELOG, SECURITY, BETA-RELEASE - docs/: All 16 core documentation files - DOCS/: All 19 deep-dive documents including embedded/ and archive/ - package/secubox/: All 123+ package READMEs - Misc: secubox-tools/, scripts/, EXAMPLES/, config-backups/, streamlit-apps/ Total: 346 translation files created Each file includes language switcher links for easy navigation between English, French, and Chinese versions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
18 KiB
迁移计划: Netifyd 到 nDPId
🌐 Languages: English | Français | 中文
执行摘要
本文档提供了一个全面的迁移计划,将 SecuBox OpenWrt 项目中的 Netifyd v5.2.1 替换为 nDPId,同时保持与现有 CrowdSec 和 Netdata 消费者的完全兼容性。
关键发现: Netifyd 和 nDPId 都构建在 nDPI(底层 DPI 库)之上。Netifyd 本质上是 nDPI 的一个功能丰富的包装器,带有云集成,而 nDPId 是一个极简、高性能的守护进程,具有微服务架构。
当前架构分析
Netifyd 集成概览
| 组件 | 位置 | 用途 |
|---|---|---|
| 基础包 | secubox-app-netifyd |
Netifyd v5.2.1 DPI 引擎 |
| LuCI 应用 | luci-app-secubox-netifyd |
带实时监控的 Web UI |
| RPCD 后端 | /usr/libexec/rpcd/luci.secubox-netifyd |
15 个读取 + 9 个写入 RPC 方法 |
| UCI 配置 | /etc/config/secubox-netifyd |
功能开关、插件、sinks |
| 状态文件 | /var/run/netifyd/status.json |
汇总统计(不是流) |
| Socket | /var/run/netifyd/netifyd.sock |
JSON 流接口 |
| 收集器 | /usr/bin/netifyd-collector |
周期性统计到 /tmp/netifyd-stats.json |
当前数据消费者
- CrowdSec: 没有直接集成。独立运行。
- Netdata: 单独的仪表板。通过
/proc读取系统指标,不是 DPI 数据。 - LuCI 仪表板: 通过 RPCD 后端的主要消费者。
Netifyd 输出格式
汇总统计 (/var/run/netifyd/status.json):
{
"flow_count": 150,
"flows_active": 42,
"devices": [...],
"stats": {
"br-lan": {
"ip_bytes": 1234567,
"wire_bytes": 1345678,
"tcp": 1200,
"udp": 300,
"icmp": 50
}
},
"dns_hint_cache": { "cache_size": 500 },
"uptime": 86400
}
流数据 (启用 sink 时,非默认):
{
"flow_id": "abc123",
"src_ip": "192.168.1.100",
"dst_ip": "8.8.8.8",
"src_port": 54321,
"dst_port": 443,
"protocol": "tcp",
"application": "google",
"category": "search_engine",
"bytes_rx": 1500,
"bytes_tx": 500,
"packets_rx": 10,
"packets_tx": 5
}
nDPId 架构
核心组件
| 组件 | 用途 |
|---|---|
| nDPId | 使用 libpcap + libnDPI 的流量捕获守护进程 |
| nDPIsrvd | 将事件分发给多个消费者的 Broker |
| libnDPI | 核心 DPI 库(与 Netifyd 共享) |
nDPId 事件系统
消息格式: [5位长度][JSON]\n
01223{"flow_event_id":7,"flow_event_name":"detection-update",...}\n
事件类别:
| 类别 | 事件 | 描述 |
|---|---|---|
| 错误 | 17 种 | 数据包处理失败、内存问题 |
| 守护进程 | 4 种 | init, shutdown, reconnect, status |
| 数据包 | 2 种 | packet, packet-flow (base64 编码) |
| 流 | 9 种 | new, end, idle, update, detected, guessed, detection-update, not-detected, analyse |
nDPId 流事件示例
{
"flow_event_id": 5,
"flow_event_name": "detected",
"thread_id": 0,
"packet_id": 12345,
"source": "eth0",
"flow_id": 1001,
"flow_state": "finished",
"flow_src_packets_processed": 15,
"flow_dst_packets_processed": 20,
"flow_first_seen": 1704067200000,
"flow_src_last_pkt_time": 1704067260000,
"flow_dst_last_pkt_time": 1704067258000,
"flow_idle_time": 2000,
"flow_src_tot_l4_payload_len": 1500,
"flow_dst_tot_l4_payload_len": 2000,
"l3_proto": "ip4",
"src_ip": "192.168.1.100",
"dst_ip": "142.250.185.78",
"l4_proto": "tcp",
"src_port": 54321,
"dst_port": 443,
"ndpi": {
"proto": "TLS.Google",
"proto_id": 91,
"proto_by_ip": 0,
"encrypted": 1,
"breed": "Safe",
"category_id": 5,
"category": "Web"
}
}
迁移策略
第一阶段: 兼容层开发
创建一个翻译守护进程,将 nDPId 事件转换为 Netifyd 兼容格式。
新组件: secubox-ndpid-compat
nDPId → nDPIsrvd → secubox-ndpid-compat → 现有消费者
↓
/var/run/netifyd/status.json (兼容)
/tmp/netifyd-stats.json (兼容)
RPCD 后端 (不变)
第二阶段: 软件包开发
2.1 新软件包: secubox-app-ndpid
Makefile:
PKG_NAME:=ndpid
PKG_VERSION:=1.7.0
PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/utoni/nDPId.git
DEPENDS:=+libndpi +libpcap +libjson-c +libpthread
构建要求:
- libnDPI >=5.0.0
- libpcap
- libjson-c
- CMake 构建系统
2.2 新软件包: secubox-ndpid-compat
翻译层脚本:
- 连接到 nDPIsrvd socket
- 将流事件聚合为 Netifyd 兼容格式
- 写入
/var/run/netifyd/status.json - 提供相同的 RPCD 接口
第三阶段: 输出格式翻译
3.1 状态文件翻译映射
| Netifyd 字段 | nDPId 来源 | 翻译逻辑 |
|---|---|---|
flow_count |
流事件计数 | 在 new 时增加,在 end/idle 时减少 |
flows_active |
活动流跟踪 | 计算没有 end/idle 事件的流 |
stats.{iface}.tcp |
l4_proto == "tcp" |
按接口聚合 |
stats.{iface}.udp |
l4_proto == "udp" |
按接口聚合 |
stats.{iface}.ip_bytes |
flow_*_tot_l4_payload_len |
按接口求和 |
uptime |
守护进程 status 事件 |
直接映射 |
3.2 流数据翻译映射
| Netifyd 字段 | nDPId 字段 | 注释 |
|---|---|---|
src_ip |
src_ip |
直接 |
dst_ip |
dst_ip |
直接 |
src_port |
src_port |
直接 |
dst_port |
dst_port |
直接 |
protocol |
l4_proto |
小写 |
application |
ndpi.proto |
从 "TLS.Google" 解析 → "google" |
category |
ndpi.category |
直接 |
bytes_rx |
flow_dst_tot_l4_payload_len |
注意:反转(从流视角 dst=rx) |
bytes_tx |
flow_src_tot_l4_payload_len |
注意:反转 |
3.3 应用名称规范化
nDPId 使用 TLS.Google、QUIC.YouTube 等格式。规范化为小写基础名:
TLS.Google → google
QUIC.YouTube → youtube
HTTP.Facebook → facebook
DNS → dns
第四阶段: 消费者兼容性
4.1 CrowdSec 集成(新)
由于没有现有的 CrowdSec 集成,我们可以正确设计它:
采集配置 (/etc/crowdsec/acquis.d/ndpid.yaml):
source: file
filenames:
- /tmp/ndpid-flows.log
labels:
type: ndpid
---
source: journalctl
journalctl_filter:
- "_SYSTEMD_UNIT=ndpid.service"
labels:
type: syslog
解析器 (/etc/crowdsec/parsers/s02-enrich/ndpid-flows.yaml):
name: secubox/ndpid-flows
description: "解析 nDPId 流检测事件"
filter: "evt.Parsed.program == 'ndpid'"
onsuccess: next_stage
statics:
- parsed: flow_application
expression: evt.Parsed.ndpi_proto
nodes:
- grok:
pattern: '%{IP:src_ip}:%{INT:src_port} -> %{IP:dst_ip}:%{INT:dst_port} %{WORD:proto} %{DATA:app}'
场景 (/etc/crowdsec/scenarios/ndpid-suspicious-app.yaml):
type: leaky
name: secubox/ndpid-suspicious-app
description: "检测可疑应用程序使用"
filter: evt.Parsed.flow_application in ["bittorrent", "tor", "vpn_udp"]
groupby: evt.Parsed.src_ip
capacity: 5
leakspeed: 10m
blackhole: 1h
labels:
remediation: true
4.2 Netdata 集成(新)
为 nDPId 创建自定义 Netdata 收集器:
收集器 (/usr/lib/netdata/plugins.d/ndpid.chart.sh):
#!/bin/bash
# nDPId Netdata 收集器
NDPID_STATUS="/var/run/netifyd/status.json"
# 图表定义
cat << EOF
CHART ndpid.flows '' "网络流" "流" ndpid ndpid.flows area
DIMENSION active '' absolute 1 1
DIMENSION total '' absolute 1 1
EOF
while true; do
if [ -f "$NDPID_STATUS" ]; then
active=$(jq -r '.flows_active // 0' "$NDPID_STATUS")
total=$(jq -r '.flow_count // 0' "$NDPID_STATUS")
echo "BEGIN ndpid.flows"
echo "SET active = $active"
echo "SET total = $total"
echo "END"
fi
sleep 1
done
第五阶段: 插件系统迁移
5.1 IPSet 操作
Netifyd 插件 → nDPId 外部处理器:
| Netifyd 插件 | nDPId 等效 |
|---|---|
libnetify-plugin-ipset.so |
消费流事件的外部脚本 |
libnetify-plugin-nftables.so |
外部 nftables 更新器 |
nDPId 流操作脚本 (/usr/bin/ndpid-flow-actions):
#!/bin/bash
# 处理 nDPId 事件并更新 ipsets
socat -u UNIX-RECV:/tmp/ndpid-actions.sock - | while read -r line; do
# 解析 5 位长度前缀
json="${line:5}"
event=$(echo "$json" | jq -r '.flow_event_name')
app=$(echo "$json" | jq -r '.ndpi.proto' | tr '.' '\n' | tail -1 | tr '[:upper:]' '[:lower:]')
case "$event" in
detected)
case "$app" in
bittorrent)
src_ip=$(echo "$json" | jq -r '.src_ip')
ipset add secubox-bittorrent "$src_ip" timeout 900 2>/dev/null
;;
esac
;;
esac
done
实施阶段
第一阶段: 基础 (第1-2周)
- 创建
secubox-app-ndpid软件包 - 为 OpenWrt 构建 nDPId + nDPIsrvd
- 测试基本流检测
- 创建 UCI 配置模式
第二阶段: 兼容层 (第3-4周)
- 开发
secubox-ndpid-compat翻译守护进程 - 实现 status.json 生成
- 实现流事件聚合
- 用现有 LuCI 仪表板测试
第三阶段: RPCD 后端更新 (第5周)
- 更新 RPCD 方法以使用 nDPId 数据
- 确保所有 15 个读取方法正常工作
- 确保所有 9 个写入方法正常工作
- 测试 LuCI 应用兼容性
第四阶段: 消费者集成 (第6-7周)
- 创建 CrowdSec 解析器/场景
- 创建 Netdata 收集器
- 测试端到端数据流
- 记录新集成
第五阶段: 迁移与清理 (第8周)
- 为现有用户创建迁移脚本
- 更新文档
- 移除 Netifyd 软件包(可选,可以共存)
- 最终测试和发布
迁移后的文件结构
package/secubox/
├── secubox-app-ndpid/ # 新: nDPId 软件包
│ ├── Makefile
│ ├── files/
│ │ ├── ndpid.config # UCI 配置
│ │ ├── ndpid.init # procd init 脚本
│ │ └── ndpisrvd.init # nDPIsrvd init
│ └── patches/ # 如需要的 OpenWrt 补丁
│
├── secubox-ndpid-compat/ # 新: 兼容层
│ ├── Makefile
│ └── files/
│ ├── ndpid-compat.lua # 翻译守护进程
│ ├── ndpid-flow-actions # IPSet/nftables 处理器
│ └── ndpid-collector # 统计聚合器
│
├── luci-app-secubox-netifyd/ # 修改: 与两者兼容
│ └── root/usr/libexec/rpcd/
│ └── luci.secubox-netifyd # 为 nDPId 兼容更新
│
└── secubox-app-netifyd/ # 弃用: 保留作为后备
配置映射
UCI 配置翻译
Netifyd (/etc/config/secubox-netifyd):
config settings 'settings'
option enabled '1'
option socket_type 'unix'
config sink 'sink'
option enabled '1'
option type 'unix'
option unix_path '/tmp/netifyd-flows.json'
nDPId (/etc/config/secubox-ndpid):
config ndpid 'main'
option enabled '1'
option interfaces 'br-lan br-wan'
option collector_socket '/tmp/ndpid-collector.sock'
config ndpisrvd 'distributor'
option enabled '1'
option listen_socket '/tmp/ndpisrvd.sock'
option tcp_port '7000'
config compat 'compat'
option enabled '1'
option netifyd_status '/var/run/netifyd/status.json'
option netifyd_socket '/var/run/netifyd/netifyd.sock'
风险评估
| 风险 | 影响 | 缓解 |
|---|---|---|
| 检测准确性差异 | 中 | 两者都使用 libnDPI;预期结果相似 |
| 性能退化 | 低 | nDPId 更轻量;应该提高性能 |
| 插件兼容性 | 高 | 必须在外部重新实现流操作 |
| 破坏现有仪表板 | 高 | 兼容层确保相同的输出格式 |
| 缺少 Netifyd 功能 | 中 | 记录功能差距;优先处理关键功能 |
功能比较
| 功能 | Netifyd | nDPId | 迁移影响 |
|---|---|---|---|
| 协议检测 | 是 | 是 | 无 |
| 应用检测 | 是 | 是 | 无 |
| 流跟踪 | 是 | 是 | 无 |
| JSON 输出 | 是 | 是 | 需要格式翻译 |
| Socket 流 | 是 | 是 | 格式不同 |
| 云集成 | 是 | 否 | 功能移除 |
| 插件架构 | 内置 | 外部 | 重新实现 |
| 内存占用 | ~50MB | ~15MB | 改进 |
| 启动时间 | ~5s | ~1s | 改进 |
测试计划
单元测试
- 翻译准确性: 验证 nDPId 事件正确映射到 Netifyd 格式
- 统计聚合: 验证流计数、字节数、数据包数匹配
- 应用检测: 比较两个引擎之间的检测结果
集成测试
- LuCI 仪表板: 所有视图正确渲染
- RPCD 方法: 所有 24 个方法返回预期数据
- IPSet 操作: BitTorrent/流媒体检测触发 ipset 更新
- CrowdSec 解析: 流事件被解析且场景触发
性能测试
- 吞吐量: 测量最大流/秒
- 内存: 比较负载下的 RAM 使用
- CPU: 比较流量突发期间的 CPU 使用
回滚计划
如果迁移失败:
- 停止 nDPId 服务:
/etc/init.d/ndpid stop && /etc/init.d/ndpisrvd stop - 启动 Netifyd:
/etc/init.d/netifyd start - 兼容层自动检测并切换来源
- 无数据丢失;两者可以共存
参考
附录 A: nDPId 事件模式参考
流事件字段
{
"flow_event_id": "整数 (0-8)",
"flow_event_name": "字符串 (new|end|idle|update|detected|guessed|detection-update|not-detected|analyse)",
"thread_id": "整数",
"packet_id": "整数",
"source": "字符串 (接口名)",
"flow_id": "整数",
"flow_state": "字符串 (skipped|finished|info)",
"l3_proto": "字符串 (ip4|ip6)",
"src_ip": "字符串",
"dst_ip": "字符串",
"l4_proto": "字符串 (tcp|udp|icmp|...)",
"src_port": "整数",
"dst_port": "整数",
"flow_src_packets_processed": "整数",
"flow_dst_packets_processed": "整数",
"flow_first_seen": "整数 (毫秒时间戳)",
"flow_src_tot_l4_payload_len": "整数 (字节)",
"flow_dst_tot_l4_payload_len": "整数 (字节)",
"ndpi": {
"proto": "字符串 (例如 TLS.Google)",
"proto_id": "整数",
"encrypted": "整数 (0|1)",
"breed": "字符串 (Safe|Acceptable|Fun|Unsafe|...)",
"category_id": "整数",
"category": "字符串"
}
}
守护进程状态事件字段
{
"daemon_event_id": 3,
"daemon_event_name": "status",
"global_ts_usec": "整数",
"uptime": "整数 (秒)",
"packets": "整数",
"packet_bytes": "整数",
"flows_active": "整数",
"flows_idle": "整数",
"flows_detected": "整数",
"compressions": "整数",
"decompressions": "整数"
}
附录 B: 兼容层示例代码
#!/usr/bin/env lua
-- secubox-ndpid-compat: nDPId 到 Netifyd 格式翻译器
local socket = require("socket")
local json = require("cjson")
local NDPISRVD_SOCK = "/tmp/ndpisrvd.sock"
local OUTPUT_STATUS = "/var/run/netifyd/status.json"
local UPDATE_INTERVAL = 1
-- 状态跟踪
local state = {
flows = {},
flow_count = 0,
flows_active = 0,
stats = {},
devices = {},
uptime = 0,
start_time = os.time()
}
-- 处理传入的 nDPId 事件
local function process_event(raw)
-- 去除 5 位长度前缀
local json_str = raw:sub(6)
local ok, event = pcall(json.decode, json_str)
if not ok then return end
local event_name = event.flow_event_name or event.daemon_event_name
if event_name == "new" then
state.flows[event.flow_id] = event
state.flow_count = state.flow_count + 1
state.flows_active = state.flows_active + 1
elseif event_name == "end" or event_name == "idle" then
state.flows[event.flow_id] = nil
state.flows_active = state.flows_active - 1
elseif event_name == "detected" then
if state.flows[event.flow_id] then
state.flows[event.flow_id].detected = event.ndpi
end
-- 更新接口统计
local iface = event.source or "unknown"
if not state.stats[iface] then
state.stats[iface] = {ip_bytes=0, tcp=0, udp=0, icmp=0}
end
local proto = event.l4_proto or ""
if proto == "tcp" then state.stats[iface].tcp = state.stats[iface].tcp + 1 end
if proto == "udp" then state.stats[iface].udp = state.stats[iface].udp + 1 end
if proto == "icmp" then state.stats[iface].icmp = state.stats[iface].icmp + 1 end
local bytes = (event.flow_src_tot_l4_payload_len or 0) + (event.flow_dst_tot_l4_payload_len or 0)
state.stats[iface].ip_bytes = state.stats[iface].ip_bytes + bytes
elseif event_name == "status" then
state.uptime = event.uptime or (os.time() - state.start_time)
end
end
-- 生成 Netifyd 兼容的 status.json
local function generate_status()
return json.encode({
flow_count = state.flow_count,
flows_active = state.flows_active,
stats = state.stats,
devices = state.devices,
uptime = state.uptime,
dns_hint_cache = { cache_size = 0 }
})
end
-- 主循环
local function main()
-- 创建输出目录
os.execute("mkdir -p /var/run/netifyd")
local sock = socket.unix()
local ok, err = sock:connect(NDPISRVD_SOCK)
if not ok then
print("连接 nDPIsrvd 失败: " .. (err or "未知"))
os.exit(1)
end
sock:settimeout(0.1)
local last_write = 0
while true do
local line, err = sock:receive("*l")
if line then
process_event(line)
end
-- 周期性写入状态文件
local now = os.time()
if now - last_write >= UPDATE_INTERVAL then
local f = io.open(OUTPUT_STATUS, "w")
if f then
f:write(generate_status())
f:close()
end
last_write = now
end
end
end
main()
文档版本: 1.0 创建日期: 2026-01-09 作者: Claude Code 助手