直接用 net.ParseIP 和 net.IPNet.Contains 即可完成基础 IP 段匹配,无需第三方库;net.ParseCIDR 解析 CIDR 并返回 *net.IPNet,其 Contains 方法零分配、纯位运算、自动适配 IPv4/IPv6,性能高且安全。

直接用 net.ParseIP 和 net.IPNet.Contains 就能完成基础 IP 段匹配,无需引入第三方库或解析二进制 IP 库——除非你要查地理位置或运营商信息。
为什么不用自己写 CIDR 解析逻辑
Go 标准库的 net.IPNet 已完整支持 CIDR 表达式(如 "192.168.1.0/24")的解析与判断,net.ParseCIDR 会返回 *net.IPNet,后续调用 .Contains() 是零分配、纯位运算,性能极高。自己手写掩码计算容易出错,且不处理 IPv6 或边界对齐问题。
-
net.ParseCIDR("10.0.0.0/8")返回有效*net.IPNet,可直接复用 - 传入
net.ParseIP("10.1.2.3")后调用ipnet.Contains(ip)即得布尔结果 - 错误处理只需检查
ParseCIDR是否返回err != nil,无隐藏陷阱
封装成可复用的 IP 段匹配器
把多个 CIDR 规则预加载为 []*net.IPNet,封装成结构体方法,避免每次请求都重复解析:
type IPPrefixMatcher struct {
rules []*net.IPNet
}
func NewIPPrefixMatcher(cidrs ...string) (*IPPrefixMatcher, error) {
rules := make([]*net.IPNet, 0, len(cidrs))
for _, cidr := range cidrs {
_, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, fmt.Errorf("invalid CIDR %q: %w", cidr, err)
}
rules = append(rules, ipnet)
}
return &IPPrefixMatcher{rules: rules}, nil
}
func (m *IPPrefixMatcher) Match(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
for _, ipnet := range m.rules {
if ipnet.Contains(ip) {
return true
}
}
return false
}
- 构造时一次性解析所有 CIDR,后续
Match()是纯内存遍历 + 位运算 - 不区分 IPv4/IPv6:只要输入合法、规则合法,
Contains()自动适配 - 若规则极多(>1000 条),可按地址族分桶(
ipv4Rules/ipv6Rules)避免无效遍历
常见踩坑点:字符串格式与地址有效性
用户常传入非法字符串导致 ParseIP 返回 nil,或误用 net.ParseIP("192.168.1.1/24")(这是 CIDR,不是 IP):
立即学习“go语言免费学习笔记(深入)”;
-
net.ParseIP只接受纯 IP 字符串("192.168.1.1"或"2001:db8::1"),含斜杠即失败 -
net.ParseCIDR才接受带前缀长度的字符串,且必须是标准格式(不能是"192.168.1.0 255.255.255.0") - 注意:
net.ParseIP对 IPv4 兼容"192.168.1"这类省略写法,但生产环境应严格校验输入是否符合 dotted-decimal 或完整 IPv6
真正需要“定位”地理信息时,才该引入 QQWry 或 MaxMind 数据库;仅做白名单、内网识别、路由分流等场景,标准库已足够——别过早抽象,也别漏掉 ip.IsLoopback() 或 ip.IsPrivate() 这类现成判断。


















