登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go net/netip 如何比较 IPv4 与 IPv4-mapped IPv6

来源:17golang原创

时间:2026-09-12 22:27:03 391浏览 收藏

在 Go 网络程序里,1.2.3.4::ffff:1.2.3.4 可能来自不同的套接字或代理层。它们指向同一个 IPv4 地址,但字符串不同,直接比较字符串会误判;直接比较两个 netip.Addr 的原值,也保留了“IPv4”和“IPv4-mapped IPv6”这两种表示的差异。

要点速览
  • mapped 地址要用 Is4In6() 识别,不能只看 Is4()
  • 需要把两种表示视为同一 IPv4 地址时,比较 a.Unmap() == b.Unmap()
  • 如果比较的是端点,还要同时比较端口;普通 IPv6 不应被错误压成 IPv4。

先看清 netip.Addr 的三种判断

net/netip 把 IPv4-mapped IPv6 视为 IPv6 地址。因此下面三类输入的判断并不相同:

输入Is4Is6Is4In6含义
1.2.3.4truefalsefalse原生 IPv4
::ffff:1.2.3.4falsetruetrueIPv4-mapped IPv6
2001:db8::1falsetruefalse普通 IPv6

特别容易踩坑的是第二行:Is6() 为 true 并不表示它一定是独立的 IPv6 主机地址。要判断 RFC 4291 定义的 mapped 形式,应使用 Is4In6()

Go netip Addr 的 IPv4、IPv4-mapped IPv6 与普通 IPv6 类型关系示意
图1:Go netip 地址类型关系操作示意图,区分 Is4、Is6 和 Is4In6 的判断边界。

用 Unmap 统一同一 IPv4 地址的表示

Unmap() 只会移除 IPv4-mapped IPv6 的前缀;如果输入是原生 IPv4 或普通 IPv6,它会返回不变的地址。这样可以把“是否只是换了一种 IPv4 表示”交给标准库处理:

package main

import (
    "fmt"
    "net/netip"
)

func sameIPv4(a, b string) (bool, error) {
    left, err := netip.ParseAddr(a)
    if err != nil {
        return false, fmt.Errorf("解析左地址失败: %w", err)
    }
    right, err := netip.ParseAddr(b)
    if err != nil {
        return false, fmt.Errorf("解析右地址失败: %w", err)
    }

    // Unmap 只规范化 IPv4-mapped IPv6,普通 IPv6 仍保持 IPv6 身份。
    return left.Unmap() == right.Unmap(), nil
}

func main() {
    equal, err := sameIPv4("1.2.3.4", "::ffff:1.2.3.4")
    if err != nil {
        panic(err)
    }
    fmt.Println(equal) // true
}

这里比较的是规范化后的 Addr,不是 String() 的输出。生产代码还应保留解析错误,不要把非法地址当成“不相等”吞掉;否则配置错误和正常的比较失败会混在一起。

需要排序时,不要把 Compare 当成“映射相等”

Addr.Compare 给出稳定的地址顺序,返回 -101。它适合排序原始地址或生成确定性列表,但排序和业务等值不是一回事。若业务要求 mapped IPv6 与 IPv4 算作同一对象,先对两端调用 Unmap(),再比较或排序。

可以按下面的决策使用:

需求推荐写法不要做什么
判断同一个 IPv4a.Unmap() == b.Unmap()比较字符串
只识别 mapped 输入a.Is4In6()Is6() 代替
对原始地址排序a.Compare(b)把排序结果当端点等值
比较 IP:端口比较 Port() 与规范化后的 Addr()只比较地址而忽略端口
Go netip Unmap 后比较 Addr 与 AddrPort 端口边界示意
图2:Go netip Unmap 后的比较结果示意,展示地址规范化与端口判断必须分开。

AddrPort、zone 和安全判断要单独处理

如果数据来自网络端点,地址相同不代表端点相同。1.2.3.4:443::ffff:1.2.3.4:8443 的 IP 可以相同,但端口不同,不能当成同一连接。可封装一个明确的端点比较函数:

func sameEndpoint(a, b netip.AddrPort) bool {
    // 端口是服务身份的一部分,不能只比较 Addr。
    return a.Port() == b.Port() && a.Addr().Unmap() == b.Addr().Unmap()
}

另外,Unmap() 只处理 IPv4-mapped IPv6,不会把所有 IPv6 表示都变成 IPv4。它也不是完整的公网、内网或 SSRF 安全分类器;需要做访问控制时,应根据业务允许的网段、协议和代理信任边界单独设计规则。

常见问题

直接用 == 比较两个 Addr 可以吗?

可以,但它比较的是当前 Addr 表示。要把 IPv4 与 mapped IPv6 视为相同对象,应先分别调用 Unmap()

为什么 mapped 地址的 Is4() 是 false?

因为 netip 将它保留为 IPv6 表示;使用 Is4In6() 可以识别它属于 IPv4-mapped IPv6 范围。

Unmap 会影响普通 IPv6 吗?

不会。普通 IPv6 不满足 mapped 前缀条件,Unmap() 会原样返回。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>