]> git.ipfire.org Git - thirdparty/squid.git/blob - src/comm/TcpAcceptor.cc
Cleanup: remove xstrerror()
[thirdparty/squid.git] / src / comm / TcpAcceptor.cc
1 /*
2 * Copyright (C) 1996-2016 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 "anyp/PortCfg.h"
13 #include "base/TextException.h"
14 #include "client_db.h"
15 #include "comm/AcceptLimiter.h"
16 #include "comm/comm_internal.h"
17 #include "comm/Connection.h"
18 #include "comm/Loops.h"
19 #include "comm/TcpAcceptor.h"
20 #include "CommCalls.h"
21 #include "eui/Config.h"
22 #include "fd.h"
23 #include "fde.h"
24 #include "globals.h"
25 #include "ip/Intercept.h"
26 #include "ip/QosConfig.h"
27 #include "MasterXaction.h"
28 #include "profiler/Profiler.h"
29 #include "SquidConfig.h"
30 #include "SquidTime.h"
31 #include "StatCounters.h"
32
33 #include <cerrno>
34 #ifdef HAVE_NETINET_TCP_H
35 // required for accept_filter to build.
36 #include <netinet/tcp.h>
37 #endif
38
39 CBDATA_NAMESPACED_CLASS_INIT(Comm, TcpAcceptor);
40
41 Comm::TcpAcceptor::TcpAcceptor(const Comm::ConnectionPointer &newConn, const char *, const Subscription::Pointer &aSub) :
42 AsyncJob("Comm::TcpAcceptor"),
43 errcode(0),
44 isLimited(0),
45 theCallSub(aSub),
46 conn(newConn),
47 listenPort_()
48 {}
49
50 Comm::TcpAcceptor::TcpAcceptor(const AnyP::PortCfgPointer &p, const char *, const Subscription::Pointer &aSub) :
51 AsyncJob("Comm::TcpAcceptor"),
52 errcode(0),
53 isLimited(0),
54 theCallSub(aSub),
55 conn(p->listenConn),
56 listenPort_(p)
57 {}
58
59 void
60 Comm::TcpAcceptor::subscribe(const Subscription::Pointer &aSub)
61 {
62 debugs(5, 5, HERE << 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, HERE << status() << " AsyncCall Subscription " << theCallSub << " removed: " << reason);
71 theCallSub = NULL;
72 }
73
74 void
75 Comm::TcpAcceptor::start()
76 {
77 debugs(5, 5, HERE << 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
91 Comm::TcpAcceptor::doneAll() const
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 == NULL) {
100 return AsyncJob::doneAll();
101 }
102
103 // open FD with handlers...keep accepting.
104 return false;
105 }
106
107 void
108 Comm::TcpAcceptor::swanSong()
109 {
110 debugs(5,5, HERE);
111 unsubscribe("swanSong");
112 if (IsConnOpen(conn)) {
113 if (closer_ != NULL)
114 comm_remove_close_handler(conn->fd, closer_);
115 conn->close();
116 }
117
118 conn = NULL;
119 AcceptLimiter::Instance().removeDead(this);
120 AsyncJob::swanSong();
121 }
122
123 const char *
124 Comm::TcpAcceptor::status() const
125 {
126 if (conn == NULL)
127 return "[nil connection]";
128
129 static char ipbuf[MAX_IPSTRLEN] = {'\0'};
130 if (ipbuf[0] == '\0')
131 conn->local.toHostStr(ipbuf, MAX_IPSTRLEN);
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
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 */
150 void
151 Comm::TcpAcceptor::setListen()
152 {
153 errcode = errno = 0;
154 if (listen(conn->fd, Squid_MaxFD >> 2) < 0) {
155 errcode = errno;
156 debugs(50, DBG_CRITICAL, "ERROR: listen(" << status() << ", " << (Squid_MaxFD >> 2) << "): " << 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 (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 }
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 (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 }
178 #else
179 debugs(5, DBG_CRITICAL, "WARNING: accept_filter not supported on your OS");
180 #endif
181 }
182
183 #if 0
184 // Untested code.
185 // Set TOS if needed.
186 // To correctly implement TOS values on listening sockets, probably requires
187 // more work to inherit TOS values to created connection objects.
188 if (conn->tos)
189 Ip::Qos::setSockTos(conn, conn->tos)
190 #if SO_MARK
191 if (conn->nfmark)
192 Ip::Qos::setSockNfmark(conn, conn->nfmark);
193 #endif
194 #endif
195
196 typedef CommCbMemFunT<Comm::TcpAcceptor, CommCloseCbParams> Dialer;
197 closer_ = JobCallback(5, 4, Dialer, this, Comm::TcpAcceptor::handleClosure);
198 comm_add_close_handler(conn->fd, closer_);
199 }
200
201 /// called when listening descriptor is closed by an external force
202 /// such as clientHttpConnectionsClose()
203 void
204 Comm::TcpAcceptor::handleClosure(const CommCloseCbParams &)
205 {
206 closer_ = NULL;
207 conn = NULL;
208 Must(done());
209 }
210
211 /**
212 * This private callback is called whenever a filedescriptor is ready
213 * to dupe itself and fob off an accept()ed connection
214 *
215 * It will either do that accept operation. Or if there are not enough FD
216 * available to do the clone safely will push the listening FD into a list
217 * of deferred operations. The list gets kicked and the dupe/accept() actually
218 * done later when enough sockets become available.
219 */
220 void
221 Comm::TcpAcceptor::doAccept(int fd, void *data)
222 {
223 try {
224 debugs(5, 2, HERE << "New connection on FD " << fd);
225
226 Must(isOpen(fd));
227 TcpAcceptor *afd = static_cast<TcpAcceptor*>(data);
228
229 if (!okToAccept()) {
230 AcceptLimiter::Instance().defer(afd);
231 } else {
232 afd->acceptNext();
233 }
234 SetSelect(fd, COMM_SELECT_READ, Comm::TcpAcceptor::doAccept, afd, 0);
235
236 } catch (const std::exception &e) {
237 fatalf("FATAL: error while accepting new client connection: %s\n", e.what());
238 } catch (...) {
239 fatal("FATAL: error while accepting new client connection: [unknown]\n");
240 }
241 }
242
243 bool
244 Comm::TcpAcceptor::okToAccept()
245 {
246 static time_t last_warn = 0;
247
248 if (fdNFree() >= RESERVED_FD)
249 return true;
250
251 if (last_warn + 15 < squid_curtime) {
252 debugs(5, DBG_CRITICAL, "WARNING! Your cache is running out of filedescriptors");
253 last_warn = squid_curtime;
254 }
255
256 return false;
257 }
258
259 void
260 Comm::TcpAcceptor::acceptOne()
261 {
262 /*
263 * We don't worry about running low on FDs here. Instead,
264 * doAccept() will use AcceptLimiter if we reach the limit
265 * there.
266 */
267
268 /* Accept a new connection */
269 ConnectionPointer newConnDetails = new Connection();
270 const Comm::Flag flag = oldAccept(newConnDetails);
271
272 /* Check for errors */
273 if (!newConnDetails->isOpen()) {
274
275 if (flag == Comm::NOMESSAGE) {
276 /* register interest again */
277 debugs(5, 5, HERE << "try later: " << conn << " handler Subscription: " << theCallSub);
278 SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
279 return;
280 }
281
282 // A non-recoverable error; notify the caller */
283 debugs(5, 5, HERE << "non-recoverable error:" << status() << " handler Subscription: " << theCallSub);
284 notify(flag, newConnDetails);
285 mustStop("Listener socket closed");
286 return;
287 }
288
289 debugs(5, 5, HERE << "Listener: " << conn <<
290 " accepted new connection " << newConnDetails <<
291 " handler Subscription: " << theCallSub);
292 notify(flag, newConnDetails);
293 }
294
295 void
296 Comm::TcpAcceptor::acceptNext()
297 {
298 Must(IsConnOpen(conn));
299 debugs(5, 2, HERE << "connection on " << conn);
300 acceptOne();
301 }
302
303 void
304 Comm::TcpAcceptor::notify(const Comm::Flag flag, const Comm::ConnectionPointer &newConnDetails) const
305 {
306 // listener socket handlers just abandon the port with Comm::ERR_CLOSING
307 // it should only happen when this object is deleted...
308 if (flag == Comm::ERR_CLOSING) {
309 return;
310 }
311
312 if (theCallSub != NULL) {
313 AsyncCall::Pointer call = theCallSub->callback();
314 CommAcceptCbParams &params = GetCommParams<CommAcceptCbParams>(call);
315 params.xaction = new MasterXaction;
316 params.xaction->squidPort = listenPort_;
317 params.fd = conn->fd;
318 params.conn = params.xaction->tcpClient = newConnDetails;
319 params.flag = flag;
320 params.xerrno = errcode;
321 ScheduleCallHere(call);
322 }
323 }
324
325 /**
326 * accept() and process
327 * Wait for an incoming connection on our listener socket.
328 *
329 * \retval Comm::OK success. details parameter filled.
330 * \retval Comm::NOMESSAGE attempted accept() but nothing useful came in.
331 * \retval Comm::COMM_ERROR an outright failure occured.
332 * Or if this client has too many connections already.
333 */
334 Comm::Flag
335 Comm::TcpAcceptor::oldAccept(Comm::ConnectionPointer &details)
336 {
337 PROF_start(comm_accept);
338 ++statCounter.syscalls.sock.accepts;
339 int sock;
340 struct addrinfo *gai = NULL;
341 Ip::Address::InitAddr(gai);
342
343 errcode = 0; // reset local errno copy.
344 if ((sock = accept(conn->fd, gai->ai_addr, &gai->ai_addrlen)) < 0) {
345 errcode = errno; // store last accept errno locally.
346
347 Ip::Address::FreeAddr(gai);
348
349 PROF_stop(comm_accept);
350
351 if (ignoreErrno(errcode)) {
352 debugs(50, 5, status() << ": " << xstrerr(errcode));
353 return Comm::NOMESSAGE;
354 } else if (ENFILE == errno || EMFILE == errno) {
355 debugs(50, 3, status() << ": " << xstrerr(errcode));
356 return Comm::COMM_ERROR;
357 } else {
358 debugs(50, DBG_IMPORTANT, MYNAME << status() << ": " << xstrerr(errcode));
359 return Comm::COMM_ERROR;
360 }
361 }
362
363 Must(sock >= 0);
364 details->fd = sock;
365 details->remote = *gai;
366
367 if ( Config.client_ip_max_connections >= 0) {
368 if (clientdbEstablished(details->remote, 0) > Config.client_ip_max_connections) {
369 debugs(50, DBG_IMPORTANT, "WARNING: " << details->remote << " attempting more than " << Config.client_ip_max_connections << " connections.");
370 Ip::Address::FreeAddr(gai);
371 PROF_stop(comm_accept);
372 return Comm::COMM_ERROR;
373 }
374 }
375
376 // lookup the local-end details of this new connection
377 Ip::Address::InitAddr(gai);
378 details->local.setEmpty();
379 if (getsockname(sock, gai->ai_addr, &gai->ai_addrlen) != 0) {
380 int xerrno = errno;
381 debugs(50, DBG_IMPORTANT, "ERROR: getsockname() failed to locate local-IP on " << details << ": " << xstrerr(xerrno));
382 Ip::Address::FreeAddr(gai);
383 PROF_stop(comm_accept);
384 return Comm::COMM_ERROR;
385 }
386 details->local = *gai;
387 Ip::Address::FreeAddr(gai);
388
389 /* fdstat update */
390 // XXX : these are not all HTTP requests. use a note about type and ip:port details->
391 // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
392 fd_open(sock, FD_SOCKET, "HTTP Request");
393
394 fdd_table[sock].close_file = NULL;
395 fdd_table[sock].close_line = 0;
396
397 fde *F = &fd_table[sock];
398 details->remote.toStr(F->ipaddr,MAX_IPSTRLEN);
399 F->remote_port = details->remote.port();
400 F->local_addr = details->local;
401 F->sock_family = details->local.isIPv6()?AF_INET6:AF_INET;
402
403 // set socket flags
404 commSetCloseOnExec(sock);
405 commSetNonBlocking(sock);
406
407 /* IFF the socket is (tproxy) transparent, pass the flag down to allow spoofing */
408 F->flags.transparent = fd_table[conn->fd].flags.transparent; // XXX: can we remove this line yet?
409
410 // Perform NAT or TPROXY operations to retrieve the real client/dest IP addresses
411 if (conn->flags&(COMM_TRANSPARENT|COMM_INTERCEPTION) && !Ip::Interceptor.Lookup(details, conn)) {
412 debugs(50, DBG_IMPORTANT, "ERROR: NAT/TPROXY lookup failed to locate original IPs on " << details);
413 // Failed.
414 PROF_stop(comm_accept);
415 return Comm::COMM_ERROR;
416 }
417
418 #if USE_SQUID_EUI
419 if (Eui::TheConfig.euiLookup) {
420 if (details->remote.isIPv4()) {
421 details->remoteEui48.lookup(details->remote);
422 } else if (details->remote.isIPv6()) {
423 details->remoteEui64.lookup(details->remote);
424 }
425 }
426 #endif
427
428 PROF_stop(comm_accept);
429 return Comm::OK;
430 }
431