35

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.

R Hoffmann
  • 67
  • 3
  • 7
vakas
  • 1,799
  • 5
  • 23
  • 40

6 Answers6

60

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;
}
ympostor
  • 909
  • 7
  • 16
Gabriel Graves
  • 1,751
  • 1
  • 22
  • 40
  • 2
    Note that IPv6 also defines private blocks https://en.wikipedia.org/wiki/Private_network#IPv6 – Eric J. Feb 28 '20 at 17:09
  • 1
    For IPv6 you can use IPAddress.IsIPv6LinkLocal. https://learn.microsoft.com/en-us/dotnet/api/system.net.ipaddress.isipv6linklocal – Iamsodarncool Jan 30 '21 at 02:43
  • 1
    @Iamsodarncool Is that true? I though link local was something different than private, at least in Ipv4 (taking up the 169.254.0.0./16 spaces iirc). – Beltway Jul 22 '21 at 05:49
  • This answer is missing 169.254.0.0/16 and 0.0.0.0 which is a valid alias for localhost connections which should be considered private, too, along with 127.0.0.0/8. – Mikko Rantalainen Oct 03 '22 at 09:05
  • @MikkoRantalainen I'm pretty sure they were asking for only RFC1918 and not loopback IP addresses. Loop back is not RFC1918. – Gabriel Graves Oct 06 '22 at 17:24
22

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.

Anders Abel
  • 67,989
  • 17
  • 150
  • 217
19

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();
Prince Owen
  • 1,225
  • 12
  • 20
Aidan Connelly
  • 164
  • 3
  • 13
11

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();
        }
    }
}
angularsen
  • 8,160
  • 1
  • 69
  • 83
10

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;
        }
    }
Óscar Andreu
  • 1,630
  • 13
  • 32
  • 4
    The IPv6 case here is only "localhost", but that protocol defines additional private blocks https://en.wikipedia.org/wiki/Private_network#IPv6 – Eric J. Feb 28 '20 at 17:10
3
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\.)
Faisal Nasim
  • 253
  • 2
  • 8
  • 3
    Possibly easier to convert to uint32 and then use bitwise operations: `((address & 0xFF000000U) == 0x0A000000U) || ...`. If there are a lot of address to check it should also be quicker. – Richard Nov 13 '11 at 18:31
  • 2
    I suspect that this will work, but regexps are conceptually the wrong tool for testing if small integers are in the right range. Comparing numbers will be much more efficient than matching strings. – Anthony Jun 14 '13 at 13:18
  • Convert the current IP to the numerical representation and then see if it fits in any of that classes (use numerical comparison only). It's working perfectly for me. – Edi Nov 09 '15 at 13:00
  • Please note that 127.0.0.0/8 is reserved for loopback (not only 127.0.0.1 but 127.*.*.*), see https://en.wikipedia.org/wiki/Loopback – zpon Sep 02 '16 at 08:50