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 sys
import warnings
from eventlet import greenio
from eventlet import greenpool from eventlet import greenpool
from eventlet import greenthread from eventlet import greenthread
from eventlet.green import socket from eventlet.green import socket
@@ -22,7 +22,11 @@ def connect(addr, family=socket.AF_INET, bind=None):
return sock 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 """Convenience function for opening server sockets. This
socket can be used in :func:`~eventlet.serve` or a custom ``accept()`` loop. 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. :return: The listening green socket object.
""" """
sock = socket.socket(family, socket.SOCK_STREAM) 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) 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 # NOTE(zhengwei): linux kernel >= 3.9
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(addr) sock.bind(addr)

View File

@@ -1,7 +1,8 @@
import os import os
import warnings
import eventlet import eventlet
from eventlet import debug, event from eventlet import convenience, debug
from eventlet.green import socket from eventlet.green import socket
from eventlet.support import six from eventlet.support import six
import tests import tests
@@ -90,7 +91,7 @@ class TestServe(tests.LimitedTestCase):
gt.wait() gt.wait()
def test_concurrency(self): def test_concurrency(self):
evt = event.Event() evt = eventlet.Event()
def waiter(sock, addr): def waiter(sock, addr):
sock.sendall(b'hi') sock.sendall(b'hi')
@@ -128,18 +129,41 @@ class TestServe(tests.LimitedTestCase):
client.sendall(b"echo") client.sendall(b"echo")
self.assertEqual(b"echo", client.recv(1024)) 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)) lsock1 = eventlet.listen(('localhost', 0))
port = lsock1.getsockname()[1] 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'): if hasattr(socket, 'SO_REUSEPORT'):
lsock2 = eventlet.listen(('localhost', port)) lsock2 = eventlet.listen(addr)
else: else:
try: try:
lsock2 = eventlet.listen(('localhost', port)) lsock2 = eventlet.listen(addr)
assert lsock2 assert lsock2
lsock2.close() lsock2.close()
except socket.error: except socket.error:
pass pass
lsock1.close() 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)