]> git.ipfire.org Git - thirdparty/squid.git/blob - src/comm/TcpAcceptor.cc
Supply AccessLogEntry (ALE) for more fast ACL checks. (#182)
[thirdparty/squid.git] / src / comm / TcpAcceptor.cc
1 /*
2 * Copyright (C) 1996-2018 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/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"
23 #include "fd.h"
24 #include "fde.h"
25 #include "globals.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"
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
41 CBDATA_NAMESPACED_CLASS_INIT(Comm, TcpAcceptor);
42
43 Comm::TcpAcceptor::TcpAcceptor(const Comm::ConnectionPointer &newConn, const char *, const Subscription::Pointer &aSub) :
44 AsyncJob("Comm::TcpAcceptor"),
45 errcode(0),
46 isLimited(0),
47 theCallSub(aSub),
48 conn(newConn),
49 listenPort_()
50 {}
51
52 Comm::TcpAcceptor::TcpAcceptor(const AnyP::PortCfgPointer &p, const char *, const Subscription::Pointer &aSub) :
53 AsyncJob("Comm::TcpAcceptor"),
54 errcode(0),
55 isLimited(0),
56 theCallSub(aSub),
57 conn(p->listenConn),
58 listenPort_(p)
59 {}
60
61 void
62 Comm::TcpAcceptor::subscribe(const Subscription::Pointer &aSub)
63 {
64 debugs(5, 5, HERE << status() << " AsyncCall Subscription: " << aSub);
65 unsubscribe("subscription change");
66 theCallSub = aSub;
67 }
68
69 void
70 Comm::TcpAcceptor::unsubscribe(const char *reason)
71 {
72 debugs(5, 5, HERE << status() << " AsyncCall Subscription " << theCallSub << " removed: " << reason);
73 theCallSub = NULL;
74 }
75
76 void
77 Comm::TcpAcceptor::start()
78 {
79 debugs(5, 5, HERE << status() << " AsyncCall Subscription: " << theCallSub);
80
81 Must(IsConnOpen(conn));
82
83 setListen();
84
85 conn->noteStart();
86
87 // if no error so far start accepting connections.
88 if (errcode == 0)
89 SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
90 }
91
92 bool
93 Comm::TcpAcceptor::doneAll() const
94 {
95 // stop when FD is closed
96 if (!IsConnOpen(conn)) {
97 return AsyncJob::doneAll();
98 }
99
100 // stop when handlers are gone
101 if (theCallSub == NULL) {
102 return AsyncJob::doneAll();
103 }
104
105 // open FD with handlers...keep accepting.
106 return false;
107 }
108
109 void
110 Comm::TcpAcceptor::swanSong()
111 {
112 debugs(5,5, HERE);
113 unsubscribe("swanSong");
114 if (IsConnOpen(conn)) {
115 if (closer_ != NULL)
116 comm_remove_close_handler(conn->fd, closer_);
117 conn->close();
118 }
119
120 conn = NULL;
121 AcceptLimiter::Instance().removeDead(this);
122 AsyncJob::swanSong();
123 }
124
125 const char *
126 Comm::TcpAcceptor::status() const
127 {
128 if (conn == NULL)
129 return "[nil connection]";
130
131 static char ipbuf[MAX_IPSTRLEN] = {'\0'};
132 if (ipbuf[0] == '\0')
133 conn->local.toHostStr(ipbuf, MAX_IPSTRLEN);
134
135 static MemBuf buf;
136 buf.reset();
137 buf.appendf(" FD %d, %s",conn->fd, ipbuf);
138
139 const char *jobStatus = AsyncJob::status();
140 buf.append(jobStatus, strlen(jobStatus));
141
142 return buf.content();
143 }
144
145 /**
146 * New-style listen and accept routines
147 *
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.
151 */
152 void
153 Comm::TcpAcceptor::setListen()
154 {
155 errcode = errno = 0;
156 if (listen(conn->fd, Squid_MaxFD >> 2) < 0) {
157 errcode = errno;
158 debugs(50, DBG_CRITICAL, "ERROR: listen(" << status() << ", " << (Squid_MaxFD >> 2) << "): " << xstrerr(errcode));
159 return;
160 }
161
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) {
169 int xerrno = errno;
170 debugs(5, DBG_CRITICAL, "WARNING: SO_ACCEPTFILTER '" << Config.accept_filter << "': '" << xstrerr(xerrno));
171 }
172 #elif defined(TCP_DEFER_ACCEPT)
173 int seconds = 30;
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) {
177 int xerrno = errno;
178 debugs(5, DBG_CRITICAL, "WARNING: TCP_DEFER_ACCEPT '" << Config.accept_filter << "': '" << xstrerr(xerrno));
179 }
180 #else
181 debugs(5, DBG_CRITICAL, "WARNING: accept_filter not supported on your OS");
182 #endif
183 }
184
185 #if 0
186 // Untested code.
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.
190 if (conn->tos)
191 Ip::Qos::setSockTos(conn, conn->tos)
192 #if SO_MARK
193 if (conn->nfmark)
194 Ip::Qos::setSockNfmark(conn, conn->nfmark);
195 #endif
196 #endif
197
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_);
201 }
202
203 /// called when listening descriptor is closed by an external force
204 /// such as clientHttpConnectionsClose()
205 void
206 Comm::TcpAcceptor::handleClosure(const CommCloseCbParams &)
207 {
208 closer_ = NULL;
209 conn = NULL;
210 Must(done());
211 }
212
213 /**
214 * This private callback is called whenever a filedescriptor is ready
215 * to dupe itself and fob off an accept()ed connection
216 *
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.
221 */
222 void
223 Comm::TcpAcceptor::doAccept(int fd, void *data)
224 {
225 try {
226 debugs(5, 2, HERE << "New connection on FD " << fd);
227
228 Must(isOpen(fd));
229 TcpAcceptor *afd = static_cast<TcpAcceptor*>(data);
230
231 if (!okToAccept()) {
232 AcceptLimiter::Instance().defer(afd);
233 } else {
234 afd->acceptNext();
235 }
236 SetSelect(fd, COMM_SELECT_READ, Comm::TcpAcceptor::doAccept, afd, 0);
237
238 } catch (const std::exception &e) {
239 fatalf("FATAL: error while accepting new client connection: %s\n", e.what());
240 } catch (...) {
241 fatal("FATAL: error while accepting new client connection: [unknown]\n");
242 }
243 }
244
245 bool
246 Comm::TcpAcceptor::okToAccept()
247 {
248 static time_t last_warn = 0;
249
250 if (fdNFree() >= RESERVED_FD)
251 return true;
252
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;
256 }
257
258 return false;
259 }
260
261 static void
262 logAcceptError(const Comm::ConnectionPointer &conn)
263 {
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;
270 ch.al = al;
271 accessLogLog(al, &ch);
272 }
273
274 void
275 Comm::TcpAcceptor::acceptOne()
276 {
277 /*
278 * We don't worry about running low on FDs here. Instead,
279 * doAccept() will use AcceptLimiter if we reach the limit
280 * there.
281 */
282
283 /* Accept a new connection */
284 ConnectionPointer newConnDetails = new Connection();
285 const Comm::Flag flag = oldAccept(newConnDetails);
286
287 /* Check for errors */
288 if (!newConnDetails->isOpen()) {
289
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);
294 return;
295 }
296
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");
303 return;
304 }
305
306 newConnDetails->nfConnmark = Ip::Qos::getNfConnmark(newConnDetails, Ip::Qos::dirAccepted);
307
308 debugs(5, 5, HERE << "Listener: " << conn <<
309 " accepted new connection " << newConnDetails <<
310 " handler Subscription: " << theCallSub);
311 notify(flag, newConnDetails);
312 }
313
314 void
315 Comm::TcpAcceptor::acceptNext()
316 {
317 Must(IsConnOpen(conn));
318 debugs(5, 2, HERE << "connection on " << conn);
319 acceptOne();
320 }
321
322 void
323 Comm::TcpAcceptor::notify(const Comm::Flag flag, const Comm::ConnectionPointer &newConnDetails) const
324 {
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) {
328 return;
329 }
330
331 if (theCallSub != NULL) {
332 AsyncCall::Pointer call = theCallSub->callback();
333 CommAcceptCbParams &params = 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;
338 params.flag = flag;
339 params.xerrno = errcode;
340 ScheduleCallHere(call);
341 }
342 }
343
344 /**
345 * accept() and process
346 * Wait for an incoming connection on our listener socket.
347 *
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.
352 */
353 Comm::Flag
354 Comm::TcpAcceptor::oldAccept(Comm::ConnectionPointer &details)
355 {
356 PROF_start(comm_accept);
357 ++statCounter.syscalls.sock.accepts;
358 int sock;
359 struct addrinfo *gai = NULL;
360 Ip::Address::InitAddr(gai);
361
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.
365
366 Ip::Address::FreeAddr(gai);
367
368 PROF_stop(comm_accept);
369
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;
376 } else {
377 debugs(50, DBG_IMPORTANT, MYNAME << status() << ": " << xstrerr(errcode));
378 return Comm::COMM_ERROR;
379 }
380 }
381
382 Must(sock >= 0);
383 details->fd = sock;
384 details->remote = *gai;
385
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;
392 }
393 }
394
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) {
399 int xerrno = errno;
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;
404 }
405 details->local = *gai;
406 Ip::Address::FreeAddr(gai);
407
408 /* fdstat update */
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");
412
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;
418
419 // set socket flags
420 commSetCloseOnExec(sock);
421 commSetNonBlocking(sock);
422
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?
425
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);
429 // Failed.
430 PROF_stop(comm_accept);
431 return Comm::COMM_ERROR;
432 }
433
434 #if USE_SQUID_EUI
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);
440 }
441 }
442 #endif
443
444 PROF_stop(comm_accept);
445 return Comm::OK;
446 }
447