]> git.ipfire.org Git - thirdparty/squid.git/blame - src/comm/TcpAcceptor.cc
SourceLayout: Move time related tools to time/libtime.la (#1001)
[thirdparty/squid.git] / src / comm / TcpAcceptor.cc
CommitLineData
04f55905 1/*
bf95c10a 2 * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
04f55905 3 *
bbc27441
AJ
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.
04f55905
AJ
7 */
8
bbc27441
AJ
9/* DEBUG: section 05 Listener Socket Handler */
10
582c2af2 11#include "squid.h"
da6dbcd1 12#include "acl/FilledChecklist.h"
94bfd31f 13#include "anyp/PortCfg.h"
a9870624 14#include "base/TextException.h"
95e6d864 15#include "client_db.h"
04f55905
AJ
16#include "comm/AcceptLimiter.h"
17#include "comm/comm_internal.h"
5b67dfa4 18#include "comm/Connection.h"
d841c88d 19#include "comm/Loops.h"
cbff89ba 20#include "comm/TcpAcceptor.h"
602d9612 21#include "CommCalls.h"
83b62d3f 22#include "eui/Config.h"
c4ad1349 23#include "fd.h"
04f55905 24#include "fde.h"
67679543 25#include "globals.h"
40d34a62 26#include "ip/Intercept.h"
c6f168c1 27#include "ip/QosConfig.h"
da6dbcd1 28#include "log/access_log.h"
94bfd31f 29#include "MasterXaction.h"
4d5904f7 30#include "SquidConfig.h"
e4f1fdae 31#include "StatCounters.h"
04f55905 32
1a30fdf5 33#include <cerrno>
f842580f
AJ
34#ifdef HAVE_NETINET_TCP_H
35// required for accept_filter to build.
36#include <netinet/tcp.h>
37#endif
21d845b1 38
f842580f 39CBDATA_NAMESPACED_CLASS_INIT(Comm, TcpAcceptor);
a9870624 40
ced8def3 41Comm::TcpAcceptor::TcpAcceptor(const Comm::ConnectionPointer &newConn, const char *, const Subscription::Pointer &aSub) :
f53969cc
SM
42 AsyncJob("Comm::TcpAcceptor"),
43 errcode(0),
f53969cc
SM
44 theCallSub(aSub),
45 conn(newConn),
46 listenPort_()
fa720bfb
AJ
47{}
48
ced8def3 49Comm::TcpAcceptor::TcpAcceptor(const AnyP::PortCfgPointer &p, const char *, const Subscription::Pointer &aSub) :
f53969cc
SM
50 AsyncJob("Comm::TcpAcceptor"),
51 errcode(0),
f53969cc
SM
52 theCallSub(aSub),
53 conn(p->listenConn),
54 listenPort_(p)
cbff89ba 55{}
0ba55a12
AJ
56
57void
cbff89ba 58Comm::TcpAcceptor::subscribe(const Subscription::Pointer &aSub)
0ba55a12 59{
bf95c10a 60 debugs(5, 5, status() << " AsyncCall Subscription: " << aSub);
5b67dfa4
AJ
61 unsubscribe("subscription change");
62 theCallSub = aSub;
4c5518e5
AJ
63}
64
0ba55a12 65void
cbff89ba 66Comm::TcpAcceptor::unsubscribe(const char *reason)
0ba55a12 67{
bf95c10a 68 debugs(5, 5, status() << " AsyncCall Subscription " << theCallSub << " removed: " << reason);
5b67dfa4 69 theCallSub = NULL;
0ba55a12
AJ
70}
71
a9870624 72void
cbff89ba 73Comm::TcpAcceptor::start()
a9870624 74{
ccfbe8f4
AR
75 if (listenPort_)
76 CodeContext::Reset(listenPort_);
bf95c10a 77 debugs(5, 5, status() << " AsyncCall Subscription: " << theCallSub);
a9870624
AJ
78
79 Must(IsConnOpen(conn));
80
81 setListen();
82
8aec3e1b
CT
83 conn->noteStart();
84
a9870624
AJ
85 // if no error so far start accepting connections.
86 if (errcode == 0)
8bbb16e3 87 SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
a9870624
AJ
88}
89
90bool
cbff89ba 91Comm::TcpAcceptor::doneAll() const
a9870624 92{
6f8536c0 93 // stop when FD is closed
a9870624 94 if (!IsConnOpen(conn)) {
a9870624
AJ
95 return AsyncJob::doneAll();
96 }
97
5b67dfa4
AJ
98 // stop when handlers are gone
99 if (theCallSub == NULL) {
a9870624
AJ
100 return AsyncJob::doneAll();
101 }
102
5b67dfa4 103 // open FD with handlers...keep accepting.
a9870624
AJ
104 return false;
105}
106
107void
cbff89ba 108Comm::TcpAcceptor::swanSong()
a9870624 109{
bf95c10a 110 debugs(5,5, MYNAME);
5b67dfa4 111 unsubscribe("swanSong");
6f09127c
AR
112 if (IsConnOpen(conn)) {
113 if (closer_ != NULL)
114 comm_remove_close_handler(conn->fd, closer_);
115 conn->close();
116 }
117
a9870624 118 conn = NULL;
5b67dfa4 119 AcceptLimiter::Instance().removeDead(this);
a9870624
AJ
120 AsyncJob::swanSong();
121}
122
cbff89ba
AJ
123const char *
124Comm::TcpAcceptor::status() const
125{
8bbb16e3
AJ
126 if (conn == NULL)
127 return "[nil connection]";
128
cbff89ba
AJ
129 static char ipbuf[MAX_IPSTRLEN] = {'\0'};
130 if (ipbuf[0] == '\0')
4dd643d5 131 conn->local.toHostStr(ipbuf, MAX_IPSTRLEN);
cbff89ba
AJ
132
133 static MemBuf buf;
134 buf.reset();
4391cd15 135 buf.appendf(" FD %d, %s",conn->fd, ipbuf);
cbff89ba
AJ
136
137 const char *jobStatus = AsyncJob::status();
138 buf.append(jobStatus, strlen(jobStatus));
139
140 return buf.content();
141}
142
0ba55a12
AJ
143/**
144 * New-style listen and accept routines
145 *
146 * setListen simply registers our interest in an FD for listening.
147 * The constructor takes a callback to call when an FD has been
148 * accept()ed some time later.
149 */
150void
cbff89ba 151Comm::TcpAcceptor::setListen()
0ba55a12 152{
5dc67d58 153 errcode = errno = 0;
a9870624 154 if (listen(conn->fd, Squid_MaxFD >> 2) < 0) {
0ba55a12 155 errcode = errno;
ccfbe8f4 156 debugs(50, DBG_CRITICAL, "ERROR: listen(..., " << (Squid_MaxFD >> 2) << ") system call failed: " << xstrerr(errcode));
0ba55a12
AJ
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));
5b67dfa4 164 debugs(5, DBG_IMPORTANT, "Installing accept filter '" << Config.accept_filter << "' on " << conn);
0ba55a12 165 xstrncpy(afa.af_name, Config.accept_filter, sizeof(afa.af_name));
b69e9ffa
AJ
166 if (setsockopt(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 }
0ba55a12
AJ
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);
b69e9ffa
AJ
174 if (setsockopt(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 }
0ba55a12 178#else
5b67dfa4 179 debugs(5, DBG_CRITICAL, "WARNING: accept_filter not supported on your OS");
0ba55a12
AJ
180#endif
181 }
6f09127c
AR
182
183 typedef CommCbMemFunT<Comm::TcpAcceptor, CommCloseCbParams> Dialer;
184 closer_ = JobCallback(5, 4, Dialer, this, Comm::TcpAcceptor::handleClosure);
185 comm_add_close_handler(conn->fd, closer_);
186}
187
188/// called when listening descriptor is closed by an external force
189/// such as clientHttpConnectionsClose()
190void
ced8def3 191Comm::TcpAcceptor::handleClosure(const CommCloseCbParams &)
6f09127c
AR
192{
193 closer_ = NULL;
2b6b1bcb
AR
194 if (conn) {
195 conn->noteClosure();
196 conn = nullptr;
197 }
6f09127c 198 Must(done());
04f55905
AJ
199}
200
201/**
202 * This private callback is called whenever a filedescriptor is ready
203 * to dupe itself and fob off an accept()ed connection
204 *
205 * It will either do that accept operation. Or if there are not enough FD
206 * available to do the clone safely will push the listening FD into a list
207 * of deferred operations. The list gets kicked and the dupe/accept() actually
208 * done later when enough sockets become available.
209 */
210void
cbff89ba 211Comm::TcpAcceptor::doAccept(int fd, void *data)
04f55905 212{
5b67dfa4 213 try {
bf95c10a 214 debugs(5, 2, "New connection on FD " << fd);
04f55905 215
5b67dfa4 216 Must(isOpen(fd));
cbff89ba 217 TcpAcceptor *afd = static_cast<TcpAcceptor*>(data);
04f55905 218
5b67dfa4
AJ
219 if (!okToAccept()) {
220 AcceptLimiter::Instance().defer(afd);
221 } else {
222 afd->acceptNext();
223 }
5b67dfa4 224
db98b2bd 225 } catch (const std::exception &e) {
cbff89ba 226 fatalf("FATAL: error while accepting new client connection: %s\n", e.what());
db98b2bd 227 } catch (...) {
fbdf945d 228 fatal("FATAL: error while accepting new client connection: [unknown]\n");
04f55905 229 }
04f55905
AJ
230}
231
232bool
cbff89ba 233Comm::TcpAcceptor::okToAccept()
04f55905
AJ
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) {
d816f28d 241 debugs(5, DBG_CRITICAL, "WARNING: Your cache is running out of filedescriptors");
04f55905
AJ
242 last_warn = squid_curtime;
243 }
244
245 return false;
246}
247
ccfbe8f4
AR
248void
249Comm::TcpAcceptor::logAcceptError(const ConnectionPointer &tcpClient) const
da6dbcd1
EB
250{
251 AccessLogEntry::Pointer al = new AccessLogEntry;
ccfbe8f4
AR
252 CodeContext::Reset(al);
253 al->tcpClient = tcpClient;
da6dbcd1 254 al->url = "error:accept-client-connection";
bec110e4 255 al->setVirginUrlForMissingRequest(al->url);
da6dbcd1 256 ACLFilledChecklist ch(nullptr, nullptr, nullptr);
ccfbe8f4
AR
257 ch.src_addr = tcpClient->remote;
258 ch.my_addr = tcpClient->local;
cb365059 259 ch.al = al;
da6dbcd1 260 accessLogLog(al, &ch);
ccfbe8f4
AR
261
262 CodeContext::Reset(listenPort_);
da6dbcd1
EB
263}
264
971581ee 265void
cbff89ba 266Comm::TcpAcceptor::acceptOne()
04f55905
AJ
267{
268 /*
269 * We don't worry about running low on FDs here. Instead,
270 * doAccept() will use AcceptLimiter if we reach the limit
271 * there.
272 */
273
274 /* Accept a new connection */
5b67dfa4 275 ConnectionPointer newConnDetails = new Connection();
c8407295 276 const Comm::Flag flag = oldAccept(newConnDetails);
04f55905 277
29a0bb6a 278 if (flag == Comm::COMM_ERROR) {
04f55905 279 // A non-recoverable error; notify the caller */
bf95c10a 280 debugs(5, 5, "non-recoverable error:" << status() << " handler Subscription: " << theCallSub);
da6dbcd1
EB
281 if (intendedForUserConnections())
282 logAcceptError(newConnDetails);
8bbb16e3 283 notify(flag, newConnDetails);
9fd3e68c 284 // XXX: not under async job call protections
5b67dfa4 285 mustStop("Listener socket closed");
971581ee 286 return;
04f55905
AJ
287 }
288
29a0bb6a
AJ
289 if (flag == Comm::NOMESSAGE) {
290 /* register interest again */
291 debugs(5, 5, "try later: " << conn << " handler Subscription: " << theCallSub);
292 } else {
ccfbe8f4
AR
293 // TODO: When ALE, MasterXaction merge, use them or ClientConn instead.
294 CodeContext::Reset(newConnDetails);
29a0bb6a
AJ
295 debugs(5, 5, "Listener: " << conn <<
296 " accepted new connection " << newConnDetails <<
297 " handler Subscription: " << theCallSub);
298 notify(flag, newConnDetails);
ccfbe8f4 299 CodeContext::Reset(listenPort_);
29a0bb6a 300 }
653d9927 301
f3b976f7 302 SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
04f55905
AJ
303}
304
305void
cbff89ba 306Comm::TcpAcceptor::acceptNext()
04f55905 307{
a9870624 308 Must(IsConnOpen(conn));
bf95c10a 309 debugs(5, 2, "connection on " << conn);
971581ee 310 acceptOne();
04f55905
AJ
311}
312
313void
c8407295 314Comm::TcpAcceptor::notify(const Comm::Flag flag, const Comm::ConnectionPointer &newConnDetails) const
04f55905 315{
c8407295 316 // listener socket handlers just abandon the port with Comm::ERR_CLOSING
04f55905 317 // it should only happen when this object is deleted...
c8407295 318 if (flag == Comm::ERR_CLOSING) {
04f55905
AJ
319 return;
320 }
321
5b67dfa4
AJ
322 if (theCallSub != NULL) {
323 AsyncCall::Pointer call = theCallSub->callback();
324 CommAcceptCbParams &params = GetCommParams<CommAcceptCbParams>(call);
5ceaee75 325 params.xaction = new MasterXaction(XactionInitiator::initClient);
fa720bfb 326 params.xaction->squidPort = listenPort_;
a9870624 327 params.fd = conn->fd;
94bfd31f 328 params.conn = params.xaction->tcpClient = newConnDetails;
0ba55a12
AJ
329 params.flag = flag;
330 params.xerrno = errcode;
331 ScheduleCallHere(call);
0ba55a12 332 }
04f55905
AJ
333}
334
335/**
97b8ac39 336 * accept() and process
5b67dfa4
AJ
337 * Wait for an incoming connection on our listener socket.
338 *
61beade2
AJ
339 * \retval Comm::OK success. details parameter filled.
340 * \retval Comm::NOMESSAGE attempted accept() but nothing useful came in.
61beade2 341 * Or this client has too many connections already.
29a0bb6a 342 * \retval Comm::COMM_ERROR an outright failure occurred.
273f66c4 343 */
c8407295 344Comm::Flag
8bbb16e3 345Comm::TcpAcceptor::oldAccept(Comm::ConnectionPointer &details)
04f55905 346{
e4f1fdae 347 ++statCounter.syscalls.sock.accepts;
04f55905
AJ
348 int sock;
349 struct addrinfo *gai = NULL;
851614a8 350 Ip::Address::InitAddr(gai);
04f55905 351
1fc32b95 352 errcode = 0; // reset local errno copy.
a9870624 353 if ((sock = accept(conn->fd, gai->ai_addr, &gai->ai_addrlen)) < 0) {
1fc32b95 354 errcode = errno; // store last accept errno locally.
04f55905 355
851614a8 356 Ip::Address::FreeAddr(gai);
04f55905 357
fb730aad 358 if (ignoreErrno(errcode) || errcode == ECONNABORTED) {
b69e9ffa 359 debugs(50, 5, status() << ": " << xstrerr(errcode));
c8407295 360 return Comm::NOMESSAGE;
fb730aad 361 } else if (errcode == ENFILE || errcode == EMFILE) {
b69e9ffa 362 debugs(50, 3, status() << ": " << xstrerr(errcode));
4ee57cbe 363 return Comm::COMM_ERROR;
04f55905 364 } else {
ccfbe8f4 365 debugs(50, DBG_IMPORTANT, "ERROR: failed to accept an incoming connection: " << xstrerr(errcode));
4ee57cbe 366 return Comm::COMM_ERROR;
04f55905
AJ
367 }
368 }
369
a9870624 370 Must(sock >= 0);
72bfd2f2 371 ++incoming_sockets_accepted;
9205b24a
AR
372
373 // Sync with Comm ASAP so that abandoned details can properly close().
374 // XXX : these are not all HTTP requests. use a note about type and ip:port details->
375 // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
376 fd_open(sock, FD_SOCKET, "HTTP Request");
5b67dfa4 377 details->fd = sock;
4831b46c 378 details->enterOrphanage();
9205b24a 379
5b67dfa4 380 details->remote = *gai;
04f55905 381
903198a7 382 // lookup the local-end details of this new connection
851614a8 383 Ip::Address::InitAddr(gai);
4dd643d5 384 details->local.setEmpty();
e42281f9 385 if (getsockname(sock, gai->ai_addr, &gai->ai_addrlen) != 0) {
b69e9ffa
AJ
386 int xerrno = errno;
387 debugs(50, DBG_IMPORTANT, "ERROR: getsockname() failed to locate local-IP on " << details << ": " << xstrerr(xerrno));
851614a8 388 Ip::Address::FreeAddr(gai);
4ee57cbe 389 return Comm::COMM_ERROR;
e42281f9 390 }
5b67dfa4 391 details->local = *gai;
851614a8 392 Ip::Address::FreeAddr(gai);
04f55905 393
40d34a62
AJ
394 // Perform NAT or TPROXY operations to retrieve the real client/dest IP addresses
395 if (conn->flags&(COMM_TRANSPARENT|COMM_INTERCEPTION) && !Ip::Interceptor.Lookup(details, conn)) {
ae0d2b74 396 debugs(50, DBG_IMPORTANT, "ERROR: NAT/TPROXY lookup failed to locate original IPs on " << details);
9fd3e68c 397 return Comm::NOMESSAGE;
40d34a62 398 }
04f55905 399
83b62d3f
AJ
400#if USE_SQUID_EUI
401 if (Eui::TheConfig.euiLookup) {
68e47c3e
AJ
402 if (details->remote.isIPv4()) {
403 details->remoteEui48.lookup(details->remote);
404 } else if (details->remote.isIPv6()) {
405 details->remoteEui64.lookup(details->remote);
83b62d3f
AJ
406 }
407 }
408#endif
409
29a0bb6a
AJ
410 details->nfConnmark = Ip::Qos::getNfConnmark(details, Ip::Qos::dirAccepted);
411
412 if (Config.client_ip_max_connections >= 0) {
413 if (clientdbEstablished(details->remote, 0) > Config.client_ip_max_connections) {
414 debugs(50, DBG_IMPORTANT, "WARNING: " << details->remote << " attempting more than " << Config.client_ip_max_connections << " connections.");
29a0bb6a
AJ
415 return Comm::NOMESSAGE;
416 }
417 }
418
29a0bb6a
AJ
419 fde *F = &fd_table[sock];
420 details->remote.toStr(F->ipaddr,MAX_IPSTRLEN);
421 F->remote_port = details->remote.port();
422 F->local_addr = details->local;
423 F->sock_family = details->local.isIPv6()?AF_INET6:AF_INET;
424
425 // set socket flags
426 commSetCloseOnExec(sock);
427 commSetNonBlocking(sock);
2b80935b
EB
428 if (listenPort_)
429 Comm::ApplyTcpKeepAlive(sock, listenPort_->tcp_keepalive);
29a0bb6a
AJ
430
431 /* IFF the socket is (tproxy) transparent, pass the flag down to allow spoofing */
432 F->flags.transparent = fd_table[conn->fd].flags.transparent; // XXX: can we remove this line yet?
433
c8407295 434 return Comm::OK;
04f55905 435}
f53969cc 436