Skip to content

Commit

Permalink
server: correctly adjudicate collision bind() of specific port
Browse files Browse the repository at this point in the history
On Linux (at least) SO_REUSEADDR, which allows a new listener to
bind while an existing sock is in FIN-WAIT.  Apparently this allows
any number of sockets to bind(), but only when listen() to succeed.

Further, on Linux there is a known documented race condition which
can result in all listen() failing.  It isn't clear how to handle
this case without a potentially infinite loop, so ignore it.
If this happens, then eg. no PVA server will get port 5075.

So when probing for another listener, it is necessary to enter the
listening state.  When this fails, the socket is no longer usable
for another bind(), so it is necessary to allocate another for the
next attempt.
  • Loading branch information
mdavidsaver committed Aug 15, 2024
1 parent 5fa743d commit c4f74b3
Showing 1 changed file with 29 additions and 19 deletions.
48 changes: 29 additions & 19 deletions src/serverconn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,18 +408,40 @@ ServIface::ServIface(const SockAddr &addr, server::Server::Pvt *server, bool fal
server->acceptor_loop.assertInLoop();
auto orig_port = bind_addr.port();

sock = evsocket(bind_addr.family(), SOCK_STREAM, 0);

if(evutil_make_listen_socket_reuseable(sock.sock))
log_warn_printf(connsetup, "Unable to make socket reusable%s", "\n");

// try to bind to requested port, then fallback to a random port
while(true) {

sock = evsocket(bind_addr.family(), SOCK_STREAM, 0);

if(evutil_make_listen_socket_reuseable(sock.sock))
log_warn_printf(connsetup, "Unable to make socket reusable%s", "\n");

try {
sock.bind(bind_addr);
// symantics of SO_REUSEADDR on *nix allow multiple bind() to success.
// however, only one listen() will succeed

const int backlog = 4;
auto list(evconnlistener_new(server->acceptor_loop.base, onConnS, this, LEV_OPT_DISABLED|LEV_OPT_CLOSE_ON_EXEC, backlog, sock.sock));
if(!list) {
int err = evutil_socket_geterror(sock);
throw std::system_error(err, std::system_category());
}
listener = evlisten(__FILE__, __LINE__, list);

// added in libevent 2.1.1
#ifndef LEV_OPT_DISABLED
# define LEV_OPT_DISABLED 0
#endif

if(!LEV_OPT_DISABLED)
evconnlistener_disable(listener.get());

} catch(std::system_error& e) {
if(fallback && e.code().value()==SOCK_EADDRINUSE) {
log_debug_printf(connsetup, "Address %s in use\n", bind_addr.tostring().c_str());
if(fallback && (e.code().value()==SOCK_EADDRINUSE || e.code().value()==SOCK_EACCES)) {
log_debug_printf(connsetup, "Address %s in use or not permitted: %s\n",
bind_addr.tostring().c_str(),
e.what());
bind_addr.setPort(0);
fallback = false;
continue;
Expand All @@ -438,18 +460,6 @@ ServIface::ServIface(const SockAddr &addr, server::Server::Pvt *server, bool fal
if(orig_port && bind_addr.port() != orig_port) {
log_warn_printf(connsetup, "Server unable to bind port %u, falling back to %s\n", orig_port, name.c_str());
}

// added in libevent 2.1.1
#ifndef LEV_OPT_DISABLED
# define LEV_OPT_DISABLED 0
#endif

const int backlog = 4;
listener = evlisten(__FILE__, __LINE__,
evconnlistener_new(server->acceptor_loop.base, onConnS, this, LEV_OPT_DISABLED|LEV_OPT_CLOSE_ON_EXEC, backlog, sock.sock));

if(!LEV_OPT_DISABLED)
evconnlistener_disable(listener.get());
}

void ServIface::onConnS(struct evconnlistener *listener, evutil_socket_t sock, struct sockaddr *peer, int socklen, void *raw)
Expand Down

0 comments on commit c4f74b3

Please sign in to comment.