vakas
vakas

Reputation: 1817

How to determine whether an IP address is private?

So far I have this code:

NetworkInterface[] adapters = NetworkInterface.GetAllNetworkInterfaces();

foreach (NetworkInterface adapter in adapters)
{
  IPInterfaceProperties properties = adapter.GetIPProperties();

  foreach (IPAddressInformation uniCast in properties.UnicastAddresses)
  {

    // Ignore loop-back addresses & IPv6
    if (!IPAddress.IsLoopback(uniCast.Address) && 
      uniCast.Address.AddressFamily!= AddressFamily.InterNetworkV6)
        Addresses.Add(uniCast.Address);
  }
}

How can I filter the private IP addresses as well? In the same way I am filtering the loopback IP addresses.

Upvotes: 36

Views: 27639

Answers (7)

Greg
Greg

Reputation: 1024

With .NET 8+, IPNetwork can be used:

public static class IpAddressHelper
{
    private static readonly IPNetwork IpV4BlockLoopback = new(IPAddress.Parse("127.0.0.0"), 8);
    private static readonly IPNetwork IpV4Block24Bits = new(IPAddress.Parse("10.0.0.0"), 8);
    private static readonly IPNetwork IpV4Block20Bits = new(IPAddress.Parse("172.16.0.0"), 12);
    private static readonly IPNetwork IpV4Block16Bits = new(IPAddress.Parse("192.168.0.0"), 16);
    private static readonly IPNetwork IpV6Block = new(IPAddress.Parse("fc00::"), 7);

    public static bool IsPrivateIp(IPAddress address)
    {
        if (address.IsIPv4MappedToIPv6)
        {
            address = address.MapToIPv4();
        }

        return address.AddressFamily switch
        {
            AddressFamily.InterNetwork => IsPrivateIpV4(address),
            AddressFamily.InterNetworkV6 => IsPrivateIpV6(address),
            _ => throw new ArgumentException(null, nameof(address)),
        };
    }

    private static bool IsPrivateIpV4(IPAddress address)
    {
        return IpV4BlockLoopback.Contains(address)
               || IpV4Block24Bits.Contains(address)
               || IpV4Block20Bits.Contains(address)
               || IpV4Block16Bits.Contains(address);
    }

    private static bool IsPrivateIpV6(IPAddress address)
    {
        return address.Equals(IPAddress.IPv6Loopback)
               || address.IsIPv6LinkLocal
               || IpV6Block.Contains(address);
    }
}

Compared to the currently accepted answer, this code:

  • supports IPv6
  • considers loopback as private
  • is 20x more performant

A quick benchmark yields

Method Mean Error StdDev Gen0 Allocated
AcceptedAnswer 163.122 ns 0.9956 ns 0.9313 ns 0.0336 285 B
ThisAnswer 8.365 ns 0.0703 ns 0.0658 ns - -

Upvotes: 2

angularsen
angularsen

Reputation: 8668

This implementation + tests cover:

  • Loopback (IPv4, IPv6)
  • Link local (IPv4, IPv6)
  • Site local (IPv6)
  • Unique local (IPv6, requires .NET6)
  • IPv4 mapped to IPv6

Tested on .NET Core 3.1 and .NET 6.

https://gist.github.com/angularsen/f77b53ee9966fcd914025e25a2b3a085

Implementation

using System;
using System.Net;
using System.Net.Sockets;

namespace MyNamespace
{
    /// <summary>
    /// Extension methods on <see cref="System.Net.IPAddress"/>.
    /// </summary>
    public static class IPAddressExtensions
    {
        /// <summary>
        /// Returns true if the IP address is in a private range.<br/>
        /// IPv4: Loopback, link local ("169.254.x.x"), class A ("10.x.x.x"), class B ("172.16.x.x" to "172.31.x.x") and class C ("192.168.x.x").<br/>
        /// IPv6: Loopback, link local, site local, unique local and private IPv4 mapped to IPv6.<br/>
        /// </summary>
        /// <param name="ip">The IP address.</param>
        /// <returns>True if the IP address was in a private range.</returns>
        /// <example><code>bool isPrivate = IPAddress.Parse("127.0.0.1").IsPrivate();</code></example>
        public static bool IsPrivate(this IPAddress ip)
        {
            // Map back to IPv4 if mapped to IPv6, for example "::ffff:1.2.3.4" to "1.2.3.4".
            if (ip.IsIPv4MappedToIPv6)
                ip = ip.MapToIPv4();

            // Checks loopback ranges for both IPv4 and IPv6.
            if (IPAddress.IsLoopback(ip)) return true;

            // IPv4
            if (ip.AddressFamily == AddressFamily.InterNetwork)
                return IsPrivateIPv4(ip.GetAddressBytes());

            // IPv6
            if (ip.AddressFamily == AddressFamily.InterNetworkV6)
            {
                return ip.IsIPv6LinkLocal ||
#if NET6_0
                       ip.IsIPv6UniqueLocal ||
#endif
                       ip.IsIPv6SiteLocal;
            }

            throw new NotSupportedException(
                    $"IP address family {ip.AddressFamily} is not supported, expected only IPv4 (InterNetwork) or IPv6 (InterNetworkV6).");
        }

        private static bool IsPrivateIPv4(byte[] ipv4Bytes)
        {
            // Link local (no IP assigned by DHCP): 169.254.0.0 to 169.254.255.255 (169.254.0.0/16)
            bool IsLinkLocal() => ipv4Bytes[0] == 169 && ipv4Bytes[1] == 254;

            // Class A private range: 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
            bool IsClassA() => ipv4Bytes[0] == 10;

            // Class B private range: 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
            bool IsClassB() => ipv4Bytes[0] == 172 && ipv4Bytes[1] >= 16 && ipv4Bytes[1] <= 31;

            // Class C private range: 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
            bool IsClassC() => ipv4Bytes[0] == 192 && ipv4Bytes[1] == 168;

            return IsLinkLocal() || IsClassA() || IsClassC() || IsClassB();
        }
    }
}

Tests

using System.Diagnostics.CodeAnalysis;
using System.Net;
using MyNamespace;
using FluentAssertions;
using Xunit;

namespace MyNamespace.Tests
{
    [SuppressMessage("ReSharper", "InvokeAsExtensionMethod")]
    public class IPAddressExtensionsTests
    {
        [Theory]
        [InlineData("1.1.1.1"     )] // Cloudflare DNS
        [InlineData("8.8.8.8"     )] // Google DNS
        [InlineData("20.112.52.29")] // microsoft.com
        public void IsPrivate_ReturnsFalse_PublicIPv4(string ip)
        {
            var ipAddress = IPAddress.Parse(ip);
            IPAddressExtensions.IsPrivate(ipAddress).Should().BeFalse();
        }

        [Theory]
        [InlineData("::ffff:1.1.1.1"     )] // Cloudflare DNS
        [InlineData("::ffff:8.8.8.8"     )] // Google DNS
        [InlineData("::ffff:20.112.52.29")] // microsoft.com
        public void IsPrivate_ReturnsFalse_PublicIPv4MappedToIPv6(string ip)
        {
            var ipAddress = IPAddress.Parse(ip);
            IPAddressExtensions.IsPrivate(ipAddress).Should().BeFalse();
        }

        [Theory]
        [InlineData("127.0.0.1"      )] // Loopback IPv4 127.0.0.1 - 127.255.255.255 (127.0.0.0/8)
        [InlineData("127.10.20.30"   )]
        [InlineData("127.255.255.255")]
        [InlineData("10.0.0.0"       )] // Class A private IP 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
        [InlineData("10.20.30.40"    )]
        [InlineData("10.255.255.255" )]
        [InlineData("172.16.0.0"     )] // Class B private IP 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
        [InlineData("172.20.30.40"   )]
        [InlineData("172.31.255.255" )]
        [InlineData("192.168.0.0"    )] // Class C private IP 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
        [InlineData("192.168.30.40"  )]
        [InlineData("192.168.255.255")]
        [InlineData("169.254.0.0"    )] // Link local (169.254.x.x)
        [InlineData("169.254.30.40"  )]
        [InlineData("169.254.255.255")]
        public void IsPrivate_ReturnsTrue_PrivateIPv4(string ip)
        {
            var ipAddress = IPAddress.Parse(ip);
            IPAddressExtensions.IsPrivate(ipAddress).Should().BeTrue();
        }

        [Theory]
        [InlineData("::ffff:127.0.0.1"      )] // Loopback IPv4 127.0.0.1 - 127.255.255.254 (127.0.0.0/8)
        [InlineData("::ffff:127.10.20.30"   )]
        [InlineData("::ffff:127.255.255.254")]
        [InlineData("::ffff:10.0.0.0"       )] // Class A private IP 10.0.0.0 – 10.255.255.255 (10.0.0.0/8)
        [InlineData("::ffff:10.20.30.40"    )]
        [InlineData("::ffff:10.255.255.255" )]
        [InlineData("::ffff:172.16.0.0"     )] // Class B private IP 172.16.0.0 – 172.31.255.255 (172.16.0.0/12)
        [InlineData("::ffff:172.20.30.40"   )]
        [InlineData("::ffff:172.31.255.255" )]
        [InlineData("::ffff:192.168.0.0"    )] // Class C private IP 192.168.0.0 – 192.168.255.255 (192.168.0.0/16)
        [InlineData("::ffff:192.168.30.40"  )]
        [InlineData("::ffff:192.168.255.255")]
        [InlineData("::ffff:169.254.0.0"    )] // Link local (169.254.x.x)
        [InlineData("::ffff:169.254.30.40"  )]
        [InlineData("::ffff:169.254.255.255")]
        public void IsPrivate_ReturnsTrue_PrivateIPv4MappedToIPv6(string ip)
        {
            var ipAddress = IPAddress.Parse(ip);
            IPAddressExtensions.IsPrivate(ipAddress).Should().BeTrue();
        }

        [Theory]
        [InlineData("::1"              )]  // Loopback
        [InlineData("fe80::"           )]  // Link local
        [InlineData("fe80:1234:5678::1")]  // Link local
        [InlineData("fc00::"           )]  // Unique local, globally assigned.
        [InlineData("fc00:1234:5678::1")]  // Unique local, globally assigned.
        [InlineData("fd00::"           )]  // Unique local, locally assigned.
        [InlineData("fd12:3456:789a::1")]  // Unique local, locally assigned.
        public void IsPrivate_ReturnsTrue_PrivateIPv6(string ip)
        {
            var ipAddress = IPAddress.Parse(ip);
            IPAddressExtensions.IsPrivate(ipAddress).Should().BeTrue();
        }

        [Theory]
        [InlineData("2606:4700:4700::64"                     )] // Cloudflare DNS
        [InlineData("2001:4860:4860::8888"                   )] // Google DNS
        [InlineData("2001:0db8:85a3:0000:0000:8a2e:0370:7334")] // Commonly used example.
        public void IsPrivate_ReturnsFalse_PublicIPv6(string ip)
        {
            var ipAddress = IPAddress.Parse(ip);
            IPAddressExtensions.IsPrivate(ipAddress).Should().BeFalse();
        }
    }
}

Upvotes: 15

Aidan Connelly
Aidan Connelly

Reputation: 184

The best way to do this would be an extension method to the IP Address class

    /// <summary>
    /// An extension method to determine if an IP address is internal, as specified in RFC1918
    /// </summary>
    /// <param name="toTest">The IP address that will be tested</param>
    /// <returns>Returns true if the IP is internal, false if it is external</returns>
    public static bool IsInternal(this IPAddress toTest)
    {
        if (IPAddress.IsLoopback(toTest)) return true;
        else if (toTest.ToString() == "::1") return false;

        byte[] bytes = toTest.GetAddressBytes();
        switch( bytes[ 0 ] )
        {
            case 10:
                return true;
            case 172:
                return bytes[ 1 ] < 32 && bytes[ 1 ] >= 16;
            case 192:
                return bytes[ 1 ] == 168;
            default:
                return false;
        }
    }

Then, one may call the method on an instance of the IP address class

    bool isIpInternal = ipAddressInformation.Address.IsInternal();

Upvotes: 21

&#211;scar Andreu
&#211;scar Andreu

Reputation: 1700

Added IPv6 and localhost cases.

    /* An IP should be considered as internal when:

       ::1          -   IPv6  loopback
       10.0.0.0     -   10.255.255.255  (10/8 prefix)
       127.0.0.0    -   127.255.255.255  (127/8 prefix)
       172.16.0.0   -   172.31.255.255  (172.16/12 prefix)
       192.168.0.0  -   192.168.255.255 (192.168/16 prefix)
     */
    public bool IsInternal(string testIp)
    {
        if(testIp == "::1") return true;

        byte[] ip = IPAddress.Parse(testIp).GetAddressBytes();
        switch (ip[0])
        {
            case 10:
            case 127:
                return true;
            case 172:
                return ip[1] >= 16 && ip[1] < 32;
            case 192:
                return ip[1] == 168;
            default:
                return false;
        }
    }

Upvotes: 11

Gabriel Graves
Gabriel Graves

Reputation: 1785

A more detailed response is here:

private bool _IsPrivate(string ipAddress)
{
    int[] ipParts = ipAddress.Split(new String[] { "." }, StringSplitOptions.RemoveEmptyEntries)
                             .Select(s => int.Parse(s)).ToArray();
    // in private ip range
    if (ipParts[0] == 10 ||
        (ipParts[0] == 192 && ipParts[1] == 168) ||
        (ipParts[0] == 172 && (ipParts[1] >= 16 && ipParts[1] <= 31))) {
        return true;
    }

    // IP Address is probably public.
    // This doesn't catch some VPN ranges like OpenVPN and Hamachi.
    return false;
}

Upvotes: 61

Faisal Nasim
Faisal Nasim

Reputation: 253

10.0.0.0        -   10.255.255.255  (10/8 prefix)
172.16.0.0      -   172.31.255.255  (172.16/12 prefix)
192.168.0.0     -   192.168.255.255 (192.168/16 prefix)

Use the ranges defined in the RFC (as suggested by Anders); than use regular expression to detect/remove the private IP address from the list.

Here is a sample RegEx to detect private IP addresses. (Not tested by me)

(^127\.0\.0\.1)|
(^10\.)|
(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|
(^192\.168\.)

Upvotes: 3

Anders Abel
Anders Abel

Reputation: 69260

The private address ranges are defined in RFC1918. They are:

  • 10.0.0.0 - 10.255.255.255 (10/8 prefix)
  • 172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
  • 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)

You might also want to filter out link-local addresses (169.254/16) as defined in RFC3927.

Upvotes: 22

Related Questions