Skip to content

Instantly share code, notes, and snippets.

@jathanism
Last active April 13, 2021 19:15
Show Gist options
  • Save jathanism/6757ad79d2a38b0c4af783fd60aa1daa to your computer and use it in GitHub Desktop.
Save jathanism/6757ad79d2a38b0c4af783fd60aa1daa to your computer and use it in GitHub Desktop.
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.")
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