Skip to content

Commit

Permalink
Merge branch 'master' into py310
Browse files Browse the repository at this point in the history
  • Loading branch information
temoto committed Oct 7, 2021
2 parents a5d9e9e + 690464a commit 158f918
Show file tree
Hide file tree
Showing 14 changed files with 232 additions and 25 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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 }
Expand Down
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
0.32.0
======
* greendns: compatibility with dnspython v2 https://github.com/eventlet/eventlet/pull/722
* green.ssl: wrap_socket now accepts argument `ciphers` https://github.com/eventlet/eventlet/pull/718
* websocket: control frames are now always uncompressed per RFC 7692; Thanks to Onno Kortmann

0.31.1
======
* ssl: py3.6 using client certificates raised ValueError: check_hostname needs server_hostname argument https://github.com/eventlet/eventlet/pull/705
Expand Down
5 changes: 5 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ codecov:
coverage:
precision: 0
round: down
status:
project:
default:
target: auto
threshold: 3%
2 changes: 1 addition & 1 deletion eventlet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
DeprecationWarning,
)

version_info = (0, 31, 1)
version_info = (0, 32, 0)
__version__ = '.'.join(map(str, version_info))
# This is to make Debian packaging easier, it ignores import
# errors of greenlet so that the packager can still at least
Expand Down
10 changes: 10 additions & 0 deletions eventlet/green/ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,16 @@ def verify_flags(self, value):
def verify_mode(self, value):
super(_original_sslcontext, _original_sslcontext).verify_mode.__set__(self, value)

if hasattr(_original_sslcontext, "maximum_version"):
@_original_sslcontext.maximum_version.setter
def maximum_version(self, value):
super(_original_sslcontext, _original_sslcontext).maximum_version.__set__(self, value)

if hasattr(_original_sslcontext, "minimum_version"):
@_original_sslcontext.minimum_version.setter
def minimum_version(self, value):
super(_original_sslcontext, _original_sslcontext).minimum_version.__set__(self, value)

SSLContext = GreenSSLContext

if hasattr(__ssl, 'create_default_context'):
Expand Down
86 changes: 76 additions & 10 deletions eventlet/support/greendns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""

Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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
Expand Down
42 changes: 32 additions & 10 deletions eventlet/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,12 @@ def send_hundred_continue_response(self):
# Reinitialize chunk_length (expect more data)
self.chunk_length = -1

@property
def should_send_hundred_continue(self):
return self.wfile is not None and not self.is_hundred_continue_response_sent

def _do_read(self, reader, length=None):
if self.wfile is not None and not self.is_hundred_continue_response_sent:
if self.should_send_hundred_continue:
# 100 Continue response
self.send_hundred_continue_response()
self.is_hundred_continue_response_sent = True
Expand All @@ -152,7 +156,7 @@ def _do_read(self, reader, length=None):
return read

def _chunked_read(self, rfile, length=None, use_readline=False):
if self.wfile is not None and not self.is_hundred_continue_response_sent:
if self.should_send_hundred_continue:
# 100 Continue response
self.send_hundred_continue_response()
self.is_hundred_continue_response_sent = True
Expand Down Expand Up @@ -464,9 +468,11 @@ def handle_one_response(self):
start = time.time()
headers_set = []
headers_sent = []
# Grab the request input now; app may try to replace it in the environ
request_input = self.environ['eventlet.input']
# Push the headers-sent state into the Input so it won't send a
# 100 Continue response if we've already started a response.
self.environ['wsgi.input'].headers_sent = headers_sent
request_input.headers_sent = headers_sent

wfile = self.wfile
result = None
Expand Down Expand Up @@ -563,10 +569,15 @@ def cap(x):
result = self.application(self.environ, start_response)

# Set content-length if possible
if headers_set and \
not headers_sent and hasattr(result, '__len__') and \
'Content-Length' not in [h for h, _v in headers_set[1]]:
headers_set[1].append(('Content-Length', str(sum(map(len, result)))))
if headers_set and not headers_sent and hasattr(result, '__len__'):
# We've got a complete final response
if 'Content-Length' not in [h for h, _v in headers_set[1]]:
headers_set[1].append(('Content-Length', str(sum(map(len, result)))))
if request_input.should_send_hundred_continue:
# We've got a complete final response, and never sent a 100 Continue.
# There's no chance we'll need to read the body as we stream out the
# response, so we can be nice and send a Connection: close header.
self.close_connection = 1

towrite = []
towrite_size = 0
Expand Down Expand Up @@ -607,11 +618,22 @@ def cap(x):
finally:
if hasattr(result, 'close'):
result.close()
request_input = self.environ['eventlet.input']
if request_input.should_send_hundred_continue:
# We just sent the final response, no 100 Continue. Client may or
# may not have started to send a body, and if we keep the connection
# open we've seen clients either
# * send a body, then start a new request
# * skip the body and go straight to a new request
# Looks like the most broadly compatible option is to close the
# connection and let the client retry.
# https://curl.se/mail/lib-2004-08/0002.html
# Note that we likely *won't* send a Connection: close header at this point
self.close_connection = 1

if (request_input.chunked_input or
request_input.position < (request_input.content_length or 0)):
# Read and discard body if there was no pending 100-continue
if not request_input.wfile and self.close_connection == 0:
# Read and discard body if connection is going to be reused
if self.close_connection == 0:
try:
request_input.discard()
except ChunkReadError as e:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
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',
Expand Down
2 changes: 1 addition & 1 deletion tests/greendns_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ def test_noraise_dns_tcp(self):
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():
Expand Down
3 changes: 2 additions & 1 deletion tests/isolated/socket_resolve_green.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import time
import dns.message
import dns.query
import dns.flags

n = 10
delay = 0.01
Expand All @@ -17,7 +18,7 @@ def slow_udp(q, *a, **kw):
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)
Expand Down
12 changes: 12 additions & 0 deletions tests/isolated/ssl_context_version_setters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
__test__ = False

if __name__ == "__main__":
import eventlet
eventlet.monkey_patch()
import ssl

context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.maximum_version = ssl.TLSVersion.TLSv1_2

print("pass")
4 changes: 4 additions & 0 deletions tests/ssl_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,7 @@ def serve(listener):
client.send(b"check_hostname works")
client.recv(64)
server_coro.wait()

@tests.skip_if(sys.version_info < (3, 7))
def test_context_version_setters(self):
tests.run_isolated("ssl_context_version_setters.py")

0 comments on commit 158f918

Please sign in to comment.