Created
October 20, 2017 17:37
-
-
Save craigmbooth/eb1b305e03294641491791ef50e96b00 to your computer and use it in GitHub Desktop.
Given a CIDR block produce an iterator over IP addresses
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """CIDR blocks share the first part of the bit sequence that comprises the | |
| binary representation of the IP address | |
| We determine the network portion of the address by applying a bitwise AND | |
| operation to between the address and the netmask. A bitwise AND operation will | |
| basically save the networking portion of the address and discard the host | |
| portion. | |
| In the CIDR notation 192.0.1.0/24, the prefix is 192.0.1.0, and the total number | |
| of bits in the address is 24. The least significant 8 bits are the IP addresses | |
| that are part of this CIDR block. | |
| 10.10.1.16/32 is an address prefix with 32 bits, which is the highest number of | |
| bits allowed in IPv4. | |
| For example, we could express the idea that the IP address 192.168.0.15 is | |
| associated with the netmask 255.255.255.0 by using the CIDR notation of | |
| 92.168.0.15/24. This means that the first 24 bits of the IP address given are | |
| considered significant for the network routing. | |
| """ | |
| import re | |
| class InvalidIPError(Exception): | |
| pass | |
| class Ip(object): | |
| def __init__(self, decimal=None, binary=None): | |
| """Initialize an IP address given either its decimal (192.168.0.0) or | |
| binary (32 characters, each of which is 0 or 1) form""" | |
| if decimal is not None: | |
| self.raw = decimal | |
| self.decimal = self.raw | |
| self.binary = self._binary_from_decimal() | |
| elif binary is not None: | |
| self.raw = binary | |
| self.binary = binary | |
| self.decimal = self._decimal_from_binary() | |
| self._verify() | |
| def _verify(self): | |
| """Perform a quick validation that the decimal representation of the | |
| IP address is valid | |
| """ | |
| octets = [int(x) for x in self.decimal.split(".")] | |
| if len(octets) != 4: | |
| raise InvalidIPError("{} is not a valid IP".format(self.decimal)) | |
| for octet in octets: | |
| if octet not in range(256): | |
| raise InvalidIPError("{} is not a valid IP".format( | |
| self.decimal)) | |
| def _decimal_from_binary(self): | |
| """Takes a binary IP address and translates to decimal""" | |
| octets = [] | |
| for i in range(0, len(self.binary), 8): | |
| frac = self.binary[i:i + 8] | |
| octets.append(str(int(frac, 2))) | |
| return ".".join(octets) | |
| def _binary_from_decimal(self): | |
| """Takes a decimal IP address and translates to binary""" | |
| binrep = "" | |
| for octet in self.raw.split("."): | |
| binrep += "{0:b}".format(int(octet)).zfill(8) | |
| return binrep | |
| class Netmask(object): | |
| """Object represents the part after the / in a CIDR block""" | |
| def __init__(self, input_string): | |
| self.raw = input_string | |
| self.decimal = int(input_string) | |
| self.binary = self._binary() | |
| def _binary(self): | |
| """Return a string with `netmask` 1's padded up to 32 characters with | |
| zeroes | |
| """ | |
| return "1"*self.decimal + "0"*(32-self.decimal) | |
| class Cidr(object): | |
| """Class representing a CIDR block""" | |
| def __init__(self, input_string): | |
| # Match a regex against x.x.x.x/y | |
| result = re.match( | |
| "(?P<ip>^[0-9]+.[0-9]+.[0-9]+.[0-9]+)/(?P<netmask>[0-9]{1,2})", | |
| input_string) | |
| self.ip = Ip(decimal=result.group("ip")) | |
| self.netmask = Netmask(result.group("netmask")) | |
| self.network_prefix = self._get_network_prefix() | |
| def _get_network_prefix(self): | |
| """Make a binary representation of the network prefix. This will match | |
| the IP address of the CIDR block up to the first netmask bytes. | |
| Returns a string, 32 characters in length | |
| """ | |
| np = "" | |
| for letter in zip(self.ip.binary, self.netmask.binary): | |
| # bitwise AND the binary IP with the binary netmask | |
| np += "1" if letter[0] == "1" and letter[1] == "1" else "0" | |
| return np | |
| def enumerate_ips(self): | |
| """Yield an Ip for each IP address inside of this CIDR block""" | |
| if self.netmask.decimal == 32: | |
| yield Ip(binary=self.network_prefix) | |
| raise StopIteration | |
| max_val = 2 ** (32-self.netmask.decimal) | |
| for i in range(max_val): | |
| binary_rep = (self.network_prefix[:self.netmask.decimal] + | |
| "{0:b}".format(i).zfill(32-self.netmask.decimal)) | |
| yield Ip(binary=binary_rep) | |
| def test_enumeration(): | |
| # Test a single IP | |
| single_ip = list(Cidr("128.128.128.128/32").enumerate_ips()) | |
| assert len(single_ip) == 1 | |
| assert single_ip[0].decimal == "128.128.128.128" | |
| # Do a non-power of two netmask | |
| cidr = Cidr("192.168.50.48/30") | |
| expected = ["192.168.50.48", "192.168.50.49", "192.168.50.50", | |
| "192.168.50.51"] | |
| for res in zip(expected, list(cidr.enumerate_ips())): | |
| assert res[1].decimal == res[0] | |
| # Check the length of a larger list | |
| assert len(list(Cidr("127.0.0.0/16").enumerate_ips())) == 65536 | |
| if __name__ == "__main__": | |
| print "Testing IP enumerator..." | |
| test_enumeration() | |
| print "Tests passed" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment