Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add netmask <> wildcardmask utils #616

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/user/include_jinja_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@
| is_ip_within | netutils.ip.is_ip_within |
| is_netmask | netutils.ip.is_netmask |
| is_network | netutils.ip.is_network |
| is_wildcardmask | netutils.ip.is_wildcardmask |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to change this to is_reversible_wildcardmask as 0.0.255.0 is a proper wildcard mask, just not reversible to a subnet.

| netmask_to_cidr | netutils.ip.netmask_to_cidr |
| netmask_to_wildcardmask | netutils.ip.netmask_to_wildcardmask |
| wildcardmask_to_netmask | netutils.ip.wildcardmask_to_netmask |
| get_napalm_getters | netutils.lib_helpers.get_napalm_getters |
| get_oui | netutils.mac.get_oui |
| is_valid_mac | netutils.mac.is_valid_mac |
Expand Down
79 changes: 79 additions & 0 deletions netutils/ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,37 @@ def is_netmask(netmask: str) -> bool:
return False


def is_wildcardmask(wildcardmask: str) -> bool:
"""Verifies whether a wildcard mask is valid or not.

Args:
wildcardmask: A wildcard mask

Returns:
True if the wildcard mask is valid. Otherwise false.

Examples:
>>> from netutils.ip import is_wildcardmask
>>> is_wildcardmask('0.0.0.255')
True
>>> is_wildcardmask('0.0.255.0')
False
"""
try:
parts = wildcardmask.split(".")
if len(parts) != 4 or any(not p.isdigit() for p in parts):
return False
octets = [int(p) for p in parts]
if any(o < 0 or o > 255 for o in octets):
return False
except ValueError:
return False

inverted_octets = [255 - o for o in octets]
inverted_str = ".".join(str(i) for i in inverted_octets)
return is_netmask(inverted_str)


def is_network(ip_network: str) -> bool:
"""Verifies whether or not a string is a valid IP Network with a Mask.

Expand Down Expand Up @@ -650,3 +681,51 @@ def get_ips_sorted(ips: t.Union[str, t.List[str]], sort_type: str = "network") -
return [str(ip) for ip in sorted_list]
except ValueError as err:
raise ValueError(f"Invalid IP of {sort_type} input: {err}") from err


def netmask_to_wildcardmask(netmask: str) -> str:
"""
Convert a standard IPv4 netmask to its wildcardmask.

Args:
netmask (str): The IPv4 netmask (e.g. "255.255.255.0").

Returns:
str: The corresponding wildcardmask (e.g. "0.0.0.255").

Examples:
>>> from netutils.ip import netmask_to_wildcardmask
>>> netmask_to_wildcardmask("255.255.255.0")
'0.0.0.255'
>>> netmask_to_wildcardmask("255.255.0.0")
'0.0.255.255'
"""
if is_netmask(netmask):
octets: t.List[int] = [int(o) for o in netmask.split(".")]
inverted = [255 - octet for octet in octets]
return ".".join(str(i) for i in inverted)
raise ValueError("Subnet mask is not valid.")


def wildcardmask_to_netmask(wildcardmask: str) -> str:
"""
Convert a wildcardmask to its corresponding IPv4 netmask.

Args:
wildcardmask (str): The IPv4 wildcardmask (e.g. "0.0.0.255").

Returns:
str: The corresponding netmask (e.g. "255.255.255.0").

Examples:
>>> from netutils.ip import wildcardmask_to_netmask
>>> wildcardmask_to_netmask("0.0.0.255")
'255.255.255.0'
>>> wildcardmask_to_netmask("0.0.255.255")
'255.255.0.0'
"""
if is_wildcardmask(wildcardmask):
octets: t.List[int] = [int(o) for o in wildcardmask.split(".")]
inverted = [255 - octet for octet in octets]
return ".".join(str(i) for i in inverted)
raise ValueError("Wildcard mask is not valid.")
3 changes: 3 additions & 0 deletions netutils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@
"is_ip_range": "ip.is_ip_range",
"is_ip_within": "ip.is_ip_within",
"is_netmask": "ip.is_netmask",
"is_wildcardmask": "ip.is_wildcardmask",
"is_network": "ip.is_network",
"netmask_to_cidr": "ip.netmask_to_cidr",
"cidr_to_netmask": "ip.cidr_to_netmask",
"cidr_to_netmaskv6": "ip.cidr_to_netmaskv6",
"netmask_to_wildcardmask": "ip.netmask_to_wildcardmask",
"wildcardmask_to_netmask": "ip.wildcardmask_to_netmask",
"get_all_host": "ip.get_all_host",
"get_broadcast_address": "ip.get_broadcast_address",
"get_first_usable": "ip.get_first_usable",
Expand Down
50 changes: 50 additions & 0 deletions tests/unit/test_ip.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,14 @@
{"sent": {"netmask": "ffff:ffff:ffff:ffff:ffff::"}, "received": True},
]

IS_WILDCARDMASK = [
{"sent": {"wildcardmask": "0.0.0.255"}, "received": True},
{"sent": {"wildcardmask": "0.0.0.0"}, "received": True},
{"sent": {"wildcardmask": "0.0.0.1"}, "received": True},
{"sent": {"wildcardmask": "0.0.0.2"}, "received": False},
{"sent": {"wildcardmask": "0.0.255.0"}, "received": False},
]

NETMASK_CIDR = [
{"sent": {"netmask": "255.255.255.0"}, "received": 24},
{"sent": {"netmask": "255.192.0.0"}, "received": 10},
Expand All @@ -464,6 +472,28 @@
{"sent": {"cidr": 80}, "received": "ffff:ffff:ffff:ffff:ffff::"},
]

NETMASK_WILDCARDMASK = [
{"sent": {"netmask": "255.255.255.254"}, "received": "0.0.0.1"},
{"sent": {"netmask": "255.255.255.252"}, "received": "0.0.0.3"},
{"sent": {"netmask": "255.255.255.0"}, "received": "0.0.0.255"},
{"sent": {"netmask": "255.255.254.0"}, "received": "0.0.1.255"},
{"sent": {"netmask": "255.255.252.0"}, "received": "0.0.3.255"},
{"sent": {"netmask": "255.255.0.0"}, "received": "0.0.255.255"},
{"sent": {"netmask": "255.254.0.0"}, "received": "0.1.255.255"},
{"sent": {"netmask": "255.0.0.0"}, "received": "0.255.255.255"},
]

WILDCARDMASK_NETMASK = [
{"sent": {"wildcardmask": "0.0.0.1"}, "received": "255.255.255.254"},
{"sent": {"wildcardmask": "0.0.0.3"}, "received": "255.255.255.252"},
{"sent": {"wildcardmask": "0.0.0.255"}, "received": "255.255.255.0"},
{"sent": {"wildcardmask": "0.0.1.255"}, "received": "255.255.254.0"},
{"sent": {"wildcardmask": "0.0.3.255"}, "received": "255.255.252.0"},
{"sent": {"wildcardmask": "0.0.255.255"}, "received": "255.255.0.0"},
{"sent": {"wildcardmask": "0.1.255.255"}, "received": "255.254.0.0"},
{"sent": {"wildcardmask": "0.255.255.255"}, "received": "255.0.0.0"},
]

COUNT_BITS = [
{"sent": 0, "received": 0},
{"sent": 234, "received": 5},
Expand Down Expand Up @@ -604,6 +634,11 @@ def test_is_netmask(data):
assert ip.is_netmask(**data["sent"]) == data["received"]


@pytest.mark.parametrize("data", IS_WILDCARDMASK)
def test_is_wildcardmask(data):
assert ip.is_wildcardmask(**data["sent"]) == data["received"]


@pytest.mark.parametrize("data", NETMASK_CIDR)
def test_netmask_to_cidr(data):
assert ip.netmask_to_cidr(**data["sent"]) == data["received"]
Expand Down Expand Up @@ -637,6 +672,21 @@ def test_cidr_to_netmask_fail():
ip.cidr_to_netmask(**data)


@pytest.mark.parametrize("data", NETMASK_WILDCARDMASK)
def test_netmask_to_wildcardmask(data):
assert ip.netmask_to_wildcardmask(**data["sent"]) == data["received"]


@pytest.mark.parametrize("data", WILDCARDMASK_NETMASK)
def test_wildcardmask_to_netmask(data):
assert ip.wildcardmask_to_netmask(**data["sent"]) == data["received"]

jtdub marked this conversation as resolved.
Show resolved Hide resolved

def test_wildcardmask_to_netmask_invalid():
with pytest.raises(ValueError, match="Wildcard mask is not valid."):
ip.wildcardmask_to_netmask("0.0.255.0")


@pytest.mark.parametrize("data", GET_PEER)
def test_get_peer_ip(data):
assert ip.get_peer_ip(**data["sent"]) == data["received"]
Expand Down
Loading