TcpAcceptor.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 /* DEBUG: section 05 Listener Socket Handler */
10 
11 #include "squid.h"
12 #include "acl/FilledChecklist.h"
13 #include "anyp/PortCfg.h"
14 #include "base/CodeContext.h"
15 #include "base/TextException.h"
16 #include "client_db.h"
17 #include "comm/AcceptLimiter.h"
18 #include "comm/comm_internal.h"
19 #include "comm/Connection.h"
20 #include "comm/Loops.h"
21 #include "comm/TcpAcceptor.h"
22 #include "CommCalls.h"
23 #include "compat/socket.h"
24 #include "eui/Config.h"
25 #include "fd.h"
26 #include "fde.h"
27 #include "globals.h"
28 #include "ip/Intercept.h"
29 #include "ip/QosConfig.h"
30 #include "log/access_log.h"
31 #include "MasterXaction.h"
32 #include "sbuf/Stream.h"
33 #include "SquidConfig.h"
34 #include "StatCounters.h"
35 
36 #include <cerrno>
37 #ifdef HAVE_NETINET_TCP_H
38 // required for accept_filter to build.
39 #include <netinet/tcp.h>
40 #endif
41 
43 
45  AsyncJob("Comm::TcpAcceptor"),
46  errcode(0),
47  theCallSub(aSub),
48  conn(newConn),
49  listenPort_()
50 {}
51 
53  AsyncJob("Comm::TcpAcceptor"),
54  errcode(0),
55  theCallSub(aSub),
56  conn(p->listenConn),
57  listenPort_(p)
58 {}
59 
60 void
62 {
63  debugs(5, 5, status() << " AsyncCall Subscription: " << aSub);
64  unsubscribe("subscription change");
65  theCallSub = aSub;
66 }
67 
68 void
69 Comm::TcpAcceptor::unsubscribe(const char *reason)
70 {
71  debugs(5, 5, status() << " AsyncCall Subscription " << theCallSub << " removed: " << reason);
72  theCallSub = nullptr;
73 }
74 
75 void
77 {
78  debugs(5, 5, status() << " AsyncCall Subscription: " << theCallSub);
79 
80  Must(IsConnOpen(conn));
81 
82  setListen();
83 
84  conn->noteStart();
85 
86  // if no error so far start accepting connections.
87  if (errcode == 0)
88  SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
89 }
90 
91 bool
93 {
94  // stop when FD is closed
95  if (!IsConnOpen(conn)) {
96  return AsyncJob::doneAll();
97  }
98 
99  // stop when handlers are gone
100  if (theCallSub == nullptr) {
101  return AsyncJob::doneAll();
102  }
103 
104  // open FD with handlers...keep accepting.
105  return false;
106 }
107 
108 void
110 {
111  debugs(5,5, MYNAME);
112  unsubscribe("swanSong");
113  if (IsConnOpen(conn)) {
114  if (closer_ != nullptr)
115  comm_remove_close_handler(conn->fd, closer_);
116  conn->close();
117  }
118 
119  conn = nullptr;
122 }
123 
124 const char *
126 {
127  if (conn == nullptr)
128  return "[nil connection]";
129 
130  char ipbuf[MAX_IPSTRLEN];
131  conn->local.toHostStr(ipbuf, MAX_IPSTRLEN); // XXX: report port using toUrl()
132 
133  static MemBuf buf;
134  buf.reset();
135  buf.appendf(" FD %d, %s",conn->fd, ipbuf);
136 
137  const char *jobStatus = AsyncJob::status();
138  buf.append(jobStatus, strlen(jobStatus));
139 
140  return buf.content();
141 }
142 
150 void
152 {
153  errcode = errno = 0;
154  if (xlisten(conn->fd, Squid_MaxFD >> 2) < 0) {
155  errcode = errno;
156  debugs(50, DBG_CRITICAL, "ERROR: listen(..., " << (Squid_MaxFD >> 2) << ") system call failed: " << xstrerr(errcode));
157  return;
158  }
159 
160  if (Config.accept_filter && strcmp(Config.accept_filter, "none") != 0) {
161 #ifdef SO_ACCEPTFILTER
162  struct accept_filter_arg afa;
163  bzero(&afa, sizeof(afa));
164  debugs(5, DBG_IMPORTANT, "Installing accept filter '" << Config.accept_filter << "' on " << conn);
165  xstrncpy(afa.af_name, Config.accept_filter, sizeof(afa.af_name));
166  if (xsetsockopt(conn->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0) {
167  int xerrno = errno;
168  debugs(5, DBG_CRITICAL, "WARNING: SO_ACCEPTFILTER '" << Config.accept_filter << "': '" << xstrerr(xerrno));
169  }
170 #elif defined(TCP_DEFER_ACCEPT)
171  int seconds = 30;
172  if (strncmp(Config.accept_filter, "data=", 5) == 0)
173  seconds = atoi(Config.accept_filter + 5);
174  if (xsetsockopt(conn->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &seconds, sizeof(seconds)) < 0) {
175  int xerrno = errno;
176  debugs(5, DBG_CRITICAL, "WARNING: TCP_DEFER_ACCEPT '" << Config.accept_filter << "': '" << xstrerr(xerrno));
177  }
178 #else
179  debugs(5, DBG_CRITICAL, "WARNING: accept_filter not supported on your OS");
180 #endif
181  }
182 
184  closer_ = JobCallback(5, 4, Dialer, this, Comm::TcpAcceptor::handleClosure);
185  comm_add_close_handler(conn->fd, closer_);
186 }
187 
190 void
192 {
193  closer_ = nullptr;
194  if (conn) {
195  conn->noteClosure();
196  conn = nullptr;
197  }
198  Must(done());
199 }
200 
210 void
211 Comm::TcpAcceptor::doAccept(int fd, void *data)
212 {
213  try {
214  debugs(5, 2, "New connection on FD " << fd);
215 
216  Must(isOpen(fd));
217  TcpAcceptor *afd = static_cast<TcpAcceptor*>(data);
218 
219  if (!okToAccept()) {
221  } else {
222  afd->acceptNext();
223  }
224 
225  } catch (const std::exception &e) {
226  fatalf("FATAL: error while accepting new client connection: %s\n", e.what());
227  } catch (...) {
228  fatal("FATAL: error while accepting new client connection: [unknown]\n");
229  }
230 }
231 
232 bool
234 {
235  static time_t last_warn = 0;
236 
237  if (fdNFree() >= RESERVED_FD)
238  return true;
239 
240  if (last_warn + 15 < squid_curtime) {
241  debugs(5, DBG_CRITICAL, "WARNING: Your cache is running out of filedescriptors");
242  last_warn = squid_curtime;
243  }
244 
245  return false;
246 }
247 
248 void
250 {
252  CallBack(al, [&] {
253  al->tcpClient = tcpClient;
254  al->url = "error:accept-client-connection";
256  ACLFilledChecklist ch(nullptr, nullptr);
257  ch.src_addr = tcpClient->remote;
258  ch.my_addr = tcpClient->local;
259  ch.al = al;
260  accessLogLog(al, &ch);
261  });
262 }
263 
264 void
266 {
267  /*
268  * We don't worry about running low on FDs here. Instead,
269  * doAccept() will use AcceptLimiter if we reach the limit
270  * there.
271  */
272 
273  /* Accept a new connection */
274  ConnectionPointer newConnDetails = new Connection();
275  try {
276  if (acceptInto(newConnDetails)) {
277  Assure(newConnDetails->isOpen());
278  CallBack(newConnDetails, [&] {
279  debugs(5, 5, "Listener: " << conn <<
280  " accepted new connection " << newConnDetails <<
281  " handler Subscription: " << theCallSub);
282  notify(Comm::OK, newConnDetails);
283  });
284  } else {
285  debugs(5, 5, "try later: " << conn << " handler Subscription: " << theCallSub);
286  newConnDetails->close(); // paranoid manual closure (and may already be closed)
287  }
288 
289  SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
290  return;
291  } catch (...) {
292  const auto debugLevel = intendedForUserConnections() ? DBG_CRITICAL : 3;
293  debugs(5, debugLevel, "ERROR: Stopped accepting connections:" <<
294  Debug::Extra << "error: " << CurrentException);
295  }
296 
297  if (intendedForUserConnections())
298  logAcceptError(newConnDetails);
299 
300  // do not expose subscribers to a "usable" descriptor of a failed connection
301  newConnDetails->close(); // may already be closed
302 
303  CallBack(newConnDetails, [&] {
304  notify(Comm::COMM_ERROR, newConnDetails);
305  });
306 
307  // XXX: Not under AsyncJob call protections but, if placed there, may cause
308  // problems like making the corresponding HttpSockets entry (if any) stale.
309  mustStop("unrecoverable accept failure");
310 }
311 
312 void
314 {
315  Must(IsConnOpen(conn));
316  debugs(5, 2, "connection on " << conn);
317  acceptOne();
318 }
319 
320 void
321 Comm::TcpAcceptor::notify(const Comm::Flag flag, const Comm::ConnectionPointer &newConnDetails) const
322 {
323  // listener socket handlers just abandon the port with Comm::ERR_CLOSING
324  // it should only happen when this object is deleted...
325  if (flag == Comm::ERR_CLOSING) {
326  return;
327  }
328 
329  if (theCallSub != nullptr) {
330  AsyncCall::Pointer call = theCallSub->callback();
331  CommAcceptCbParams &params = GetCommParams<CommAcceptCbParams>(call);
332  params.port = listenPort_;
333  params.fd = conn->fd;
334  params.conn = newConnDetails;
335  params.flag = flag;
336  params.xerrno = errcode;
337  ScheduleCallHere(call);
338  }
339 }
340 
344 bool
346 {
347  ++statCounter.syscalls.sock.accepts;
348 
349  errcode = 0; // reset local errno copy.
350  struct sockaddr_storage remoteAddress = {};
351  socklen_t remoteAddressSize = sizeof(remoteAddress);
352  const auto rawSock = xaccept(conn->fd, reinterpret_cast<struct sockaddr *>(&remoteAddress), &remoteAddressSize);
353  if (rawSock < 0) {
354  errcode = errno; // store last accept errno locally.
355  if (ignoreErrno(errcode) || errcode == ECONNABORTED) {
356  debugs(50, 5, status() << ": " << xstrerr(errcode));
357  return false;
358  } else {
359  throw TextException(ToSBuf("Failed to accept an incoming connection: ", xstrerr(errcode)), Here());
360  }
361  }
362 
364 
365  // Sync with Comm ASAP so that abandoned details can properly close().
366  // XXX : these are not all HTTP requests. use a note about type and ip:port details->
367  // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
368  const auto sock = rawSock;
369  fd_open(sock, FD_SOCKET, "HTTP Request");
370  details->fd = sock;
371  details->enterOrphanage();
372 
373  Assure(remoteAddressSize <= socklen_t(sizeof(remoteAddress)));
374  details->remote = remoteAddress;
375 
376  // lookup the local-end details of this new connection
377  struct sockaddr_storage localAddress = {};
378  socklen_t localAddressSize = sizeof(localAddress);
379  if (xgetsockname(sock, reinterpret_cast<struct sockaddr *>(&localAddress), &localAddressSize) != 0) {
380  int xerrno = errno;
381  debugs(50, DBG_IMPORTANT, "ERROR: Closing accepted TCP connection after failing to obtain its local IP address" <<
382  Debug::Extra << "accepted connection: " << details <<
383  Debug::Extra << "getsockname(2) error: " << xstrerr(xerrno));
384  return false;
385  }
386  Assure(localAddressSize <= socklen_t(sizeof(localAddress)));
387  details->local = localAddress;
388 
389  if (conn->flags & COMM_TRANSPARENT) { // the real client/dest IP address must be already available via getsockname()
390  details->flags |= COMM_TRANSPARENT;
391  if (!Ip::Interceptor.TransparentActive()) {
392  debugs(50, DBG_IMPORTANT, "ERROR: Cannot use transparent " << details << " because TPROXY mode became inactive");
393  // TODO: consider throwing instead
394  return false;
395  }
396  } else if (conn->flags & COMM_INTERCEPTION) { // request the real client/dest IP address from NAT
397  details->flags |= COMM_INTERCEPTION;
398  if (!Ip::Interceptor.LookupNat(*details)) {
399  debugs(50, DBG_IMPORTANT, "ERROR: NAT lookup failed to locate original IPs on " << details);
400  return false;
401  }
402  }
403 
404 #if USE_SQUID_EUI
405  if (Eui::TheConfig.euiLookup) {
406  if (details->remote.isIPv4()) {
407  details->remoteEui48.lookup(details->remote);
408  } else if (details->remote.isIPv6()) {
409  details->remoteEui64.lookup(details->remote);
410  }
411  }
412 #endif
413 
415 
418  debugs(50, DBG_IMPORTANT, "WARNING: " << details->remote << " attempting more than " << Config.client_ip_max_connections << " connections.");
419  return false;
420  }
421  }
422 
423  fde *F = &fd_table[sock];
424  details->remote.toStr(F->ipaddr,MAX_IPSTRLEN);
425  F->remote_port = details->remote.port();
426  F->local_addr = details->local;
427  F->sock_family = details->local.isIPv6()?AF_INET6:AF_INET;
428 
429  // set socket flags
430  commSetCloseOnExec(sock);
431  commSetNonBlocking(sock);
432  if (listenPort_)
433  Comm::ApplyTcpKeepAlive(sock, listenPort_->tcp_keepalive);
434 
435  /* IFF the socket is (tproxy) transparent, pass the flag down to allow spoofing */
436  F->flags.transparent = fd_table[conn->fd].flags.transparent; // XXX: can we remove this line yet?
437 
438  return true;
439 }
440 
void fatal(const char *message)
Definition: fatal.cc:28
const char * xstrerr(int error)
Definition: xstrerror.cc:83
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:942
#define Here()
source code location of the caller
Definition: Here.h:15
int incoming_sockets_accepted
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition: Packable.h:61
#define DBG_CRITICAL
Definition: Stream.h:37
struct StatCounters::@115::@120 sock
char * accept_filter
Definition: SquidConfig.h:518
Ip::Address src_addr
void enterOrphanage()
close the still-open connection when its last reference is gone
Definition: Connection.h:90
int xaccept(int socketFd, struct sockaddr *sa, socklen_t *saLength)
POSIX accept(2) equivalent.
Definition: socket.h:62
void commSetCloseOnExec(int)
Definition: minimal.cc:27
virtual void swanSong()
Definition: AsyncJob.h:61
void swanSong() override
Definition: TcpAcceptor.cc:109
Eui::Eui64 remoteEui64
Definition: Connection.h:181
AnyP::PortCfgPointer port
the configuration listening port this call relates to (may be nil)
Definition: CommCalls.h:100
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
void handleClosure(const CommCloseCbParams &io)
Definition: TcpAcceptor.cc:191
@ FD_SOCKET
Definition: enums.h:16
static uint32 F(uint32 X, uint32 Y, uint32 Z)
Definition: md4.c:46
void setVirginUrlForMissingRequest(const SBuf &vu)
Remember Client URI (or equivalent) when there is no HttpRequest.
int clientdbEstablished(const Ip::Address &addr, int delta)
Definition: client_db.cc:182
int commSetNonBlocking(int fd)
Definition: comm.cc:1044
Abstraction layer for TCP, UDP, TLS, UDS and filedescriptor sockets.
Definition: AcceptLimiter.h:16
int fd
FD which the call was about. Set by the async call creator.
Definition: CommCalls.h:85
char * xstrncpy(char *dst, const char *src, size_t n)
Definition: xstring.cc:37
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:27
@ OK
Definition: Flag.h:16
void accessLogLog(const AccessLogEntryPointer &, ACLChecklist *)
Definition: access_log.cc:136
bool isIPv4() const
Definition: Address.cc:178
void CallBack(const CodeContext::Pointer &callbackContext, Fun &&callback)
Definition: CodeContext.h:126
void fd_open(const int fd, unsigned int, const char *description)
Definition: minimal.cc:15
@ ERR_CLOSING
Definition: Flag.h:24
int xerrno
The last errno to occur. non-zero if flag is Comm::COMM_ERROR.
Definition: CommCalls.h:83
Definition: fde.h:51
int socklen_t
Definition: types.h:137
char * toStr(char *buf, const unsigned int blen, int force=AF_UNSPEC) const
Definition: Address.cc:812
bool lookup(const Ip::Address &c)
Definition: Eui48.cc:162
bool acceptInto(Comm::ConnectionPointer &)
Definition: TcpAcceptor.cc:345
#define MAX_IPSTRLEN
Length of buffer that needs to be allocated to old a null-terminated IP-string.
Definition: forward.h:25
#define COMM_INTERCEPTION
Definition: Connection.h:51
#define COMM_TRANSPARENT
Definition: Connection.h:50
int xgetsockname(int socketFd, struct sockaddr *sa, socklen_t *saLength)
POSIX getsockname(2) equivalent.
Definition: socket.h:80
TcpAcceptor(const TcpAcceptor &)
void append(const char *c, int sz) override
Definition: MemBuf.cc:209
virtual bool doneAll() const
whether positive goal has been reached
Definition: AsyncJob.cc:112
bool doneAll() const override
whether positive goal has been reached
Definition: TcpAcceptor.cc:92
unsigned short port() const
Definition: Address.cc:798
bool isIPv6() const
Definition: Address.cc:184
Definition: MemBuf.h:23
Ip::Address local
Definition: Connection.h:149
int xsetsockopt(int socketFd, int level, int option, const void *value, socklen_t valueLength)
POSIX setsockopt(2) equivalent.
Definition: socket.h:122
Comm::ConnectionPointer tcpClient
TCP/IP level details about the client connection.
Comm::ConnectionPointer conn
Definition: CommCalls.h:80
Ip::Address remote
Definition: Connection.h:152
SSL Connection
Definition: Session.h:49
void fatalf(const char *fmt,...)
Definition: fatal.cc:68
nfmark_t nfConnmark
Definition: Connection.h:174
void unsubscribe(const char *reason)
Definition: TcpAcceptor.cc:69
std::ostream & CurrentException(std::ostream &os)
prints active (i.e., thrown but not yet handled) exception
void ApplyTcpKeepAlive(int fd, const TcpKeepAlive &)
apply configured TCP keep-alive settings to the given FD socket
Definition: Tcp.cc:49
Comm::Flag flag
comm layer result status.
Definition: CommCalls.h:82
@ COMM_ERROR
Definition: Flag.h:17
EuiConfig TheConfig
Definition: Config.cc:12
#define COMM_SELECT_READ
Definition: defines.h:24
Eui::Eui48 remoteEui48
Definition: Connection.h:180
#define Assure(condition)
Definition: Assure.h:35
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:70
void notify(const Comm::Flag flag, const Comm::ConnectionPointer &details) const
Call the subscribed callback handler with details about a new connection.
Definition: TcpAcceptor.cc:321
time_t squid_curtime
Definition: stub_libtime.cc:20
static std::ostream & Extra(std::ostream &)
Definition: debug.cc:1316
int Squid_MaxFD
static AcceptLimiter & Instance()
CBDATA_NAMESPACED_CLASS_INIT(Comm, TcpAcceptor)
virtual const char * status() const
internal cleanup; do not call directly
Definition: AsyncJob.cc:182
void defer(const TcpAcceptor::Pointer &afd)
Flag
Definition: Flag.h:15
static void doAccept(int fd, void *data)
Method callback for whenever an FD is ready to accept a client connection.
Definition: TcpAcceptor.cc:211
int ignoreErrno(int ierrno)
Definition: comm.cc:1407
#define fd_table
Definition: fde.h:189
Intercept Interceptor
Definition: Intercept.h:135
nfmark_t getNfConnmark(const Comm::ConnectionPointer &conn, const ConnectionDirection connDir)
Definition: QosConfig.cc:151
an std::runtime_error with thrower location info
Definition: TextException.h:20
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
Definition: ModDevPoll.cc:220
char * content()
start of the added data
Definition: MemBuf.h:41
int fdNFree(void)
Definition: fd.cc:262
SBuf ToSBuf(Args &&... args)
slowly stream-prints all arguments into a freshly allocated SBuf
Definition: Stream.h:63
int RESERVED_FD
#define Must(condition)
Definition: TextException.h:75
const char * status() const override
internal cleanup; do not call directly
Definition: TcpAcceptor.cc:125
int client_ip_max_connections
Definition: SquidConfig.h:528
#define DBG_IMPORTANT
Definition: Stream.h:38
#define MYNAME
Definition: Stream.h:219
void reset()
Definition: MemBuf.cc:129
struct StatCounters::@115 syscalls
@ dirAccepted
accepted (from a client by Squid)
Definition: QosConfig.h:71
void removeDead(const TcpAcceptor::Pointer &afd)
bool isOpen() const
Definition: Connection.h:101
void start() override
called by AsyncStart; do not call directly
Definition: TcpAcceptor.cc:76
bool isOpen(const int fd)
Definition: comm.cc:91
void logAcceptError(const ConnectionPointer &tcpClient) const
Definition: TcpAcceptor.cc:249
static bool okToAccept()
Definition: TcpAcceptor.cc:233
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
void subscribe(const Subscription::Pointer &aSub)
Definition: TcpAcceptor.cc:61
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:971
class SquidConfig Config
Definition: SquidConfig.cc:12
int xlisten(int socketFd, int backlog)
POSIX listen(2) equivalent.
Definition: socket.h:86
bool lookup(const Ip::Address &c)
Definition: Eui64.cc:47
StatCounters statCounter
Definition: StatCounters.cc:12

 

Introduction

Documentation

Support

Miscellaneous