golang区分IPv4 IPv6

问题描述 投票:0回答:7

对于我正在开发的程序,我必须检查IP(将我连接到互联网的IP)是公共的还是私有的。为此,我需要区分 IP 是 IPv4 还是 IPv6。

我想通过IP的长度来检查:

conn, err := net.Dial("udp", "8.9.10.11:2342")
if err != nil {
    fmt.Println("Error", err)
}

localaddr := conn.LocalAddr()

addr, _ := net.ResolveUDPAddr("udp", localaddr.String())

ip := addr.IP

fmt.Println(ip)
fmt.Println(len(ip))

嗯,我的 IP 是 192.168.2.100,所以是 IPv4,但是 len(ip) 告诉我长度是 16,这就是 IPv6。 我的错误是什么?是否存在任何其他始终有效的区分 IPv4 和 IPv6 的方法?

go ip
7个回答
59
投票

jimt 的答案是正确的,但相当复杂。我只是简单地检查一下

ip.To4() != nil
。由于文档说“如果 ip 不是 IPv4 地址,To4 返回 nil”,当且仅当该地址是 IPv4 地址时,此条件才应返回
true


20
投票

Evanip.To4() != nil)的接受的答案

解决了这个问题。然而,有一些评论和其他答案检查表示形式是 IPv4 还是 IPv6,但它们并不总是准确的:

注意:下面分别列出了几个有效的 IPv4 和 IPv6 有效符号列表。每个条目都代表第一个基本条目的变体。可以根据需要组合变体,但由于篇幅原因,除了消除歧义之外,没有列出变体组合。

有效的 IPv4 符号:

  • "192.168.0.1"
    :基本
  • "192.168.0.1:80"
    :带有端口信息
有效的 IPv6 符号:

  • "::FFFF:C0A8:1"
    :基本
  • "::FFFF:C0A8:0001"
    :前导零
  • "0000:0000:0000:0000:0000:FFFF:C0A8:1"
    :双冒号扩大
  • "::FFFF:C0A8:1%1"
    :带有区域信息
  • "::FFFF:192.168.0.1"
    :IPv4 文字
  • "[::FFFF:C0A8:1]:80"
    :带有端口信息
  • "[::FFFF:C0A8:1%1]:80"
    :带有区域和端口信息
所有这些情况(IPv4 和 IPv6 列表)都将被

net

视为 IPv4 地址。 IPv6 列表的 IPv4 字面变体将被 govalidator 视为 IPv4。

检查它是 IPv4 还是 IPv6 表示法的最简单方法如下:

import strings func IsIPv4(address string) bool { return strings.Count(address, ":") < 2 } func IsIPv6(address string) bool { return strings.Count(address, ":") >= 2 }
    

17
投票
IP 的长度几乎总是 16,因为

net.IP

 的内部表示对于任何一种情况都是相同的。来自
文档

请注意,在本文档中,将 IP 地址称为 IPv4 地址或 IPv6 地址是地址的语义属性,而不仅仅是字节切片的长度:16 字节切片仍然可以是 IPv4 地址。

区分这两种类型取决于 IP 的初始化方式。查看

net.IPv4()

的代码,可以看到它被初始化为16个字节,其中前12个字节被设置为v4InV6Prefix
的值。

// IPv4 returns the IP address (in 16-byte form) of the // IPv4 address a.b.c.d. func IPv4(a, b, c, d byte) IP { p := make(IP, IPv6len) copy(p, v4InV6Prefix) p[12] = a p[13] = b p[14] = c p[15] = d return p }

其中

v4InV6Prefix

定义为:

var v4InV6Prefix = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}

如果您想要可靠的区分,请查看

net.IP.To4

 的源代码如何处理它:

// To4 converts the IPv4 address ip to a 4-byte representation. // If ip is not an IPv4 address, To4 returns nil. func (ip IP) To4() IP { if len(ip) == IPv4len { return ip } if len(ip) == IPv6len && isZeros(ip[0:10]) && ip[10] == 0xff && ip[11] == 0xff { return ip[12:16] } return nil }

isZeros

 不会导出,因此您必须在本地复制该代码。然后您只需执行与上述相同的操作即可确定您是否拥有 IPv4 或 IPv6。
    


5
投票

govalidator 检查字符串中是否存在 :

func IsIPv6(str string) bool { ip := net.ParseIP(str) return ip != nil && strings.Contains(str, ":") }
    

4
投票
我会使用 net 包中的 .To4()、.To16() 来查找它是 IPv4 还是 IPv6 地址。 检查

博客文章。例如,查看博客的最后一部分可能会对您有所帮助。


0
投票
IPAddress Go 库

可以通过几行多态代码来完成此操作,同时使用 IPv4 和 IPv6 地址。 存储库在这里。免责声明:我是项目经理。 它可以处理 Adirio 答案中列出的所有格式以及其他格式。

IPv4有“私有”的概念,IPv6有“唯一本地”的概念,两者都有“链路本地”的概念。

type AddrDetails struct { isLinkLocal, // both IPv4/6 have the concept of link-local isAnyLocal bool // the zero address for either IPv4 or IPv6 } type IPv4AddrDetails struct { isIPv4Private bool } type IPv6AddrDetails struct { isIPv6UniqueLocal bool } func checkAddrStr(addrStr string) ( address *ipaddr.IPAddress, version ipaddr.IPVersion, details AddrDetails, ipv4Details IPv4AddrDetails, ipv6Details IPv6AddrDetails, ) { if host := ipaddr.NewHostName(addrStr); host.IsAddress() { address = host.GetAddress() version = address.GetIPVersion() details = AddrDetails{address.IsLinkLocal(), address.IsAnyLocal()} if address.IsIPv4() { ipv4Details.isIPv4Private = address.ToIPv4().IsPrivate() } else { ipv6Details.isIPv6UniqueLocal = address.ToIPv6().IsUniqueLocal() } } return }

使用您的示例“8.9.10.11:2342”、Adirio 的列表以及其他一些示例进行尝试:

addrStrs := []string{ "8.9.10.11:2342", "192.168.0.1", "192.168.0.1:80", "::FFFF:C0A8:1", "::FFFF:C0A8:0001", "0000:0000:0000:0000:0000:FFFF:C0A8:1", "::FFFF:C0A8:1%1", "::FFFF:192.168.0.1", "[::FFFF:C0A8:1]:80", "[::FFFF:C0A8:1%1]:80", "::", "0.0.0.0", "169.254.0.1", "fe80::1", "fc00::1", "foo", } for _, addrStr := range addrStrs { addr, version, details, ipv4Details, ipv6Details := checkAddrStr(addrStr) if addr == nil { fmt.Printf("%s\nnot an address\n", addrStr) } else { var extra interface{} = ipv4Details if addr.IsIPv6() { extra = ipv6Details } unwrap := func(details interface{}) string { str := fmt.Sprintf("%+v", details) return str[1 : len(str)-1] } fmt.Printf("%s\n%v %v %+v %+v\n\n", addrStr, addr, version, unwrap(details), unwrap(extra)) } }

输出:

8.9.10.11:2342 8.9.10.11 IPv4 isLinkLocal:false isAnyLocal:false isIPv4Private:false 192.168.0.1 192.168.0.1 IPv4 isLinkLocal:false isAnyLocal:false isIPv4Private:true 192.168.0.1:80 192.168.0.1 IPv4 isLinkLocal:false isAnyLocal:false isIPv4Private:true ::FFFF:C0A8:1 ::ffff:c0a8:1 IPv6 isLinkLocal:false isAnyLocal:false isIPv6UniqueLocal:false ::FFFF:C0A8:0001 ::ffff:c0a8:1 IPv6 isLinkLocal:false isAnyLocal:false isIPv6UniqueLocal:false 0000:0000:0000:0000:0000:FFFF:C0A8:1 ::ffff:c0a8:1 IPv6 isLinkLocal:false isAnyLocal:false isIPv6UniqueLocal:false ::FFFF:C0A8:1%1 ::ffff:c0a8:1%1 IPv6 isLinkLocal:false isAnyLocal:false isIPv6UniqueLocal:false ::FFFF:192.168.0.1 ::ffff:c0a8:1 IPv6 isLinkLocal:false isAnyLocal:false isIPv6UniqueLocal:false [::FFFF:C0A8:1]:80 ::ffff:c0a8:1 IPv6 isLinkLocal:false isAnyLocal:false isIPv6UniqueLocal:false [::FFFF:C0A8:1%1]:80 ::ffff:c0a8:1%1 IPv6 isLinkLocal:false isAnyLocal:false isIPv6UniqueLocal:false :: :: IPv6 isLinkLocal:false isAnyLocal:true isIPv6UniqueLocal:false 0.0.0.0 0.0.0.0 IPv4 isLinkLocal:false isAnyLocal:true isIPv4Private:false 169.254.0.1 169.254.0.1 IPv4 isLinkLocal:true isAnyLocal:false isIPv4Private:false fe80::1 fe80::1 IPv6 isLinkLocal:true isAnyLocal:false isIPv6UniqueLocal:false fc00::1 fc00::1 IPv6 isLinkLocal:false isAnyLocal:false isIPv6UniqueLocal:true foo not an address



0
投票
(net.IP).To4()

将为它们返回非零结果。例如,

2001:db8::
有一个 

net/netip

包实际上可以区分地址族,而无需隐式重新映射:

addr, err := netip.ParseAddr(ip)
if err != nil {
    return fmt.Errorf("not an IP address: %w")
}
if !addr.Is4() {
    return errors.New("not an IPv4 address, but IPv6")
}

	
© www.soinside.com 2019 - 2024. All rights reserved.