最安全方式是用ip.to4() != nil判断ipv4,因net.ip内部统一用16字节存储,ipv4可能以::ffff:a.b.c.d形式存在,导致len(ip)恒为16而误判;to16()仅用于标准化输出,非类型判断工具。

用 ip.To4() 判断 IPv4 地址最安全
直接检查 len(ip) 是错的:Go 的 net.IP 内部统一用 16 字节存储,IPv4 地址(如 "192.168.1.1")可能被表示为 IPv4-mapped IPv6 格式(::ffff:c0a8:0101),此时 len(ip) 也是 16。只有 ip.To4() != nil 才能真正确认它是语义上的 IPv4。
常见错误场景:
- 把
ip.To16() != nil当作“是 IPv6”的判断依据 —— 实际上 IPv4 地址调用To16()也返回非 nil 的 16 字节切片 - 未判空就直接对
ip.To4()结果做字节操作,导致 panic(比如ip.To4()[0]在 IPv6 上会越界)
正确写法示例:
if v4 := ip.To4(); v4 != nil {
// 安全使用 v4,它一定是 4 字节
fmt.Printf("IPv4: %d.%d.%d.%d", v4[0], v4[1], v4[2], v4[3])
}
ip.To16() 不是“只给 IPv6 用”,而是“标准化输出”
To16() 的作用是把任意合法 IP(IPv4 或 IPv6)转成标准 16 字节格式,不是类型判断工具。IPv4 会转成 ::ffff:a.b.c.d 形式;IPv6 原样保留(或补零归一化)。它适合需要统一长度的场景,比如哈希、存储、二进制序列化。
典型用途:
- 存入 map 或数据库时保证键长一致(避免 IPv4/IPv6 混用导致 key 不等价)
- 和 CIDR 掩码做位运算前,统一为 16 字节便于处理
- 调试时打印完整字节:
fmt.Printf("%x", ip.To16())
注意:To16() 对非法 IP(如 nil)返回 nil,必须先确保 ip != nil。
区分类型后该用哪个方法过滤私网/回环地址
IsPrivate() 和 IsLoopback() 对 IPv4 和 IPv6 都有效,但行为有差异:
-
IsLoopback():对"127.0.0.1"和"::1"都返回true,无需预判类型 -
IsPrivate():只识别 IPv4 私有网段(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16);IPv6 的 ULA(fc00::/7)不在此列,得用IsLinkLocalUnicast()或手动匹配前缀 -
IsGlobalUnicast():对 IPv4 映射地址(::ffff:10.0.0.1)返回false,容易误筛;CDN 任播地址(如 Cloudflare)也可能返回false,不能单靠它判断可达性
建议组合使用:
if ip := net.ParseIP(addr); ip != nil {
if v4 := ip.To4(); v4 != nil && v4.IsPrivate() {
// 纯 IPv4 私网
} else if ip.To16() != nil && (ip.IsLinkLocalUnicast() || isULA(ip)) {
// IPv6 私网或 ULA
}
}
DNS 查询结果里混着 IPv4/IPv6,怎么拆开存
net.LookupIP() 返回的是 []net.IP,里面 IPv4 和 IPv6 可能交错。别用 len(ip) == 4 或 == 16 分类 —— 同样踩长度陷阱。
标准做法是遍历 + To4()/To16() 双检:
- 先用
ip.To4() != nil提取所有 IPv4,存进[]net.IP(内部仍是 4 字节,但 Go 会自动扩展为 16 字节存储) - 再用
ip.To4() == nil && ip.To16() != nil提取 IPv6(排除掉无效地址) - 注意:
ip.To16() != nil是必要条件,因为nilIP(解析失败)也会让To4()返回nil,但不该算作 IPv6
精简示例:
v4s, v6s := make([]net.IP, 0), make([]net.IP, 0)
for _, ip := range ips {
if v4 := ip.To4(); v4 != nil {
v4s = append(v4s, v4) // 自动转为 net.IP 类型
} else if v6 := ip.To16(); v6 != nil {
v6s = append(v6s, v6)
}
}
最后提醒一句:To4/To16 的返回值是新分配的切片,不是原 net.IP 的引用,别指望修改它会影响原始值。如果要做原地转换(比如把 IPv4 映射地址转纯 IPv4),必须显式赋值。











