Go实现黑白名单拦截最常用方式是HTTP Handler包装器,通过结构体封装原始handler,在ServeHTTP中先提取并校验IP(需处理X-Forwarded-For与RemoteAddr端口剥离),再调用独立判断函数,命中则http.Error返回403,否则放行;大规模黑名单可引入Bloom Filter预筛并二次确认,超高流量场景需结合ipset+iptables内核级拦截。

Go HTTP Handler包装器实现黑白名单拦截
直接在请求进入业务逻辑前做IP判断,是应用层最常用、最可控的方式。它不依赖系统工具,也不需要内核权限,适合绝大多数 Web 服务场景。
关键不是“写个过滤函数”,而是把判断嵌入到 http.Handler 链路里——用结构体包装原始 handler,在 ServeHTTP 中先检查再放行。
-
r.RemoteAddr默认带端口(如"192.168.1.100:54321"),必须用strings.Split(r.RemoteAddr, ":")[0]提取纯 IP;否则黑名单永远不命中 - 若部署在 Nginx / ALB / Cloudflare 后,
r.RemoteAddr是代理地址,应优先读r.Header.Get("X-Forwarded-For")并校验可信跳数,否则真实用户 IP 会被漏判 - 黑白名单逻辑建议拆成独立函数,比如
isBlacklisted(ip string) bool或isWhitelisted(ip string) bool,方便后续替换为 Redis 查询或 Bloom Filter - 拒绝请求时用
http.Error(w, "...", http.StatusForbidden),不要 return 后继续调用next.ServeHTTP,否则可能造成双响应
用 Bloom Filter 做高性能黑名单预筛
当黑名单规模超 10 万条且 QPS 过万时,纯内存 map 查找会成为瓶颈。此时 Bloom Filter 是合理选择:它不保证 100% 准确,但能用极小内存(~200KB)扛住百万级 Contains 调用,且漏判率为零。
主流库如 github.com/willf/bloom 不依赖 CGO,可直接集成进 HTTP handler。
- 初始化时参数
m(位数组长度)和k(哈希次数)必须按预期容量n和误判率P反推,硬写死bloom.New(1e6, 10)容易导致误判率飙升或内存浪费 - IPv4 地址必须标准化:用
net.ParseIP(ipStr).To4()转为 4 字节 slice 再喂给Filter.Add();直接传字符串会导致哈希分布不均 -
Filter.Contains([]byte(ip))返回true仅表示“可能在黑名单”,必须立刻触发二次确认(如查本地 SQLite 白名单表或 Redis 缓存),不能直接拒接 - 别在
Accept()阶段查 Bloom Filter——此时拿不到完整 HTTP 头,也识别不了真实客户端 IP,更会误杀健康检查连接
用 ipset + iptables 实现内核级硬拦截
当攻击流量已达百万 PPS、应用层已无法承载时,就得下探到内核。Go 本身不能写 XDP 或 netfilter 规则,但可以通过 github.com/coreos/go-iptables 和 github.com/vishvananda/netlink 控制 ipset 集合与 iptables 规则。
这是真正“丢包不进协议栈”的方式,CPU 占用比 Go 应用层低一个数量级以上。
- 必须用
ipset(而非直接往iptables -s写规则),否则插入/删除 10 万条规则时性能断崖式下跌 - 集合类型推荐
hash:ip(纯 IP 黑名单)或hash:net(CIDR 段),避免用list:set——后者 O(n) 查找,不适用于高频匹配 - iptables 规则要加在
INPUT链顶部,且用-m set --match-set bl_ip src -j DROP,确保在连接跟踪前就丢弃 - Go 程序更新黑名单时,应先
ipset add bl_ip 192.168.1.1,再确保对应 iptables 规则存在;顺序颠倒会导致短暂放行窗口
动态更新与一致性边界
黑白名单不是静态配置,运营侧随时可能增删 IP。但“热更新”容易踩坑:内存 map 并发读写不加锁会 panic,Bloom Filter 重建期间有空窗,ipset 更新未同步到所有网卡会导致漏拦。
- 内存 map 必须配
sync.RWMutex,读多写少场景下RLock()性能远好于全互斥 - Bloom Filter 不支持原地更新,每次 reload 需新建实例并原子替换指针,旧实例需确保无 goroutine 正在调用其
Contains - ipset 更新后,建议用
ipset list bl_ip | grep 192.168.1.1校验是否生效,避免因权限或路径错误导致静默失败 - 不要让黑名单逻辑耦合业务 handler——例如在某个
POST /login里临时封 IP,这会导致策略分散、难以审计


















