If you're using a reverse proxy, a CDN such as Cloudflare, a third-party Wide Application Firewall, or any other intermediate caching and/or forwarding service to serve your website over the internet, you might want to ensure that the underlying web server (typically called "origin") is only accessible to the IP addresses of such service (the "edge"). Doing that is very important from an IT security perspective because it will prevent malicious attackers (assuming they manage to retrieve the origin server's IP address) to bypass the edge proxy and directly connect to the origin server.
To comply with that using IIS, we're published several posts explaining how to set up the IP Addresses restriction built-in IIS feature using either the IIS Manager GUI or the web.config file. Such an approach is great most of the time, but what if you want to achieve the same outcome programmatically? More precisely, is there a way to check if a given IP address - let's say, the caller's IP address - is part of a given Subnet Mask in C#?
IsInSubnetMask helper method
As a matter of fact, the answer is YES! Here's a neat method that can be used to achieve such a result, compatible with IPv4 and IPv6 as long as we use CIDR notation (IPAddress/PrefixLength - example: 90.98.102.116/24).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
/// <summary> /// Returns TRUE if the given IP address is contained in the given subnetmask, FALSE otherwise. /// Examples: /// - IsInSubnet("192.168.5.1", "192.168.5.85/24") -> TRUE /// - IsInSubnet("192.168.5.1", "192.168.5.85/32") -> FALSE /// ref.: https://stackoverflow.com/a/56461160 /// </summary> /// <param name="address">The IP Address to check</param> /// <param name="subnetMask">The SubnetMask</param> /// <returns></returns> /// <exception cref="NotSupportedException"></exception> /// <exception cref="ArgumentException"></exception> public static bool IsInSubnetMask(string ipAddress, string subnetMask) { var address = IPAddress.Parse(ipAddress); 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) { var maskAddressBits = BitConverter.ToUInt32(maskAddress.GetAddressBytes().Reverse().ToArray(), 0); var ipAddressBits = BitConverter.ToUInt32(address.GetAddressBytes().Reverse().ToArray(), 0); 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; } return false; } |
The above method is strongly based on this StackOverflow answer by Christoph Sonntag: from what we have seen, it works perfectly! If you take a look at the original answer, you'll also find some useful XUnit tests.
Conclusions
That's it: we hope that the above helper method will help other C# and ASP.NET Core developers to check if a specific IP address is part of a given Subnet Mask.