6

I am trying to determine if a given IPv6 address is private or not in C# and I was tempted to simply use the 'IsIPv6SiteLocal' property on the IPAddress class. However, as explained in this comment, the logic implemented in this property is deprecated. I ran the following unit test:

[TestMethod]
public void IsPrivate_ipv6_True()
{
    // This sample private IPv6 address was generated using: http://unique-local-ipv6.com/
    var ip = IPAddress.Parse("fd44:fda4:e1ba::1");
    Assert.IsTrue(ip.IsIPv6SiteLocal);
}

The assertion in the unit test fails which confirms that IsIPv6SiteLocal does not correctly determines if an address is local. So I need an alternative.

I wrote the following extension method and I was wondering if anybody can think of a scenario where it would not properly determine if the address is private/public.

public static bool IsPrivateIPv6(this IPAddress address)
{
    var addressAsString = address.ToString();
    var firstWord = addressAsString.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[0];

    // Make sure we are dealing with an IPv6 address
    if (address.AddressFamily != AddressFamily.InterNetworkV6) return false;

    // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Unfortunately IsIPv6SiteLocal only checks for the original deprecated version:
    else if (address.IsIPv6SiteLocal) return true;

    // These days Unique Local Addresses (ULA) are used in place of Site Local. 
    // ULA has two variants: 
    //      fc00::/8 is not defined yet, but might be used in the future for internal-use addresses that are registered in a central place (ULA Central). 
    //      fd00::/8 is in use and does not have to registered anywhere.
    else if (firstWord.Substring(0, 2) == "fc" && firstWord.Length >= 4) return true;
    else if (firstWord.Substring(0, 2) == "fd" && firstWord.Length >= 4) return true;

    // Link local addresses (prefixed with fe80) are not routable
    else if (firstWord == "fe80") return true;

    // Discard Prefix
    else if (firstWord == "100") return true;

    // Any other IP address is not Unique Local Address (ULA)
    else return false;
}

EDITED 2/13/2016:

  • make sure the first word is at least 4 characters long as suggested by @RonMaupin
  • improved comment above 'else return false' as suggested by @RonMaupin
  • check for 'fe80' prefix as suggested by @KevinBurdett
  • check for 'Discard' prefix as suggested by @KevinBurdett
Community
  • 1
  • 1
desautelsj
  • 3,587
  • 4
  • 37
  • 55
  • 1
    Any reason you are still checking for the deprecated block? It has been deprecated since 2004. Also 'fc' is still undefined, you only need to check for 'fd'. You can find more information here. https://en.wikipedia.org/wiki/Unique_local_address – Will Feb 13 '16 at 00:14
  • 2
    Seems like an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Why do you think you need to know whether the IPv6 address is a unique local address vs a global address? How would you use this information? Are you have a _specific_ problem with the code you posted? Do you have a specific problem statement at all? As stated, your question is pretty broad ("can anyone think of anything?") – Peter Duniho Feb 13 '16 at 00:31
  • @Will, a simple check for `fd` will fail on something like `fd::` (reserved). Adding a check to see if the first word has four digits will make that correct. – Ron Maupin Feb 13 '16 at 02:28
  • You comment `// Any other IP address is public` is inaccurate. Public addresses are those which can be routed on the public Internet. Only some (most) addresses in the `2000::/3` range qualify as public addresses. – Ron Maupin Feb 13 '16 at 20:17
  • I realize that my question is very broad, basically I'm trying to determine if a given ipv6 address is conceptually equivalent to an ipv4 address in the 192.168.x.x range, or 10.0.x.x for example. I've always called them 'private' addresses, some people call them 'local' but I think the more technically correct way to describe then is 'non-routable' (right?). – desautelsj Feb 14 '16 at 00:05
  • @RonMaupin what would be a more accurate comment? – desautelsj Feb 14 '16 at 00:07
  • Something like `// Any other IP address is not Unique Local Address (ULA)` as described in the RFC. These addresses are not really the equivalent of IPv4 RFC 1918 private addresses. Besides just checking if the first two digits are `fc` or `fd`, you need to make sure that the first word is four digits. A two-digit first word that starts with either of those is in reserved address space, not a ULA. – Ron Maupin Feb 14 '16 at 00:18
  • @RonMaupin I improved my code based on your feedback – desautelsj Feb 14 '16 at 01:54
  • Using `StringSplitOptions.RemoveEmptyEntries` in your split will give you an incorrect first word, for example in case of `::fe80`. – RoadBump Oct 26 '17 at 12:49

4 Answers4

3

Improved @desautelsj's answer by adding a special case for ::1 and avoiding an ArgumentException from his solution (that would happen in the Substring() call):

public static bool IsPrivateIPv6(IPAddress address)
{
    // Make sure we are dealing with an IPv6 address
    if (address.AddressFamily != AddressFamily.InterNetworkV6)
        throw new ArgumentException("IP address is not V6", "address");

    var addressAsString = address.ToString();
    var firstWord = addressAsString.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[0];

    // equivalent of 127.0.0.1 in IPv6
    if (addressAsString == "::1")
        return true;

    // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Unfortunately IsIPv6SiteLocal only checks for the original deprecated version:
    else if (address.IsIPv6SiteLocal)
        return true;

    // These days Unique Local Addresses (ULA) are used in place of Site Local. 
    // ULA has two variants: 
    //      fc00::/8 is not defined yet, but might be used in the future for internal-use addresses that are registered in a central place (ULA Central). 
    //      fd00::/8 is in use and does not have to registered anywhere.
    else if (firstWord.Length >= 4 && firstWord.Substring(0, 2) == "fc")
        return true;
    else if (firstWord.Length >= 4 && firstWord.Substring(0, 2) == "fd")
        return true;

    // Link local addresses (prefixed with fe80) are not routable
    else if (firstWord == "fe80")
        return true;

    // Discard Prefix
    else if (firstWord == "100")
        return true;

    // Any other IP address is not Unique Local Address (ULA)
    return false;
}

And in F#:

let private IsIpv6AddressPrivate (address: IPAddress) =
    if address.AddressFamily = AddressFamily.InterNetwork then
        invalidArg "address" "address must be IPv6"

    // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Unfortunately IsIPv6SiteLocal only checks for the original deprecated version:
    elif address.IsIPv6SiteLocal then
        true
    else
        let addressAsString = address.ToString()

        // equivalent of 127.0.0.1 in IPv6
        if addressAsString = "::1" then
            true
        else
            let firstWord = addressAsString.Split([|':'|], StringSplitOptions.RemoveEmptyEntries).[0]
            // These days Unique Local Addresses (ULA) are used in place of Site Local. 
            // ULA has two variants: 
            //      fc00::/8 is not defined yet, but might be used in the future for internal-use addresses that are registered in a central place (ULA Central). 
            //      fd00::/8 is in use and does not have to registered anywhere.
            if (firstWord.Length >= 4 && firstWord.Substring(0, 2) = "fc") ||
               (firstWord.Length >= 4 && firstWord.Substring(0, 2) = "fd") ||
               // Link local addresses (prefixed with fe80) are not routable
               (firstWord = "fe80") ||
               // Discard Prefix
               (firstWord = "100") then
                true
            else
                false
knocte
  • 16,941
  • 11
  • 79
  • 125
1

While not particularly elegant... I can't really think of anything better :)

However, I would also check for link local addresses, prefixed with fe80:. They are not routable, but if you are pulling the IP directly from an interface, it may still report the link local when no other IPv6 addresses are present.

The wikipedia article on IPv6 (https://en.wikipedia.org/wiki/Reserved_IP_addresses#IPv6) also shows 100: as a discard prefix. Depending on what you are trying to accomplish, you may want to check for these too.

Kevin Burdett
  • 2,892
  • 1
  • 12
  • 19
  • Besides all the Reserved and special address ranges, there are some non-Internet-routable address ranges in the Global address range. Multicast is not routable on the Internet, either. I assume the problem deals with addresses which cannot be routed on the Internet since the ULA range is routable locally. – Ron Maupin Feb 13 '16 at 02:33
0

Here's the final code I used and so far it seems to be working as intended:

public static bool IsPrivateIPv6(this IPAddress address)
{
    var addressAsString = address.ToString();
    var firstWord = addressAsString.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[0];

    // Make sure we are dealing with an IPv6 address
    if (address.AddressFamily != AddressFamily.InterNetworkV6) return false;

    // The original IPv6 Site Local addresses (fec0::/10) are deprecated. Unfortunately IsIPv6SiteLocal only checks for the original deprecated version:
    else if (address.IsIPv6SiteLocal) return true;

    // These days Unique Local Addresses (ULA) are used in place of Site Local. 
    // ULA has two variants: 
    //      fc00::/8 is not defined yet, but might be used in the future for internal-use addresses that are registered in a central place (ULA Central). 
    //      fd00::/8 is in use and does not have to registered anywhere.
    else if (firstWord.Substring(0, 2) == "fc" && firstWord.Length >= 4) return true;
    else if (firstWord.Substring(0, 2) == "fd" && firstWord.Length >= 4) return true;

    // Link local addresses (prefixed with fe80) are not routable
    else if (firstWord == "fe80") return true;

    // Discard Prefix
    else if (firstWord == "100") return true;

    // Any other IP address is not Unique Local Address (ULA)
    else return false;
}
desautelsj
  • 3,587
  • 4
  • 37
  • 55
  • 1
    Concerning link local addresses "fe80", the address block is fe80::/10 ( see https://tools.ietf.org/html/rfc4291#section-2.4 or https://en.wikipedia.org/wiki/Reserved_IP_addresses#IPv6 ) First address fe80:: Last address febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff Test must be changed to `firstWord >= "fe80" && firstWord <= "febf" return true` – Stephane Mar 25 '19 at 10:23
  • fails with ::1 also length check should be before substring – Sean Mar 07 '22 at 00:50
0

Some of the answers provided here include a check for "::1" which is the IPv6 address for localhost.

Technically localhost is not a private ip address and should be checked for separately.


For a check including IPv4 and IPv6 addresses and optionally localhost you can port this javascript function.



This should be a comment but my reputation is not high enough yet.