How to check if an IP address is within a particular subnet

Using the answers from Thomas and Chris together with Ciscos Subnetting Examples I finally got something to work for IPv4 and IPv6 if you use the CIDR notation (IPAddress/PrefixLength). My IPv6-Implementation might be a bit too straight forward but as there is no UInt128-datatype I couldn't adapt Thomas's solution. Here is the code that seems to work well:

public static bool IsInSubnet(this IPAddress address, string subnetMask)
{
    var slashIdx = subnetMask.IndexOf("/");
    if (slashIdx == -1)
    { // We only handle netmasks in format "IP/PrefixLength".
        throw new NotSupportedException("Only SubNetMasks with a given prefix length are supported.");
    }

    // First parse the address of the netmask before the prefix length.
    var maskAddress = IPAddress.Parse(subnetMask.Substring(0, slashIdx));

    if (maskAddress.AddressFamily != address.AddressFamily)
    { // We got something like an IPV4-Address for an IPv6-Mask. This is not valid.
        return false;
    }

    // Now find out how long the prefix is.
    int maskLength = int.Parse(subnetMask.Substring(slashIdx + 1));

    if (maskLength == 0)
    {
        return true;
    }

    if (maskLength < 0)
    {
        throw new NotSupportedException("A Subnetmask should not be less than 0.");
    }

    if (maskAddress.AddressFamily == AddressFamily.InterNetwork)
    {
        // Convert the mask address to an unsigned integer.
        var maskAddressBits = BitConverter.ToUInt32(maskAddress.GetAddressBytes().Reverse().ToArray(), 0);

        // And convert the IpAddress to an unsigned integer.
        var ipAddressBits = BitConverter.ToUInt32(address.GetAddressBytes().Reverse().ToArray(), 0);

        // Get the mask/network address as unsigned integer.
        uint mask = uint.MaxValue << (32 - maskLength);

        // https://stackoverflow.com/a/1499284/3085985
        // Bitwise AND mask and MaskAddress, this should be the same as mask and IpAddress
        // as the end of the mask is 0000 which leads to both addresses to end with 0000
        // and to start with the prefix.
        return (maskAddressBits & mask) == (ipAddressBits & mask);
    }

    if (maskAddress.AddressFamily == AddressFamily.InterNetworkV6)
    {
        // Convert the mask address to a BitArray. Reverse the BitArray to compare the bits of each byte in the right order.
        var maskAddressBits = new BitArray(maskAddress.GetAddressBytes().Reverse().ToArray());

        // And convert the IpAddress to a BitArray. Reverse the BitArray to compare the bits of each byte in the right order.
        var ipAddressBits = new BitArray(address.GetAddressBytes().Reverse().ToArray());
        var ipAddressLength = ipAddressBits.Length;

        if (maskAddressBits.Length != ipAddressBits.Length)
        {
            throw new ArgumentException("Length of IP Address and Subnet Mask do not match.");
        }

        // Compare the prefix bits.
        for (var i = ipAddressLength - 1; i >= ipAddressLength - maskLength; i--)
        {
            if (ipAddressBits[i] != maskAddressBits[i])
            {
                return false;
            }
        }

        return true;
    }

    throw new NotSupportedException("Only InterNetworkV6 or InterNetwork address families are supported.");
}

And this are the XUnit tests I tested it with:

public class IpAddressExtensionsTests
{
    [Theory]
    [InlineData("192.168.5.85/24", "192.168.5.1")]
    [InlineData("192.168.5.85/24", "192.168.5.254")]
    [InlineData("10.128.240.50/30", "10.128.240.48")]
    [InlineData("10.128.240.50/30", "10.128.240.49")]
    [InlineData("10.128.240.50/30", "10.128.240.50")]
    [InlineData("10.128.240.50/30", "10.128.240.51")]
    [InlineData("192.168.5.85/0", "0.0.0.0")]
    [InlineData("192.168.5.85/0", "255.255.255.255")]
    public void IpV4SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress)
    {
        var ipAddressObj = IPAddress.Parse(ipAddress);
        Assert.True(ipAddressObj.IsInSubnet(netMask));
    }

    [Theory]
    [InlineData("192.168.5.85/24", "192.168.4.254")]
    [InlineData("192.168.5.85/24", "191.168.5.254")]
    [InlineData("10.128.240.50/30", "10.128.240.47")]
    [InlineData("10.128.240.50/30", "10.128.240.52")]
    [InlineData("10.128.240.50/30", "10.128.239.50")]
    [InlineData("10.128.240.50/30", "10.127.240.51")]
    public void IpV4SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress)
    {
        var ipAddressObj = IPAddress.Parse(ipAddress);
        Assert.False(ipAddressObj.IsInSubnet(netMask));
    }

    [Theory]
    [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")]
    [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF")]
    [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:0001:0000:0000:0000")]
    [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFF0")]
    [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0000")]
    [InlineData("2001:db8:abcd:5678::0/53", "2001:0db8:abcd:5000:0000:0000:0000:0000")]
    [InlineData("2001:db8:abcd:5678::0/53", "2001:0db8:abcd:57ff:ffff:ffff:ffff:ffff")]
    [InlineData("2001:db8:abcd:0012::0/0", "::")]
    [InlineData("2001:db8:abcd:0012::0/0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")]
    public void IpV6SubnetMaskMatchesValidIpAddress(string netMask, string ipAddress)
    {
        var ipAddressObj = IPAddress.Parse(ipAddress);
        Assert.True(ipAddressObj.IsInSubnet(netMask));
    }

    [Theory]
    [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFFF")]
    [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0000:0000:0000:0000")]
    [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0013:0001:0000:0000:0000")]
    [InlineData("2001:db8:abcd:0012::0/64", "2001:0DB8:ABCD:0011:FFFF:FFFF:FFFF:FFF0")]
    [InlineData("2001:db8:abcd:0012::0/128", "2001:0DB8:ABCD:0012:0000:0000:0000:0001")]
    [InlineData("2001:db8:abcd:5678::0/53", "2001:0db8:abcd:4999:0000:0000:0000:0000")]
    [InlineData("2001:db8:abcd:5678::0/53", "2001:0db8:abcd:5800:0000:0000:0000:0000")]
    public void IpV6SubnetMaskDoesNotMatchInvalidIpAddress(string netMask, string ipAddress)
    {
        var ipAddressObj = IPAddress.Parse(ipAddress);
        Assert.False(ipAddressObj.IsInSubnet(netMask));
    }
}

As base for the tests I used Ciscos Subnetting Examples and IBMs IPV6 address examples.

I hope someone finds this helpful ;)


Take a look at IP Address Calculations with C# on MSDN blogs. It contains an extension method (IsInSameSubnet) that should meet your needs as well as some other goodies.

public static class IPAddressExtensions
{
    public static IPAddress GetBroadcastAddress(this IPAddress address, IPAddress subnetMask)
    {
        byte[] ipAdressBytes = address.GetAddressBytes();
        byte[] subnetMaskBytes = subnetMask.GetAddressBytes();

        if (ipAdressBytes.Length != subnetMaskBytes.Length)
            throw new ArgumentException("Lengths of IP address and subnet mask do not match.");

        byte[] broadcastAddress = new byte[ipAdressBytes.Length];
        for (int i = 0; i < broadcastAddress.Length; i++)
        {
            broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
        }
        return new IPAddress(broadcastAddress);
    }

    public static IPAddress GetNetworkAddress(this IPAddress address, IPAddress subnetMask)
    {
        byte[] ipAdressBytes = address.GetAddressBytes();
        byte[] subnetMaskBytes = subnetMask.GetAddressBytes();

        if (ipAdressBytes.Length != subnetMaskBytes.Length)
            throw new ArgumentException("Lengths of IP address and subnet mask do not match.");

        byte[] broadcastAddress = new byte[ipAdressBytes.Length];
        for (int i = 0; i < broadcastAddress.Length; i++)
        {
            broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
        }
        return new IPAddress(broadcastAddress);
    }

    public static bool IsInSameSubnet(this IPAddress address2, IPAddress address, IPAddress subnetMask)
    {
        IPAddress network1 = address.GetNetworkAddress(subnetMask);
        IPAddress network2 = address2.GetNetworkAddress(subnetMask);

        return network1.Equals(network2);
    }
}