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

Reply via email to