From fc3a2a116bd19f0cbc604826e9ecb251dc6ca47a Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Mon, 15 Apr 2024 12:41:26 +0200 Subject: [PATCH] fix: netaddr fallback to is_private when is_global is not available (#348) * fix: netaddr fallback to is_private when is_global is not available * fix: adjust fallback logic to edge cases * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix: address pylint errors * fix: comment spacing in test task broken by prettier * changelog update * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: Marcin Lewandowski Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ruchi Pakhle --- .../ipaddress_is_global_fallback.yaml | 5 +++ plugins/plugin_utils/base/ipaddr_utils.py | 38 ++++++++++++++++++- .../utils_ipaddr_filter/tasks/ipaddr.yaml | 26 ++++++++++++- .../utils_ipaddr_filter/vars/main.yaml | 8 ++++ 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/ipaddress_is_global_fallback.yaml diff --git a/changelogs/fragments/ipaddress_is_global_fallback.yaml b/changelogs/fragments/ipaddress_is_global_fallback.yaml new file mode 100644 index 0000000..f1a8a62 --- /dev/null +++ b/changelogs/fragments/ipaddress_is_global_fallback.yaml @@ -0,0 +1,5 @@ +--- +minor_changes: + - Implemented fallback to the IPAddress.is_private() method in cases where IPAddress.is_global() is not available. This adjustment ensures compatibility with netaddr versions older than 0.10.0. + - With the release of netaddr 1.0 (on 2024-02-10), the IPAddress.is_private() method was removed. Consequently, ansible.utils < 4.0 is only compatible with netaddr < 1.0, while ansible.utils >= 4.0 requires netaddr >= 0.10.1. + - To address compatibility issues with older netaddr versions, this pull request introduces backward compatibility by reverting to IPAddress.is_private() when IPAddress.is_global() is unavailable. This ensures smooth operation with netaddr versions prior to 0.10.0. diff --git a/plugins/plugin_utils/base/ipaddr_utils.py b/plugins/plugin_utils/base/ipaddr_utils.py index a427a94..15040d4 100644 --- a/plugins/plugin_utils/base/ipaddr_utils.py +++ b/plugins/plugin_utils/base/ipaddr_utils.py @@ -288,8 +288,42 @@ def _previous_usable_query(v, vtype): return str(netaddr.IPAddress(int(v.ip) - 1)) +def _ip_is_global(ip): + # fallback to support netaddr < 1.0.0 + # attempt to emulate IPAddress.is_global() if it's not available + # note that there still might be some behavior differences (e.g. exceptions) + has_is_global = callable(getattr(ip, "is_global", None)) + return ( + ip.is_global() + if has_is_global + else ( + not (ip.is_private() or ip.is_link_local() or ip.is_reserved()) + and all( + ip not in netaddr.IPNetwork(ipv6net) + for ipv6net in [ + "::1/128", + "::/128", + "::ffff:0:0/96", + "64:ff9b:1::/48", + "100::/64", + "2001::/23", + "2001:db8::/32", + "2002::/16", + ] + ) + or ip in netaddr.IPRange("239.0.0.0", "239.255.255.255") # Administrative Multicast + or ip in netaddr.IPNetwork("233.252.0.0/24") # Multicast test network + or ip in netaddr.IPRange("234.0.0.0", "238.255.255.255") + or ip in netaddr.IPRange("225.0.0.0", "231.255.255.255") + or ip in netaddr.IPNetwork("192.88.99.0/24") # 6to4 anycast relays (RFC 3068) + or ip in netaddr.IPNetwork("192.0.0.9/32") + or ip in netaddr.IPNetwork("192.0.0.10/32") + ) + ) + + def _private_query(v, value): - if not v.ip.is_global(): + if not _ip_is_global(v.ip): return value @@ -298,7 +332,7 @@ def _public_query(v, value): if all( [ v_ip.is_unicast(), - v_ip.is_global(), + _ip_is_global(v_ip), not v_ip.is_loopback(), not v_ip.is_netmask(), not v_ip.is_hostmask(), diff --git a/tests/integration/targets/utils_ipaddr_filter/tasks/ipaddr.yaml b/tests/integration/targets/utils_ipaddr_filter/tasks/ipaddr.yaml index aaa79c0..d7569bb 100644 --- a/tests/integration/targets/utils_ipaddr_filter/tasks/ipaddr.yaml +++ b/tests/integration/targets/utils_ipaddr_filter/tasks/ipaddr.yaml @@ -10,6 +10,22 @@ - "42540766412265424405338506004571095040/64" - true +- name: Set ipaddress list for private/public test + ansible.builtin.set_fact: + value_private_public: "{{ value + value_private_public_additional }}" + vars: + value_private_public_additional: + # valid private subnet address + - 172.16.0.1 + # exception: globally reachable address + - 192.0.0.9 + # multicast test network: considered global in netaddr + - 233.252.0.1 + - 234.0.0.1 + # administrative multicast: considered global in netaddr + - 225.0.0.1 + - 239.0.0.1 + - name: Ipaddr filter with empty string query ansible.builtin.set_fact: result1: "{{ value | ansible.utils.ipaddr }}" @@ -28,12 +44,20 @@ - name: Ipaddr filter with public network query ansible.builtin.set_fact: - result3: "{{ value | ansible.utils.ipaddr('public') }}" + result3: "{{ value_private_public | ansible.utils.ipaddr('public') }}" - name: Assert result for ipaddr public network query ansible.builtin.assert: that: "{{ result3 == ipaddr_result3 }}" +- name: Ipaddr filter with private network query + ansible.builtin.set_fact: + result_private: "{{ value_private_public | ansible.utils.ipaddr('private') }}" + +- name: Assert result for ipaddr private network query + ansible.builtin.assert: + that: "{{ result_private == ipaddr_result_private }}" + - name: Ipaddr filter with network query ansible.builtin.set_fact: result4: "{{ value | ansible.utils.ipaddr('net') }}" diff --git a/tests/integration/targets/utils_ipaddr_filter/vars/main.yaml b/tests/integration/targets/utils_ipaddr_filter/vars/main.yaml index aa44955..0f9c884 100644 --- a/tests/integration/targets/utils_ipaddr_filter/vars/main.yaml +++ b/tests/integration/targets/utils_ipaddr_filter/vars/main.yaml @@ -26,11 +26,19 @@ ipaddr_result2: ipaddr_result3: - 192.24.2.1 + - 192.0.0.9 ipaddr_result4: - 192.168.32.0/24 - 2001:db8:32c:faad::/64 +ipaddr_result_private: + - ::1 + - 192.168.32.0/24 + - fe80::100/10 + - 2001:db8:32c:faad::/64 + - 172.16.0.1 + ipwrap_result1: - "192.24.2.1" - "host.fqdn"