Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian....@packages.debian.org
Usertags: pu

Dear release team,

[ Reason ]
Eventlet 0.26.1 was unforunately incompatible with DNSPython 2.x
which is in Bullseye as well. This makes a few OpenStack components
fails, for example the cname middleware of Swift, and everything
that's using the greendns API of Eventlet.

Recently, Filippo Giunchedi from Wikimedia Foundation managed to
fix the situation with this patch:

https://github.com/eventlet/eventlet/pull/722

He nicely backported it to Eventlet 0.26.1 and provided me with
a patch that they use in production.

[ Impact ]
Failing cname swift middleware, as well as everything that's
using Eventlet's greendns.

[ Tests ]
Wikimedia foundation is using this in production.
There's also the unit tests at build time from the Eventlet package.

[ Risks ]
Hopefully, this wont break anything... :)

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

Cheers,

Thomas Goirand (zigo)
diff -Nru python-eventlet-0.26.1/debian/changelog 
python-eventlet-0.26.1/debian/changelog
--- python-eventlet-0.26.1/debian/changelog     2021-05-11 08:03:43.000000000 
+0200
+++ python-eventlet-0.26.1/debian/changelog     2021-09-10 21:42:12.000000000 
+0200
@@ -1,3 +1,12 @@
+python-eventlet (0.26.1-7+deb11u1) bullseye; urgency=medium
+
+  [ Filippo Giunchedi ]
+  * Fix dnspython 2 compat
+    See also https://github.com/eventlet/eventlet/pull/722 and
+    https://phabricator.wikimedia.org/T283714
+
+ -- Thomas Goirand <z...@debian.org>  Fri, 10 Sep 2021 21:42:12 +0200
+
 python-eventlet (0.26.1-7) unstable; urgency=medium
 
   * CVE-2021-21419: Malicious peer may exhaust memory on Eventlet side
diff -Nru python-eventlet-0.26.1/debian/greendns.orig.py 
python-eventlet-0.26.1/debian/greendns.orig.py
--- python-eventlet-0.26.1/debian/greendns.orig.py      2021-05-11 
08:03:43.000000000 +0200
+++ python-eventlet-0.26.1/debian/greendns.orig.py      2021-09-10 
21:42:12.000000000 +0200
@@ -120,12 +120,13 @@
     return is_ipv4_addr(host) or is_ipv6_addr(host)
 
 
-def compute_expiration(query, timeout):
-    # NOTE(ralonsoh): in dnspython v2.0.0, "_compute_expiration" was replaced
-    # by "_compute_times".
-    if hasattr(query, '_compute_expiration'):
+# 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:
+else:
+    def compute_expiration(query, timeout):
         return query._compute_times(timeout)[1]
 
 
@@ -669,8 +670,21 @@
                 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.
 
@@ -695,7 +709,21 @@
     @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:
@@ -717,7 +745,10 @@
         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 = compute_expiration(dns.query, timeout)
@@ -765,14 +796,23 @@
     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.
 
@@ -794,7 +834,19 @@
     @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:
@@ -810,7 +862,10 @@
         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 = compute_expiration(dns.query, timeout)
@@ -838,7 +893,9 @@
         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
diff -Nru python-eventlet-0.26.1/debian/greendns.py 
python-eventlet-0.26.1/debian/greendns.py
--- python-eventlet-0.26.1/debian/greendns.py   2021-05-11 08:03:43.000000000 
+0200
+++ python-eventlet-0.26.1/debian/greendns.py   2021-09-10 21:42:12.000000000 
+0200
@@ -120,12 +120,13 @@
     return is_ipv4_addr(host) or is_ipv6_addr(host)
 
 
-def compute_expiration(query, timeout):
-    # NOTE(ralonsoh): in dnspython v2.0.0, "_compute_expiration" was replaced
-    # by "_compute_times".
-    if hasattr(query, '_compute_expiration'):
+# 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:
+else:
+    def compute_expiration(query, timeout):
         return query._compute_times(timeout)[1]
 
 
@@ -669,8 +670,21 @@
                 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.
 
@@ -695,7 +709,21 @@
     @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:
@@ -717,7 +745,10 @@
         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 = compute_expiration(dns.query, timeout)
@@ -765,14 +796,23 @@
     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.
 
@@ -794,7 +834,19 @@
     @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:
@@ -810,7 +862,10 @@
         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 = compute_expiration(dns.query, timeout)
@@ -838,7 +893,9 @@
         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
diff -Nru 
python-eventlet-0.26.1/debian/patches/add_handling_for_new_dnspython_arguments.patch
 
python-eventlet-0.26.1/debian/patches/add_handling_for_new_dnspython_arguments.patch
--- 
python-eventlet-0.26.1/debian/patches/add_handling_for_new_dnspython_arguments.patch
        1970-01-01 01:00:00.000000000 +0100
+++ 
python-eventlet-0.26.1/debian/patches/add_handling_for_new_dnspython_arguments.patch
        2021-09-10 21:42:12.000000000 +0200
@@ -0,0 +1,143 @@
+Description: Add handling for new dnspython arguments
+  one_rr_per_rrset and ignore_trailing are present very long ago,
+  raise_on_truncation and sock are added since dnspython 2.0.
+Author: Felix Yan <felixonm...@archlinux.org>
+Date: Tue, 17 Aug 2021 15:42:23 +0800
+Origin: 
https://github.com/eventlet/eventlet/pull/722/commits/4496d99294233383250c0aa38f37b2a0751a346d
+
+---
+ eventlet/support/greendns.py | 72 ++++++++++++++++++++++++++++++++----
+ 1 file changed, 64 insertions(+), 8 deletions(-)
+
+Index: python-eventlet/eventlet/support/greendns.py
+===================================================================
+--- python-eventlet.orig/eventlet/support/greendns.py
++++ python-eventlet/eventlet/support/greendns.py
+@@ -668,8 +668,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.
+ 
+@@ -694,7 +707,21 @@ def udp(q, where, timeout=DNS_QUERY_TIME
+     @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:
+@@ -716,7 +743,10 @@ def udp(q, where, timeout=DNS_QUERY_TIME
+         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 = compute_expiration(dns.query, timeout)
+@@ -764,14 +794,23 @@ def udp(q, where, timeout=DNS_QUERY_TIME
+     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.
+ 
+@@ -793,7 +832,19 @@ def tcp(q, where, timeout=DNS_QUERY_TIME
+     @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:
+@@ -809,7 +860,10 @@ def tcp(q, where, timeout=DNS_QUERY_TIME
+         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 = compute_expiration(dns.query, timeout)
+@@ -837,7 +891,9 @@ def tcp(q, where, timeout=DNS_QUERY_TIME
+         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
diff -Nru 
python-eventlet-0.26.1/debian/patches/Check_compute_expiration_at_module_level.patch
 
python-eventlet-0.26.1/debian/patches/Check_compute_expiration_at_module_level.patch
--- 
python-eventlet-0.26.1/debian/patches/Check_compute_expiration_at_module_level.patch
        1970-01-01 01:00:00.000000000 +0100
+++ 
python-eventlet-0.26.1/debian/patches/Check_compute_expiration_at_module_level.patch
        2021-09-10 21:42:12.000000000 +0200
@@ -0,0 +1,32 @@
+Description: check _compute_expiration at module level
+Author: Felix Yan <felixonm...@archlinux.org>
+Date: Tue, 17 Aug 2021 00:46:49 +0800
+Origin: 
https://github.com/eventlet/eventlet/pull/722/commits/ecd55ad127ea630e45b630e45c53146bc064a65c
+
+---
+ eventlet/support/greendns.py | 11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+Index: python-eventlet/eventlet/support/greendns.py
+===================================================================
+--- python-eventlet.orig/eventlet/support/greendns.py
++++ python-eventlet/eventlet/support/greendns.py
+@@ -118,12 +118,13 @@ def is_ip_addr(host):
+     return is_ipv4_addr(host) or is_ipv6_addr(host)
+ 
+ 
+-def compute_expiration(query, timeout):
+-    # NOTE(ralonsoh): in dnspython v2.0.0, "_compute_expiration" was replaced
+-    # by "_compute_times".
+-    if hasattr(query, '_compute_expiration'):
++# 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:
++else:
++    def compute_expiration(query, timeout):
+         return query._compute_times(timeout)[1]
+ 
+ 
diff -Nru python-eventlet-0.26.1/debian/patches/series 
python-eventlet-0.26.1/debian/patches/series
--- python-eventlet-0.26.1/debian/patches/series        2021-05-11 
08:03:43.000000000 +0200
+++ python-eventlet-0.26.1/debian/patches/series        2021-09-10 
21:42:12.000000000 +0200
@@ -8,6 +8,8 @@
 0009-Removed-test_urllib-that-is-failing-in-py36.patch
 0016-imp-rename.patch
 Replace-dnspython-_compute_expiration-by-_compute_times.patch
+Check_compute_expiration_at_module_level.patch
+add_handling_for_new_dnspython_arguments.patch
 fix-test_noraise_dns_tcp.patch
 remove-non-deterministic-test_communicate_with_poll.patch
 0017-py39-Add-_at_fork_reinit-method-to-Semaphores.patch

Reply via email to