2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 05 Listener Socket Handler */
12 #include "acl/FilledChecklist.h"
13 #include "anyp/PortCfg.h"
14 #include "base/TextException.h"
15 #include "client_db.h"
16 #include "comm/AcceptLimiter.h"
17 #include "comm/comm_internal.h"
18 #include "comm/Connection.h"
19 #include "comm/Loops.h"
20 #include "comm/TcpAcceptor.h"
21 #include "CommCalls.h"
22 #include "eui/Config.h"
26 #include "ip/Intercept.h"
27 #include "ip/QosConfig.h"
28 #include "log/access_log.h"
29 #include "MasterXaction.h"
30 #include "profiler/Profiler.h"
31 #include "SquidConfig.h"
32 #include "SquidTime.h"
33 #include "StatCounters.h"
36 #ifdef HAVE_NETINET_TCP_H
37 // required for accept_filter to build.
38 #include <netinet/tcp.h>
41 CBDATA_NAMESPACED_CLASS_INIT(Comm
, TcpAcceptor
);
43 Comm::TcpAcceptor::TcpAcceptor(const Comm::ConnectionPointer
&newConn
, const char *, const Subscription::Pointer
&aSub
) :
44 AsyncJob("Comm::TcpAcceptor"),
52 Comm::TcpAcceptor::TcpAcceptor(const AnyP::PortCfgPointer
&p
, const char *, const Subscription::Pointer
&aSub
) :
53 AsyncJob("Comm::TcpAcceptor"),
62 Comm::TcpAcceptor::subscribe(const Subscription::Pointer
&aSub
)
64 debugs(5, 5, HERE
<< status() << " AsyncCall Subscription: " << aSub
);
65 unsubscribe("subscription change");
70 Comm::TcpAcceptor::unsubscribe(const char *reason
)
72 debugs(5, 5, HERE
<< status() << " AsyncCall Subscription " << theCallSub
<< " removed: " << reason
);
77 Comm::TcpAcceptor::start()
79 debugs(5, 5, HERE
<< status() << " AsyncCall Subscription: " << theCallSub
);
81 Must(IsConnOpen(conn
));
87 // if no error so far start accepting connections.
89 SetSelect(conn
->fd
, COMM_SELECT_READ
, doAccept
, this, 0);
93 Comm::TcpAcceptor::doneAll() const
95 // stop when FD is closed
96 if (!IsConnOpen(conn
)) {
97 return AsyncJob::doneAll();
100 // stop when handlers are gone
101 if (theCallSub
== NULL
) {
102 return AsyncJob::doneAll();
105 // open FD with handlers...keep accepting.
110 Comm::TcpAcceptor::swanSong()
113 unsubscribe("swanSong");
114 if (IsConnOpen(conn
)) {
116 comm_remove_close_handler(conn
->fd
, closer_
);
121 AcceptLimiter::Instance().removeDead(this);
122 AsyncJob::swanSong();
126 Comm::TcpAcceptor::status() const
129 return "[nil connection]";
131 static char ipbuf
[MAX_IPSTRLEN
] = {'\0'};
132 if (ipbuf
[0] == '\0')
133 conn
->local
.toHostStr(ipbuf
, MAX_IPSTRLEN
);
137 buf
.appendf(" FD %d, %s",conn
->fd
, ipbuf
);
139 const char *jobStatus
= AsyncJob::status();
140 buf
.append(jobStatus
, strlen(jobStatus
));
142 return buf
.content();
146 * New-style listen and accept routines
148 * setListen simply registers our interest in an FD for listening.
149 * The constructor takes a callback to call when an FD has been
150 * accept()ed some time later.
153 Comm::TcpAcceptor::setListen()
156 if (listen(conn
->fd
, Squid_MaxFD
>> 2) < 0) {
158 debugs(50, DBG_CRITICAL
, "ERROR: listen(" << status() << ", " << (Squid_MaxFD
>> 2) << "): " << xstrerr(errcode
));
162 if (Config
.accept_filter
&& strcmp(Config
.accept_filter
, "none") != 0) {
163 #ifdef SO_ACCEPTFILTER
164 struct accept_filter_arg afa
;
165 bzero(&afa
, sizeof(afa
));
166 debugs(5, DBG_IMPORTANT
, "Installing accept filter '" << Config
.accept_filter
<< "' on " << conn
);
167 xstrncpy(afa
.af_name
, Config
.accept_filter
, sizeof(afa
.af_name
));
168 if (setsockopt(conn
->fd
, SOL_SOCKET
, SO_ACCEPTFILTER
, &afa
, sizeof(afa
)) < 0) {
170 debugs(5, DBG_CRITICAL
, "WARNING: SO_ACCEPTFILTER '" << Config
.accept_filter
<< "': '" << xstrerr(xerrno
));
172 #elif defined(TCP_DEFER_ACCEPT)
174 if (strncmp(Config
.accept_filter
, "data=", 5) == 0)
175 seconds
= atoi(Config
.accept_filter
+ 5);
176 if (setsockopt(conn
->fd
, IPPROTO_TCP
, TCP_DEFER_ACCEPT
, &seconds
, sizeof(seconds
)) < 0) {
178 debugs(5, DBG_CRITICAL
, "WARNING: TCP_DEFER_ACCEPT '" << Config
.accept_filter
<< "': '" << xstrerr(xerrno
));
181 debugs(5, DBG_CRITICAL
, "WARNING: accept_filter not supported on your OS");
187 // Set TOS if needed.
188 // To correctly implement TOS values on listening sockets, probably requires
189 // more work to inherit TOS values to created connection objects.
191 Ip::Qos::setSockTos(conn
, conn
->tos
)
194 Ip::Qos::setSockNfmark(conn
, conn
->nfmark
);
198 typedef CommCbMemFunT
<Comm::TcpAcceptor
, CommCloseCbParams
> Dialer
;
199 closer_
= JobCallback(5, 4, Dialer
, this, Comm::TcpAcceptor::handleClosure
);
200 comm_add_close_handler(conn
->fd
, closer_
);
203 /// called when listening descriptor is closed by an external force
204 /// such as clientHttpConnectionsClose()
206 Comm::TcpAcceptor::handleClosure(const CommCloseCbParams
&)
214 * This private callback is called whenever a filedescriptor is ready
215 * to dupe itself and fob off an accept()ed connection
217 * It will either do that accept operation. Or if there are not enough FD
218 * available to do the clone safely will push the listening FD into a list
219 * of deferred operations. The list gets kicked and the dupe/accept() actually
220 * done later when enough sockets become available.
223 Comm::TcpAcceptor::doAccept(int fd
, void *data
)
226 debugs(5, 2, HERE
<< "New connection on FD " << fd
);
229 TcpAcceptor
*afd
= static_cast<TcpAcceptor
*>(data
);
232 AcceptLimiter::Instance().defer(afd
);
236 SetSelect(fd
, COMM_SELECT_READ
, Comm::TcpAcceptor::doAccept
, afd
, 0);
238 } catch (const std::exception
&e
) {
239 fatalf("FATAL: error while accepting new client connection: %s\n", e
.what());
241 fatal("FATAL: error while accepting new client connection: [unknown]\n");
246 Comm::TcpAcceptor::okToAccept()
248 static time_t last_warn
= 0;
250 if (fdNFree() >= RESERVED_FD
)
253 if (last_warn
+ 15 < squid_curtime
) {
254 debugs(5, DBG_CRITICAL
, "WARNING! Your cache is running out of filedescriptors");
255 last_warn
= squid_curtime
;
262 logAcceptError(const Comm::ConnectionPointer
&conn
)
264 AccessLogEntry::Pointer al
= new AccessLogEntry
;
265 al
->tcpClient
= conn
;
266 al
->url
= "error:accept-client-connection";
267 ACLFilledChecklist
ch(nullptr, nullptr, nullptr);
268 ch
.src_addr
= conn
->remote
;
269 ch
.my_addr
= conn
->local
;
271 accessLogLog(al
, &ch
);
275 Comm::TcpAcceptor::acceptOne()
278 * We don't worry about running low on FDs here. Instead,
279 * doAccept() will use AcceptLimiter if we reach the limit
283 /* Accept a new connection */
284 ConnectionPointer newConnDetails
= new Connection();
285 const Comm::Flag flag
= oldAccept(newConnDetails
);
287 /* Check for errors */
288 if (!newConnDetails
->isOpen()) {
290 if (flag
== Comm::NOMESSAGE
) {
291 /* register interest again */
292 debugs(5, 5, HERE
<< "try later: " << conn
<< " handler Subscription: " << theCallSub
);
293 SetSelect(conn
->fd
, COMM_SELECT_READ
, doAccept
, this, 0);
297 // A non-recoverable error; notify the caller */
298 debugs(5, 5, HERE
<< "non-recoverable error:" << status() << " handler Subscription: " << theCallSub
);
299 if (intendedForUserConnections())
300 logAcceptError(newConnDetails
);
301 notify(flag
, newConnDetails
);
302 mustStop("Listener socket closed");
306 newConnDetails
->nfConnmark
= Ip::Qos::getNfConnmark(newConnDetails
, Ip::Qos::dirAccepted
);
308 debugs(5, 5, HERE
<< "Listener: " << conn
<<
309 " accepted new connection " << newConnDetails
<<
310 " handler Subscription: " << theCallSub
);
311 notify(flag
, newConnDetails
);
315 Comm::TcpAcceptor::acceptNext()
317 Must(IsConnOpen(conn
));
318 debugs(5, 2, HERE
<< "connection on " << conn
);
323 Comm::TcpAcceptor::notify(const Comm::Flag flag
, const Comm::ConnectionPointer
&newConnDetails
) const
325 // listener socket handlers just abandon the port with Comm::ERR_CLOSING
326 // it should only happen when this object is deleted...
327 if (flag
== Comm::ERR_CLOSING
) {
331 if (theCallSub
!= NULL
) {
332 AsyncCall::Pointer call
= theCallSub
->callback();
333 CommAcceptCbParams
¶ms
= GetCommParams
<CommAcceptCbParams
>(call
);
334 params
.xaction
= new MasterXaction(XactionInitiator::initClient
);
335 params
.xaction
->squidPort
= listenPort_
;
336 params
.fd
= conn
->fd
;
337 params
.conn
= params
.xaction
->tcpClient
= newConnDetails
;
339 params
.xerrno
= errcode
;
340 ScheduleCallHere(call
);
345 * accept() and process
346 * Wait for an incoming connection on our listener socket.
348 * \retval Comm::OK success. details parameter filled.
349 * \retval Comm::NOMESSAGE attempted accept() but nothing useful came in.
350 * \retval Comm::COMM_ERROR an outright failure occured.
351 * Or if this client has too many connections already.
354 Comm::TcpAcceptor::oldAccept(Comm::ConnectionPointer
&details
)
356 PROF_start(comm_accept
);
357 ++statCounter
.syscalls
.sock
.accepts
;
359 struct addrinfo
*gai
= NULL
;
360 Ip::Address::InitAddr(gai
);
362 errcode
= 0; // reset local errno copy.
363 if ((sock
= accept(conn
->fd
, gai
->ai_addr
, &gai
->ai_addrlen
)) < 0) {
364 errcode
= errno
; // store last accept errno locally.
366 Ip::Address::FreeAddr(gai
);
368 PROF_stop(comm_accept
);
370 if (ignoreErrno(errcode
)) {
371 debugs(50, 5, status() << ": " << xstrerr(errcode
));
372 return Comm::NOMESSAGE
;
373 } else if (ENFILE
== errno
|| EMFILE
== errno
) {
374 debugs(50, 3, status() << ": " << xstrerr(errcode
));
375 return Comm::COMM_ERROR
;
377 debugs(50, DBG_IMPORTANT
, MYNAME
<< status() << ": " << xstrerr(errcode
));
378 return Comm::COMM_ERROR
;
384 details
->remote
= *gai
;
386 if ( Config
.client_ip_max_connections
>= 0) {
387 if (clientdbEstablished(details
->remote
, 0) > Config
.client_ip_max_connections
) {
388 debugs(50, DBG_IMPORTANT
, "WARNING: " << details
->remote
<< " attempting more than " << Config
.client_ip_max_connections
<< " connections.");
389 Ip::Address::FreeAddr(gai
);
390 PROF_stop(comm_accept
);
391 return Comm::COMM_ERROR
;
395 // lookup the local-end details of this new connection
396 Ip::Address::InitAddr(gai
);
397 details
->local
.setEmpty();
398 if (getsockname(sock
, gai
->ai_addr
, &gai
->ai_addrlen
) != 0) {
400 debugs(50, DBG_IMPORTANT
, "ERROR: getsockname() failed to locate local-IP on " << details
<< ": " << xstrerr(xerrno
));
401 Ip::Address::FreeAddr(gai
);
402 PROF_stop(comm_accept
);
403 return Comm::COMM_ERROR
;
405 details
->local
= *gai
;
406 Ip::Address::FreeAddr(gai
);
409 // XXX : these are not all HTTP requests. use a note about type and ip:port details->
410 // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
411 fd_open(sock
, FD_SOCKET
, "HTTP Request");
413 fde
*F
= &fd_table
[sock
];
414 details
->remote
.toStr(F
->ipaddr
,MAX_IPSTRLEN
);
415 F
->remote_port
= details
->remote
.port();
416 F
->local_addr
= details
->local
;
417 F
->sock_family
= details
->local
.isIPv6()?AF_INET6
:AF_INET
;
420 commSetCloseOnExec(sock
);
421 commSetNonBlocking(sock
);
423 /* IFF the socket is (tproxy) transparent, pass the flag down to allow spoofing */
424 F
->flags
.transparent
= fd_table
[conn
->fd
].flags
.transparent
; // XXX: can we remove this line yet?
426 // Perform NAT or TPROXY operations to retrieve the real client/dest IP addresses
427 if (conn
->flags
&(COMM_TRANSPARENT
|COMM_INTERCEPTION
) && !Ip::Interceptor
.Lookup(details
, conn
)) {
428 debugs(50, DBG_IMPORTANT
, "ERROR: NAT/TPROXY lookup failed to locate original IPs on " << details
);
430 PROF_stop(comm_accept
);
431 return Comm::COMM_ERROR
;
435 if (Eui::TheConfig
.euiLookup
) {
436 if (details
->remote
.isIPv4()) {
437 details
->remoteEui48
.lookup(details
->remote
);
438 } else if (details
->remote
.isIPv6()) {
439 details
->remoteEui64
.lookup(details
->remote
);
444 PROF_stop(comm_accept
);