diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-09-25 17:15:55 +0000 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-09-26 09:46:27 +0200 |
| commit | 3a04d88a8c0ebdc356829a512642525885a72582 (patch) | |
| tree | 2427fa2a018480eafc650eb9f9e56348f5b17339 /python | |
| parent | 8dedc30c1af2442c28a7ededa41690c23832a7b3 (diff) | |
| download | vyos-1x-3a04d88a8c0ebdc356829a512642525885a72582.tar.gz vyos-1x-3a04d88a8c0ebdc356829a512642525885a72582.zip | |
vyos.utils: T8977: fix regressions in address and port validation
Whether an interface belongs to a VRF was only established when a VRF was
named explicitly. A lookup for the default VRF - what most callers ask for -
thus reported an address living in an arbitrary VRF, or on a VRF device
itself, as locally assigned, and accepted a configuration the daemon cannot
bind later on. Naming the VRF device remains the way to ask for the
addresses it carries.
Probing the next address family after a failed bind was unreachable, as the
argument is validated as a literal beforehand and then resolves to exactly
one family. The early return is restored.
An interface address carries host bits and must stay valid, but the range
variants classify untrusted input handed over to nftables, which only
accepts a prefix in canonical form. Those reject host bits again.
All of it is covered by tests.
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/utils/network.py | 56 |
1 files changed, 38 insertions, 18 deletions
diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index e5fa398cf..cb5863e51 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -423,13 +423,10 @@ def check_port_availability(address: str = None, port: int = 0, s.bind(sockaddr) return True # port is free to use except OSError: - # This address family is busy, but another might be free. - # Continue to the next one. - pass + return False # port is already in use - # If we reach this point, the port is already in use - # or no socket was tested, in which case it is safer - # to assume the port being in use + # if we reach this point, no socket was tested and we assume the port is + # already in use - better safe then sorry return False @@ -523,13 +520,16 @@ def is_addr_assigned( return True for interface in interfaces(): - # Check if interface belongs to the requested VRF, if this is not the - # case there is no need to proceed with this data set - continue loop - # with next element - if vrf is not None and not include_vrf and interface != vrf: + # Only an interface of the requested VRF may satisfy the lookup - an + # enslaved interface and a VRF device alike belong to their own L3 + # domain, not to the default one. Naming the VRF device is the way to + # ask for the addresses it carries itself. + if not include_vrf and interface != vrf: tmp = get_interface_config(interface) if dict_search('master', tmp) != vrf: continue + if dict_search('linkinfo.info_kind', tmp) == 'vrf': + continue if is_intf_addr_assigned(interface, ip_address): return True @@ -833,23 +833,40 @@ def is_ipv6_range(addr: str) -> bool: def is_ipv4_address_or_range(addr: str) -> bool: """ Validates if the provided address is a valid IPv4, CIDR or IPv4 range + + A prefix with host bits set (192.0.2.1/24) is rejected - this classifies + untrusted input which is handed over to nftables, where only a prefix in + its canonical form is accepted. :param addr: address to test :return: bool: True if provided address is valid """ - from vyos.template import is_ipv4 + from ipaddress import ip_network - return is_ipv4(addr) or is_ipv4_range(addr) + if '-' in addr: + return is_ipv4_range(addr) + try: + return ip_network(addr).version == 4 + except Exception: + return False def is_ipv6_address_or_range(addr: str) -> bool: """ Validates if the provided address is a valid IPv6, CIDR or IPv6 range + + A prefix with host bits set (2001:db8::1/64) is rejected, see the IPv4 + counterpart above. :param addr: address to test :return: bool: True if provided address is valid """ - from vyos.template import is_ipv6 + from ipaddress import ip_network - return is_ipv6(addr) or is_ipv6_range(addr) + if '-' in addr: + return is_ipv6_range(addr) + try: + return ip_network(addr).version == 6 + except Exception: + return False def get_interfaces_by_ip(ip_address: str, vrf=None, include_vrf: bool = False) -> list: @@ -868,13 +885,16 @@ def get_interfaces_by_ip(ip_address: str, vrf=None, include_vrf: bool = False) - ifaces = [] for interface in netifaces.interfaces(): - # Check if interface belongs to the requested VRF, if this is not the - # case there is no need to proceed with this data set - continue loop - # with next element - if vrf is not None and not include_vrf and interface != vrf: + # Only an interface of the requested VRF may satisfy the lookup - an + # enslaved interface and a VRF device alike belong to their own L3 + # domain, not to the default one. Naming the VRF device is the way to + # ask for the addresses it carries itself. + if not include_vrf and interface != vrf: tmp = get_interface_config(interface) if dict_search('master', tmp) != vrf: continue + if dict_search('linkinfo.info_kind', tmp) == 'vrf': + continue if is_intf_addr_assigned(interface, ip_address): ifaces.append(interface) |
