TcpAcceptor.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 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 "eui/Config.h"
24 #include "fd.h"
25 #include "fde.h"
26 #include "globals.h"
27 #include "ip/Intercept.h"
28 #include "ip/QosConfig.h"
29 #include "log/access_log.h"
30 #include "MasterXaction.h"
31 #include "sbuf/Stream.h"
32 #include "SquidConfig.h"
33 #include "StatCounters.h"
34 
35 #include <cerrno>
36 #ifdef HAVE_NETINET_TCP_H
37 // required for accept_filter to build.
38 #include <netinet/tcp.h>
39 #endif
40 
42 
44  AsyncJob("Comm::TcpAcceptor"),
45  errcode(0),
46  theCallSub(aSub),
47  conn(newConn),
48  listenPort_()
49 {}
50 
52  AsyncJob("Comm::TcpAcceptor"),
53  errcode(0),
54  theCallSub(aSub),
55  conn(p->listenConn),
56  listenPort_(p)
57 {}
58 
59 void
61 {
62  debugs(5, 5, status() << " AsyncCall Subscription: " << aSub);
63  unsubscribe("subscription change");
64  theCallSub = aSub;
65 }
66 
67 void
68 Comm::TcpAcceptor::unsubscribe(const char *reason)
69 {
70  debugs(5, 5, status() << " AsyncCall Subscription " << theCallSub << " removed: " << reason);
71  theCallSub = nullptr;
72 }
73 
74 void
76 {
77  debugs(5, 5, status() << " AsyncCall Subscription: " << theCallSub);
78 
79  Must(IsConnOpen(conn));
80 
81  setListen();
82 
83  conn->noteStart();
84 
85  // if no error so far start accepting connections.
86  if (errcode == 0)
87  SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
88 }
89 
90 bool
92 {
93  // stop when FD is closed
94  if (!IsConnOpen(conn)) {
95  return AsyncJob::doneAll();
96  }
97 
98  // stop when handlers are gone
99  if (theCallSub == nullptr) {
100  return AsyncJob::doneAll();
101  }
102 
103  // open FD with handlers...keep accepting.
104  return false;
105 }
106 
107 void
109 {
110  debugs(5,5, MYNAME);
111  unsubscribe("swanSong");
112  if (IsConnOpen(conn)) {
113  if (closer_ != nullptr)
114  comm_remove_close_handler(conn->fd, closer_);
115  conn->close();
116  }
117 
118  conn = nullptr;
121 }
122 
123 const char *
125 {
126  if (conn == nullptr)
127  return "[nil connection]";
128 
129  char ipbuf[MAX_IPSTRLEN];
130  conn->local.toHostStr(ipbuf, MAX_IPSTRLEN); // XXX: report port using toUrl()
131 
132  static MemBuf buf;
133  buf.reset();
134  buf.appendf(" FD %d, %s",conn->fd, ipbuf);
135 
136  const char *jobStatus = AsyncJob::status();
137  buf.append(jobStatus, strlen(jobStatus));
138 
139  return buf.content();
140 }
141 
149 void
151 {
152  errcode = errno = 0;
153  if (listen(conn->fd, Squid_MaxFD >> 2) < 0) {
154  errcode = errno;
155  debugs(50, DBG_CRITICAL, "ERROR: listen(..., " << (Squid_MaxFD >> 2) << ") system call failed: " << xstrerr(errcode));
156  return;
157  }
158 
159  if (Config.accept_filter && strcmp(Config.accept_filter, "none") != 0) {
160 #ifdef SO_ACCEPTFILTER
161  struct accept_filter_arg afa;
162  bzero(&afa, sizeof(afa));
163  debugs(5, DBG_IMPORTANT, "Installing accept filter '" << Config.accept_filter << "' on " << conn);
164  xstrncpy(afa.af_name, Config.accept_filter, sizeof(afa.af_name));
165  if (setsockopt(conn->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0) {
166  int xerrno = errno;
167  debugs(5, DBG_CRITICAL, "WARNING: SO_ACCEPTFILTER '" << Config.accept_filter << "': '" << xstrerr(xerrno));
168  }
169 #elif defined(TCP_DEFER_ACCEPT)
170  int seconds = 30;
171  if (strncmp(Config.accept_filter, "data=", 5) == 0)
172  seconds = atoi(Config.accept_filter + 5);
173  if (setsockopt(conn->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &seconds, sizeof(seconds)) < 0) {
174  int xerrno = errno;
175  debugs(5, DBG_CRITICAL, "WARNING: TCP_DEFER_ACCEPT '" << Config.accept_filter << "': '" << xstrerr(xerrno));
176  }
177 #else
178  debugs(5, DBG_CRITICAL, "WARNING: accept_filter not supported on your OS");
179 #endif
180  }
181 
183  closer_ = JobCallback(5, 4, Dialer, this, Comm::TcpAcceptor::handleClosure);
184  comm_add_close_handler(conn->fd, closer_);
185 }
186 
189 void
191 {
192  closer_ = nullptr;
193  if (conn) {
194  conn->noteClosure();
195  conn = nullptr;
196  }
197  Must(done());
198 }
199 
209 void
210 Comm::TcpAcceptor::doAccept(int fd, void *data)
211 {
212  try {
213  debugs(5, 2, "New connection on FD " << fd);
214 
215  Must(isOpen(fd));
216  TcpAcceptor *afd = static_cast<TcpAcceptor*>(data);
217 
218  if (!okToAccept()) {
220  } else {
221  afd->acceptNext();
222  }
223 
224  } catch (const std::exception &e) {
225  fatalf("FATAL: error while accepting new client connection: %s\n", e.what());
226  } catch (...) {
227  fatal("FATAL: error while accepting new client connection: [unknown]\n");
228  }
229 }
230 
231 bool
233 {
234  static time_t last_warn = 0;
235 
236  if (fdNFree() >= RESERVED_FD)
237  return true;
238 
239  if (last_warn + 15 < squid_curtime) {
240  debugs(5, DBG_CRITICAL, "WARNING: Your cache is running out of filedescriptors");
241  last_warn = squid_curtime;
242  }
243 
244  return false;
245 }
246 
247 void
249 {
251  CallBack(al, [&] {
252  al->tcpClient = tcpClient;
253  al->url = "error:accept-client-connection";
255  ACLFilledChecklist ch(nullptr, nullptr);
256  ch.src_addr = tcpClient->remote;
257  ch.my_addr = tcpClient->local;
258  ch.al = al;
259  accessLogLog(al, &ch);
260  });
261 }
262 
263 void
265 {
266  /*
267  * We don't worry about running low on FDs here. Instead,
268  * doAccept() will use AcceptLimiter if we reach the limit
269  * there.
270  */
271 
272  /* Accept a new connection */
273  ConnectionPointer newConnDetails = new Connection();
274  try {
275  if (acceptInto(newConnDetails)) {
276  Assure(newConnDetails->isOpen());
277  CallBack(newConnDetails, [&] {
278  debugs(5, 5, "Listener: " << conn <<
279  " accepted new connection " << newConnDetails <<
280  " handler Subscription: " << theCallSub);
281  notify(Comm::OK, newConnDetails);
282  });
283  } else {
284  debugs(5, 5, "try later: " << conn << " handler Subscription: " << theCallSub);
285  newConnDetails->close(); // paranoid manual closure (and may already be closed)
286  }
287 
288  SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
289  return;
290  } catch (...) {
291  const auto debugLevel = intendedForUserConnections() ? DBG_CRITICAL : 3;
292  debugs(5, debugLevel, "ERROR: Stopped accepting connections:" <<
293  Debug::Extra << "error: " << CurrentException);
294  }
295 
296  if (intendedForUserConnections())
297  logAcceptError(newConnDetails);
298 
299  // do not expose subscribers to a "usable" descriptor of a failed connection
300  newConnDetails->close(); // may already be closed
301 
302  CallBack(newConnDetails, [&] {
303  notify(Comm::COMM_ERROR, newConnDetails);
304  });
305 
306  // XXX: Not under AsyncJob call protections but, if placed there, may cause
307  // problems like making the corresponding HttpSockets entry (if any) stale.
308  mustStop("unrecoverable accept failure");
309 }
310 
311 void
313 {
314  Must(IsConnOpen(conn));
315  debugs(5, 2, "connection on " << conn);
316  acceptOne();
317 }
318 
319 void
320 Comm::TcpAcceptor::notify(const Comm::Flag flag, const Comm::ConnectionPointer &newConnDetails) const
321 {
322  // listener socket handlers just abandon the port with Comm::ERR_CLOSING
323  // it should only happen when this object is deleted...
324  if (flag == Comm::ERR_CLOSING) {
325  return;
326  }
327 
328  if (theCallSub != nullptr) {
329  AsyncCall::Pointer call = theCallSub->callback();
330  CommAcceptCbParams &params = GetCommParams<CommAcceptCbParams>(call);
331  params.port = listenPort_;
332  params.fd = conn->fd;
333  params.conn = newConnDetails;
334  params.flag = flag;
335  params.xerrno = errcode;
336  ScheduleCallHere(call);
337  }
338 }
339 
343 bool
345 {
346  ++statCounter.syscalls.sock.accepts;
347 
348  errcode = 0; // reset local errno copy.
349  struct sockaddr_storage remoteAddress = {};
350  socklen_t remoteAddressSize = sizeof(remoteAddress);
351  const auto rawSock = accept(conn->fd, reinterpret_cast<struct sockaddr *>(&remoteAddress), &remoteAddressSize);
352  if (rawSock < 0) {
353  errcode = errno; // store last accept errno locally.
354  if (ignoreErrno(errcode) || errcode == ECONNABORTED) {
355  debugs(50, 5, status() << ": " << xstrerr(errcode));
356  return false;
357  } else {
358  throw TextException(ToSBuf("Failed to accept an incoming connection: ", xstrerr(errcode)), Here());
359  }
360  }
361 
363 
364  // Sync with Comm ASAP so that abandoned details can properly close().
365  // XXX : these are not all HTTP requests. use a note about type and ip:port details->
366  // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
367  const auto sock = rawSock;
368  fd_open(sock, FD_SOCKET, "HTTP Request");
369  details->fd = sock;
370  details->enterOrphanage();
371 
372  Assure(remoteAddressSize <= socklen_t(sizeof(remoteAddress)));
373  details->remote = remoteAddress;
374 
375  // lookup the local-end details of this new connection
376  struct sockaddr_storage localAddress = {};
377  socklen_t localAddressSize = sizeof(localAddress);
378  if (getsockname(sock, reinterpret_cast<struct sockaddr *>(&localAddress), &localAddressSize) != 0) {
379  int xerrno = errno;
380  debugs(50, DBG_IMPORTANT, "ERROR: Closing accepted TCP connection after failing to obtain its local IP address" <<
381  Debug::Extra << "accepted connection: " << details <<
382  Debug::Extra << "getsockname(2) error: " << xstrerr(xerrno));
383  return false;
384  }
385  Assure(localAddressSize <= socklen_t(sizeof(localAddress)));
386  details->local = localAddress;
387 
388  if (conn->flags & COMM_TRANSPARENT) { // the real client/dest IP address must be already available via getsockname()
389  details->flags |= COMM_TRANSPARENT;
390  if (!Ip::Interceptor.TransparentActive()) {
391  debugs(50, DBG_IMPORTANT, "ERROR: Cannot use transparent " << details << " because TPROXY mode became inactive");
392  // TODO: consider throwing instead
393  return false;
394  }
395  } else if (conn->flags & COMM_INTERCEPTION) { // request the real client/dest IP address from NAT
396  details->flags |= COMM_INTERCEPTION;
397  if (!Ip::Interceptor.LookupNat(*details)) {
398  debugs(50, DBG_IMPORTANT, "ERROR: NAT lookup failed to locate original IPs on " << details);
399  return false;
400  }
401  }
402 
403 #if USE_SQUID_EUI
404  if (Eui::TheConfig.euiLookup) {
405  if (details->remote.isIPv4()) {
406  details->remoteEui48.lookup(details->remote);
407  } else if (details->remote.isIPv6()) {
408  details->remoteEui64.lookup(details->remote);
409  }
410  }
411 #endif
412 
414 
417  debugs(50, DBG_IMPORTANT, "WARNING: " << details->remote << " attempting more than " << Config.client_ip_max_connections << " connections.");
418  return false;
419  }
420  }
421 
422  fde *F = &fd_table[sock];
423  details->remote.toStr(F->ipaddr,MAX_IPSTRLEN);
424  F->remote_port = details->remote.port();
425  F->local_addr = details->local;
426  F->sock_family = details->local.isIPv6()?AF_INET6:AF_INET;
427 
428  // set socket flags
429  commSetCloseOnExec(sock);
430  commSetNonBlocking(sock);
431  if (listenPort_)
432  Comm::ApplyTcpKeepAlive(sock, listenPort_->tcp_keepalive);
433 
434  /* IFF the socket is (tproxy) transparent, pass the flag down to allow spoofing */
435  F->flags.transparent = fd_table[conn->fd].flags.transparent; // XXX: can we remove this line yet?
436 
437  return true;
438 }
439 
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:952
#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
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
void commSetCloseOnExec(int)
Definition: minimal.cc:27
virtual void swanSong()
Definition: AsyncJob.h:61
void swanSong() override
Definition: TcpAcceptor.cc:108
Eui::Eui64 remoteEui64
Definition: Connection.h:178
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:190
@ 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:1054
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:160
bool acceptInto(Comm::ConnectionPointer &)
Definition: TcpAcceptor.cc:344
#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
struct StatCounters::@119 syscalls
#define COMM_TRANSPARENT
Definition: Connection.h:50
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:91
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:146
Comm::ConnectionPointer tcpClient
TCP/IP level details about the client connection.
Comm::ConnectionPointer conn
Definition: CommCalls.h:80
Ip::Address remote
Definition: Connection.h:149
struct StatCounters::@119::@124 sock
SSL Connection
Definition: Session.h:49
void fatalf(const char *fmt,...)
Definition: fatal.cc:68
nfmark_t nfConnmark
Definition: Connection.h:171
void unsubscribe(const char *reason)
Definition: TcpAcceptor.cc:68
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:51
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:177
#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:320
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:210
int ignoreErrno(int ierrno)
Definition: comm.cc:1422
#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:150
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:267
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:124
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
@ 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:75
bool isOpen(const int fd)
Definition: comm.cc:89
void logAcceptError(const ConnectionPointer &tcpClient) const
Definition: TcpAcceptor.cc:248
static bool okToAccept()
Definition: TcpAcceptor.cc:232
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
void subscribe(const Subscription::Pointer &aSub)
Definition: TcpAcceptor.cc:60
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:981
class SquidConfig Config
Definition: SquidConfig.cc:12
bool lookup(const Ip::Address &c)
Definition: Eui64.cc:47
StatCounters statCounter
Definition: StatCounters.cc:12

 

Introduction

Documentation

Support

Miscellaneous