iRaffnix/erlkoenig_nft
GitHub: iRaffnix/erlkoenig_nft
纯 Erlang 实现的 Linux nf_tables 防火墙引擎,通过 Netlink 直连内核实现 O(1) 封禁、自动威胁检测和热重载配置。
Stars: 2 | Forks: 0
# erlkoenig_nft
一个用纯 Erlang 编写的防火墙引擎,通过 `AF_NETLINK` 直接与 Linux 内核通信。无需 C 代码。无需 NIFs。无需 `os:cmd("nft ...")`。
零外部依赖。
```
erlkoenig_nft:ban("203.0.113.42").
%% Blocked in microseconds — kernel hash set, O(1) lookup.
%% Automatic expiry. No rule changes. No restarts.
```
## 为什么选择它
每个防火墙工具都会调用 `nft` 或 `iptables`。每次封禁都是一个子进程。每次规则更改都是一个可能解析失败的字符串。
erlkoenig_nft 直接使用内核的二进制协议。一次 Erlang 函数调用即对应一条 netlink 消息。规则是数据结构,而非字符串。编译器会帮你发现拼写错误,而不是等到内核执行时才报错。
```
%% This is your entire firewall at runtime
erlkoenig_nft:status().
erlkoenig_nft:rates().
erlkoenig_nft:ct_top(10).
erlkoenig_nft:guard_stats().
erlkoenig_nft:reload().
```
## 安装
```
curl -fsSL https://raw.githubusercontent.com/iRaffnix/erlkoenig_nft/main/install.sh | sudo sh
```
自动检测架构 (x86_64/aarch64) 和 libc (glibc/musl),下载匹配的 release 版本,将 CLI 安装到 `/usr/local/bin/erlkoenig`,设置默认配置,并可选择安装 systemd 单元。
无需安装 Erlang 或 Elixir —— release 包自带运行时。
| 归档文件 | 适用系统 |
|---------|-----|
| `erlkoenig_nft-v*-x86_64-glibc.tar.gz` | Standard Linux (Debian, Ubuntu, Fedora, ...) |
| `erlkoenig_nft-v*-x86_64-musl.tar.gz` | Alpine / static linking |
| `erlkoenig_nft-v*-aarch64-glibc.tar.gz` | ARM64 Linux (Raspberry Pi, AWS Graviton, ...) |
选项:`--prefix /path` (默认 `/opt/erlkoenig_nft`),`--version vX.Y.Z`,`--no-systemd`。
安装后:
```
sudo erlkoenig_nft start # start the daemon
sudo erlkoenig_nft status # check status
sudo nft list ruleset # verify kernel rules
erlkoenig --help # CLI tool
```
## 从源码构建
```
make # build Erlang core + Elixir DSL
make erl # or just the Erlang core
# 选择配置
cp examples/hardened_webserver.exs etc/firewall.exs
rebar3 shell # needs CAP_NET_ADMIN
```
## Elixir DSL
可选的 DSL (`dsl/`) 编译为运行时加载的 Erlang term 格式。编写读起来像防火墙配置的防火墙配置。
### 生产环境边缘服务器
展示了 sets, counters, quotas, verdict maps, flowtables, SYN proxy, rate metering, OS fingerprinting, NFQUEUE, conntrack marks, RPF checks, connection limits, 以及 NFLOG —— 所有这些都在一个配置中。
```
defmodule Firewall.Edge do
use ErlkoenigNft.Firewall
firewall "edge" do
counters [:ssh, :http, :https, :dns, :banned, :dropped]
set "blocklist", :ipv4_addr
set "blocklist6", :ipv6_addr
quota :bandwidth, 10_000_000_000, flags: 0 # 10 GB soft quota
# Concat set: match (ip, port) pairs in a single O(1) lookup
concat_set "allowpairs", [:ipv4_addr, :inet_service]
# Verdict map: dispatch TCP ports to per-service chains
vmap "port_dispatch", :inet_service, id: 10
# Hardware flow offloading for established connections
flowtable "fastpath", hook: :ingress, priority: -100, devices: ["eth0"]
chain "prerouting", hook: :prerouting, priority: -300, policy: :accept do
rpf_check # FIB reverse-path filter
drop_if_in_set "blocklist", counter: :banned
drop_if_in_set "blocklist6", counter: :banned
synproxy [80, 443], mss: 1460, wscale: 7 # SYN cookie protection
end
chain "inbound", hook: :input, policy: :drop do
accept :established
offload "fastpath" # offload established flows
accept :loopback
notrack 53, :udp # skip conntrack for DNS
# Per-source rate limiting via kernel meter
meter_limit "ssh_meter", 22, :tcp, rate: 10, burst: 3, unit: :minute
accept_tcp 22, counter: :ssh, limit: {25, burst: 5}
accept_tcp [80, 443], counter: :https
accept_udp 53, counter: :dns
accept_tcp_range 8000, 8099 # app ports
accept_udp_range 27000, 27015 # game traffic
accept_from {10, 0, 1, 0, 0, 0, 0, 0} # private subnet
connlimit_drop 200 # DDoS protection
# Conntrack marks: tag and match traffic
mark_connection 42
match_mark 42, verdict: :accept
# Dispatch TCP by port to per-service chains via verdict map
dispatch :tcp, "port_dispatch"
# OS fingerprinting: only allow Linux clients on port 9090
match_os "Linux", :accept
# NFQUEUE: send suspicious traffic to userspace IDS
queue_to 443, :tcp, queue: 0, fanout: true
# Cgroup-based filtering (container/systemd isolation)
match_cgroup 1234, :accept
accept :icmp
accept_protocol :icmpv6
log_and_drop_nflog "EDGE-DROP: ", group: 1, counter: :dropped
end
end
end
```
### 威胁检测 + 计数器监控
```
defmodule MyGuard do
use ErlkoenigNft.Guard
guard do
detect :conn_flood, threshold: 50, window: 10
detect :port_scan, threshold: 20, window: 60
ban_duration 3600
whitelist {127, 0, 0, 1}
whitelist {10, 0, 0, 1}
cleanup_interval 15_000
end
end
defmodule MyWatch do
use ErlkoenigNft.Watch
watch :traffic do
counter :ssh, :pps, threshold: 50
counter :http, :pps, threshold: 5000
counter :dropped, :pps, threshold: 200
interval 2000
on_alert :log
on_alert {:webhook, "https://alerts.internal/fw"}
end
end
# 简单配置的快速 profiles
ErlkoenigNft.Firewall.Profiles.get(:strict, allow_tcp: [22, 443])
ErlkoenigNft.Firewall.Profiles.get(:standard, allow_udp: [51820])
```
## 示例
所有 15 个场景都作为 Elixir DSL 配置包含在 [`examples/`](examples/) 中:
| 场景 | 配置 |
|----------|--------|
| **默认 (首次运行时安装)** | [`examples/default.exs`](examples/default.exs) |
| 加固的 Web 服务器 | [`examples/hardened_webserver.exs`](examples/hardened_webserver.exs) |
| 邮件服务器 (强制 TLS) | [`examples/mail_server.exs`](examples/mail_server.exs) |
| 数据库服务器 (私有子网) | [`examples/database_server.exs`](examples/database_server.exs) |
| VPN 网关 (NAT + 转发) | [`examples/vpn_gateway.exs`](examples/vpn_gateway.exs) |
| DNS 服务器 (限速) | [`examples/dns_server.exs`](examples/dns_server.exs) |
| 游戏服务器 (UDP 端口范围) | [`examples/game_server.exs`](examples/game_server.exs) |
| 反向代理 (DNAT + connlimit) | [`examples/reverse_proxy.exs`](examples/reverse_proxy.exs) |
| Docker 主机 (网桥 + 容器) | [`examples/docker_host.exs`](examples/docker_host.exs) |
| 开发服务器 (宽松, reject) | [`examples/dev_server.exs`](examples/dev_server.exs) |
| 基于来源的速率限制器 (meters + quotas) | [`examples/rate_limiter.exs`](examples/rate_limiter.exs) |
| SYN 代理服务器 (抗 DDoS) | [`examples/synproxy_server.exs`](examples/synproxy_server.exs) |
| IDS 网关 (NFQUEUE + OS 指纹) | [`examples/ids_gateway.exs`](examples/ids_gateway.exs) |
| 服务网格 (cgroups + flowtables) | [`examples/service_mesh.exs`](examples/service_mesh.exs) |
| 防欺骗边缘路由器 (FIB + vmaps) | [`examples/anti_spoofing.exs`](examples/anti_spoofing.exs) |
## Erlang Term 配置
DSL 编译为您可以手动编写的相同格式:
```
#{
table => <<"erlkoenig">>,
sets => [
{<<"blocklist">>, ipv4_addr},
{<<"blocklist6">>, ipv6_addr}
],
counters => [ssh, http, https, banned, dropped],
chains => [
#{name => <<"input">>, hook => input, type => filter,
priority => 0, policy => drop,
rules => [
ct_established_accept,
iif_accept,
{tcp_accept_limited, 22, ssh, #{rate => 25, burst => 5}},
{tcp_accept, 80, http},
{tcp_accept, 443, https},
{protocol_accept, icmp},
{log_drop_nflog, <<"ERLKOENIG: ">>, 1, dropped}
]}
],
watch => #{
interval => 2000,
thresholds => [
{ssh_flood, ssh, pps, '>', 50.0},
{http_flood, http, pps, '>', 500.0}
]
},
ct_guard => #{
conn_flood => {50, 10},
port_scan => {20, 60},
ban_duration => 3600
}
}.
```
某些功能仅在 Erlang term 格式中可用:
| 功能 | Term 语法 | 备注 |
|---------|-------------|-------|
| NFLOG 捕获 | `{nflog_capture_udp, Port, Prefix, Group}` | SPA 数据包捕获 |
| NFLOG 丢弃 | `{log_drop_nflog, Prefix, Group, Counter}` | 记录到 NFLOG 组 |
| Set-gated UDP | `{set_lookup_udp_accept, Set, Port}` | WireGuard SPA 门控 |
| 接口接受 | `{iifname_accept, <<"wg0">>}` | 信任指定接口 |
| 伪装 | `masq` | 动态 SNAT |
| DNAT | `{dnat, {10,0,0,5}, Port}` | 目标 NAT |
| 转发已建立连接 | `forward_established` | Forward 链 conntrack |
## 运行时 API
```
%% Firewall
erlkoenig_nft:status(). %% applied config overview
erlkoenig_nft:reload(). %% hot-reload from config file
%% Banning — kernel hash set, O(1), automatic IPv4/IPv6 detection
erlkoenig_nft:ban("203.0.113.42"). %% string, binary, tuple, or raw bytes
erlkoenig_nft:unban("203.0.113.42").
%% Live counters — packets/sec and bytes/sec per named counter
erlkoenig_nft:rates().
%% => #{ssh => #{pps => 12.5, bps => 8340.0},
%% http => #{pps => 245.0, bps => 198400.0},
%% dropped => #{pps => 0.5, bps => 320.0}}
%% Connection tracking — real-time via netlink multicast
erlkoenig_nft:ct_count(). %% total active connections
erlkoenig_nft:ct_count("10.0.0.5"). %% connections from one IP
erlkoenig_nft:ct_top(10). %% top 10 talkers
erlkoenig_nft:ct_connections(). %% full connection list
erlkoenig_nft:ct_mode(). %% full | aggregate
erlkoenig_nft:ct_stats(). %% operational metrics
%% Threat detection
erlkoenig_nft:guard_stats().
%% => #{floods_detected => 3, scans_detected => 1, active_bans => 2}
erlkoenig_nft:guard_banned().
%% => [#{ip => "203.0.113.42", reason => conn_flood, expires_in => 2847}]
```
## CLI
`erlkoenig` 命令行工具提供守护进程交互和本地配置操作。使用 Elixir DSL (`dsl/`) 构建。
```
mix escript.build # in dsl/
```
### 守护进程命令
通过 Unix socket 与运行中的 erlkoenig_nft 守护进程通信:
```
erlkoenig status # firewall status overview
erlkoenig counters # live counter values
erlkoenig ban 203.0.113.42 # add IP to blocklist
erlkoenig unban 203.0.113.42 # remove IP from blocklist
erlkoenig reload # hot-reload config
erlkoenig apply config.exs # compile and apply new config
erlkoenig guard stats # threat detection statistics
erlkoenig guard banned # list currently banned IPs
```
### 本地命令
无需运行守护进程即可处理配置文件:
```
erlkoenig show config.exs # pretty-print compiled config
erlkoenig compile config.exs # compile to .term (stdout or -o file)
erlkoenig validate config.exs # check config for errors
erlkoenig inspect config.exs # show internal IR structure
erlkoenig diff a.exs b.exs # diff two compiled configs
erlkoenig list # list .exs configs in current dir
erlkoenig version # print version
erlkoenig completions bash # shell completions (bash/zsh/fish)
```
## 无需 Root 测试
内置的 nf_tables 虚拟机 (`nft_vm`) 在纯 Erlang 中执行规则。与内核语义相同:16 个寄存器,从左到右求值,不匹配时 BREAK。构建合成数据包,验证判决,检查执行跟踪 —— 全部无需 CAP_NET_ADMIN。
```
%% Does SSH get accepted?
Pkt = nft_vm_pkt:tcp(#{saddr => {10,0,0,5}, dport => 22}),
Rules = nft_rules:tcp_accept(22),
{accept, _} = nft_vm:eval_chain([Rules], Pkt, drop).
%% Does port 3306 get dropped?
Pkt2 = nft_vm_pkt:tcp(#{saddr => {10,0,0,5}, dport => 3306}),
{drop, _} = nft_vm:eval_chain([Rules], Pkt2, drop).
%% Full chain with trace
Chain = [
nft_rules:ct_established_accept(),
nft_rules:iif_accept(),
nft_rules:tcp_accept(22),
nft_rules:tcp_accept(80)
],
Pkt3 = nft_vm_pkt:tcp(#{dport => 80}),
{accept, Trace} = nft_vm:eval_chain(Chain, Pkt3, drop),
nft_vm:print_trace(Trace). %% step-by-step register state
%% UDP, ICMP, conntrack states
UdpPkt = nft_vm_pkt:udp(#{dport => 53}),
IcmpPkt = nft_vm_pkt:icmp(#{}, #{type => echo_request}),
EstPkt = nft_vm_pkt:tcp(#{dport => 22}, #{}, #{ct_state => established}).
%% Set membership testing
Sets = #{<<"blocklist">> => sets:from_list([<<203,0,113,42:32>>])},
BanPkt = nft_vm_pkt:with_sets(
nft_vm_pkt:tcp(#{saddr => {203,0,113,42}, dport => 80}),
Sets).
```
356 个单元测试 + 48 个内核集成测试 + DSL 测试:
```
make check # ct + dialyzer + DSL tests
make test # Erlang common test (unit, kernel tests skipped without root)
sudo make test # includes kernel integration tests (requires root)
make test-dsl # Elixir DSL tests only
make dialyzer # static analysis
```
## 架构
```
erlkoenig_nft_sup (rest_for_one)
|
+-- pg (erlkoenig_nft) Event broadcast (counter_events, ct_events, nflog_events)
+-- nfnl_server Shared AF_NETLINK socket, sequence management, batch I/O
+-- erlkoenig_nft_nflog NFLOG packet receiver (SPA capture, dropped packet forensics)
+-- erlkoenig_nft_ct Conntrack monitor (full + aggregate dual-mode)
+-- erlkoenig_nft_ct_guard Threat detection: flood + scan -> auto-ban
+-- erlkoenig_nft_watch_sup Dynamic supervisor for counter workers
| +-- erlkoenig_nft_counter One gen_server per named counter (polls, computes rates)
+-- erlkoenig_nft_firewall Config owner: reads .term, builds rules, applies via netlink
```
### 数据流水线
```
Config (.term file or DSL output)
|
v
Rule builders (nft_rules) High-level: tcp_accept, set_lookup_drop, ...
|
v
Expression IR (nft_expr_ir) Semantic terms: {meta, #{key => l4proto, dreg => 1}}
| |
+---> nft_vm (testing) +---> nft_encode (production)
Pure Erlang emulator IR -> netlink binary
|
v
nft_batch Atomic transaction wrapper
|
v
nfnl_server Send + collect ACKs
|
v
Kernel Apply all-or-nothing
```
### 事件系统
所有监控使用 OTP `pg` 进程组。可从任何进程订阅:
```
%% Counter rate events
pg:join(erlkoenig_nft, counter_events, self()),
receive {counter_event, ssh, #{pps := PPS}} -> PPS end.
%% Connection tracking events
pg:join(erlkoenig_nft, ct_events, self()),
receive {ct_new, #{src := Src, dport := 22}} -> Src end.
%% NFLOG packet events
pg:join(erlkoenig_nft, nflog_events, self()),
receive {nflog_event, #{prefix := <<"SPA:">>, src := Src}} -> Src end.
```
### 封禁原理
Ban 不会添加规则。它将一个元素插入到内核哈希集合中:
```
erlkoenig_nft:ban("10.0.0.5")
-> erlkoenig_nft_ip:normalize/1 Convert to 4-byte binary
-> nft_set_elem:add_elem(...) Netlink NEWSETELEM message
-> nfnl_server:apply_msgs/2 Send in batch
-> Kernel hash table insert O(1)
```
一条规则匹配整个集合:`ip saddr @blocklist drop`。封禁 1 个 IP 或 100,000 个 —— 相同的规则,相同的 O(1) 性能。在 DDoS 攻击下,conntrack 监控器自动从逐连接跟踪(约 100 字节/连接)切换到逐来源聚合(约 10 字节/来源) —— 内存减少 10 倍。
### 代码生成
87 个模块中有 38 个是通过 `codegen/nft_gen.escript` 从内核的 `nf_tables.h` 生成的。每种表达式类型一个模块(payload, meta, cmp, counter, log, nat, ...)。生成器处理 TLV 属性样板代码;所有语义逻辑均为手写。内核常量集中在单一头文件(`include/nft_constants.hrl`)中。
总计约 12,300 行代码。49 个手写模块(10,216 行),38 个生成模块(2,088 行)。
## 系统要求
| 需求 | 最低版本 |
|-------------|---------|
| Linux | >= 5.0 (nf_tables) |
| Erlang/OTP | >= 27 |
| Elixir | >= 1.18 (仅 DSL,可选) |
| Capabilities | CAP_NET_ADMIN (测试不需要) |
## 文档
| 文档 | 内容 |
|----------|----------|
| [配置参考](docs/CONFIGURATION.md) | 所有规则类型、sets、counters、guard、watch 选项 |
| [API 参考](docs/API.md) | 完整的 `erlkoenig_nft` 模块 API 及示例 |
| [Elixir DSL 参考](docs/DSL.md) | 所有宏:firewall、guard、watch、profiles |
| [技术深度解析](docs/FIREWALL.md) | Netlink 协议、表达式 IR、代码生成、VM 内部机制 |
## 许可证
Apache-2.0
标签:Erlang, Google搜索, IP 封禁, Linux 内核, Netlink, nftables, OTP 27, Systemd, 入侵防御, 原子事务, 扫描检测, 泛洪检测, 热加载, 系统守护进程, 纯 Erlang, 网络安全, 规则模拟器, 连接追踪, 防火墙引擎, 隐私保护, 零依赖