golang如何区分IPv4和IPv6。

huangapple go评论153阅读模式
英文:

golang distinguish IPv4 IPv6

问题

我正在为一个程序工作,需要检查一个IP(连接我到互联网的IP)是公共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?

英文:

For a program I'm working on, I have to check whether an IP (the IP which connects me to the Internet) is public or private. For that, I need to distinguish if an IP is IPv4 or IPv6.

I wanted to check it by the length of the 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))

Well, my IP is 192.168.2.100, so IPv4, but len(ip) tells me that the length is 16 which would be IPv6.
What is my mistake? Does any other method exist to distinguish between IPv4 and IPv6 which works always?

答案1

得分: 57

jimt的答案是正确的,但相对复杂。我会简单地检查ip.To4() != nil。由于文档中说“如果ip不是IPv4地址,To4返回nil”,这个条件应该在且仅在地址是IPv4地址时返回true

英文:

jimt's answer is correct, but fairly complicated. I would simply check ip.To4() != nil. Since the documentation says "if ip is not an IPv4 address, To4 returns nil" this condition should return true if and only if the address is an IPv4 address.

答案2

得分: 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
}
英文:

The accepted answer by Evan (ip.To4() != nil) solves the question. However there have been some comments and others answers that check if the representation is IPv4 or IPv6 and they are not always accurate:

NOTE: A couple of lists of valid IPv4 and IPv6 valid notations respectively follow. Every entry represents a variation over the first basic one. The variations could be combined as desired, but for space reasons no variation combination is listed except for eliminating ambiguity.

Valid IPv4 notations:

  • &quot;192.168.0.1&quot;: basic
  • &quot;192.168.0.1:80&quot;: with port info

Valid IPv6 notations:

  • &quot;::FFFF:C0A8:1&quot;: basic
  • &quot;::FFFF:C0A8:0001&quot;: leading zeros
  • &quot;0000:0000:0000:0000:0000:FFFF:C0A8:1&quot;: double colon expanded
  • &quot;::FFFF:C0A8:1%1&quot;: with zone info
  • &quot;::FFFF:192.168.0.1&quot;: IPv4 literal
  • &quot;[::FFFF:C0A8:1]:80&quot;: with port info
  • &quot;[::FFFF:C0A8:1%1]:80&quot;: with zone and port info

All these cases (both IPv4 and IPv6 lists) would be considered IPv4 addresses by the net package.
The IPv4 literal variation of the IPv6 list would be considered to be IPv4 by govalidator.

The easiest way to check if it is a IPv4 or IPv6 notation would be the following:

import strings

func IsIPv4(address string) bool {
    return strings.Count(address, &quot;:&quot;) &lt; 2
}

func IsIPv6(address string) bool {
    return strings.Count(address, &quot;:&quot;) &gt;= 2
}

答案3

得分: 17

IP的长度几乎总是16,因为net.IP的内部表示对于任何情况都是相同的。根据文档

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

区分这两种类型取决于IP的初始化方式。查看net.IPv4()的代码,可以看到它被初始化为16字节,其中前12字节的值设置为v4InV6Prefix的值。

// IPv4返回IPv4地址a.b.c.d的IP地址(以16字节形式)。
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将IPv4地址ip转换为4字节表示。
// 如果ip不是IPv4地址,则To4返回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。

英文:

The length of IP is almost always 16, because the internal representation of net.IP is the same for either case. From the documentation:

> Note that in this documentation, referring to an IP address as an IPv4 address or an IPv6 address is a semantic property of the address, not just the length of the byte slice: a 16-byte slice can still be an IPv4 address.

Separating the two types depends on how the IP was initialized. Looking at the code for net.IPv4(), you can see it is initialized to 16 bytes, for which the first 12 bytes are set to the value of 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
}

Where v4InV6Prefix is defined as:

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

If you want a reliable differentiation, look at how the source for net.IP.To4 handles it:

// 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 &amp;&amp;
            isZeros(ip[0:10]) &amp;&amp;
            ip[10] == 0xff &amp;&amp;
            ip[11] == 0xff {
            return ip[12:16]
    }
    return nil
}

isZeros is not exported, so you will have to copy that code locally. Then you can simply do the same as above to determine if you have IPv4 or IPv6.

答案4

得分: 5

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

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

govalidator checks for the presence of : in the string.

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

答案5

得分: 4

我会使用.net包中的.To4()和.To16()方法来判断一个地址是IPv4还是IPv6地址。请查看博客文章。在博客的最后一节中,有一些示例可能对你有帮助。

英文:

I would use .To4(), .To16() ,from the net package, to find if it is an IPv4 or IPv6 address.
check the blog post. check the last section of the blog for example that might help you.

答案6

得分: 0

IPAddress Go库可以通过几行代码来实现,这些代码是多态的,可以处理IPv4和IPv6地址。仓库在这里。免责声明:我是项目经理。

它可以处理Adirio答案中列出的所有格式,以及其他格式。

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

type AddrDetails struct {
	isLinkLocal, // IPv4/6都有链路本地概念
	isAnyLocal bool // IPv4或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\n不是一个地址\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))
	}
}
```输出:
```go
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
不是一个地址
英文:

The IPAddress Go library can do this with a few lines of code that is polymorphic, working with both IPv4 and IPv6 addresses. Repository here. Disclaimer: I am the project manager.

It can handle all the formats listed in the answer from Adirio, plus others.

IPv4 has the concept of "private", IPv6 has the concept of "unique-local", and both have "link-local".

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
}

Trying it out with your example "8.9.10.11:2342", the list from Adirio, and a few others:

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

Output:

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

huangapple
  • 本文由 发表于 2014年3月31日 06:58:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/22751035.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定