30

Here I have a static reference the ranges I need to check:

private static List<string> Ip_Range = new List<string>()
{
    "12.144.86.0/23",
    "31.201.1.176/30",
    "46.36.198.101/32",
    "46.36.198.102/31",
    "46.36.198.104/31",
    "46.136.172.0/24",
    "63.65.11.0/24",
    "63.65.12.0/25",
    "63.65.12.128/26",
    "63.65.12.192/27",
    "63.65.12.224/28",
    "63.65.12.240/29",
    "63.65.12.248/30",
    "63.65.12.252/31",
    "63.65.12.254/32",
    "65.173.56.0/21",
    "67.23.241.179/32",
    "67.23.241.180/30",
    "67.23.241.184/29",
    "67.23.241.192/30",
    "67.23.241.196/31",
    "67.23.241.198/32",
    "72.32.164.56/29",
    "72.46.244.32/28",
    "74.91.16.48/29",
    "74.91.16.208/29",
    "74.91.20.48/28",
    "74.91.20.64/29",
    "74.112.134.120/29",
    "74.112.135.104/29",
    "74.205.37.16/29",
    "78.24.205.32/28",
    "98.129.27.88/29",
    "98.129.91.40/29",
    "166.114.0.0/16",
    "167.157.0.0/16",
    "174.143.165.80/29",
    "186.0.156.0/22",
    "186.2.0.0/17",
    "186.27.0.0/17",
    "190.0.248.0/21",
    "190.3.184.0/21"
};

Here's some pseudo code on how I see it working:

public static bool IpIsWithinRange(string ip) //Something like 127.0.0.1 or 184.56.26.35
{
    IPAddress incomingIp = IPAddress.Parse(ip);
    foreach (var subnet in Ip_Range)
    {
        IPAddress sub = IPAddress.Parse(subnet); ?????
        if (incomingIp "is in" sub) ?
            return true;            
    }
    return false;
}

Any suggestions on how to code up this functionality?

Only Bolivian Here
  • 35,719
  • 63
  • 161
  • 257
  • Are you essentially asking for us to do the work to make this work? – Tony The Lion Mar 08 '12 at 18:42
  • 2
    The [IPNetwork](http://ipnetwork.codeplex.com/) project should be able to handle the majority of what you're looking to do. – M.Babcock Mar 08 '12 at 18:43
  • 3
    @Tony: A nudge in the right direction is equally good. That's the point of this site. :) I've done this in Ruby, but I'm not really familiar with how this could be done in C#. – Only Bolivian Here Mar 08 '12 at 18:43
  • 1
    So, how did you solve this problem in Ruby? Which part of that are you having trouble translating to C#? – Greg Hewgill Mar 08 '12 at 18:44
  • @M.Babcock: Pretty easy with that library! Thanks, do you want to post an answer, or should I post the code I used to make this work? – Only Bolivian Here Mar 08 '12 at 18:49
  • @SergioTapia - I posted an answer, but it probably wouldn't hurt for you to document your result with an answer as well. – M.Babcock Mar 08 '12 at 18:53
  • 2
    @M.Babcock The IPNetwork project has moved to [github.com/lduchosal/ipnetwork](https://github.com/lduchosal/ipnetwork). I think I've changed all the references I could in the answers here, but if you recall linking to it elsewhere you might want to update those references too if you feel like it. – Andrew Morton Apr 19 '17 at 08:34
  • Does this answer your question? [How to check a input IP fall in a specific IP range](https://stackoverflow.com/questions/2138706/how-to-check-a-input-ip-fall-in-a-specific-ip-range) – Michael Freidgeim May 01 '22 at 21:24

7 Answers7

40

If you don't want/can't add another library (as the IPnetwork one) to your project and just need to deal with IPv4 CIDR ranges, here's a quick solution to your problem

// true if ipAddress falls inside the CIDR range, example
// bool result = IsInRange("10.50.30.7", "10.0.0.0/8");
private bool IsInRange(string ipAddress, string CIDRmask)
{
    string[] parts = CIDRmask.Split('/');

    int IP_addr = BitConverter.ToInt32(IPAddress.Parse(ipAddress).GetAddressBytes(), 0);
    int CIDR_addr = BitConverter.ToInt32(IPAddress.Parse(parts[0]).GetAddressBytes(), 0);
    int CIDR_mask = IPAddress.HostToNetworkOrder(-1 << (32 - int.Parse(parts[1])));

    return ((IP_addr & CIDR_mask) == (CIDR_addr & CIDR_mask));
}

the above will allow you to quickly check if a given IPv4 address falls inside a given CIDR range; notice that the above code is barebone, it's up to you to check if a given IP (string) and CIDR range are correct before feeding them to the function (you may just use the tryparse or whatever...)

Jahorse
  • 70
  • 1
  • 6
ObiWan
  • 401
  • 4
  • 2
  • 5
    It doesn't really effect the outcome, but you should probably swap the value being parsed for IP_addr and CIDR_addr. – Jeffrey Harmon Aug 01 '16 at 02:33
  • 1
    This does not pass the CIDR range of 0.0.0.0/0 in other words all. Other than that it's great! – pijemcolu Oct 02 '17 at 13:03
  • In case of CIDR mask being zero I have simply added an if to return true as anything valid would match. – pijemcolu Oct 02 '17 at 13:10
  • The IP_addr and CIDR_addr variables were swapped as Jeffrey suggested. – Jahorse Aug 13 '21 at 16:51
  • Could someone explain this line: IPAddress.HostToNetworkOrder(-1 << (32 - int.Parse(parts[1]))); ? I don't understand why -1 and why host to network order function... :') – KitAndKat Oct 01 '21 at 10:28
25

Decided to answer my own question so people can benefit. If it can be improved, please do!

I used the IPNetwork library and it worked out fantastically!

nuget install IPNetwork2

Below is the code I used:

using System.Net;

public static class RedirectHelpers
{
    public static bool IpIsWithinBoliviaRange(string ip)
    {
        IPAddress incomingIp = IPAddress.Parse(ip);
        foreach (var subnet in Bolivia_Ip_Range)
        {
            IPNetwork network = IPNetwork.Parse(subnet);

            if (IPNetwork.Contains(network, incomingIp))
                return true;
        }
        return false;
    }

    private static List<string> Bolivia_Ip_Range = new List<string>()
    {
        "12.144.86.0/23",
        "31.201.1.176/30",
        "46.36.198.101/32",
        "46.36.198.102/31",
        "46.36.198.104/31",
        "46.136.172.0/24",
        "63.65.11.0/24",
        "63.65.12.0/25",
        "63.65.12.128/26",
        "63.65.12.192/27",
        "63.65.12.224/28",
        "63.65.12.240/29",
        "63.65.12.248/30",
        "63.65.12.252/31",
        "63.65.12.254/32",
        "65.173.56.0/21",
        "67.23.241.179/32",
        "67.23.241.180/30",
        "67.23.241.184/29",
        "67.23.241.192/30",
        "67.23.241.196/31",
        "67.23.241.198/32",
        "72.32.164.56/29",
        "72.46.244.32/28",
        "74.91.16.48/29",
        "74.91.16.208/29",
        "74.91.20.48/28",
        "74.91.20.64/29",
        "74.112.134.120/29",
        "74.112.135.104/29",
        "74.205.37.16/29",
        "78.24.205.32/28",
        "98.129.27.88/29",
        "98.129.91.40/29",
        "166.114.0.0/16",
        "167.157.0.0/16",
        "174.143.165.80/29",
        "186.0.156.0/22",
        "186.2.0.0/17",
        "186.27.0.0/17",
        "190.0.248.0/21",
        "190.3.184.0/21",
        "166.114.0.0/16",
        "167.157.0.0/16",
        "186.2.0.0/18",
        "190.11.64.0/20",
        "190.11.80.0/20",
        "190.103.64.0/20",
        "190.104.0.0/19",
        "190.107.32.0/20",
        "190.129.0.0/17",
        "190.181.0.0/18",
        "190.186.0.0/18",
        "190.186.64.0/18",
        "190.186.128.0/18",
        "200.7.160.0/20",
        "200.58.64.0/20",
        "200.58.80.0/20",
        "200.58.160.0/20",
        "200.58.176.0/20",
        "200.75.160.0/20",
        "200.85.128.0/20",
        "200.87.0.0/17",
        "200.87.128.0/17",
        "200.105.128.0/19",
        "200.105.160.0/19",
        "200.105.192.0/19",
        "200.112.192.0/20",
        "200.119.192.0/20",
        "200.119.208.0/20",
        "201.222.64.0/19",
        "201.222.96.0/19"
    };
}
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
Only Bolivian Here
  • 35,719
  • 63
  • 161
  • 257
  • The bulk (pretty much the `foreach`) of your `IpIsWithinBoliviaRange` method could be replaced with a simple LINQ query: `return Bolivia_Ip_Range.Any(subnet => IPNetwork.Contains(IPNetwork.Parse(subnet), incomingIp));` – M.Babcock Mar 09 '12 at 00:30
  • 1
    First impresions, this solution looks very inefficient. you are performing IPNetwork network = IPNetwork.Parse(subnet) on every subnet in the list for every call of the method !!!! You know that it will always parse to the same result so why not memoize that? Likewise, you could also (depending on how many repeat IP visitors you'd likely receive) memoize the final result against the incoming IP address to save lookup - although this might not actually be a good idea if large numbers of different ip addresses expected with low repeats. – arcseldon Feb 02 '16 at 21:31
11

Luckily most of the work has already been done for you (so we don't have to). Check out the IPNetwork project. You'll parse all of your CIDR addresses with IPNetwork.Parse. Then to see if a specific IPAddress is in range just use IPNetwork.Contains method.


I got bored so here's a method you can use to test whether an IP address is in range or not:

private Dictionary<string, IPNetwork> netCache = null;
public bool IsInRange(string ipAddress)
{
    if (netCache == null)
        netCache = Ip_Range.ToDictionary((keyItem) => keyItem, (valueItem) => IPNetwork.Parse(valueItem));

    return netCache.Values.Any(net => IPNetwork.Contains(net, IPAddress.Parse(ipAddress)));
}

This is dependent on the Ip_Range list from your question but translates them to IPNetwork instances (missing sanity checks for brevity).

Usage:

List<string> addrList = new List<string> { "12.144.86.1", "10.254.6.172" };
addrList.ForEach(addr => Console.WriteLine("{0}: {1}", addr, IsInRange(addr)));

Test Output:

12.144.86.1: True
10.254.6.172: False

Of course there is still a lot that could (and probably should) be done with it, but this proves the concept.

Andrew Morton
  • 24,203
  • 9
  • 60
  • 84
M.Babcock
  • 18,753
  • 6
  • 54
  • 84
  • Can't get IPNetwork to run in a VB.Net project... Intellisense likes it, but it barfs on run, wanting to find IPNetwork.cs! :( Might be restricted to C#? – Compassionate Narcissist May 13 '14 at 02:33
  • 1
    @PatTrainor - Please open a new question so your situation can be reviewed by the appropriate members of the community. I can assure you, it's not possible to restrict the languages which are able to use an assembly. – M.Babcock May 13 '14 at 16:04
  • I'm with you on that! Dangest thing I ever saw... Will create new thread... [No way to ask the author, either...] – Compassionate Narcissist May 13 '14 at 22:54
  • @M.Babcock. Please could you explain why are you using a Dictionary instead of List? – fravelgue Aug 10 '15 at 12:08
  • @fravelgue - It just happened to be what came to my mind while writing the answer 3 years ago. There are arguably several performance improvements that could be done to the presented code for which a Dictionary would be warranted. – M.Babcock Aug 10 '15 at 17:18
4
    public static bool IpIsInRange(string subnet, string ip)
    {
        var splitSubnet = subnet.Split('/');
        var maskBits = 32 - int.Parse(splitSubnet[1]);
        if (maskBits == 32)
        {
            return true;
        }
        var subnetIp = BitConverter.ToInt32(IPAddress.Parse(splitSubnet[0]).GetAddressBytes().Reverse().ToArray(), 0) >> maskBits << maskBits;
        var clientIp = BitConverter.ToInt32(IPAddress.Parse(ip).GetAddressBytes().Reverse().ToArray(), 0) >> maskBits << maskBits;
        return subnetIp == clientIp;
    }
2

If you understand the CIDR notation, you can easily do the math in your parse method.

You basically know that an IPv4 address is 32bits long and that the CIDR notation means that the number of bits behind the "/" are the network address bits (ie the masked out bits), therefore the leftover bits are represent the number of hosts in the subnet.

From wikipedia article:

The number of addresses of a subnet defined by the mask or prefix can be calculated as 2address size - prefix size, in which the address size for IPv6 is 128 and 32 for IPv4. For example, in IPv4, a prefix size of /29 gives: 232-29 = 23 = 8 addresses.

So you could (no I'm not going to work out the details for you) convert your addresses into binary and do the binary AND with the given mask (also in binary form), then you have the network address part of the IP address left, which should match with whatever address you're checking against to see if it's in a particular subnet.

Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
  • The fact that you state "no I'm not going to work out the details for you" suggests this is likely reinventing the wheel (time consuming tasks for every day problems like this get automated v.quickly). – arcseldon Feb 02 '16 at 21:40
1

For start, you should use that:

IPNetwork ipnetwork = IPNetwork.Parse("192.168.168.100/29");
Console.WriteLine("CIDR: {0}", ipnetwork.Cidr);

Output

CIDR: 29
RePRO
  • 265
  • 6
  • 18
-1

I use the below method to determine whether a given IP Address is a public one or private/internal:

private bool IsInternalIpAddress(string ipAddress)
    {
        // notes: http://whatismyipaddress.com/private-ip

        var internalIpRanges = Enumerable
            .Range(16, 31)
            .Select(i => "172.{0}.".FormatWith(i))
            .Concat(new[] {"192.168.", "10.", "127."})
            .ToArray();

        return ipAddress.StartsWith(internalIpRanges);
    }
Dynamic
  • 1,553
  • 2
  • 19
  • 25