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, 网络安全, 规则模拟器, 连接追踪, 防火墙引擎, 隐私保护, 零依赖