summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Shepelev <temotor@gmail.com>2017-05-12 01:32:02 +0300
committerSergey Shepelev <temotor@gmail.com>2017-05-12 01:32:02 +0300
commit0ec4df6cbe1771493d112c3f893b8ac1d89b5dbf (patch)
tree4aa82324a7c9d93b8da935b871fc58ac61122d19
parentf72cc96a70f9e9d23fc6e00d994263829b672be0 (diff)
downloadeventlet-0ec4df6cbe1771493d112c3f893b8ac1d89b5dbf.tar.gz
convenience: skip SO_REUSEPORT for bind on random port (0)listen-reuse
https://github.com/eventlet/eventlet/issues/411
-rw-r--r--eventlet/convenience.py21
-rw-r--r--tests/convenience_test.py54
2 files changed, 56 insertions, 19 deletions
diff --git a/eventlet/convenience.py b/eventlet/convenience.py
index 88343a9..560de23 100644
--- a/eventlet/convenience.py
+++ b/eventlet/convenience.py
@@ -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)
diff --git a/tests/convenience_test.py b/tests/convenience_test.py
index 37b6cd4..6f996fe 100644
--- a/tests/convenience_test.py
+++ b/tests/convenience_test.py
@@ -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):
- 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
+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))
+ 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)