对于我正在开发的程序,我必须检查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 的方法?
jimt 的答案是正确的,但相当复杂。我只是简单地检查一下
ip.To4() != nil
。由于文档说“如果 ip 不是 IPv4 地址,To4 返回 nil”,当且仅当该地址是 IPv4 地址时,此条件才应返回 true
。
解决了这个问题。然而,有一些评论和其他答案检查表示形式是 IPv4 还是 IPv6,但它们并不总是准确的:
注意:下面分别列出了几个有效的 IPv4 和 IPv6 有效符号列表。每个条目都代表第一个基本条目的变体。可以根据需要组合变体,但由于篇幅原因,除了消除歧义之外,没有列出变体组合。
有效的 IPv4 符号:
"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"
:IPv4 文字
"[::FFFF:C0A8:1]:80"
:带有端口信息
"[::FFFF:C0A8:1%1]:80"
:带有区域和端口信息
包视为 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
}
net.IP
的内部表示对于任何一种情况都是相同的。来自文档:
请注意,在本文档中,将 IP 地址称为 IPv4 地址或 IPv6 地址是地址的语义属性,而不仅仅是字节切片的长度:16 字节切片仍然可以是 IPv4 地址。区分这两种类型取决于 IP 的初始化方式。查看的代码,可以看到它被初始化为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}
如果您想要可靠的区分,请查看 的源代码如何处理它:
// 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
}
不会导出,因此您必须在本地复制该代码。然后您只需执行与上述相同的操作即可确定您是否拥有 IPv4 或 IPv6。
govalidator 检查字符串中是否存在 :
。
func IsIPv6(str string) bool {
ip := net.ParseIP(str)
return ip != nil && strings.Contains(str, ":")
}
可以通过几行多态代码来完成此操作,同时使用 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
(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")
}