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

最后都变了- 提交于 2019-11-27 08:49:40

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 (!subnetMask.Contains("/"))
            { // 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 (maskAddress.AddressFamily == AddressFamily.InterNetwork)
            {
                // Convert the mask address to an unsigned integer.
                var maskAddressBits = BitConverter.ToUInt32(maskAddress.GetAddressBytes().Reverse().ToArray());

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

                // 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) == (ipAdressBits & mask);
            }

            if (maskAddress.AddressFamily == AddressFamily.InterNetworkV6)
            {
                // Convert the mask address to a BitArray.
                var maskAddressBits = new BitArray(maskAddress.GetAddressBytes());

                // And convert the IpAddress to a BitArray.
                var ipAdressBits = new BitArray(address.GetAddressBytes());

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

                // Compare the prefix bits.
                for (int i = 0; i < maskLength; i++)
                {
                    if (ipAdressBits[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")]
        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")]
        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")]
        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);
    }
}

Bit manipulation works. Stuff the IP into a 32-bits unsigned integer, do the same with the subnet's address, &-mask both with 0xFFFFFFFF << (32-20) and compare:

unsigned int net = ..., ip = ...;
int network_bits = 20;
unsigned int mask = 0xFFFFFFFF << (32 - network_bits);
if ((net & mask) == (ip & mask)) {
  // ...
}

Since the MSDN blog code relies on a broadcast and IPv6 doesn't have one, I don't know if it works with IPv6.

I ended up with these methods (thanks to nu everest). You can get the subnet and mask from a CIDR notation ("1.2.3.4/5") and check whether an adress is within this network or not.

This works for IPv4 and IPv6:

public static class IpAddresses
{
    public static Tuple<IPAddress, IPAddress> GetSubnetAndMaskFromCidr(string cidr)
    {
        var delimiterIndex = cidr.IndexOf('/');
        string ipSubnet = cidr.Substring(0, delimiterIndex);
        string mask = cidr.Substring(delimiterIndex + 1);

        var subnetAddress = IPAddress.Parse(ipSubnet);

        if (subnetAddress.AddressFamily == AddressFamily.InterNetworkV6)
        {
            // ipv6
            var ip = BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", NumberStyles.HexNumber) << (128 - int.Parse(mask));

            var maskBytes = new[]
            {
                (byte)((ip & BigInteger.Parse("00FF000000000000000000000000000000", NumberStyles.HexNumber)) >> 120),
                (byte)((ip & BigInteger.Parse("0000FF0000000000000000000000000000", NumberStyles.HexNumber)) >> 112),
                (byte)((ip & BigInteger.Parse("000000FF00000000000000000000000000", NumberStyles.HexNumber)) >> 104),
                (byte)((ip & BigInteger.Parse("00000000FF000000000000000000000000", NumberStyles.HexNumber)) >> 96),
                (byte)((ip & BigInteger.Parse("0000000000FF0000000000000000000000", NumberStyles.HexNumber)) >> 88),
                (byte)((ip & BigInteger.Parse("000000000000FF00000000000000000000", NumberStyles.HexNumber)) >> 80),
                (byte)((ip & BigInteger.Parse("00000000000000FF000000000000000000", NumberStyles.HexNumber)) >> 72),
                (byte)((ip & BigInteger.Parse("0000000000000000FF0000000000000000", NumberStyles.HexNumber)) >> 64),
                (byte)((ip & BigInteger.Parse("000000000000000000FF00000000000000", NumberStyles.HexNumber)) >> 56),
                (byte)((ip & BigInteger.Parse("00000000000000000000FF000000000000", NumberStyles.HexNumber)) >> 48),
                (byte)((ip & BigInteger.Parse("0000000000000000000000FF0000000000", NumberStyles.HexNumber)) >> 40),
                (byte)((ip & BigInteger.Parse("000000000000000000000000FF00000000", NumberStyles.HexNumber)) >> 32),
                (byte)((ip & BigInteger.Parse("00000000000000000000000000FF000000", NumberStyles.HexNumber)) >> 24),
                (byte)((ip & BigInteger.Parse("0000000000000000000000000000FF0000", NumberStyles.HexNumber)) >> 16),
                (byte)((ip & BigInteger.Parse("000000000000000000000000000000FF00", NumberStyles.HexNumber)) >> 8),
                (byte)((ip & BigInteger.Parse("00000000000000000000000000000000FF", NumberStyles.HexNumber)) >> 0),
            };

            return Tuple.Create(subnetAddress, new IPAddress(maskBytes));
        }
        else
        {
            // ipv4
            uint ip = 0xFFFFFFFF << (32 - int.Parse(mask));

            var maskBytes = new[]
            {
                (byte)((ip & 0xFF000000) >> 24),
                (byte)((ip & 0x00FF0000) >> 16),
                (byte)((ip & 0x0000FF00) >> 8),
                (byte)((ip & 0x000000FF) >> 0),
            };

            return Tuple.Create(subnetAddress, new IPAddress(maskBytes));
        }
    }

    public static bool IsAddressOnSubnet(IPAddress address, IPAddress subnet, IPAddress mask)
    {
        byte[] addressOctets = address.GetAddressBytes();
        byte[] subnetOctets = mask.GetAddressBytes();
        byte[] networkOctets = subnet.GetAddressBytes();

        // ensure that IPv4 isn't mixed with IPv6
        if (addressOctets.Length != subnetOctets.Length
            || addressOctets.Length != networkOctets.Length)
        {
            return false;
        }

        for (int i = 0; i < addressOctets.Length; i += 1)
        {
            var addressOctet = addressOctets[i];
            var subnetOctet = subnetOctets[i];
            var networkOctet = networkOctets[i];

            if (networkOctet != (addressOctet & subnetOctet))
            {
                return false;
            }
        }
        return true;
    }
}

Example usage:

var subnetAndMask = IpAddresses.GetSubnetAndMaskFromCidr("10.132.0.0/20");
bool result = IpAddresses.IsAddressOnSubnet(
    IPAddress.Parse("10.132.12.34"),
    subnetAndMask.Item1,
    subnetAndMask.Item2);

I'm late to the party here, but had a similar need, and put together a quick package to do exactly this.

https://www.nuget.org/packages/IpMatcher/

and source:

https://github.com/jchristn/IpMatcher

Simple use:

using IpMatcher;

Matcher matcher = new Matcher();
matcher.Add("192.168.1.0", "255.255.255.0");
matcher.Add("192.168.2.0", "255.255.255.0");
matcher.Remove("192.168.2.0");
matcher.Exists("192.168.1.0", "255.255.255.0");  // true
matcher.Match("192.168.1.34"); // true
matcher.Match("10.10.10.10");  // false

The solution is to convert the IP Address into bytes using System.Net.IPAddress and perform bitwise comparisons on the address, subnet, and mask octets.

The Binary AND Operator & copies a bit to the result if it exists in both operands.

The code:

using System.Net;   // Used to access IPAddress

bool IsAddressOnSubnet(string address, string subnet, string mask)
{
    try
    {
        IPAddress Address = IPAddress.Parse(address);
        IPAddress Subnet = IPAddress.Parse(subnet);
        IPAddress Mask = IPAddress.Parse(mask);            

        Byte[] addressOctets = Address.GetAddressBytes();
        Byte[] subnetOctets = Mask.GetAddressBytes();
        Byte[] networkOctets = Subnet.GetAddressBytes();

        return
            ((networkOctets[0] & subnetOctets[0]) == (addressOctets[0] & subnetOctets[0])) &&
            ((networkOctets[1] & subnetOctets[1]) == (addressOctets[1] & subnetOctets[1])) &&
            ((networkOctets[2] & subnetOctets[2]) == (addressOctets[2] & subnetOctets[2])) &&
            ((networkOctets[3] & subnetOctets[3]) == (addressOctets[3] & subnetOctets[3]));
    }
    catch (System.Exception ex)
    {
        return false;                
    }
}

Special Thanks to Спасибо! Прекрасное решение! Reference

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!