Using Classic ASP, I want to check if a given IP is within an IP range in CIDR (e.g. 192.168.0.0/24).
There are already some questions covering scenarios where I could get IP ranges from their CIDR, but that would still leave me with math to do to tell a given IP address is within the provided CIDR.
I know by routing protocols that binary calculation could be done to tell at once whether an address belongs to a given CIDR (route), so I wouldn't like to get a range, split it in four twice, and check every number for greater than or smaller than. But I couldn't find any such implementation for Classic ASP / VBScript.
Some examples would be:
192.168.0.1
belongs to 192.168.0.0/24
? (yes)192.168.0.100
belongs to 192.168.0.0/24
? (yes)192.168.0.130
belongs to 192.168.0.0/25
? (no! this one is 192.168.0.0-192.168.0.127
)And, of course, going out with the bitmasks from /32
to /0
(well the latter one should say "yes" to everything)
The actual use case is to check if a CloudFlare-proxied connection claiming CF-Connecting-IP header is actually coming from CloudFlare by the list they provide of ipv4 endpoints.
As I didn't find anything doing what I needed, I've written it my own the way I believe IPv4 works, getting to this:
function ip4toint(ip)
ip_fields = split(ip, ".")
ip4toint = cint(ip_fields(0))*(256^3) + cint(ip_fields(1))*(256^2) + cint(ip_fields(2))*(256) + cint(ip_fields(3))
end function
function ip4netmask(ip, cidr)
cidr_fields = split(cidr, "/")
cidr_ip = cidr_fields(0)
cidr_int = ip4toint(cidr_ip)
mask_bits = cidr_fields(1)
if mask_bits = 0 then ' no need for more math. always true
ip4netmask = true
exit function
end if
ip_int = ip4toint(ip)
mask_int = cint("&hffffffff")
if mask_bits < 32 and mask_bits > 0 then
mask_int = mask_int - (2^(32 - cint(mask_bits)) - 1)
end if
ip4netmask = (cidr_int and mask_int) = (ip_int and mask_int)
end function
Thus I used this to test the results:
function testip4n(ip, range)
testip4n = "ip:" & ip & " range cidr: " & range & " ip in range: " & ip4netmask(ip, range)
end function
Then calling it from a test page with various values and the expected result:
<%=testip4n("10.3.172.198", "10.3.172.198/32")%> [expect:true]<br />
<%=testip4n("10.3.172.198", "10.3.172.199/32")%> [expect:false]<br />
<%=testip4n("10.3.172.198", "10.3.172.197/32")%> [expect:false]<br />
<%=testip4n("10.3.172.198", "10.3.172.199/31")%> [expect:true]<br />
<%=testip4n("10.3.172.198", "10.3.172.197/31")%> [expect:false]<br />
<%=testip4n("10.3.172.198", "10.3.172.100/24")%> [expect:true]<br />
<%=testip4n("10.3.172.198", "10.3.172.205/24")%> [expect:true]<br />
<%=testip4n("10.3.172.198", "10.3.174.198/24")%> [expect:false]<br />
<%=testip4n("10.3.172.198", "10.3.170.198/20")%> [expect:true]<br />
<%=testip4n("10.3.172.198", "10.3.170.198/0")%> [expect:true]<br />
<%=testip4n("10.1.172.198", "10.3.170.198/0")%> [expect:true]<br />
<%=testip4n("10.3.112.198", "10.3.170.198/0")%> [expect:true]<br />
So far, so good. But once I go wild and compare these against actual IP addresses, I'm haunted by overflow exceptions in ASP. That's because I'm going as far as (2^32 - 1) whereas ASP's integer (which is not unsigned) goes as far as 2^31 (from -2^31 + 1, or the other way around)
I guess unless I find a smart byte-concatenation way to do this, I might need to "encapsulate" the parts in strings or half-integers, which doesn't look (and perform) good at all.
EDIT: as the answer I proposed myself has an "existential flaw", I'm moving it here as "what I've tried"
For now I'm going with the "packing into" two signed integers, although a waste of memory, I believe this to be better performance-wise than if I went all out with string or maybe possible byte sequences.
function ip4netmask(ip, cidr)
cidr_fields = split(cidr, "/")
cidr_ip = cidr_fields(0)
mask_bits = cidr_fields(1)
cidr_ip_parts = split(cidr_ip, ".")
cidr_int0 = cidr_ip_parts(0)*256 + cidr_ip_parts(1)
cidr_int1 = cidr_ip_parts(2)*256 + cidr_ip_parts(3)
if mask_bits = 0 then ' no need for more math. always true
ip4netmask = true
exit function
end if
ip_parts = split(ip, ".")
ip_int0 = ip_parts(0)*256 + ip_parts(1)
ip_int1 = ip_parts(2)*256 + ip_parts(3)
mask_int0 = 65535 ' 2^16 - 1
mask_int1 = 65535
if mask_bits < 16 then
mask_int0 = mask_int0 - (2^(16 - mask_bits)) + 1
mask_int1 = 0
elseif mask_bits < 32 then
mask_int1 = mask_int1 - (2^(32 - mask_bits)) + 1
end if
ip4netmask = (cidr_int0 and mask_int0) = (ip_int0 and mask_int0) and (cidr_int1 and mask_int1) = (ip_int1 and mask_int1)
end function
The same testip4n(ip, range)
function should work to test this one. I got rid of the ip4toint()
function as I would no longer benefit from a single split() call, so hopefully I'm not compromising too much in terms of speed here.