summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-09-25 17:15:55 +0000
committerChristian Breunig <christian@breunig.cc>2026-09-26 09:46:27 +0200
commit3a04d88a8c0ebdc356829a512642525885a72582 (patch)
tree2427fa2a018480eafc650eb9f9e56348f5b17339 /python
parent8dedc30c1af2442c28a7ededa41690c23832a7b3 (diff)
downloadvyos-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.py56
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)