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>
685 lines
18 KiB
Markdown
685 lines
18 KiB
Markdown
# 迁移计划: Netifyd 到 nDPId
|
||
|
||
🌐 **Languages:** [English](../DOCS/MIGRATION-NETIFYD-TO-NDPID.md) | [Français](../DOCS-fr/MIGRATION-NETIFYD-TO-NDPID.md) | 中文
|
||
|
||
## 执行摘要
|
||
|
||
本文档提供了一个全面的迁移计划,将 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`):
|
||
```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 时,非默认):
|
||
```json
|
||
{
|
||
"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 流事件示例
|
||
|
||
```json
|
||
{
|
||
"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**:
|
||
```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.Google`、`QUIC.YouTube` 等格式。规范化为小写基础名:
|
||
```
|
||
TLS.Google → google
|
||
QUIC.YouTube → youtube
|
||
HTTP.Facebook → facebook
|
||
DNS → dns
|
||
```
|
||
|
||
### 第四阶段: 消费者兼容性
|
||
|
||
#### 4.1 CrowdSec 集成(新)
|
||
|
||
由于没有现有的 CrowdSec 集成,我们可以正确设计它:
|
||
|
||
**采集配置** (`/etc/crowdsec/acquis.d/ndpid.yaml`):
|
||
```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`):
|
||
```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`):
|
||
```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`):
|
||
```bash
|
||
#!/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`):
|
||
```bash
|
||
#!/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. 无数据丢失;两者可以共存
|
||
|
||
---
|
||
|
||
## 参考
|
||
|
||
- [nDPId GitHub 仓库](https://github.com/utoni/nDPId)
|
||
- [nDPI 库](https://github.com/ntop/nDPI)
|
||
- [Netifyd 文档](https://www.netify.ai/documentation/)
|
||
- [CrowdSec 采集](https://docs.crowdsec.net/docs/data_sources/intro)
|
||
- [Netdata 外部插件](https://learn.netdata.cloud/docs/agent/collectors/plugins.d)
|
||
|
||
---
|
||
|
||
## 附录 A: nDPId 事件模式参考
|
||
|
||
### 流事件字段
|
||
|
||
```json
|
||
{
|
||
"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": "字符串"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 守护进程状态事件字段
|
||
|
||
```json
|
||
{
|
||
"daemon_event_id": 3,
|
||
"daemon_event_name": "status",
|
||
"global_ts_usec": "整数",
|
||
"uptime": "整数 (秒)",
|
||
"packets": "整数",
|
||
"packet_bytes": "整数",
|
||
"flows_active": "整数",
|
||
"flows_idle": "整数",
|
||
"flows_detected": "整数",
|
||
"compressions": "整数",
|
||
"decompressions": "整数"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 附录 B: 兼容层示例代码
|
||
|
||
```lua
|
||
#!/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 助手*
|