From aeab205ab7b68a192743d61a4dc3f56203b9ee0d Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Thu, 18 Nov 2021 09:50:15 +0100 Subject: [PATCH 1/2] Change order of ipv4_to_ipv6 arguments As pointed out by our core CNaaS team, the argument order of the filter function was confusing, as the filter would normally be used in Jinja templates by piping a host's IPv4 address through the filter. --- src/cnaas_nms/tools/jinja_filters.py | 4 ++-- src/cnaas_nms/tools/tests/test_jinja_filters.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cnaas_nms/tools/jinja_filters.py b/src/cnaas_nms/tools/jinja_filters.py index 4aa41a0b..235a1df7 100644 --- a/src/cnaas_nms/tools/jinja_filters.py +++ b/src/cnaas_nms/tools/jinja_filters.py @@ -86,14 +86,14 @@ def isofy_ipv4(ip_string, prefix=''): @template_filter() def ipv4_to_ipv6( - v6_network: Union[str, ipaddress.IPv6Network], v4_address: Union[str, ipaddress.IPv4Interface] + v4_address: Union[str, ipaddress.IPv4Interface], v6_network: Union[str, ipaddress.IPv6Network] ): """Transforms an IPv4 address to an IPv6 interface address. This will combine an arbitrary IPv6 network address with the 32 address bytes of an IPv4 address into a valid IPv6 address + prefix length notation - the equivalent of dotted quad compatible notation. E.g.: - >>> ipv6 = ipv4_to_ipv6("2001:700:dead:babe::/64", "127.0.0.1") + >>> ipv6 = ipv4_to_ipv6("127.0.0.1", "2001:700:dead:babe::/64") >>> ipv6 IPv6Interface('2001:700:dead:babe::7f00:1/64') >>> ipv6 == ipaddress.IPv6Interface('2001:700:dead:babe::127.0.0.1/64') diff --git a/src/cnaas_nms/tools/tests/test_jinja_filters.py b/src/cnaas_nms/tools/tests/test_jinja_filters.py index ce8bb77d..ef40ac40 100644 --- a/src/cnaas_nms/tools/tests/test_jinja_filters.py +++ b/src/cnaas_nms/tools/tests/test_jinja_filters.py @@ -68,23 +68,23 @@ class IPv6ToIPv4Tests(unittest.TestCase): def test_should_convert_short_network_properly(self): self.assertEqual( - ipv4_to_ipv6('2001:700::/64', '10.0.0.1'), + ipv4_to_ipv6('10.0.0.1', '2001:700::/64'), ipaddress.IPv6Interface('2001:700::10.0.0.1/64'), ) def test_should_convert_long_network_properly(self): self.assertEqual( - ipv4_to_ipv6('2001:700:dead:c0de:babe::/80', '10.0.0.1'), + ipv4_to_ipv6('10.0.0.1', '2001:700:dead:c0de:babe::/80'), ipaddress.IPv6Interface('2001:700:dead:c0de:babe::10.0.0.1/80'), ) def test_should_raise_on_invalid_network(self): with self.assertRaises(ValueError): invalid_network = '2001:700:0:::/64' - ipv4_to_ipv6(invalid_network, '10.0.0.1') + ipv4_to_ipv6('10.0.0.1', invalid_network) def test_should_return_an_ipv6interface(self): - result = ipv4_to_ipv6('2001:700::/64', '10.0.0.1') + result = ipv4_to_ipv6('10.0.0.1', '2001:700::/64') self.assertIsInstance(result, ipaddress.IPv6Interface) From dc7d90d4444f579a453334d17171cb63ab44d203 Mon Sep 17 00:00:00 2001 From: Morten Brekkevold Date: Wed, 24 Nov 2021 10:23:21 +0100 Subject: [PATCH 2/2] Make ipv4_to_ipv6 output "human-readable" addrs The requirements keep changing back and forth, but apparently, it was still important that the resulting IPv6 address was somewhat human-readably similar to the original IPv4 address. Also, it isn't always necessary to keep the full IPv4 address inside the IPv6 address. Mostly, the host address from the IPv4 subnetwork should be readable as the same on the corresponding IPV6 subnetwork, so an option to keep only a number of IPv4 octets in the result was added. --- src/cnaas_nms/tools/jinja_filters.py | 32 ++++++++++++------- .../tools/tests/test_jinja_filters.py | 6 ++-- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/cnaas_nms/tools/jinja_filters.py b/src/cnaas_nms/tools/jinja_filters.py index 235a1df7..f9c0a0eb 100644 --- a/src/cnaas_nms/tools/jinja_filters.py +++ b/src/cnaas_nms/tools/jinja_filters.py @@ -86,22 +86,27 @@ def isofy_ipv4(ip_string, prefix=''): @template_filter() def ipv4_to_ipv6( - v4_address: Union[str, ipaddress.IPv4Interface], v6_network: Union[str, ipaddress.IPv6Network] + v4_address: Union[str, ipaddress.IPv4Interface], + v6_network: Union[str, ipaddress.IPv6Network], + keep_octets: int = 1, ): - """Transforms an IPv4 address to an IPv6 interface address. This will combine an arbitrary - IPv6 network address with the 32 address bytes of an IPv4 address into a valid IPv6 address - + prefix length notation - the equivalent of dotted quad compatible notation. + """Transforms an IPv4 address intoto an IPv6 interface address that should be more-or-less + identifiable as the same device on a different protocol family. - E.g.: - >>> ipv6 = ipv4_to_ipv6("127.0.0.1", "2001:700:dead:babe::/64") - >>> ipv6 - IPv6Interface('2001:700:dead:babe::7f00:1/64') - >>> ipv6 == ipaddress.IPv6Interface('2001:700:dead:babe::127.0.0.1/64') - True + A selected number of octets, right-to-left, of the IPv4 address will be converted to + hexadecimal notation that can be read as the decimal IPv4 address. This will then be used as + a host address on the provided IPv6 network. + + Examples: + >>> ipv4_to_ipv6("10.1.0.42", "2001:700:dead:babe::/64") + IPv6Interface('2001:700:dead:babe::42/64') + >>> ipv4_to_ipv6("10.1.0.42", "2001:700:dead:babe::/64", keep_octets=4) + IPv6Interface('2001:700:dead:babe:10:1:0:42/64') Args: - v6_network: IPv6 network in prefix notation v4_address: IPv4 address + v6_network: IPv6 network in prefix notation + keep_octets: The number of octets to keep from the IPv4 address (from right-to-left) Returns: An IPv6Address object on the given network """ @@ -109,8 +114,11 @@ def ipv4_to_ipv6( v6_network = ipaddress.IPv6Network(v6_network) if isinstance(v4_address, str): v4_address = ipaddress.IPv4Address(v4_address) + assert keep_octets > 0 <= 4 - v6_address = v6_network[int(v4_address)] + octets = str(v4_address).split('.')[-keep_octets:] + v6ified = ipaddress.IPv6Address("::" + ":".join(octets)) + v6_address = v6_network[int(v6ified)] return ipaddress.IPv6Interface(f"{v6_address}/{v6_network.prefixlen}") diff --git a/src/cnaas_nms/tools/tests/test_jinja_filters.py b/src/cnaas_nms/tools/tests/test_jinja_filters.py index ef40ac40..5519b879 100644 --- a/src/cnaas_nms/tools/tests/test_jinja_filters.py +++ b/src/cnaas_nms/tools/tests/test_jinja_filters.py @@ -69,13 +69,13 @@ class IPv6ToIPv4Tests(unittest.TestCase): def test_should_convert_short_network_properly(self): self.assertEqual( ipv4_to_ipv6('10.0.0.1', '2001:700::/64'), - ipaddress.IPv6Interface('2001:700::10.0.0.1/64'), + ipaddress.IPv6Interface('2001:700::1/64'), ) def test_should_convert_long_network_properly(self): self.assertEqual( - ipv4_to_ipv6('10.0.0.1', '2001:700:dead:c0de:babe::/80'), - ipaddress.IPv6Interface('2001:700:dead:c0de:babe::10.0.0.1/80'), + ipv4_to_ipv6('10.0.0.1', '2001:700:dead:c0de::/64', keep_octets=4), + ipaddress.IPv6Interface('2001:700:dead:c0de:10:0:0:1/64'), ) def test_should_raise_on_invalid_network(self):