2

I have a python script that grabs all IP from the arp table and assign it to a variable. I have for loop that creates another two variables start_IP containing the first IP of a subnet and last_IP containing the last IP in that same subnet. For each loop I will have a different start and last IPs.

I am trying to do check the variable containing all IPs and see how many IPs fall under each subnet.

What would be the best way to do this? Here is a hardcoded example: count = 0

arps = ['10.20.30.130','10.20.30.131','10.20.30.132', '10.20.30.133', 
'10.20.30.136', '10.20.30.137', '10.20.30.138', '10.20.30.139', '10.20.30.140', '10.20.30.141', '10.20.30.143', '10.20.30.149']
 start_ip = "10.20.30.132"
 end_ip = "10.20.30.142"
 count = 0      
 for arp in arps:
    if arp >= start_ip and arp <= end_ip:
        count = count + 1
        print count
    else:
        continue

 print "Count: ", count

Is there a better an faster way of doing this?

Neo
  • 47
  • 8

2 Answers2

3

Two ways. The simple way:

IP addresses compare octet-by-octet. Interestingly, Python lists compare element-by-element. So if you just split the IP addresses by dot and map the list to int, you can compare them correctly.

The even simpler way:

ipaddress.ip_address is comparable, as long the compared addresses are the same version (IPv4 or IPv6).

However, string comparison does not provide a correct ordering of IP addresses:

'1.12.1.1' < '1.2.1.1'
# => True (should be False)

Except for those issues, your code is fine. It could be written more concisely:

import ipaddress
arps = ['10.20.30.130','10.20.30.131','10.20.30.132', '10.20.30.133', 
    '10.20.30.136', '10.20.30.137', '10.20.30.138', '10.20.30.139', 
    '10.20.30.140', '10.20.30.141', '10.20.30.143', '10.20.30.149']
start_ip = "10.20.30.132"
end_ip = "10.20.30.142"

start_ip_ip = ipaddress.ip_address(start_ip)
end_ip_ip = ipaddress.ip_address(end_ip)

sum(1 for ip in arps if start_ip_ip <= ipaddress.ip_address(ip) <= end_ip_ip)
# => 8

If you specifically want to see addresses in specific subnet, you don't even need to use start and end addresses, if you know the subnet specification:

ipaddress.ip_address('192.168.1.17') in ipaddress.ip_network('192.168.0.0/16')
# => True
Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I do have CIDR in my database so your last solution might work perfect and minimize code. I will give it a try! Thanks – Neo Dec 20 '18 at 16:43
  • I am going to use the first solution. I am using python 2 so I had to convert all IP strings to Unicode first to make it work. – Neo Dec 20 '18 at 16:59
2

I can think of two solutions as below. Method 1 has a time complexity of O(N) and Method 2 has a time complexity of O(Nlog N). As suggested by Amadan, the IP addresses need to be preprocessed beforehand.

import bisect

arps = ['10.20.30.130','10.20.30.131','10.20.30.132', '10.20.30.133', 
'10.20.30.136', '10.20.30.137', '10.20.30.138', '10.20.30.139', '10.20.30.140', '10.20.30.141', '10.20.30.143', '10.20.30.149']
start_ip = "10.20.30.132"
end_ip = "10.20.30.142"

# Padding zeros to the IP addresses to make sure they are directly comparable
def padding(s):
    return s.zfill(3)

arps = [".".join(list(map(padding, x.split(".")))) for x in arps]
start_ip = ".".join(list(map(padding, start_ip.split("."))))
end_ip   = ".".join(list(map(padding, end_ip.split("."))))

# Method 1: Pythonic one-liner
print(sum(start_ip <= x <= end_ip for x in arps))

# Method 2: Sort and binary search
def find_lt(a, x):
    i = bisect.bisect_right(a, x)
    if i:
        return i - 1
    else:
        return 0

def find_gt(a, x):
    i = bisect.bisect_right(a, x)
    if i != len(a):
        return i
    else:
        return i

arps.sort()
print(find_gt(arps, end_ip) - find_lt(arps, start_ip))
Yi Bao
  • 165
  • 2
  • 15
  • 1
    This is not correct, due to the issue I explain in my answer - `sort` will not sort string-IPs in the right order. – Amadan Dec 20 '18 at 00:09
  • @Amadan Yeah, you are right. Let me show a workaround. – Yi Bao Dec 20 '18 at 00:14
  • Also, the Method 2 has complexity of O(log N) if you ignore the fact that you sort them. Sort itself is O(N log N). (Of course, big-O is not a measure of speed, just complexity...) – Amadan Dec 20 '18 at 00:18
  • @Amadan True. Thanks. How about now? – Yi Bao Dec 20 '18 at 00:27
  • It'll work, though I think `list(map(int, x.split(".")))` is much simpler and faster than `".".join(list(map(padding, x.split("."))))`. – Amadan Dec 20 '18 at 00:29
  • Agree. I intended to keep some meaningful forms, which may or may not be necessary. – Yi Bao Dec 20 '18 at 00:32
  • Thanks for the answer. I will give it a try and see what works best for me. Thanks again. – Neo Dec 20 '18 at 16:44
  • Method 1 worked for me. Is there any way I can also add a print for all matched IPs? – Neo Dec 20 '18 at 18:19
  • `l = [x for x in arps if start_ip <= x <= end_ip]` `print(l)` `print(len(l))` – Yi Bao Dec 20 '18 at 18:27
  • Thanks!!!!! That worked like a charm. The zfill'd IP address is messing up the output and I am trying to find a way to remove leading 0s. I need to perform further actions on the IP so when they are zfill'd they fail. I do want to keep the 0 in cse an octet only has 0. Example: 10.0.10.0 – Neo Dec 20 '18 at 18:45
  • Say you have an IP string `a`. Then you can do `".".join([x.lstrip('0') or '0' for x in a.split(".")])`. – Yi Bao Dec 20 '18 at 18:55