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?

有帮助吗?

解决方案

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.

其他提示

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:

  • "192.168.0.1": basic
  • "192.168.0.1:80": with port info

Valid IPv6 notations:

  • "::FFFF:C0A8:1": basic
  • "::FFFF:C0A8:0001": leading zeros
  • "0000:0000:0000:0000:0000:FFFF:C0A8:1": double colon expanded
  • "::FFFF:C0A8:1%1": with zone info
  • "::FFFF:192.168.0.1": IPv4 literal
  • "[::FFFF:C0A8:1]:80": with port info
  • "[::FFFF:C0A8:1%1]:80": 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 address would be the following:

import strings

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

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

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 &&
            isZeros(ip[0:10]) &&
            ip[10] == 0xff &&
            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.

govalidator checks for the presence of : in the string.

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

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.

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{
    "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))
    }
}

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
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top