Skip to content

Commit

Permalink
convenience: skip SO_REUSEPORT for bind on random port (0)
Browse files Browse the repository at this point in the history
  • Loading branch information
temoto committed May 11, 2017
1 parent f72cc96 commit 0ec4df6
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 19 deletions.
21 changes: 17 additions & 4 deletions eventlet/convenience.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand All @@ -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)
Expand Down
54 changes: 39 additions & 15 deletions tests/convenience_test.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -90,7 +91,7 @@ def stopit(conn, addr):
gt.wait()

def test_concurrency(self):
evt = event.Event()
evt = eventlet.Event()

def waiter(sock, addr):
sock.sendall(b'hi')
Expand Down Expand Up @@ -128,18 +129,41 @@ def handle(sock, addr):
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)

0 comments on commit 0ec4df6

Please sign in to comment.