secubox-openwrt/DOCS-zh/MIGRATION-NETIFYD-TO-NDPID.md
CyberMind-FR ccfb58124c docs: Add trilingual documentation (French and Chinese translations)
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>
2026-03-20 10:00:18 +01:00

18 KiB
Raw Blame History

迁移计划: 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

当前数据消费者

  1. CrowdSec: 没有直接集成。独立运行。
  2. Netdata: 单独的仪表板。通过 /proc 读取系统指标,不是 DPI 数据。
  3. 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

翻译层脚本:

  1. 连接到 nDPIsrvd socket
  2. 将流事件聚合为 Netifyd 兼容格式
  3. 写入 /var/run/netifyd/status.json
  4. 提供相同的 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.GoogleQUIC.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周)

  1. 创建 secubox-app-ndpid 软件包
  2. 为 OpenWrt 构建 nDPId + nDPIsrvd
  3. 测试基本流检测
  4. 创建 UCI 配置模式

第二阶段: 兼容层 (第3-4周)

  1. 开发 secubox-ndpid-compat 翻译守护进程
  2. 实现 status.json 生成
  3. 实现流事件聚合
  4. 用现有 LuCI 仪表板测试

第三阶段: RPCD 后端更新 (第5周)

  1. 更新 RPCD 方法以使用 nDPId 数据
  2. 确保所有 15 个读取方法正常工作
  3. 确保所有 9 个写入方法正常工作
  4. 测试 LuCI 应用兼容性

第四阶段: 消费者集成 (第6-7周)

  1. 创建 CrowdSec 解析器/场景
  2. 创建 Netdata 收集器
  3. 测试端到端数据流
  4. 记录新集成

第五阶段: 迁移与清理 (第8周)

  1. 为现有用户创建迁移脚本
  2. 更新文档
  3. 移除 Netifyd 软件包(可选,可以共存)
  4. 最终测试和发布

迁移后的文件结构

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 改进

测试计划

单元测试

  1. 翻译准确性: 验证 nDPId 事件正确映射到 Netifyd 格式
  2. 统计聚合: 验证流计数、字节数、数据包数匹配
  3. 应用检测: 比较两个引擎之间的检测结果

集成测试

  1. LuCI 仪表板: 所有视图正确渲染
  2. RPCD 方法: 所有 24 个方法返回预期数据
  3. IPSet 操作: BitTorrent/流媒体检测触发 ipset 更新
  4. CrowdSec 解析: 流事件被解析且场景触发

性能测试

  1. 吞吐量: 测量最大流/秒
  2. 内存: 比较负载下的 RAM 使用
  3. CPU: 比较流量突发期间的 CPU 使用

回滚计划

如果迁移失败:

  1. 停止 nDPId 服务: /etc/init.d/ndpid stop && /etc/init.d/ndpisrvd stop
  2. 启动 Netifyd: /etc/init.d/netifyd start
  3. 兼容层自动检测并切换来源
  4. 无数据丢失;两者可以共存

参考


附录 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 助手