When it comes to this, I just need to know if IP is present in any of the 5M bands.
I would consider an n-ary tree, where n = 256, and work with the exact address, not the converted integer.
The top level will be an array of 256 objects. A null entry means “No”, there is no range that contains the address, so if your example is 192.168.1.0/24 array [192] will contain an object, but array [100] can be zero, because for any 100.xxx/
The saved object contains a link (link to) of another array [256] and a range specifier, only one of the two will be set, therefore 192.0.0.0/8 will have a range specifier indicating that all addresses in this range correspond to filter. This will allow you to use things like 192.255.0.0/10 , where the first 10 bits of the address are significant 1100 0000 11xx xxxx - otherwise you need to check the next octet in the array of the 2nd level.
Initially coalescing overlapping ranges, if any, in large ranges ... for example. 3 .. 10 and 7 .. 16 becomes 3 .. 16 ... allows this, since you do not need to associate a given IP address with a specific range.
This requires no more than 8 comparisons. Each octet is initially used directly as an index, followed by a comparison for null, a comparison for terminal-node (this is a range or pointer to the next level of the tree)
In the worst case, memory consumption is theoretically 4 GB (256 ^ 4) if each IP address is in the filtering range, but, of course, will be combined into one range, so in reality it will be only 1 object in the range. A more realistic worst case is likely to be more like (256 ^ 3) or 16.7 MB. Use in the real world is likely to have most array nodes [256] at each level empty.
This is essentially similar to the Huffman / prefix encoding. The shortest single prefix may end as soon as an answer (range) is found, so often you will compare average values < 4 .