convenience: skip SO_REUSEPORT for bind on random port (0)
https://github.com/eventlet/eventlet/issues/411
This commit is contained in:
@@ -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)
|
||||||
|
@@ -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()
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
lsock1.close()
|
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)
|
||||||
|
Reference in New Issue
Block a user