From 1b22985f2d67b6dd43259e3c6b853498f1cff712 Mon Sep 17 00:00:00 2001 From: Bob Halley Date: Sat, 27 Jan 2024 12:38:03 -0800 Subject: [PATCH] In tests, work around musl libc and docker issues. Specifically: musl libc's getaddrinfo behavior always returns a canonical name. Docker's resolver proxy doesn't do dangling CNAMEs correctly and also answers NXDOMAIN in some cases where it should say no error, no data. --- tests/test_async.py | 14 +++++++++++--- tests/test_resolver.py | 21 +++++++++++++++------ tests/test_resolver_override.py | 19 ++++++++++++------- tests/util.py | 10 +++++++++- 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/tests/test_async.py b/tests/test_async.py index ac32431c..7c432ff5 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -53,6 +53,10 @@ except Exception: pass +# Docker too! It not only has problems with dangling CNAME, but also says NXDOMAIN when +# it should say no error no data. +_is_docker = tests.util.is_docker() + query_addresses = [] family = socket.AF_UNSPEC if tests.util.have_ipv4(): @@ -235,8 +239,10 @@ async def run5(): dns.reversename.from_address("8.8.8.8") ) - with self.assertRaises(dns.resolver.NoAnswer): - self.async_run(run5) + if not _is_docker: + # docker returns NXDOMAIN! + with self.assertRaises(dns.resolver.NoAnswer): + self.async_run(run5) def testCanonicalNameNoCNAME(self): cname = dns.name.from_text("www.google.com") @@ -255,7 +261,9 @@ async def run(): self.assertEqual(self.async_run(run), cname) - @unittest.skipIf(_systemd_resolved_present, "systemd-resolved in use") + @unittest.skipIf( + _systemd_resolved_present or _is_docker, "systemd-resolved or docker in use" + ) def testCanonicalNameDangling(self): name = dns.name.from_text("dangling-cname.dnspython.org") cname = dns.name.from_text("dangling-target.dnspython.org") diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 90378086..9550e87b 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -15,15 +15,16 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -from io import StringIO import selectors -import sys import socket +import sys import time import unittest -import pytest +from io import StringIO from unittest.mock import patch +import pytest + import dns.e164 import dns.message import dns.name @@ -61,6 +62,10 @@ class Server(object): except Exception: pass +# Docker too! It not only has problems with dangling CNAME, but also says NXDOMAIN when +# it should say no error no data. +_is_docker = tests.util.is_docker() + resolv_conf = """ /t/t @@ -690,8 +695,10 @@ def testResolveName(self): with self.assertRaises(dns.resolver.NXDOMAIN): dns.resolver.resolve_name("nxdomain.dnspython.org") - with self.assertRaises(dns.resolver.NoAnswer): - dns.resolver.resolve_name(dns.reversename.from_address("8.8.8.8")) + if not _is_docker: + # We skip docker as it says NXDOMAIN! + with self.assertRaises(dns.resolver.NoAnswer): + dns.resolver.resolve_name(dns.reversename.from_address("8.8.8.8")) @patch.object(dns.message.Message, "use_edns") def testResolveEdnsOptions(self, message_use_edns_mock): @@ -792,7 +799,9 @@ def testCanonicalNameCNAME(self): cname = dns.name.from_text("dmfrjf4ips8xa.cloudfront.net") self.assertEqual(dns.resolver.canonical_name(name), cname) - @unittest.skipIf(_systemd_resolved_present, "systemd-resolved in use") + @unittest.skipIf( + _systemd_resolved_present or _is_docker, "systemd-resolved or docker in use" + ) def testCanonicalNameDangling(self): name = dns.name.from_text("dangling-cname.dnspython.org") cname = dns.name.from_text("dangling-target.dnspython.org") diff --git a/tests/test_resolver_override.py b/tests/test_resolver_override.py index aed7a53d..be9e53f2 100644 --- a/tests/test_resolver_override.py +++ b/tests/test_resolver_override.py @@ -69,7 +69,7 @@ def test_override(self): dns.resolver.restore_system_resolver() self.assertTrue(socket.getaddrinfo is dns.resolver._original_getaddrinfo) - def equivalent_info(self, a, b): + def equivalent_info(self, a, b, q): if len(a) != len(b): return False for x in a: @@ -78,16 +78,21 @@ def equivalent_info(self, a, b): # looking for a zero protocol. y = (x[0], x[1], 0, x[3], x[4]) if y not in b: - print("NOT EQUIVALENT") - print(a) - print(b) - return False + # musl libc insists on always providing a canonical name, so + # accept that too. + y = (x[0], x[1], x[2], q, x[4]) + if y not in b: + print("NOT EQUIVALENT") + print(a) + print(b) + return False return True def equivalent(self, *args, **kwargs): + q = args[0] a = socket.getaddrinfo(*args, **kwargs) b = dns.resolver._original_getaddrinfo(*args, **kwargs) - return self.equivalent_info(a, b) + return self.equivalent_info(a, b, q) @unittest.skipIf( sys.platform == "win32", "avoid windows original getaddrinfo issues" @@ -139,7 +144,7 @@ def test_getaddrinfo_nxdomain(self): def test_getaddrinfo_service(self): a = socket.getaddrinfo("dns.google", "domain") b = socket.getaddrinfo("dns.google", 53) - self.assertTrue(self.equivalent_info(a, b)) + self.assertTrue(self.equivalent_info(a, b, "dns.google")) try: socket.getaddrinfo("dns.google", "domain", flags=socket.AI_NUMERICSERV) self.assertTrue(False) # should not happen! diff --git a/tests/util.py b/tests/util.py index 5518b418..9f0d3f46 100644 --- a/tests/util.py +++ b/tests/util.py @@ -18,7 +18,6 @@ import enum import inspect import os -import socket import dns.message import dns.name @@ -123,3 +122,12 @@ def check_enum_exports(module, eq_callback, only=None): for flag, value in attr.__members__.items(): # print(module, flag, value) eq_callback(getattr(module, flag), value) + + +def is_docker() -> bool: + # There are a lot of answers to "am I runnning in a container" and none appear + # to work reliably, so we're settling for "am I running in docker?" + try: + return os.path.isfile("/.dockerenv") + except Exception: + return False