When the python unbound library is unavailable, dns_resolve.resolve() returns None, and any host name passed to inet_parse_active() ends up producing a misleading "bad peer name format" error. The peer name format may be perfectly fine; the actual problem is that DNS support is disabled.
Add a public dns_resolve.is_enabled() helper so callers can tell "DNS unavailable" apart from "DNS lookup failed", and use it in _inet_parse_active() to raise a clearer error pointing at the missing unbound library when applicable. This does not introduce any blocking I/O: the IP-literal fast path is unchanged, and the new branch only fires after dns_resolve.resolve() has already returned None, replacing the existing exception with a more informative one. Link: https://mail.openvswitch.org/pipermail/ovs-dev/2026-May/432092.html After discussion on the v1 patch (linked above), introducing a blocking DNS fallback was rejected because synchronous DNS is unsuitable for OVS daemons. This patch takes a different approach: rather than enabling hostname resolution without unbound, it makes the existing failure mode clearer so users know to install python3-unbound. Tested: Ran the python test suite under PYTHONPATH=python; the new test_inet_parse_active_missing_unbound and test_is_enabled_without_init cases pass, and the extended test_missing_unbound now also asserts the expected dns_resolve.is_enabled() return value. Assisted-by: Claude Opus 4.7, Claude Code Signed-off-by: wlswo <[email protected]> --- NEWS | 4 ++++ python/ovs/dns_resolve.py | 12 ++++++++++++ python/ovs/socket_util.py | 4 ++++ python/ovs/tests/test_dns_resolve.py | 24 ++++++++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/NEWS b/NEWS index 1a3044c..52fac65 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,10 @@ Post-v3.7.0 - Userspace datapath: * ARP/ND lookups for native tunnel are now rate limited. The holdout timer can be configured with 'tnl/neigh/retrans_time'. + - Python: + * Python tools now report a clear error when a host name is supplied + but the python unbound library is unavailable, instead of failing + with a misleading "bad peer name format" message. v3.7.0 - 16 Feb 2026 diff --git a/python/ovs/dns_resolve.py b/python/ovs/dns_resolve.py index dbaf611..5d96e0e 100644 --- a/python/ovs/dns_resolve.py +++ b/python/ovs/dns_resolve.py @@ -294,6 +294,18 @@ def resolve(name: str) -> typing.Optional[str]: return _global_resolver.resolve(name) # type: ignore +def is_enabled() -> bool: + """Return True if DNS resolution support is available. + + Returns False if no global DNSResolver has been initialized, or if it was + initialized but DNS support is unavailable (for example, when the python + unbound library is missing or failed to initialize). Useful for + distinguishing "DNS lookup failed" from "DNS lookup was never attempted + because support is disabled" after a None result from resolve(). + """ + return _global_resolver is not None and _global_resolver.dns_enabled + + def destroy(): """Destroy the global DNSResolver diff --git a/python/ovs/socket_util.py b/python/ovs/socket_util.py index a26298b..bf6a284 100644 --- a/python/ovs/socket_util.py +++ b/python/ovs/socket_util.py @@ -235,6 +235,10 @@ def _inet_parse_active(target, default_port): host_name = str(ipaddress.ip_address(host_name)) except ValueError: host_name = dns_resolve.resolve(host_name) + if not host_name and not dns_resolve.is_enabled(): + raise ValueError("%s: cannot resolve host name; the python " + "unbound library is required for DNS support" + % target) if not host_name: raise ValueError("%s: bad peer name format" % target) return (host_name, port) diff --git a/python/ovs/tests/test_dns_resolve.py b/python/ovs/tests/test_dns_resolve.py index 0698e8f..99ec373 100644 --- a/python/ovs/tests/test_dns_resolve.py +++ b/python/ovs/tests/test_dns_resolve.py @@ -95,6 +95,30 @@ def missing_unbound(monkeypatch, request): def test_missing_unbound(missing_unbound, resolver_factory): resolver = resolver_factory() # Dont fail even w/o unbound assert resolver.dns_enabled == (not missing_unbound) + assert dns_resolve.is_enabled() == (not missing_unbound) + + +def test_is_enabled_without_init(monkeypatch): + # Without a global resolver, is_enabled() must not auto-initialize and + # must return False so callers can distinguish "DNS unavailable" from + # "DNS lookup failed". + dns_resolve.destroy() + assert dns_resolve.is_enabled() is False + + +def test_inet_parse_active_missing_unbound(missing_unbound, resolver_factory): + # IP literals work regardless of whether unbound is available. + host, port = socket_util.inet_parse_active("192.0.2.1:6640", 6640) + assert host == "192.0.2.1" + assert port == 6640 + + # When unbound is missing and the target is a host name, the user gets + # a clear error pointing at the missing dependency rather than a + # misleading "bad peer name format". + resolver_factory() + if missing_unbound: + with pytest.raises(ValueError, match="unbound library is required"): + socket_util.inet_parse_active("fake.notadomain:6640", 6640) def test_DNSRequest_defaults(): -- 2.40.1 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
