Last active
April 13, 2021 19:15
-
-
Save jathanism/6757ad79d2a38b0c4af783fd60aa1daa to your computer and use it in GitHub Desktop.
This file contains 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
import re | |
import netaddr | |
RE_COLON = re.compile(".*::$") | |
def parse_network_as_string(search): | |
""" | |
Attempts to parse a (potentially incomplete) IPAddress and return an IPNetwork. | |
eg: '10.10' should be interpreted as netaddr.IPNetwork('10.10.0.0/16') | |
""" | |
version = 4 | |
try: | |
# Disregard netmask | |
search = search.split("/")[0] | |
# Attempt to quickly assess v6 | |
if ":" in search: | |
version = 6 | |
# (IPv6) If the value ends with ":" but it's not "::", make it so. | |
if search.endswith(":") and not re.match(RE_COLON, search): | |
search += ":" | |
# (IPv6) If the value is numeric and > 255, append "::" | |
elif search.isdigit() and int(search) > 255: | |
search += "::" | |
version = 6 | |
call_map = { | |
4: _parse_ipv4, | |
6: _parse_ipv6, | |
} | |
return call_map[version](search) | |
except netaddr.core.AddrFormatError: | |
ver_map = {4: "0/32", 6: "::/128"} | |
return netaddr.IPNetwork(ver_map[version]) | |
def _parse_ipv6(value): | |
"""IPv6 addresses are 8, 16-bit fields.""" | |
# Get non-empty octets from search string | |
hexoctets = value.split(":") | |
hexoctets = list(filter(lambda h: h, hexoctets)) | |
prefix_len = 16 * len(hexoctets) | |
# Create an netaddr.IPNetwork to search within | |
hexoctets.extend(["0" for _ in range(len(hexoctets), 8)]) | |
network = ":".join(hexoctets) | |
ip = f"{network}/{prefix_len}" | |
return netaddr.IPNetwork(ip) | |
def _parse_ipv4(value): | |
"""IPv4 addresses are 4, 8-bit fields.""" | |
# Get non-empty octets from search string | |
octets = value.split(".") | |
octets = list(filter(lambda o: o, octets)) | |
prefix_len = 8 * len(octets) | |
# Create an netaddr.IPNetwork to search within | |
octets.extend(["0" for _ in range(len(octets), 4)]) | |
network = ".".join(octets) | |
ip = f"{network}/{prefix_len}" | |
return netaddr.IPNetwork(ip) | |
TESTS = { | |
"10": "10.0.0.0/8", | |
"10.": "10.0.0.0/8", | |
"10.0": "10.0.0.0/16", | |
"10.0.0.4": "10.0.0.4/32", | |
"10.0.0": "10.0.0.0/24", | |
"10.0.0.4/24": "10.0.0.4/32", | |
"10.0.0.4/24": "10.0.0.4/32", | |
"2001": "2001::/16", | |
"2001:": "2001::/16", | |
"2001::": "2001::/16", | |
"2001:db8:": "2001:db8::/32", | |
"2001:0db8::": "2001:db8::/32", | |
"2001:db8:abcd:0012::0/64": "2001:db8:abcd:12::/80", | |
} | |
def run_tests(tests=None, verbose=False): | |
if tests is None: | |
tests = TESTS | |
for test, expected in tests.items(): | |
verbose and print(f"Test: {test}, Expected: {expected}") | |
assert str(parse_network_as_string(test)) == expected | |
else: | |
print(f"\n>> {len(tests)} tests passed.") |
This file contains 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
search.run_tests(verbose=True) | |
Test: 10, Expected: 10.0.0.0/8 | |
Test: 10., Expected: 10.0.0.0/8 | |
Test: 10.0, Expected: 10.0.0.0/16 | |
Test: 10.0.0.4, Expected: 10.0.0.4/32 | |
Test: 10.0.0, Expected: 10.0.0.0/24 | |
Test: 10.0.0.4/24, Expected: 10.0.0.4/32 | |
Test: 2001, Expected: 2001::/16 | |
Test: 2001:, Expected: 2001::/16 | |
Test: 2001::, Expected: 2001::/16 | |
Test: 2001:db8:, Expected: 2001:db8::/32 | |
Test: 2001:0db8::, Expected: 2001:db8::/32 | |
Test: 2001:db8:abcd:0012::0/64, Expected: 2001:db8:abcd:12::/80 | |
>> 12 tests passed. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment