diff --git a/0001-Fix-bad-ipv6-comparison.patch b/0001-Fix-bad-ipv6-comparison.patch new file mode 100644 index 0000000000000000000000000000000000000000..375083c8d0c4d680628f256f1d4e918bb1df476c --- /dev/null +++ b/0001-Fix-bad-ipv6-comparison.patch @@ -0,0 +1,62 @@ +From 16aa295ae86791e7b773114102790af46bb7b7ce Mon Sep 17 00:00:00 2001 +From: Lon Hohberger +Date: Fri, 27 Jul 2018 16:27:31 -0400 +Subject: [PATCH 1/3] Fix bad ipv6 comparison + +When performing DNS lookups, the source address from resolv.conf +may have stray zeroes in it, or the address string returned from +socket.recvfrom may. Purge them before comparing tuples. + +Original backport was broken. + +Resolves: rhbz#1607967 + +Signed-off-by: Lon Hohberger +Signed-off-by: Lon Hohberger +--- + eventlet/support/greendns.py | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/eventlet/support/greendns.py b/eventlet/support/greendns.py +index ea0924c..412e018 100644 +--- a/eventlet/support/greendns.py ++++ b/eventlet/support/greendns.py +@@ -35,6 +35,7 @@ + import struct + import sys + ++import eventlet + from eventlet import patcher + from eventlet.green import _socket_nodns + from eventlet.green import os +@@ -674,7 +675,12 @@ def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, + if source is not None: + source = (source, source_port) + elif af == dns.inet.AF_INET6: +- destination = (where, port, 0, 0) ++ # Purge any stray zeroes in source address. When doing the tuple comparison ++ # below, we need to always ensure both our target and where we receive replies ++ # from are compared with all zeroes removed so that we don't erroneously fail. ++ # e.g. ('00::1', 53, 0, 0) != ('::1', 53, 0, 0) ++ where_trunc = dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(where)) ++ destination = (where_trunc, port, 0, 0) + if source is not None: + source = (source, source_port, 0, 0) + +@@ -697,6 +703,13 @@ def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, + # Q: Do we also need to catch coro.CoroutineSocketWake and pass? + if expiration - time.time() <= 0.0: + raise dns.exception.Timeout ++ eventlet.sleep(0.01) ++ continue ++ if dns.inet.af_for_address(from_address[0]) == dns.inet.AF_INET6: ++ # Purge all possible zeroes for ipv6 to match above logic ++ addr = from_address[0] ++ addr = dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(addr)) ++ from_address = (addr, from_address[1], from_address[2], from_address[3]) + if from_address == destination: + break + if not ignore_unexpected: +-- +2.17.1 + diff --git a/0001-dns-hosts-file-was-consulted-after-nameservers.patch b/0001-dns-hosts-file-was-consulted-after-nameservers.patch new file mode 100644 index 0000000000000000000000000000000000000000..1c0ac30f0ba0c165bafd10ac47ba0ddc983e7b69 --- /dev/null +++ b/0001-dns-hosts-file-was-consulted-after-nameservers.patch @@ -0,0 +1,287 @@ +From 475d6a7ca11b081b3394faf4590d384af2c709af Mon Sep 17 00:00:00 2001 +From: Sergey Shepelev +Date: Thu, 16 Feb 2017 01:32:04 +0300 +Subject: [PATCH] dns: hosts file was consulted after nameservers + +This bug was introduced in v0.20.1 + +Now the behavior is as expected: first check hosts file, if it contains address - short return without querying nameservers. +https://github.com/eventlet/eventlet/issues/387 + +(cherry picked from commit 503aed1310b5e4a2fad9f5a0909663636a4e8752) +--- + eventlet/support/greendns.py | 45 +++++++++++++++---------- + tests/greendns_test.py | 79 +++++++++++++++++++++++++++++--------------- + 2 files changed, 81 insertions(+), 43 deletions(-) + +diff --git a/eventlet/support/greendns.py b/eventlet/support/greendns.py +index 8d5c7d6..ea0924c 100644 +--- a/eventlet/support/greendns.py ++++ b/eventlet/support/greendns.py +@@ -306,6 +306,12 @@ class ResolverProxy(object): + tcp=False, source=None, raise_on_no_answer=True, + _hosts_rdtypes=(dns.rdatatype.A, dns.rdatatype.AAAA)): + """Query the resolver, using /etc/hosts if enabled. ++ ++ Behavior: ++ 1. if hosts is enabled and contains answer, return it now ++ 2. query nameservers for qname ++ 3. if qname did not contain dots, pretend it was top-level domain, ++ query "foobar." and append to previous result + """ + result = [None, None, 0] + +@@ -328,6 +334,21 @@ class ResolverProxy(object): + result[2] += len(a.rrset) + return True + ++ def end(): ++ if result[0] is not None: ++ if raise_on_no_answer and result[2] == 0: ++ raise dns.resolver.NoAnswer ++ return result[0] ++ if result[1] is not None: ++ if raise_on_no_answer or not isinstance(result[1], dns.resolver.NoAnswer): ++ raise result[1] ++ raise dns.resolver.NXDOMAIN(qnames=(qname,)) ++ ++ if (self._hosts and (rdclass == dns.rdataclass.IN) and (rdtype in _hosts_rdtypes)): ++ if step(self._hosts.query, qname, rdtype, raise_on_no_answer=False): ++ if (result[0] is not None) or (result[1] is not None): ++ return end() ++ + # Main query + step(self._resolver.query, qname, rdtype, rdclass, tcp, source, raise_on_no_answer=False) + +@@ -341,20 +362,7 @@ class ResolverProxy(object): + step(self._resolver.query, qname.concatenate(dns.name.root), + rdtype, rdclass, tcp, source, raise_on_no_answer=False) + +- # Return answers from /etc/hosts despite nameserver errors +- # https://github.com/eventlet/eventlet/pull/354 +- if ((result[2] == 0) and self._hosts and +- (rdclass == dns.rdataclass.IN) and (rdtype in _hosts_rdtypes)): +- step(self._hosts.query, qname, rdtype, raise_on_no_answer=False) +- +- if result[0] is not None: +- if raise_on_no_answer and result[2] == 0: +- raise dns.resolver.NoAnswer +- return result[0] +- if result[1] is not None: +- if raise_on_no_answer or not isinstance(result[1], dns.resolver.NoAnswer): +- raise result[1] +- raise dns.resolver.NXDOMAIN(qnames=(qname,)) ++ return end() + + def getaliases(self, hostname): + """Return a list of all the aliases of a given hostname""" +@@ -376,8 +384,8 @@ class ResolverProxy(object): + resolver = ResolverProxy(hosts_resolver=HostsResolver()) + + +-def resolve(name, family=socket.AF_INET, raises=True): +- """Resolve a name for a given family using the global resolver proxy ++def resolve(name, family=socket.AF_INET, raises=True, _proxy=None): ++ """Resolve a name for a given family using the global resolver proxy. + + This method is called by the global getaddrinfo() function. + +@@ -391,9 +399,12 @@ def resolve(name, family=socket.AF_INET, raises=True): + else: + raise socket.gaierror(socket.EAI_FAMILY, + 'Address family not supported') ++ ++ if _proxy is None: ++ _proxy = resolver + try: + try: +- return resolver.query(name, rdtype, raise_on_no_answer=raises) ++ return _proxy.query(name, rdtype, raise_on_no_answer=raises) + except dns.resolver.NXDOMAIN: + if not raises: + return HostsAnswer(dns.name.Name(name), +diff --git a/tests/greendns_test.py b/tests/greendns_test.py +index 7ec259f..17bcd03 100644 +--- a/tests/greendns_test.py ++++ b/tests/greendns_test.py +@@ -12,26 +12,27 @@ import tests + import tests.mock + + +-class TestHostsResolver(tests.LimitedTestCase): ++def _make_host_resolver(): ++ """Returns a HostResolver instance + +- def _make_host_resolver(self): +- """Returns a HostResolver instance ++ The hosts file will be empty but accessible as a py.path.local ++ instance using the ``hosts`` attribute. ++ """ ++ hosts = tempfile.NamedTemporaryFile() ++ hr = greendns.HostsResolver(fname=hosts.name) ++ hr.hosts = hosts ++ hr._last_stat = 0 ++ return hr + +- The hosts file will be empty but accessible as a py.path.local +- instance using the ``hosts`` attribute. +- """ +- hosts = tempfile.NamedTemporaryFile() +- hr = greendns.HostsResolver(fname=hosts.name) +- hr.hosts = hosts +- hr._last_stat = 0 +- return hr ++ ++class TestHostsResolver(tests.LimitedTestCase): + + def test_default_fname(self): + hr = greendns.HostsResolver() + assert os.path.exists(hr.fname) + + def test_readlines_lines(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr.hosts.write(b'line0\n') + hr.hosts.flush() + assert hr._readlines() == ['line0'] +@@ -44,20 +45,20 @@ class TestHostsResolver(tests.LimitedTestCase): + assert hr._readlines() == ['line0', 'line1'] + + def test_readlines_missing_file(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr.hosts.close() + hr._last_stat = 0 + assert hr._readlines() == [] + + def test_load_no_contents(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr._load() + assert not hr._v4 + assert not hr._v6 + assert not hr._aliases + + def test_load_v4_v6_cname_aliases(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr.hosts.write(b'1.2.3.4 v4.example.com v4\n' + b'dead:beef::1 v6.example.com v6\n') + hr.hosts.flush() +@@ -69,7 +70,7 @@ class TestHostsResolver(tests.LimitedTestCase): + 'v6': 'v6.example.com'} + + def test_load_v6_link_local(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr.hosts.write(b'fe80:: foo\n' + b'fe80:dead:beef::1 bar\n') + hr.hosts.flush() +@@ -78,14 +79,14 @@ class TestHostsResolver(tests.LimitedTestCase): + assert not hr._v6 + + def test_query_A(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr._v4 = {'v4.example.com': '1.2.3.4'} + ans = hr.query('v4.example.com') + assert ans[0].address == '1.2.3.4' + + def test_query_ans_types(self): + # This assumes test_query_A above succeeds +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr._v4 = {'v4.example.com': '1.2.3.4'} + hr._last_stat = time.time() + ans = hr.query('v4.example.com') +@@ -108,18 +109,18 @@ class TestHostsResolver(tests.LimitedTestCase): + assert rr.address == '1.2.3.4' + + def test_query_AAAA(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr._v6 = {'v6.example.com': 'dead:beef::1'} + ans = hr.query('v6.example.com', dns.rdatatype.AAAA) + assert ans[0].address == 'dead:beef::1' + + def test_query_unknown_raises(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + with tests.assert_raises(greendns.dns.resolver.NoAnswer): + hr.query('example.com') + + def test_query_unknown_no_raise(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + ans = hr.query('example.com', raise_on_no_answer=False) + assert isinstance(ans, greendns.dns.resolver.Answer) + assert ans.response is None +@@ -134,30 +135,30 @@ class TestHostsResolver(tests.LimitedTestCase): + assert len(ans.rrset) == 0 + + def test_query_CNAME(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr._aliases = {'host': 'host.example.com'} + ans = hr.query('host', dns.rdatatype.CNAME) + assert ans[0].target == dns.name.from_text('host.example.com') + assert str(ans[0].target) == 'host.example.com.' + + def test_query_unknown_type(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + with tests.assert_raises(greendns.dns.resolver.NoAnswer): + hr.query('example.com', dns.rdatatype.MX) + + def test_getaliases(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr._aliases = {'host': 'host.example.com', + 'localhost': 'host.example.com'} + res = set(hr.getaliases('host')) + assert res == set(['host.example.com', 'localhost']) + + def test_getaliases_unknown(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + assert hr.getaliases('host.example.com') == [] + + def test_getaliases_fqdn(self): +- hr = self._make_host_resolver() ++ hr = _make_host_resolver() + hr._aliases = {'host': 'host.example.com'} + res = set(hr.getaliases('host.example.com')) + assert res == set(['host']) +@@ -791,3 +792,29 @@ def test_proxy_resolve_unqualified(): + pass + assert any(call[0][0] == dns.name.from_text('machine') for call in m.call_args_list) + assert any(call[0][0] == dns.name.from_text('machine.') for call in m.call_args_list) ++ ++ ++def test_hosts_priority(): ++ name = 'example.com' ++ addr_from_ns = '1.0.2.0' ++ ++ hr = _make_host_resolver() ++ rp = greendns.ResolverProxy(hosts_resolver=hr, filename=None) ++ base = _make_mock_base_resolver() ++ base.rr.address = addr_from_ns ++ rp._resolver = base() ++ ++ # Default behavior ++ rrns = greendns.resolve(name, _proxy=rp).rrset[0] ++ assert rrns.address == addr_from_ns ++ ++ # Hosts result must shadow that from nameservers ++ hr.hosts.write(b'1.2.3.4 example.com\ndead:beef::1 example.com\n') ++ hr.hosts.flush() ++ hr._load() ++ rrs4 = greendns.resolve(name, family=socket.AF_INET, _proxy=rp).rrset ++ assert len(rrs4) == 1 ++ assert rrs4[0].address == '1.2.3.4', rrs4[0].address ++ rrs6 = greendns.resolve(name, family=socket.AF_INET6, _proxy=rp).rrset ++ assert len(rrs6) == 1 ++ assert rrs6[0].address == 'dead:beef::1', rrs6[0].address +-- +2.9.4 + diff --git a/0001-greendns-Treat-etc-hosts-entries-case-insensitive.patch b/0001-greendns-Treat-etc-hosts-entries-case-insensitive.patch new file mode 100644 index 0000000000000000000000000000000000000000..6f14e0358d12402b850630cb84537d0d250afe3d --- /dev/null +++ b/0001-greendns-Treat-etc-hosts-entries-case-insensitive.patch @@ -0,0 +1,68 @@ +From a98bddd75c51ee59b87273614ba1e3a0aad45d45 Mon Sep 17 00:00:00 2001 +From: Ralf Haferkamp +Date: Fri, 12 Jan 2018 13:48:09 +0100 +Subject: [PATCH] greendns: Treat /etc/hosts entries case-insensitive + +Hostname in /etc/hosts are not case-sensitive, this fixes +HostsResolver() accordingly. + +eventlet#458 + +Co-Authored-By: Thomas Bechtold +--- + eventlet/support/greendns.py | 4 +++- + tests/greendns_test.py | 14 ++++++++++++++ + 2 files changed, 17 insertions(+), 1 deletion(-) + +diff --git a/eventlet/support/greendns.py b/eventlet/support/greendns.py +index cff0581..140388e 100644 +--- a/eventlet/support/greendns.py ++++ b/eventlet/support/greendns.py +@@ -222,9 +222,10 @@ class HostsResolver(object): + ipmap = self._v6 + else: + continue +- cname = parts.pop(0) ++ cname = parts.pop(0).lower() + ipmap[cname] = ip + for alias in parts: ++ alias = alias.lower() + ipmap[alias] = ip + self._aliases[alias] = cname + self._last_load = time.time() +@@ -251,6 +252,7 @@ class HostsResolver(object): + qname = dns.name.from_text(qname) + else: + name = str(qname) ++ name = name.lower() + rrset = dns.rrset.RRset(qname, rdclass, rdtype) + rrset.ttl = self._last_load + self.interval - now + if rdclass == dns.rdataclass.IN and rdtype == dns.rdatatype.A: +diff --git a/tests/greendns_test.py b/tests/greendns_test.py +index 4a47c5e..d55cca8 100644 +--- a/tests/greendns_test.py ++++ b/tests/greendns_test.py +@@ -174,6 +174,20 @@ class TestHostsResolver(tests.LimitedTestCase): + res = set(hr.getaliases('host.example.com')) + assert res == set(['host']) + ++ def test_hosts_case_insensitive(self): ++ name = 'example.com' ++ hr = _make_host_resolver() ++ hr.hosts.write(b'1.2.3.4 ExAmPlE.CoM\n') ++ hr.hosts.flush() ++ hr._load() ++ ++ ans = hr.query(name) ++ rr = ans.rrset[0] ++ assert isinstance(rr, greendns.dns.rdtypes.IN.A.A) ++ assert rr.rdtype == dns.rdatatype.A ++ assert rr.rdclass == dns.rdataclass.IN ++ assert rr.address == '1.2.3.4' ++ + + def _make_mock_base_resolver(): + """A mocked base resolver class""" +-- +2.17.1 + diff --git a/0002-greendns-don-t-contact-nameservers-if-one-entry-is-r.patch b/0002-greendns-don-t-contact-nameservers-if-one-entry-is-r.patch new file mode 100644 index 0000000000000000000000000000000000000000..565d7f1dc5e8a4cb614b17d43dcf273e8a24c466 --- /dev/null +++ b/0002-greendns-don-t-contact-nameservers-if-one-entry-is-r.patch @@ -0,0 +1,208 @@ +From ea52caabddfab99b25dfeb6266927dfc584d3055 Mon Sep 17 00:00:00 2001 +From: Daniel Alvarez +Date: Thu, 2 Aug 2018 12:51:20 +0000 +Subject: [PATCH 1/1] greendns: don't contact nameservers if one entry is + returned from hosts file + +getaddrinfo() behaves differently from the standard implementation as +it will try to contact nameservers if only one (IPv4 or IPv6) entry +is returned from /etc/hosts file. + +This patch avoids getaddrinfo() querying nameservers if at least one +entry is fetched through the hosts file to match the behavior of +the original socket.getaddrinfo() implementation. + +Closes #515 + +Signed-off-by: Daniel Alvarez +--- + eventlet/support/greendns.py | 39 ++++++++++++++++----------- + tests/greendns_test.py | 63 +++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 83 insertions(+), 19 deletions(-) + +diff --git a/eventlet/support/greendns.py b/eventlet/support/greendns.py +index ea0924c..8eda105 100644 +--- a/eventlet/support/greendns.py ++++ b/eventlet/support/greendns.py +@@ -304,12 +304,13 @@ class ResolverProxy(object): + + def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, + tcp=False, source=None, raise_on_no_answer=True, +- _hosts_rdtypes=(dns.rdatatype.A, dns.rdatatype.AAAA)): ++ _hosts_rdtypes=(dns.rdatatype.A, dns.rdatatype.AAAA), ++ use_network=True): + """Query the resolver, using /etc/hosts if enabled. + + Behavior: + 1. if hosts is enabled and contains answer, return it now +- 2. query nameservers for qname ++ 2. query nameservers for qname if use_network is True + 3. if qname did not contain dots, pretend it was top-level domain, + query "foobar." and append to previous result + """ +@@ -346,7 +347,7 @@ class ResolverProxy(object): + + if (self._hosts and (rdclass == dns.rdataclass.IN) and (rdtype in _hosts_rdtypes)): + if step(self._hosts.query, qname, rdtype, raise_on_no_answer=False): +- if (result[0] is not None) or (result[1] is not None): ++ if (result[0] is not None) or (result[1] is not None) or (not use_network): + return end() + + # Main query +@@ -384,10 +385,12 @@ class ResolverProxy(object): + resolver = ResolverProxy(hosts_resolver=HostsResolver()) + + +-def resolve(name, family=socket.AF_INET, raises=True, _proxy=None): ++def resolve(name, family=socket.AF_INET, raises=True, _proxy=None, ++ use_network=True): + """Resolve a name for a given family using the global resolver proxy. + +- This method is called by the global getaddrinfo() function. ++ This method is called by the global getaddrinfo() function. If use_network ++ is False, only resolution via hosts file will be performed. + + Return a dns.resolver.Answer instance. If there is no answer it's + rrset will be emtpy. +@@ -404,7 +407,8 @@ def resolve(name, family=socket.AF_INET, raises=True, _proxy=None): + _proxy = resolver + try: + try: +- return _proxy.query(name, rdtype, raise_on_no_answer=raises) ++ return _proxy.query(name, rdtype, raise_on_no_answer=raises, ++ use_network=use_network) + except dns.resolver.NXDOMAIN: + if not raises: + return HostsAnswer(dns.name.Name(name), +@@ -455,16 +459,19 @@ def _getaddrinfo_lookup(host, family, flags): + addrs = [] + if family == socket.AF_UNSPEC: + err = None +- for qfamily in [socket.AF_INET6, socket.AF_INET]: +- try: +- answer = resolve(host, qfamily, False) +- except socket.gaierror as e: +- if e.errno not in (socket.EAI_AGAIN, socket.EAI_NODATA): +- raise +- err = e +- else: +- if answer.rrset: +- addrs.extend(rr.address for rr in answer.rrset) ++ for use_network in [False, True]: ++ for qfamily in [socket.AF_INET6, socket.AF_INET]: ++ try: ++ answer = resolve(host, qfamily, False, use_network=use_network) ++ except socket.gaierror as e: ++ if e.errno not in (socket.EAI_AGAIN, EAI_NONAME_ERROR.errno, EAI_NODATA_ERROR.errno): ++ raise ++ err = e ++ else: ++ if answer.rrset: ++ addrs.extend(rr.address for rr in answer.rrset) ++ if addrs: ++ break + if err is not None and not addrs: + raise err + elif family == socket.AF_INET6 and flags & socket.AI_V4MAPPED: +diff --git a/tests/greendns_test.py b/tests/greendns_test.py +index 17bcd03..963e98c 100644 +--- a/tests/greendns_test.py ++++ b/tests/greendns_test.py +@@ -173,6 +173,7 @@ def _make_mock_base_resolver(): + aliases = ['cname.example.com'] + raises = None + rr = RR() ++ rr6 = RR() + + def query(self, *args, **kwargs): + self.args = args +@@ -182,7 +183,10 @@ def _make_mock_base_resolver(): + if hasattr(self, 'rrset'): + rrset = self.rrset + else: +- rrset = [self.rr] ++ if self.rr6 and self.args[1] == dns.rdatatype.AAAA: ++ rrset = [self.rr6] ++ else: ++ rrset = [self.rr] + return greendns.HostsAnswer('foo', 1, 1, rrset, False) + + def getaliases(self, *args, **kwargs): +@@ -319,7 +323,7 @@ class TestResolve(tests.LimitedTestCase): + assert greendns.resolver.args == ('host.example.com', dns.rdatatype.A) + + def test_AAAA(self): +- greendns.resolver.rr.address = 'dead:beef::1' ++ greendns.resolver.rr6.address = 'dead:beef::1' + ans = greendns.resolve('host.example.com', socket.AF_INET6) + assert ans[0].address == 'dead:beef::1' + assert greendns.resolver.args == ('host.example.com', dns.rdatatype.AAAA) +@@ -394,7 +398,8 @@ def _make_mock_resolve(): + def __init__(self): + self.answers = {} + +- def __call__(self, name, family=socket.AF_INET, raises=True): ++ def __call__(self, name, family=socket.AF_INET, raises=True, ++ _proxy=None, use_network=True): + qname = dns.name.from_text(name) + try: + rrset = self.answers[name][family] +@@ -818,3 +823,55 @@ def test_hosts_priority(): + rrs6 = greendns.resolve(name, family=socket.AF_INET6, _proxy=rp).rrset + assert len(rrs6) == 1 + assert rrs6[0].address == 'dead:beef::1', rrs6[0].address ++ ++ ++def test_hosts_no_network(): ++ ++ name = 'example.com' ++ addr_from_ns = '1.0.2.0' ++ addr6_from_ns = 'dead:beef::1' ++ ++ hr = _make_host_resolver() ++ rp = greendns.ResolverProxy(hosts_resolver=hr, filename=None) ++ base = _make_mock_base_resolver() ++ base.rr.address = addr_from_ns ++ base.rr6.address = addr6_from_ns ++ rp._resolver = base() ++ ++ with tests.mock.patch.object(greendns, 'resolver', ++ new_callable=tests.mock.PropertyMock(return_value=rp)): ++ res = greendns.getaddrinfo('example.com', 'domain', socket.AF_UNSPEC) ++ # Default behavior ++ addr = (addr_from_ns, 53) ++ tcp = (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr) ++ udp = (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr) ++ addr = (addr6_from_ns, 53, 0, 0) ++ tcp6 = (socket.AF_INET6, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr) ++ udp6 = (socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr) ++ filt_res = [ai[:3] + (ai[4],) for ai in res] ++ assert tcp in filt_res ++ assert udp in filt_res ++ assert tcp6 in filt_res ++ assert udp6 in filt_res ++ ++ # Hosts result must shadow that from nameservers ++ hr = _make_host_resolver() ++ hr.hosts.write(b'1.2.3.4 example.com') ++ hr.hosts.flush() ++ hr._load() ++ greendns.resolver._hosts = hr ++ ++ res = greendns.getaddrinfo('example.com', 'domain', socket.AF_UNSPEC) ++ filt_res = [ai[:3] + (ai[4],) for ai in res] ++ ++ # Make sure that only IPv4 entry from hosts is present. ++ assert tcp not in filt_res ++ assert udp not in filt_res ++ assert tcp6 not in filt_res ++ assert udp6 not in filt_res ++ ++ addr = ('1.2.3.4', 53) ++ tcp = (socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, addr) ++ udp = (socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP, addr) ++ assert tcp in filt_res ++ assert udp in filt_res +-- +2.15.2 + diff --git a/0002-greendns-udp-Fix-infinite-loop-when-source-address-m.patch b/0002-greendns-udp-Fix-infinite-loop-when-source-address-m.patch new file mode 100644 index 0000000000000000000000000000000000000000..5562244f07cf3b223f35252d35f8859caaf487db --- /dev/null +++ b/0002-greendns-udp-Fix-infinite-loop-when-source-address-m.patch @@ -0,0 +1,51 @@ +From 27457b3b8dd8e530efcb337a160e5862807f539f Mon Sep 17 00:00:00 2001 +From: Lon Hohberger +Date: Tue, 31 Jul 2018 08:12:43 -0400 +Subject: [PATCH 2/3] greendns udp: Fix infinite loop when source address + mismatch + +If the source address for a packet did not match where we sent, +the udp() function would spin in an infinite loop and the timer +would never expire, causing the process to hang. + +Signed-off-by: Lon Hohberger +--- + eventlet/support/greendns.py | 18 ++++++++++++++---- + 1 file changed, 14 insertions(+), 4 deletions(-) + +diff --git a/eventlet/support/greendns.py b/eventlet/support/greendns.py +index 3ecbc9b..0e03113 100644 +--- a/eventlet/support/greendns.py ++++ b/eventlet/support/greendns.py +@@ -696,14 +696,24 @@ def udp(q, where, timeout=DNS_QUERY_TIMEOUT, port=53, + # Q: Do we also need to catch coro.CoroutineSocketWake and pass? + if expiration - time.time() <= 0.0: + raise dns.exception.Timeout +- while 1: ++ eventlet.sleep(0.01) ++ ++ tried = False ++ while True: ++ # If we've tried to receive at least once, check to see if our ++ # timer expired ++ if tried and (expiration - time.time() <= 0.0): ++ raise dns.exception.Timeout ++ # Sleep if we are retrying the operation due to a bad source ++ # address or a socket timeout. ++ if tried: ++ eventlet.sleep(0.01) ++ tried = True ++ + try: + (wire, from_address) = s.recvfrom(65535) + except socket.timeout: + # Q: Do we also need to catch coro.CoroutineSocketWake and pass? +- if expiration - time.time() <= 0.0: +- raise dns.exception.Timeout +- eventlet.sleep(0.01) + continue + if dns.inet.af_for_address(from_address[0]) == dns.inet.AF_INET6: + # Purge all possible zeroes for ipv6 to match above logic +-- +2.13.6 + diff --git a/0003-tests-Add-ipv6-tests-for-greendns-udp-function.patch b/0003-tests-Add-ipv6-tests-for-greendns-udp-function.patch new file mode 100644 index 0000000000000000000000000000000000000000..db27509077aa03e4128d10556f45fe36cfd14b4a --- /dev/null +++ b/0003-tests-Add-ipv6-tests-for-greendns-udp-function.patch @@ -0,0 +1,73 @@ +From cf9b85db781f0fdb69ffe749184b29718aa0cd02 Mon Sep 17 00:00:00 2001 +From: Lon Hohberger +Date: Tue, 31 Jul 2018 08:12:43 -0400 +Subject: [PATCH 3/3] tests: Add ipv6 tests for greendns udp() function + +Tests: + - normal operation + - no reply (timeout) + - unexpected source address (w/ timeout & + ignore_unexpected set) + - number of zeroes in ipv6 address string different + - unexpected address + +Signed-off-by: Lon Hohberger +--- + tests/greendns_test.py | 41 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 41 insertions(+) + +diff --git a/tests/greendns_test.py b/tests/greendns_test.py +index 17bcd03..373b2db 100644 +--- a/tests/greendns_test.py ++++ b/tests/greendns_test.py +@@ -191,6 +191,47 @@ def _make_mock_base_resolver(): + return Resolver + + ++class TestUdp(tests.LimitedTestCase): ++ ++ def setUp(self): ++ # Store this so we can reuse it for each test ++ self.query = greendns.dns.message.Message() ++ self.query.flags = greendns.dns.flags.QR ++ self.query_wire = self.query.to_wire() ++ super(TestUdp, self).setUp() ++ ++ def test_udp_ipv6(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ return_value=(self.query_wire, ++ ('::1', 53, 0, 0))): ++ greendns.udp(self.query, '::1') ++ ++ def test_udp_ipv6_timeout(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ side_effect=socket.timeout): ++ with tests.assert_raises(dns.exception.Timeout): ++ greendns.udp(self.query, '::1', timeout=0.1) ++ ++ def test_udp_ipv6_addr_zeroes(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ return_value=(self.query_wire, ++ ('0:00:0000::1', 53, 0, 0))): ++ greendns.udp(self.query, '::1') ++ ++ def test_udp_ipv6_wrong_addr_ignore(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ side_effect=socket.timeout): ++ with tests.assert_raises(dns.exception.Timeout): ++ greendns.udp(self.query, '::1', timeout=0.1, ignore_unexpected=True) ++ ++ def test_udp_ipv6_wrong_addr(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ return_value=(self.query_wire, ++ ('ffff:0000::1', 53, 0, 0))): ++ with tests.assert_raises(dns.query.UnexpectedSource): ++ greendns.udp(self.query, '::1') ++ ++ + class TestProxyResolver(tests.LimitedTestCase): + + def test_clear(self): +-- +2.13.6 + diff --git a/0004-tests-Add-ipv4-udp-tests-for-greendns.patch b/0004-tests-Add-ipv4-udp-tests-for-greendns.patch new file mode 100644 index 0000000000000000000000000000000000000000..1cec413f657d90ef910a0a47c172010f4fb87fb7 --- /dev/null +++ b/0004-tests-Add-ipv4-udp-tests-for-greendns.patch @@ -0,0 +1,49 @@ +From 76b7697070e11c624741c8bc7b31e7c81fb208c5 Mon Sep 17 00:00:00 2001 +From: Lon Hohberger +Date: Wed, 8 Aug 2018 17:19:52 -0400 +Subject: [PATCH] tests: Add ipv4 udp tests for greendns + +Signed-off-by: Lon Hohberger +--- + tests/greendns_test.py | 25 +++++++++++++++++++++++++ + 1 file changed, 25 insertions(+) + +diff --git a/tests/greendns_test.py b/tests/greendns_test.py +index 652f6f6..a6faae5 100644 +--- a/tests/greendns_test.py ++++ b/tests/greendns_test.py +@@ -236,6 +236,31 @@ class TestUdp(tests.LimitedTestCase): + self.query_wire = self.query.to_wire() + super(TestUdp, self).setUp() + ++ def test_udp_ipv4(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ return_value=(self.query_wire, ++ ('127.0.0.1', 53))): ++ greendns.udp(self.query, '127.0.0.1') ++ ++ def test_udp_ipv4_timeout(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ side_effect=socket.timeout): ++ with tests.assert_raises(dns.exception.Timeout): ++ greendns.udp(self.query, '127.0.0.1', timeout=0.1) ++ ++ def test_udp_ipv4_wrong_addr_ignore(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ side_effect=socket.timeout): ++ with tests.assert_raises(dns.exception.Timeout): ++ greendns.udp(self.query, '127.0.0.1', timeout=0.1, ignore_unexpected=True) ++ ++ def test_udp_ipv4_wrong_addr(self): ++ with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', ++ return_value=(self.query_wire, ++ ('127.0.0.2', 53))): ++ with tests.assert_raises(dns.query.UnexpectedSource): ++ greendns.udp(self.query, '127.0.0.1') ++ + def test_udp_ipv6(self): + with tests.mock.patch('eventlet.support.greendns.socket.socket.recvfrom', + return_value=(self.query_wire, +-- +2.17.1 + diff --git a/eventlet-0.20.1.tar.gz b/eventlet-0.20.1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..93e2f5333d0412ffe7c9f71b3e3151ac12869eb5 Binary files /dev/null and b/eventlet-0.20.1.tar.gz differ diff --git a/eventlet-0.30.0.tar.gz b/eventlet-0.30.0.tar.gz deleted file mode 100644 index 1fe4dd68121c6aed9badabd0ea7e4790097c9206..0000000000000000000000000000000000000000 Binary files a/eventlet-0.30.0.tar.gz and /dev/null differ diff --git a/python-eventlet.spec b/python-eventlet.spec index 9579de53dce17752525e4d24428feaf6d3e8c586..23c7c3ca82eebce4b2581cbf394ad17257729028 100644 --- a/python-eventlet.spec +++ b/python-eventlet.spec @@ -1,78 +1,183 @@ -%global _empty_manifest_terminate_build 0 -Name: python-eventlet -Version: 0.30.0 -Release: 1 -Summary: Highly concurrent networking library -License: MIT License -URL: https://github.com/eventlet/eventlet -Source0: https://files.pythonhosted.org/packages/0c/dd/cda72b013472d570f9d5670b9260a6d6491829bd4b7697829e8591a24168/eventlet-0.30.0.tar.gz -BuildArch: noarch - -Requires: python3-dnspython -Requires: python3-greenlet -Requires: python3-six -Requires: python3-monotonic +%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} +%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")} + +%{!?_licensedir:%global license %%doc} + +%global with_python3 0 + +Name: python-eventlet +Version: 0.20.1 +Release: 1 +Summary: Highly concurrent networking library +License: MIT +URL: http://eventlet.net +Source0: https://pypi.io/packages/source/e/eventlet/eventlet-%{version}.tar.gz +Patch1: 0001-dns-hosts-file-was-consulted-after-nameservers.patch +Patch2: 0002-greendns-don-t-contact-nameservers-if-one-entry-is-r.patch +Patch3: 0001-Fix-bad-ipv6-comparison.patch +Patch4: 0002-greendns-udp-Fix-infinite-loop-when-source-address-m.patch +Patch5: 0003-tests-Add-ipv6-tests-for-greendns-udp-function.patch +Patch6: 0004-tests-Add-ipv4-udp-tests-for-greendns.patch +Patch7: 0001-greendns-Treat-etc-hosts-entries-case-insensitive.patch + +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildArch: noarch +BuildRequires: python2-devel +BuildRequires: python2-setuptools +BuildRequires: python2-greenlet +BuildRequires: python2-pip +Requires: python2-greenlet %description -Concurrent networking library for Python +Eventlet is a networking library written in Python. It achieves high +scalability by using non-blocking io while at the same time retaining +high programmer usability by using coroutines to make the non-blocking +io operations appear blocking at the source code level. + + +%package -n python2-eventlet +Summary: Highly concurrent networking library + +BuildRequires: python2-devel +BuildRequires: python-setuptools +Requires: python-greenlet + +%{?python_provide:%python_provide python2-eventlet} +# python_provide does not exist in CBS Cloud buildroot +Provides: python-eventlet = %{version}-%{release} +Obsoletes: python-eventlet < 0.17.4-3 +%description -n python2-eventlet +Eventlet is a networking library written in Python. It achieves high +scalability by using non-blocking io while at the same time retaining +high programmer usability by using coroutines to make the non-blocking +io operations appear blocking at the source code level. + + +%if 0%{?with_python3} %package -n python3-eventlet -Summary: Highly concurrent networking library -Provides: python-eventlet -BuildRequires: python3-devel -BuildRequires: python3-setuptools +Summary: Highly concurrent networking library +BuildArch: noarch + +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: python3-greenlet + +Requires: python3-greenlet + +%{?python_provide:%python_provide python3-eventlet} + %description -n python3-eventlet -Concurrent networking library for Python +Eventlet is a networking library written in Python. It achieves high +scalability by using non-blocking io while at the same time retaining +high programmer usability by using coroutines to make the non-blocking +io operations appear blocking at the source code level. +%endif + -%package help -Summary: Development documents and examples for eventlet -Provides: python3-eventlet-doc -%description help -Concurrent networking library for Python +%package -n python2-eventlet-doc +Summary: Documentation for %{name} +BuildRequires: python-sphinx + +%{?python_provide:%python_provide python2-eventlet-doc} +# python_provide does not exist in CBS Cloud buildroot +Provides: python-eventlet-doc = %{version}-%{release} +Obsoletes: python-eventlet-doc < 0.17.4-3 + +%description -n python2-eventlet-doc +Documentation for the python-eventlet package. + +%if 0%{?with_python3} +%package -n python3-eventlet-doc +Summary: Documentation for python3-eventlet-doc +BuildRequires: python3-sphinx + +%description -n python3-eventlet-doc +Documentation for the python-eventlet package. +%endif %prep -%autosetup -n eventlet-0.30.0 +%setup -q -n eventlet-%{version} +rm -rf *.egg-info +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 +%patch7 -p1 + +# generate html docs +export PYTHONPATH="$( pwd ):$PYTHONPATH" +pushd doc +make html +# remove the sphinx-build leftovers +rm -rf html/.{doctrees,buildinfo} +popd + +%if 0%{?with_python3} +rm -rf %{py3dir} +cp -a . %{py3dir} +find %{py3dir} -name '*.py' | xargs sed -i '1s|^#!python|#!%{__python3}|' +# generate html docs +export PYTHONPATH="$( pwd ):$PYTHONPATH" +pushd doc +make html +# remove the sphinx-build leftovers +rm -rf html/.{doctrees,buildinfo} +popd +%endif %build -%py3_build +%{__python2} setup.py build + +%if 0%{?with_python3} +pushd %{py3dir} +%{__python3} setup.py build +popd +%endif %install -%py3_install -install -d -m755 %{buildroot}/%{_pkgdocdir} -if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi -if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi -if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi -if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi -pushd %{buildroot} -if [ -d usr/lib ]; then - find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst -fi -if [ -d usr/lib64 ]; then - find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst -fi -if [ -d usr/bin ]; then - find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst -fi -if [ -d usr/sbin ]; then - find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst -fi -touch doclist.lst -if [ -d usr/share/man ]; then - find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst -fi +%if 0%{?with_python3} +pushd %{py3dir} +%{__python3} setup.py install --skip-build --root %{buildroot} +rm -rf %{buildroot}/%{python3_sitelib}/tests popd -mv %{buildroot}/filelist.lst . -mv %{buildroot}/doclist.lst . +%endif + +%{__python2} setup.py install --skip-build --root %{buildroot} +rm -rf %{buildroot}/%{python2_sitelib}/tests +# FIXME: Those files are not meant to be used with Python 2.7 +# Anyway the whole module eventlet.green.http is Python 3 only +# Trying to import it will fail under Python 2.7 +# https://github.com/eventlet/eventlet/issues/369 +rm -rf %{buildroot}/%{python2_sitelib}/eventlet/green/http/{cookiejar,client}.py + -%files -n python3-eventlet -f filelist.lst -%dir %{python3_sitelib}/* +%files -n python2-eventlet +%doc README.rst AUTHORS LICENSE NEWS +%license LICENSE +%{python2_sitelib}/eventlet +%{python2_sitelib}/eventlet-%{version}-py?.?.egg-info -%files help -f doclist.lst -%{_docdir}/* +%if 0%{?with_python3} +%files -n python3-eventlet +%doc README.rst AUTHORS LICENSE NEWS +%license LICENSE +%{python3_sitelib}/eventlet +%{python3_sitelib}/eventlet-%{version}-py?.?.egg-info +%endif + +%files -n python2-eventlet-doc +%license LICENSE +%doc doc/_build/html + +%if 0%{?with_python3} +%files -n python3-eventlet-doc +%license LICENSE +%doc doc/_build/html +%endif %changelog -* Fri Jan 15 2021 Python_Bot +* Thu Apr 29 2021 openstack-sig - Package Spec generated - -* Thu Mar 12 2020 zoushuangshuang - 0.23.0-3 -- Package init diff --git a/python37.patch b/python37.patch deleted file mode 100644 index 62816babd721b1f353b0fdd28fb0b2ca10a5bdb1..0000000000000000000000000000000000000000 --- a/python37.patch +++ /dev/null @@ -1,140 +0,0 @@ -From 0d4e7bcb90800d6700b2c81c41c9770ee5f94358 Mon Sep 17 00:00:00 2001 -From: Marcel Plch -Date: Mon, 9 Jul 2018 16:45:45 +0200 -Subject: [PATCH] Fix for Python 3.7 - ---- - eventlet/green/ssl.py | 46 ++++++++++++++++++++++++++++++++++++++++------ - tests/debug_test.py | 14 ++++++++++++-- - tests/hub_test.py | 4 +++- - 3 files changed, 55 insertions(+), 9 deletions(-) - -diff --git a/eventlet/green/ssl.py b/eventlet/green/ssl.py -index 53ee9a3c..df72869e 100644 ---- a/eventlet/green/ssl.py -+++ b/eventlet/green/ssl.py -@@ -24,6 +24,7 @@ - 'create_default_context', '_create_default_https_context'] - - _original_sslsocket = __ssl.SSLSocket -+_original_wrap_socket = __ssl.wrap_socket - - - class GreenSSLSocket(_original_sslsocket): -@@ -57,11 +58,41 @@ def __init__(self, sock, keyfile=None, certfile=None, - # this assignment - self._timeout = sock.gettimeout() - -- # nonblocking socket handshaking on connect got disabled so let's pretend it's disabled -- # even when it's on -- super(GreenSSLSocket, self).__init__( -- sock.fd, keyfile, certfile, server_side, cert_reqs, ssl_version, -- ca_certs, do_handshake_on_connect and six.PY2, *args, **kw) -+ if sys.version_info >= (3, 7): -+ # Monkey-patch the sslsocket so our modified self gets -+ # injected into its _create method. -+ def fake_new(self, cls, *args, **kwargs): -+ return self -+ -+ orig_new = _original_sslsocket.__new__ -+ try: -+ _original_sslsocket.__new__ = fake_new.__get__(self, GreenSSLSocket) -+ -+ self = _original_wrap_socket( -+ sock=sock.fd, -+ keyfile=keyfile, -+ certfile=certfile, -+ server_side=server_side, -+ cert_reqs=cert_reqs, -+ ssl_version=ssl_version, -+ ca_certs=ca_certs, -+ do_handshake_on_connect=do_handshake_on_connect and six.PY2, -+ *args, **kw -+ ) -+ self.keyfile = keyfile -+ self.certfile = certfile -+ self.cert_reqs = cert_reqs -+ self.ssl_version = ssl_version -+ self.ca_certs = ca_certs -+ finally: -+ # Unpatch -+ _original_sslsocket.__new__ = orig_new -+ else: -+ # nonblocking socket handshaking on connect got disabled so let's pretend it's disabled -+ # even when it's on -+ super(GreenSSLSocket, self).__init__( -+ sock.fd, keyfile, certfile, server_side, cert_reqs, ssl_version, -+ ca_certs, do_handshake_on_connect and six.PY2, *args, **kw) - - # the superclass initializer trashes the methods so we remove - # the local-object versions of them and let the actual class -@@ -323,7 +354,10 @@ def connect(self, addr): - except NameError: - self._sslobj = sslobj - else: -- self._sslobj = SSLObject(sslobj, owner=self) -+ if sys.version_info < (3, 7): -+ self._sslobj = SSLObject(sslobj, owner=self) -+ else: -+ self._sslobj = sslobj - - if self.do_handshake_on_connect: - self.do_handshake() -diff --git a/tests/debug_test.py b/tests/debug_test.py -index 8299dede..82b3a834 100644 ---- a/tests/debug_test.py -+++ b/tests/debug_test.py -@@ -29,6 +29,11 @@ def test_unspew(self): - assert self.tracer is None - - def test_line(self): -+ if sys.version_info >= (3, 7): -+ frame_str = "f== (3, 7): -+ frame_str = "f=