Watch the Top, Ban the Bad.
小企业向的流量观测与访问控制。一个二进制,一个 SQLite 文件,scp 上去就能跑。
ntop2ban 把三件事装进同一个程序:
- 流量观测 —— XDP 采样(自动降级到 AF_PACKET),看清谁在打你的主机
- 敲门授权(knock) —— 按预设序列敲门才放行 SSH,让扫描器看不到端口
- 链路探测 —— 周期性 ICMP/TCP 探测,延迟与丢包的分布图(源自 pingping)
配上审批与审计:序列的变更需要走审批,成功授权全部留痕。
与 xdp-ban 的分工:xdp-ban 处理大流量镜像分析
(ClickHouse 分层聚合在那边),ntop2ban 走轻量路线。采样流量、敲门序列、
审批审计、探测结果都落同一个 .db 文件,拷走那个文件就是完整备份。
./ntop2ban -api-key <你的密钥>
# 监听 :8090,数据落 ./ntop2ban-data/,采样保留 40 天序列由用户在平台上设定,混合 TCP 与 ICMP,不用 UDP——很多客户端出口 环境发不出 UDP。整个序列必须在 1 分钟内完成。
一个典型序列 TCP 9001 → ICMP 56 → TCP 9003 → ICMP 90,敲门就是四条
系统自带命令,不需要任何自制客户端:
nc -z -w1 <host> 9001 # 第 1 步
ping -s 56 -c 1 <host> # 第 2 步
nc -z -w1 <host> 9003 # 第 3 步
ping -s 90 -c 1 <host> # 第 4 步
# 成功后 60 秒内可连接 SSH,只对你这个来源 IP 放行界面直接给出可复制的命令,不用自己拼。
设计取舍值得说明:暗号是固定的,不做轮换。 曾经设计过按时间窗 用 HMAC 轮换 ICMP 包长,否决了——你得先去某处查当前值才能敲门,而那个 "某处"往往也在敲门保护之后,鸡生蛋。接受的代价是被抓包后可重放,但真正 要防的是全网扫描器,它永远猜不中这个序列;而能在链路上抓包的对手已经是 中间人,敲门本来也救不了,那时靠的是 SSH 自身的密钥认证。
只记成功,不记失败。 敲错的包就是互联网噪声,记下来只会淹没审计日志。
审批不是实时闸门:它管的是"序列定义"这份配置的变更(改哪些端口、 哪些 ICMP 长度),守护进程始终按当前生效的那版工作,不会卡在等审批上。
流量采样与敲门捕获共用一个 XDP 程序(bpf/sampler.c),一次包解析
两个输出:采样走 1/N 抽样(允许丢,只服务可视化),敲门走精确匹配
(一个包都不能漏)。两者用各自的 ringbuf —— 共用一个的话,高流量下
敲门事件会被采样事件挤掉,而那是最不能丢的东西。
XDP 程序永远 XDP_PASS,只观测不拦截。封禁下发到 nftables,那是
netfilter hook,与 XDP 不同层次,所以不存在网卡挂载点冲突。这个组合是
刻意的:采样是每个包都要过的路径,放 XDP 拿驱动层的性能;封禁是低频
规则增删,nftables 够用。
挂不上 XDP 时按 native → generic → af-packet 逐级降级:
| 层级 | 说明 |
|---|---|
| xdp-native | 驱动层处理,性能最佳。需要网卡驱动支持 |
| xdp-generic | 内核在 netif_receive_skb 处模拟。任何网卡都能挂,但已在 sk_buff 分配之后。veth、容器、部分云主机的 virtio 配置常只能走这级 |
| af-packet | 完全不用 XDP。内核太老(<4.8)、XDP 被占用、或权限受限时的退路。抽样判定仍在内核侧完成(cBPF 的 ExtRand 扩展),用户态只收 1/N |
流量展示是统一的:三级数据源产出完全相同的 model.Flow,用的是同一个
聚合器,曲线口径一致。唯一体现差别的地方是 Device 字段的模式标签,
以及界面顶部单独展示的"当前数据源"——性能差一个数量级,运维需要知道
自己在哪一级,否则会把"统计偏低"误判成"流量真的少"。
降级只发生在启动时,一次决定、之后不变。不做运行时自动切换:切换会让
同一时间窗口内混入两种口径的数据,曲线上出现无法解释的跳变,而排查时
没人会想到是数据源换了。用 -datasource 可以强制指定某一级(排查用)。
AF_PACKET 模式下敲门会自己开一个独立的精确捕获 socket,因为那一层的 采样过滤器带抽样,敲门包会被抽掉。
周期性 ICMP/TCP 探测,记录每轮的 RTT 分布与丢包,画延迟/丢包图。 算法搬自 pingping, 但没有搬它的存储层——那边用 cgo 驱动,带进来会毁掉静态编译。 探测结果落 ntop2ban 已有的那一个库,只是多几张表。
探测随主程序一起启动,没有独立的命令。目标写在清单文件里,格式与 pingping 一致(从 pingping 迁过来可以直接拷原来的清单):
/etc/ntop2ban/ping.list # ICMP: host [名字] [pace=fast|normal|slow] [interval=秒]
/etc/ntop2ban/tcp.list # TCP: host:port [名字] [pace=...] [interval=秒]
echo '192.168.1.1 网关 pace=fast' >> /etc/ntop2ban/ping.list
echo '10.0.0.5:443 API网关 interval=30' >> /etc/ntop2ban/tcp.list
# 重启生效用文件而不是数据库表:探测目标是运维手写的东西,echo host >> ping.list
比在界面上点几下或写 SQL 都快。首次启动会生成带注释的示例(全部注释掉,
不会擅自去探测某个示例主机)。清单为空时界面上的探测页会提示去哪个
文件加目标,而不是显示一张空图——空图让人以为程序坏了。
保留 pingping 的核心取舍:存分布而不是均值。一轮 20 个包,记下 min/p50/p90/p99/max。均值会把"一半包 5ms、一半包 500ms"和"所有包 250ms" 画成同一条线,而这两种链路的体感完全不同。丢包突发用 robust z-score (中位数 + MAD)判定,对异常值不敏感——用均值加标准差的话,历史上的 大丢包会把基线自己抬高,变成"以前抖过,现在抖就不算异常"。
| 参数 | 默认 | 说明 |
|---|---|---|
-api-key |
—(必填) | 外部采样器上报的鉴权密钥。留空会拒绝一切上报,因此启动时直接报错退出,而不是静默起来收不到数据 |
-addr |
:8090 |
HTTP 监听地址 |
-iface |
空 | 观测网卡。XDP 模式必须指定;留空只能走 AF_PACKET |
-sampling |
100 |
抽样率 1/N;1 表示全量 |
-datasource |
空(自动降级) | 强制指定:xdp-native / xdp-generic / af-packet |
-data-dir |
./ntop2ban-data |
数据目录 |
-days |
40 |
数据保留天数(采样与探测共用) |
-probe-dir |
/etc/ntop2ban |
探测清单目录(ping.list / tcp.list) |
-no-knock |
关 | 不启动敲门 |
make build # 构建 ./ntop2ban
make test # 全部测试
make check # vet + test
make release # 交叉编译 linux/{amd64,arm64} 到 dist/CGO_ENABLED=0 是硬约束:SQLite 用 modernc.org/sqlite(纯 Go),
所以能静态编译、拷过去就跑。
- 采样存储层(SQLite)与接收端点
- 敲门序列状态机与持久化(序列版本 + 成功授权记录)
- 敲门捕获与 nftables 放行
- 链路探测(ICMP/TCP,分布存储,突发判定)
- 内化数据面:XDP 采样 + 敲门精确匹配,三级降级,展示统一
- 用户与审计(admin / viewer 两级,admin 超级权限)
- Web 界面(流量排行、探测分布图、敲门序列配置与审批、审计日志)
- 写入时富化(GeoIP / ASN / IANA 服务名)
- 从流量界面点击 IP 直接发起封禁
Apache-2.0