convenience: skip SO_REUSEPORT for bind on random port (0)

https://github.com/eventlet/eventlet/issues/411
This commit is contained in:
Sergey Shepelev 2017-05-12 01:32:02 +03:00
parent f72cc96a70
commit 0ec4df6cbe
2 changed files with 56 additions and 19 deletions

View File

@ -1,6 +1,6 @@
import sys
import warnings
from eventlet import greenio
from eventlet import greenpool
from eventlet import greenthread
from eventlet.green import socket
@ -22,7 +22,11 @@ def connect(addr, family=socket.AF_INET, bind=None):
return sock
def listen(addr, family=socket.AF_INET, backlog=50):
class ReuseRandomPortWarning(Warning):
pass
def listen(addr, family=socket.AF_INET, backlog=50, reuse_addr=True, reuse_port=None):
"""Convenience function for opening server sockets. This
socket can be used in :func:`~eventlet.serve` or a custom ``accept()`` loop.
@ -38,9 +42,18 @@ def listen(addr, family=socket.AF_INET, backlog=50):
:return: The listening green socket object.
"""
sock = socket.socket(family, socket.SOCK_STREAM)
if sys.platform[:3] != "win":
if reuse_addr and sys.platform[:3] != 'win':
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if hasattr(socket, 'SO_REUSEPORT'):
if family in (socket.AF_INET, socket.AF_INET6) and addr[1] == 0:
if reuse_port:
warnings.warn(
'''listen on random port (0) with SO_REUSEPORT is dangerous.
Double check your intent.
Example problem: https://github.com/eventlet/eventlet/issues/411''',
ReuseRandomPortWarning, stacklevel=3)
elif reuse_port is None:
reuse_port = True
if reuse_port and hasattr(socket, 'SO_REUSEPORT'):
# NOTE(zhengwei): linux kernel >= 3.9
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(addr)

View File

@ -1,7 +1,8 @@
import os
import warnings
import eventlet
from eventlet import debug, event
from eventlet import convenience, debug
from eventlet.green import socket
from eventlet.support import six
import tests
@ -90,7 +91,7 @@ class TestServe(tests.LimitedTestCase):
gt.wait()
def test_concurrency(self):
evt = event.Event()
evt = eventlet.Event()
def waiter(sock, addr):
sock.sendall(b'hi')
@ -128,18 +129,41 @@ class TestServe(tests.LimitedTestCase):
client.sendall(b"echo")
self.assertEqual(b"echo", client.recv(1024))
def test_socket_reuse(self):
def test_socket_reuse():
# pick a free port with bind to 0 - without SO_REUSEPORT
# then close it and try to bind to same port with SO_REUSEPORT
# loop helps in case something else used the chosen port before second bind
addr = None
errors = []
for _ in range(5):
lsock1 = eventlet.listen(('localhost', 0))
port = lsock1.getsockname()[1]
if hasattr(socket, 'SO_REUSEPORT'):
lsock2 = eventlet.listen(('localhost', port))
else:
try:
lsock2 = eventlet.listen(('localhost', port))
assert lsock2
lsock2.close()
except socket.error:
pass
addr = lsock1.getsockname()
lsock1.close()
try:
lsock1 = eventlet.listen(addr)
except socket.error as e:
errors.append(e)
continue
break
else:
assert False, errors
if hasattr(socket, 'SO_REUSEPORT'):
lsock2 = eventlet.listen(addr)
else:
try:
lsock2 = eventlet.listen(addr)
assert lsock2
lsock2.close()
except socket.error:
pass
lsock1.close()
def test_reuse_random_port_warning():
with warnings.catch_warnings(record=True) as w:
eventlet.listen(('localhost', 0), reuse_port=True).close()
assert len(w) == 1
assert issubclass(w[0].category, convenience.ReuseRandomPortWarning)