From 7d17a6a6e75d76e71249ac1ac4afc788c21db6f3 Mon Sep 17 00:00:00 2001 From: Eduard Bagdasaryan Date: Tue, 18 Feb 2020 20:45:00 +0000 Subject: [PATCH] Support worker-dedicated listening queues (SO_REUSEPORT) (#369) This performance optimization has a few cons, including security concerns, but it improves CPU core utilization/balance in many SMP environments and is supported by many high-performance servers. Enabled by the new `*_port worker-queues` configuration option. Worker-dedicated listening queues reduce client-worker affinity for requests submitted over different TCP connections. The effect of that reduction on Squid performance depends on the environment, but many busy SMP proxies handling modern traffic should benefit. TODO: Linux tests show load balancing effects of SO_REUSEPORT, but untested FreeBSD probably needs SO_REUSEPORT_LB to get those effects. Recommended reading: * https://blog.cloudflare.com/the-sad-state-of-linux-socket-balancing/ * https://lwn.net/Articles/542629/ * https://stackoverflow.com/a/14388707 --- src/anyp/PortCfg.cc | 2 ++ src/anyp/PortCfg.h | 1 + src/cache_cf.cc | 11 +++++++++++ src/cf.data.pre | 10 ++++++++++ src/client_side.cc | 3 ++- src/comm.cc | 15 +++++++++++++++ src/comm/Connection.h | 1 + src/ipc/StartListening.cc | 5 ++++- 8 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/anyp/PortCfg.cc b/src/anyp/PortCfg.cc index a60d6d92dd..5e08b3fa4e 100644 --- a/src/anyp/PortCfg.cc +++ b/src/anyp/PortCfg.cc @@ -39,6 +39,7 @@ AnyP::PortCfg::PortCfg() : ftp_track_dirs(false), vport(0), disable_pmtu_discovery(0), + workerQueues(false), listenConn() { memset(&tcp_keepalive, 0, sizeof(tcp_keepalive)); @@ -71,6 +72,7 @@ AnyP::PortCfg::clone() const b->vhost = vhost; b->vport = vport; b->connection_auth_disabled = connection_auth_disabled; + b->workerQueues = workerQueues; b->ftp_track_dirs = ftp_track_dirs; b->disable_pmtu_discovery = disable_pmtu_discovery; b->tcp_keepalive = tcp_keepalive; diff --git a/src/anyp/PortCfg.h b/src/anyp/PortCfg.h index 8532ede750..e3c5c379d9 100644 --- a/src/anyp/PortCfg.h +++ b/src/anyp/PortCfg.h @@ -51,6 +51,7 @@ public: int vport; ///< virtual port support. -1 if dynamic, >0 static int disable_pmtu_discovery; + bool workerQueues; ///< whether listening queues should be worker-specific struct { unsigned int idle; diff --git a/src/cache_cf.cc b/src/cache_cf.cc index 0a46567b98..d69c6de6a9 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -100,6 +100,9 @@ #if HAVE_GRP_H #include #endif +#if HAVE_SYS_SOCKET_H +#include +#endif #if HAVE_SYS_STAT_H #include #endif @@ -3740,6 +3743,14 @@ parse_port_option(AnyP::PortCfgPointer &s, char *token) s->secure.parse(token+4); } else if (strcmp(token, "ftp-track-dirs") == 0) { s->ftp_track_dirs = true; + } else if (strcmp(token, "worker-queues") == 0) { +#if !defined(SO_REUSEADDR) +#error missing system #include that #defines SO_* constants +#endif +#if !defined(SO_REUSEPORT) + throw TexcHere(ToSBuf(cfg_directive, ' ', token, " option requires building Squid where SO_REUSEPORT is supported by the TCP stack")); +#endif + s->workerQueues = true; } else { debugs(3, DBG_CRITICAL, "FATAL: Unknown " << cfg_directive << " option '" << token << "'."); self_destruct(); diff --git a/src/cf.data.pre b/src/cf.data.pre index ab485d6040..88b9dd4de5 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -2428,6 +2428,16 @@ DOC_START The proxy_protocol_access is required to whitelist downstream proxies which can be trusted. + worker-queues + Ask TCP stack to maintain a dedicated listening queue + for each worker accepting requests at this port. + Requires TCP stack that supports the SO_REUSEPORT socket + option. + + SECURITY WARNING: Enabling worker-specific queues + allows any process running as Squid's effective user to + easily accept requests destined to this port. + If you run Squid on a dual-homed machine with an internal and an external interface we recommend you to specify the internal address:port in http_port. This way Squid will only be diff --git a/src/client_side.cc b/src/client_side.cc index 80ad9fa534..445417a960 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -3406,7 +3406,8 @@ clientHttpConnectionsOpen(void) s->listenConn->local = s->s; s->listenConn->flags = COMM_NONBLOCKING | (s->flags.tproxyIntercept ? COMM_TRANSPARENT : 0) | - (s->flags.natIntercept ? COMM_INTERCEPTION : 0); + (s->flags.natIntercept ? COMM_INTERCEPTION : 0) | + (s->workerQueues ? COMM_REUSEPORT : 0); typedef CommCbFunPtrCallT AcceptCall; if (s->transport.protocol == AnyP::PROTO_HTTP) { diff --git a/src/comm.cc b/src/comm.cc index 5242795d00..abbcbacf4a 100644 --- a/src/comm.cc +++ b/src/comm.cc @@ -32,6 +32,7 @@ #include "pconn.h" #include "profiler/Profiler.h" #include "sbuf/SBuf.h" +#include "sbuf/Stream.h" #include "SquidConfig.h" #include "StatCounters.h" #include "StoreIOBuffer.h" @@ -471,6 +472,20 @@ comm_apply_flags(int new_socket, if ( addr.isNoAddr() ) debugs(5,0,"CRITICAL: Squid is attempting to bind() port " << addr << "!!"); +#if defined(SO_REUSEPORT) + if (flags & COMM_REUSEPORT) { + int on = 1; + if (setsockopt(new_socket, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&on), sizeof(on)) < 0) { + const auto savedErrno = errno; + const auto errorMessage = ToSBuf("cannot enable SO_REUSEPORT socket option when binding to ", + addr, ": ", xstrerr(savedErrno)); + if (reconfiguring) + debugs(5, DBG_IMPORTANT, "ERROR: " << errorMessage); + else + throw TexcHere(errorMessage); + } + } +#endif if (commBind(new_socket, *AI) != Comm::OK) { comm_close(new_socket); return -1; diff --git a/src/comm/Connection.h b/src/comm/Connection.h index b02f23ef10..4f97f32688 100644 --- a/src/comm/Connection.h +++ b/src/comm/Connection.h @@ -49,6 +49,7 @@ namespace Comm #define COMM_DOBIND 0x08 // requires a bind() #define COMM_TRANSPARENT 0x10 // arrived via TPROXY #define COMM_INTERCEPTION 0x20 // arrived via NAT +#define COMM_REUSEPORT 0x40 //< needs SO_REUSEPORT /** * Store data about the physical and logical attributes of a connection. diff --git a/src/ipc/StartListening.cc b/src/ipc/StartListening.cc index f6bdadfd34..c3cdd11088 100644 --- a/src/ipc/StartListening.cc +++ b/src/ipc/StartListening.cc @@ -39,7 +39,10 @@ Ipc::StartListening(int sock_type, int proto, const Comm::ConnectionPointer &lis Must(cbd); cbd->conn = listenConn; - if (UsingSmp()) { // if SMP is on, share + const auto giveEachWorkerItsOwnQueue = listenConn->flags & COMM_REUSEPORT; + if (!giveEachWorkerItsOwnQueue && UsingSmp()) { + // Ask Coordinator for a listening socket. + // All askers share one listening queue. OpenListenerParams p; p.sock_type = sock_type; p.proto = proto; -- 2.47.2