]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Support worker-dedicated listening queues (SO_REUSEPORT) (#369)
authorEduard Bagdasaryan <eduard.bagdasaryan@measurement-factory.com>
Tue, 18 Feb 2020 20:45:00 +0000 (20:45 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Wed, 19 Feb 2020 21:57:25 +0000 (21:57 +0000)
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
src/anyp/PortCfg.h
src/cache_cf.cc
src/cf.data.pre
src/client_side.cc
src/comm.cc
src/comm/Connection.h
src/ipc/StartListening.cc

index a60d6d92dd6509ef4f50027b69178be3d06edbb1..5e08b3fa4e957772facfe9112195be9dfa15982e 100644 (file)
@@ -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;
index 8532ede750b51f9c6da0230b16e962796c4c6294..e3c5c379d95df8d9fa99e16dd6513cc8ae685b48 100644 (file)
@@ -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;
index 0a46567b98fdcea5b66f4a1ee85d027ac9be47d3..d69c6de6a9d29f61fb6ffe08892f6792e09b6c36 100644 (file)
 #if HAVE_GRP_H
 #include <grp.h>
 #endif
+#if HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
 #if HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #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();
index ab485d604048c8a88ce1df88fd2eb4cb6f73a96b..88b9dd4de5ea2bf09d684d1292d0522b84c82958 100644 (file)
@@ -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
index 80ad9fa534ed5653291d0ee9f7e4278069e5f691..445417a960df3658700de85a251ec2ef31a2b009 100644 (file)
@@ -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<CommAcceptCbPtrFun> AcceptCall;
         if (s->transport.protocol == AnyP::PROTO_HTTP) {
index 5242795d00f63e6ace73e39a87e2e207778c4175..abbcbacf4a5e96dfed3026e5226b93c8275b8aac 100644 (file)
@@ -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<char*>(&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;
index b02f23ef109d8356ddd1876f791ef9a43ae72f85..4f97f32688752694f7bb018c59042fbc690f5639 100644 (file)
@@ -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.
index f6bdadfd3463b8bc17c8f11c6c952b6cc38977fa..c3cdd1108864f8aab34cd89326523b2e8d50410e 100644 (file)
@@ -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;