diff options
author | Felix Yan <felixonmars@archlinux.org> | 2021-09-01 16:40:28 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-01 11:40:28 +0300 |
commit | aeb0390094a1c3f29bb4f25a8dab96587a86b3e8 (patch) | |
tree | a447d1950cede2fa62116532886150bf41d47f6b | |
parent | c6c350eaa9eb819c6bcabe25113464aed75b9cf5 (diff) | |
download | eventlet-aeb0390094a1c3f29bb4f25a8dab96587a86b3e8.tar.gz |
greendns: compatibility with dnspython v2
Compatibility with dnspython v2:
- `_compute_expiration` was replaced by `_compute_times`
- `dns.query.{tcp,udp}` take new arguments
Main issue for tracking: https://github.com/eventlet/eventlet/issues/619
This patch discussion: https://github.com/eventlet/eventlet/pull/722
This patch deprecates dnspython<2 pin: https://github.com/eventlet/eventlet/issues/629
Co-authored-by: John Vandenberg <jayvdb@gmail.com>
Co-authored-by: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>
-rw-r--r-- | .github/workflows/test.yaml | 2 | ||||
-rw-r--r-- | eventlet/support/greendns.py | 86 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | tests/greendns_test.py | 2 | ||||
-rw-r--r-- | tests/isolated/socket_resolve_green.py | 3 | ||||
-rw-r--r-- | tox.ini | 3 |
6 files changed, 84 insertions, 14 deletions
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d5b7c45..3801d2a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -32,6 +32,7 @@ jobs: - { py: 2.7, toxenv: py27-epolls, ignore-error: false } - { py: 2.7, toxenv: py27-poll, ignore-error: false } - { py: 2.7, toxenv: py27-selects, ignore-error: false } + - { py: 2.7, toxenv: py27-dnspython1, ignore-error: false } - { py: 3.5, toxenv: py35-epolls, ignore-error: false } - { py: 3.5, toxenv: py35-poll, ignore-error: false } - { py: 3.5, toxenv: py35-selects, ignore-error: false } @@ -48,6 +49,7 @@ jobs: - { py: 3.9, toxenv: py39-epolls, ignore-error: false } - { py: 3.9, toxenv: py39-poll, ignore-error: false } - { py: 3.9, toxenv: py39-selects, ignore-error: false } + - { py: 3.9, toxenv: py39-dnspython1, ignore-error: false } - { py: 3.x, toxenv: ipv6, ignore-error: false } - { py: pypy2, toxenv: pypy2-epolls, ignore-error: true } - { py: pypy3, toxenv: pypy3-epolls, ignore-error: true } diff --git a/eventlet/support/greendns.py b/eventlet/support/greendns.py index 10dd04b..76545c7 100644 --- a/eventlet/support/greendns.py +++ b/eventlet/support/greendns.py @@ -120,6 +120,16 @@ def is_ip_addr(host): return is_ipv4_addr(host) or is_ipv6_addr(host) +# NOTE(ralonsoh): in dnspython v2.0.0, "_compute_expiration" was replaced +# by "_compute_times". +if hasattr(dns.query, '_compute_expiration'): + def compute_expiration(query, timeout): + return query._compute_expiration(timeout) +else: + def compute_expiration(query, timeout): + return query._compute_times(timeout)[1] + + class HostsAnswer(dns.resolver.Answer): """Answer class for HostsResolver object""" @@ -660,8 +670,21 @@ def _net_write(sock, data, expiration): raise dns.exception.Timeout +# Test if raise_on_truncation is an argument we should handle. +# It was newly added in dnspython 2.0 +try: + dns.message.from_wire("", raise_on_truncation=True) +except dns.message.ShortHeader: + _handle_raise_on_truncation = True +except TypeError: + # Argument error, there is no argument "raise_on_truncation" + _handle_raise_on_truncation = False + + def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, - af=None, source=None, source_port=0, ignore_unexpected=False): + af=None, source=None, source_port=0, ignore_unexpected=False, + one_rr_per_rrset=False, ignore_trailing=False, + raise_on_truncation=False, sock=None): """coro friendly replacement for dns.query.udp Return the response obtained after sending a query via UDP. @@ -686,7 +709,21 @@ def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, @type source_port: int @param ignore_unexpected: If True, ignore responses from unexpected sources. The default is False. - @type ignore_unexpected: bool""" + @type ignore_unexpected: bool + @param one_rr_per_rrset: If True, put each RR into its own + RRset. + @type one_rr_per_rrset: bool + @param ignore_trailing: If True, ignore trailing + junk at end of the received message. + @type ignore_trailing: bool + @param raise_on_truncation: If True, raise an exception if + the TC bit is set. + @type raise_on_truncation: bool + @param sock: the socket to use for the + query. If None, the default, a socket is created. Note that + if a socket is provided, it must be a nonblocking datagram socket, + and the source and source_port are ignored. + @type sock: socket.socket | None""" wire = q.to_wire() if af is None: @@ -708,10 +745,13 @@ def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, if source is not None: source = (source, source_port, 0, 0) - s = socket.socket(af, socket.SOCK_DGRAM) + if sock: + s = sock + else: + s = socket.socket(af, socket.SOCK_DGRAM) s.settimeout(timeout) try: - expiration = dns.query._compute_expiration(timeout) + expiration = compute_expiration(dns.query, timeout) if source is not None: s.bind(source) while True: @@ -756,14 +796,23 @@ def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, finally: s.close() - r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac) + if _handle_raise_on_truncation: + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing, + raise_on_truncation=raise_on_truncation) + else: + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing) if not q.is_response(r): raise dns.query.BadResponse() return r def tcp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, - af=None, source=None, source_port=0): + af=None, source=None, source_port=0, + one_rr_per_rrset=False, ignore_trailing=False, sock=None): """coro friendly replacement for dns.query.tcp Return the response obtained after sending a query via TCP. @@ -785,7 +834,19 @@ def tcp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, @type source: string @param source_port: The port from which to send the message. The default is 0. - @type source_port: int""" + @type source_port: int + @type ignore_unexpected: bool + @param one_rr_per_rrset: If True, put each RR into its own + RRset. + @type one_rr_per_rrset: bool + @param ignore_trailing: If True, ignore trailing + junk at end of the received message. + @type ignore_trailing: bool + @param sock: the socket to use for the + query. If None, the default, a socket is created. Note that + if a socket is provided, it must be a nonblocking datagram socket, + and the source and source_port are ignored. + @type sock: socket.socket | None""" wire = q.to_wire() if af is None: @@ -801,10 +862,13 @@ def tcp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, destination = (where, port, 0, 0) if source is not None: source = (source, source_port, 0, 0) - s = socket.socket(af, socket.SOCK_STREAM) + if sock: + s = sock + else: + s = socket.socket(af, socket.SOCK_STREAM) s.settimeout(timeout) try: - expiration = dns.query._compute_expiration(timeout) + expiration = compute_expiration(dns.query, timeout) if source is not None: s.bind(source) while True: @@ -829,7 +893,9 @@ def tcp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, wire = bytes(_net_read(s, l, expiration)) finally: s.close() - r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac) + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + one_rr_per_rrset=one_rr_per_rrset, + ignore_trailing=ignore_trailing) if not q.is_response(r): raise dns.query.BadResponse() return r @@ -15,7 +15,7 @@ setuptools.setup( url='http://eventlet.net', packages=setuptools.find_packages(exclude=['benchmarks', 'tests', 'tests.*']), install_requires=( - 'dnspython >= 1.15.0, < 2.0.0', + 'dnspython >= 1.15.0', 'greenlet >= 0.3', 'monotonic >= 1.4;python_version<"3.5"', 'six >= 1.10.0', diff --git a/tests/greendns_test.py b/tests/greendns_test.py index a6faae5..7419b35 100644 --- a/tests/greendns_test.py +++ b/tests/greendns_test.py @@ -901,7 +901,7 @@ class TinyDNSTests(tests.LimitedTestCase): resolver.nameserver_ports[dnsaddr[0]] = dnsaddr[1] response = resolver.query('host.example.com', 'a', tcp=True) self.assertIsInstance(response, Answer) - self.assertEqual(response.rrset.items[0].address, expected_ip) + self.assertEqual(list(response.rrset.items)[0].address, expected_ip) def test_reverse_name(): diff --git a/tests/isolated/socket_resolve_green.py b/tests/isolated/socket_resolve_green.py index 6017d5e..611d7dc 100644 --- a/tests/isolated/socket_resolve_green.py +++ b/tests/isolated/socket_resolve_green.py @@ -7,6 +7,7 @@ if __name__ == '__main__': import time import dns.message import dns.query + import dns.flags n = 10 delay = 0.01 @@ -17,7 +18,7 @@ if __name__ == '__main__': addr = addr_map[qname.to_text()] r = dns.message.make_response(q) r.index = None - r.flags = 256 + r.flags = dns.flags.QR | dns.flags.RD r.answer.append(dns.rrset.from_text(str(qname), 60, 'IN', 'A', addr)) r.time = 0.001 eventlet.sleep(delay) @@ -16,7 +16,7 @@ statistics = 1 [tox] minversion=2.5 envlist = - ipv6, pep8, py{27,35,36,37,38,39,py2,py3}-{selects,poll,epolls}, py38-openssl + ipv6, pep8, py{27,35,36,37,38,39,py2,py3}-{selects,poll,epolls}, py38-openssl, py27-dnspython1, py39-dnspython1 skipsdist = True [testenv:ipv6] @@ -74,6 +74,7 @@ deps = py{38,39}: psycopg2-binary==2.8.4 setuptools==38.5.1 {selects,poll,epolls}: pyzmq==19.0.2 + dnspython1: dnspython<2 usedevelop = True commands = nosetests --verbose {env:tox_cover_args} {posargs:tests/} |