]> git.ipfire.org Git - thirdparty/squid.git/blob - src/comm/TcpAcceptor.cc
Merge from trunk
[thirdparty/squid.git] / src / comm / TcpAcceptor.cc
1 /*
2 * DEBUG: section 05 Listener Socket Handler
3 * AUTHOR: Harvest Derived
4 *
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
7 *
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
30 *
31 *
32 * Copyright (c) 2003, Robert Collins <robertc@squid-cache.org>
33 */
34
35 #include "squid.h"
36 #include "base/TextException.h"
37 #include "client_db.h"
38 #include "comm/AcceptLimiter.h"
39 #include "CommCalls.h"
40 #include "comm/comm_internal.h"
41 #include "comm/Connection.h"
42 #include "comm/Loops.h"
43 #include "comm/TcpAcceptor.h"
44 #include "eui/Config.h"
45 #include "fd.h"
46 #include "fde.h"
47 #include "globals.h"
48 #include "ip/Intercept.h"
49 #include "profiler/Profiler.h"
50 #include "SquidConfig.h"
51 #include "SquidTime.h"
52 #include "StatCounters.h"
53
54 #if HAVE_ERRNO_H
55 #include <errno.h>
56 #endif
57 #ifdef HAVE_NETINET_TCP_H
58 // required for accept_filter to build.
59 #include <netinet/tcp.h>
60 #endif
61
62 CBDATA_NAMESPACED_CLASS_INIT(Comm, TcpAcceptor);
63
64 Comm::TcpAcceptor::TcpAcceptor(const Comm::ConnectionPointer &newConn, const char *note, const Subscription::Pointer &aSub) :
65 AsyncJob("Comm::TcpAcceptor"),
66 errcode(0),
67 isLimited(0),
68 theCallSub(aSub),
69 conn(newConn)
70 {}
71
72 void
73 Comm::TcpAcceptor::subscribe(const Subscription::Pointer &aSub)
74 {
75 debugs(5, 5, HERE << status() << " AsyncCall Subscription: " << aSub);
76 unsubscribe("subscription change");
77 theCallSub = aSub;
78 }
79
80 void
81 Comm::TcpAcceptor::unsubscribe(const char *reason)
82 {
83 debugs(5, 5, HERE << status() << " AsyncCall Subscription " << theCallSub << " removed: " << reason);
84 theCallSub = NULL;
85 }
86
87 void
88 Comm::TcpAcceptor::start()
89 {
90 debugs(5, 5, HERE << status() << " AsyncCall Subscription: " << theCallSub);
91
92 Must(IsConnOpen(conn));
93
94 setListen();
95
96 // if no error so far start accepting connections.
97 if (errcode == 0)
98 SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
99 }
100
101 bool
102 Comm::TcpAcceptor::doneAll() const
103 {
104 // stop when FD is closed
105 if (!IsConnOpen(conn)) {
106 return AsyncJob::doneAll();
107 }
108
109 // stop when handlers are gone
110 if (theCallSub == NULL) {
111 return AsyncJob::doneAll();
112 }
113
114 // open FD with handlers...keep accepting.
115 return false;
116 }
117
118 void
119 Comm::TcpAcceptor::swanSong()
120 {
121 debugs(5,5, HERE);
122 unsubscribe("swanSong");
123 conn = NULL;
124 AcceptLimiter::Instance().removeDead(this);
125 AsyncJob::swanSong();
126 }
127
128 const char *
129 Comm::TcpAcceptor::status() const
130 {
131 if (conn == NULL)
132 return "[nil connection]";
133
134 static char ipbuf[MAX_IPSTRLEN] = {'\0'};
135 if (ipbuf[0] == '\0')
136 conn->local.ToHostname(ipbuf, MAX_IPSTRLEN);
137
138 static MemBuf buf;
139 buf.reset();
140 buf.Printf(" FD %d, %s",conn->fd, ipbuf);
141
142 const char *jobStatus = AsyncJob::status();
143 buf.append(jobStatus, strlen(jobStatus));
144
145 return buf.content();
146 }
147
148 /**
149 * New-style listen and accept routines
150 *
151 * setListen simply registers our interest in an FD for listening.
152 * The constructor takes a callback to call when an FD has been
153 * accept()ed some time later.
154 */
155 void
156 Comm::TcpAcceptor::setListen()
157 {
158 errcode = 0; // reset local errno copy.
159 if (listen(conn->fd, Squid_MaxFD >> 2) < 0) {
160 debugs(50, DBG_CRITICAL, "ERROR: listen(" << status() << ", " << (Squid_MaxFD >> 2) << "): " << xstrerror());
161 errcode = errno;
162 return;
163 }
164
165 if (Config.accept_filter && strcmp(Config.accept_filter, "none") != 0) {
166 #ifdef SO_ACCEPTFILTER
167 struct accept_filter_arg afa;
168 bzero(&afa, sizeof(afa));
169 debugs(5, DBG_IMPORTANT, "Installing accept filter '" << Config.accept_filter << "' on " << conn);
170 xstrncpy(afa.af_name, Config.accept_filter, sizeof(afa.af_name));
171 if (setsockopt(conn->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0)
172 debugs(5, DBG_CRITICAL, "WARNING: SO_ACCEPTFILTER '" << Config.accept_filter << "': '" << xstrerror());
173 #elif defined(TCP_DEFER_ACCEPT)
174 int seconds = 30;
175 if (strncmp(Config.accept_filter, "data=", 5) == 0)
176 seconds = atoi(Config.accept_filter + 5);
177 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 << "': '" << xstrerror());
179 #else
180 debugs(5, DBG_CRITICAL, "WARNING: accept_filter not supported on your OS");
181 #endif
182 }
183 }
184
185 /**
186 * This private callback is called whenever a filedescriptor is ready
187 * to dupe itself and fob off an accept()ed connection
188 *
189 * It will either do that accept operation. Or if there are not enough FD
190 * available to do the clone safely will push the listening FD into a list
191 * of deferred operations. The list gets kicked and the dupe/accept() actually
192 * done later when enough sockets become available.
193 */
194 void
195 Comm::TcpAcceptor::doAccept(int fd, void *data)
196 {
197 try {
198 debugs(5, 2, HERE << "New connection on FD " << fd);
199
200 Must(isOpen(fd));
201 TcpAcceptor *afd = static_cast<TcpAcceptor*>(data);
202
203 if (!okToAccept()) {
204 AcceptLimiter::Instance().defer(afd);
205 } else {
206 afd->acceptNext();
207 }
208 SetSelect(fd, COMM_SELECT_READ, Comm::TcpAcceptor::doAccept, afd, 0);
209
210 } catch (const std::exception &e) {
211 fatalf("FATAL: error while accepting new client connection: %s\n", e.what());
212 } catch (...) {
213 fatal("FATAL: error while accepting new client connection: [unkown]\n");
214 }
215 }
216
217 bool
218 Comm::TcpAcceptor::okToAccept()
219 {
220 static time_t last_warn = 0;
221
222 if (fdNFree() >= RESERVED_FD)
223 return true;
224
225 if (last_warn + 15 < squid_curtime) {
226 debugs(5, DBG_CRITICAL, "WARNING! Your cache is running out of filedescriptors");
227 last_warn = squid_curtime;
228 }
229
230 return false;
231 }
232
233 void
234 Comm::TcpAcceptor::acceptOne()
235 {
236 /*
237 * We don't worry about running low on FDs here. Instead,
238 * doAccept() will use AcceptLimiter if we reach the limit
239 * there.
240 */
241
242 /* Accept a new connection */
243 ConnectionPointer newConnDetails = new Connection();
244 const comm_err_t flag = oldAccept(newConnDetails);
245
246 /* Check for errors */
247 if (!newConnDetails->isOpen()) {
248
249 if (flag == COMM_NOMESSAGE) {
250 /* register interest again */
251 debugs(5, 5, HERE << "try later: " << conn << " handler Subscription: " << theCallSub);
252 SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
253 return;
254 }
255
256 // A non-recoverable error; notify the caller */
257 debugs(5, 5, HERE << "non-recoverable error:" << status() << " handler Subscription: " << theCallSub);
258 notify(flag, newConnDetails);
259 mustStop("Listener socket closed");
260 return;
261 }
262
263 debugs(5, 5, HERE << "Listener: " << conn <<
264 " accepted new connection " << newConnDetails <<
265 " handler Subscription: " << theCallSub);
266 notify(flag, newConnDetails);
267 }
268
269 void
270 Comm::TcpAcceptor::acceptNext()
271 {
272 Must(IsConnOpen(conn));
273 debugs(5, 2, HERE << "connection on " << conn);
274 acceptOne();
275 }
276
277 void
278 Comm::TcpAcceptor::notify(const comm_err_t flag, const Comm::ConnectionPointer &newConnDetails) const
279 {
280 // listener socket handlers just abandon the port with COMM_ERR_CLOSING
281 // it should only happen when this object is deleted...
282 if (flag == COMM_ERR_CLOSING) {
283 return;
284 }
285
286 if (theCallSub != NULL) {
287 AsyncCall::Pointer call = theCallSub->callback();
288 CommAcceptCbParams &params = GetCommParams<CommAcceptCbParams>(call);
289 params.fd = conn->fd;
290 params.conn = newConnDetails;
291 params.flag = flag;
292 params.xerrno = errcode;
293 ScheduleCallHere(call);
294 }
295 }
296
297 /**
298 * accept() and process
299 * Wait for an incoming connection on our listener socket.
300 *
301 * \retval COMM_OK success. details parameter filled.
302 * \retval COMM_NOMESSAGE attempted accept() but nothing useful came in.
303 * \retval COMM_ERROR an outright failure occured.
304 * Or if this client has too many connections already.
305 */
306 comm_err_t
307 Comm::TcpAcceptor::oldAccept(Comm::ConnectionPointer &details)
308 {
309 PROF_start(comm_accept);
310 ++statCounter.syscalls.sock.accepts;
311 int sock;
312 struct addrinfo *gai = NULL;
313 details->local.InitAddrInfo(gai);
314
315 errcode = 0; // reset local errno copy.
316 if ((sock = accept(conn->fd, gai->ai_addr, &gai->ai_addrlen)) < 0) {
317 errcode = errno; // store last accept errno locally.
318
319 details->local.FreeAddrInfo(gai);
320
321 PROF_stop(comm_accept);
322
323 if (ignoreErrno(errno)) {
324 debugs(50, 5, HERE << status() << ": " << xstrerror());
325 return COMM_NOMESSAGE;
326 } else if (ENFILE == errno || EMFILE == errno) {
327 debugs(50, 3, HERE << status() << ": " << xstrerror());
328 return COMM_ERROR;
329 } else {
330 debugs(50, DBG_IMPORTANT, HERE << status() << ": " << xstrerror());
331 return COMM_ERROR;
332 }
333 }
334
335 Must(sock >= 0);
336 details->fd = sock;
337 details->remote = *gai;
338
339 if ( Config.client_ip_max_connections >= 0) {
340 if (clientdbEstablished(details->remote, 0) > Config.client_ip_max_connections) {
341 debugs(50, DBG_IMPORTANT, "WARNING: " << details->remote << " attempting more than " << Config.client_ip_max_connections << " connections.");
342 details->local.FreeAddrInfo(gai);
343 return COMM_ERROR;
344 }
345 }
346
347 // lookup the local-end details of this new connection
348 details->local.InitAddrInfo(gai);
349 details->local.SetEmpty();
350 if (getsockname(sock, gai->ai_addr, &gai->ai_addrlen) != 0) {
351 debugs(50, DBG_IMPORTANT, "ERROR: getsockname() failed to locate local-IP on " << details << ": " << xstrerror());
352 details->local.FreeAddrInfo(gai);
353 return COMM_ERROR;
354 }
355 details->local = *gai;
356 details->local.FreeAddrInfo(gai);
357
358 /* fdstat update */
359 // XXX : these are not all HTTP requests. use a note about type and ip:port details->
360 // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
361 fd_open(sock, FD_SOCKET, "HTTP Request");
362
363 fdd_table[sock].close_file = NULL;
364 fdd_table[sock].close_line = 0;
365
366 fde *F = &fd_table[sock];
367 details->remote.NtoA(F->ipaddr,MAX_IPSTRLEN);
368 F->remote_port = details->remote.GetPort();
369 F->local_addr = details->local;
370 F->sock_family = details->local.IsIPv6()?AF_INET6:AF_INET;
371
372 // set socket flags
373 commSetCloseOnExec(sock);
374 commSetNonBlocking(sock);
375
376 /* IFF the socket is (tproxy) transparent, pass the flag down to allow spoofing */
377 F->flags.transparent = fd_table[conn->fd].flags.transparent; // XXX: can we remove this line yet?
378
379 // Perform NAT or TPROXY operations to retrieve the real client/dest IP addresses
380 if (conn->flags&(COMM_TRANSPARENT|COMM_INTERCEPTION) && !Ip::Interceptor.Lookup(details, conn)) {
381 // Failed.
382 return COMM_ERROR;
383 }
384
385 #if USE_SQUID_EUI
386 if (Eui::TheConfig.euiLookup) {
387 if (conn->remote.IsIPv4()) {
388 conn->remoteEui48.lookup(conn->remote);
389 } else if (conn->remote.IsIPv6()) {
390 conn->remoteEui64.lookup(conn->remote);
391 }
392 }
393 #endif
394
395 PROF_stop(comm_accept);
396 return COMM_OK;
397 }