]> git.ipfire.org Git - thirdparty/squid.git/blob - src/comm/TcpAcceptor.cc
Merged 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 "CommCalls.h"
38 #include "comm/AcceptLimiter.h"
39 #include "comm/comm_internal.h"
40 #include "comm/Connection.h"
41 #include "comm/Loops.h"
42 #include "comm/TcpAcceptor.h"
43 #include "fde.h"
44 #include "ip/Intercept.h"
45 #include "protos.h"
46 #include "SquidTime.h"
47
48 namespace Comm
49 {
50 CBDATA_CLASS_INIT(TcpAcceptor);
51 };
52
53 Comm::TcpAcceptor::TcpAcceptor(const Comm::ConnectionPointer &newConn, const char *note, const Subscription::Pointer &aSub) :
54 AsyncJob("Comm::TcpAcceptor"),
55 errcode(0),
56 isLimited(0),
57 theCallSub(aSub),
58 conn(newConn)
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 // 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 conn = NULL;
113 AcceptLimiter::Instance().removeDead(this);
114 AsyncJob::swanSong();
115 }
116
117 const char *
118 Comm::TcpAcceptor::status() const
119 {
120 if (conn == NULL)
121 return "[nil connection]";
122
123 static char ipbuf[MAX_IPSTRLEN] = {'\0'};
124 if (ipbuf[0] == '\0')
125 conn->local.ToHostname(ipbuf, MAX_IPSTRLEN);
126
127 static MemBuf buf;
128 buf.reset();
129 buf.Printf(" FD %d, %s",conn->fd, ipbuf);
130
131 const char *jobStatus = AsyncJob::status();
132 buf.append(jobStatus, strlen(jobStatus));
133
134 return buf.content();
135 }
136
137 /**
138 * New-style listen and accept routines
139 *
140 * setListen simply registers our interest in an FD for listening.
141 * The constructor takes a callback to call when an FD has been
142 * accept()ed some time later.
143 */
144 void
145 Comm::TcpAcceptor::setListen()
146 {
147 errcode = 0; // reset local errno copy.
148 if (listen(conn->fd, Squid_MaxFD >> 2) < 0) {
149 debugs(50, DBG_CRITICAL, "ERROR: listen(" << status() << ", " << (Squid_MaxFD >> 2) << "): " << xstrerror());
150 errcode = errno;
151 return;
152 }
153
154 if (Config.accept_filter && strcmp(Config.accept_filter, "none") != 0) {
155 #ifdef SO_ACCEPTFILTER
156 struct accept_filter_arg afa;
157 bzero(&afa, sizeof(afa));
158 debugs(5, DBG_IMPORTANT, "Installing accept filter '" << Config.accept_filter << "' on " << conn);
159 xstrncpy(afa.af_name, Config.accept_filter, sizeof(afa.af_name));
160 if (setsockopt(conn->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0)
161 debugs(5, DBG_CRITICAL, "WARNING: SO_ACCEPTFILTER '" << Config.accept_filter << "': '" << xstrerror());
162 #elif defined(TCP_DEFER_ACCEPT)
163 int seconds = 30;
164 if (strncmp(Config.accept_filter, "data=", 5) == 0)
165 seconds = atoi(Config.accept_filter + 5);
166 if (setsockopt(conn->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &seconds, sizeof(seconds)) < 0)
167 debugs(5, DBG_CRITICAL, "WARNING: TCP_DEFER_ACCEPT '" << Config.accept_filter << "': '" << xstrerror());
168 #else
169 debugs(5, DBG_CRITICAL, "WARNING: accept_filter not supported on your OS");
170 #endif
171 }
172 }
173
174 /**
175 * This private callback is called whenever a filedescriptor is ready
176 * to dupe itself and fob off an accept()ed connection
177 *
178 * It will either do that accept operation. Or if there are not enough FD
179 * available to do the clone safely will push the listening FD into a list
180 * of deferred operations. The list gets kicked and the dupe/accept() actually
181 * done later when enough sockets become available.
182 */
183 void
184 Comm::TcpAcceptor::doAccept(int fd, void *data)
185 {
186 try {
187 debugs(5, 2, HERE << "New connection on FD " << fd);
188
189 Must(isOpen(fd));
190 TcpAcceptor *afd = static_cast<TcpAcceptor*>(data);
191
192 if (!okToAccept()) {
193 AcceptLimiter::Instance().defer(afd);
194 } else {
195 afd->acceptNext();
196 }
197 SetSelect(fd, COMM_SELECT_READ, Comm::TcpAcceptor::doAccept, afd, 0);
198
199 } catch (const std::exception &e) {
200 fatalf("FATAL: error while accepting new client connection: %s\n", e.what());
201 } catch (...) {
202 fatal("FATAL: error while accepting new client connection: [unkown]\n");
203 }
204 }
205
206 bool
207 Comm::TcpAcceptor::okToAccept()
208 {
209 static time_t last_warn = 0;
210
211 if (fdNFree() >= RESERVED_FD)
212 return true;
213
214 if (last_warn + 15 < squid_curtime) {
215 debugs(5, DBG_CRITICAL, "WARNING! Your cache is running out of filedescriptors");
216 last_warn = squid_curtime;
217 }
218
219 return false;
220 }
221
222 void
223 Comm::TcpAcceptor::acceptOne()
224 {
225 /*
226 * We don't worry about running low on FDs here. Instead,
227 * doAccept() will use AcceptLimiter if we reach the limit
228 * there.
229 */
230
231 /* Accept a new connection */
232 ConnectionPointer newConnDetails = new Connection();
233 const comm_err_t flag = oldAccept(newConnDetails);
234
235 /* Check for errors */
236 if (!newConnDetails->isOpen()) {
237
238 if (flag == COMM_NOMESSAGE) {
239 /* register interest again */
240 debugs(5, 5, HERE << "try later: " << conn << " handler Subscription: " << theCallSub);
241 SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0);
242 return;
243 }
244
245 // A non-recoverable error; notify the caller */
246 debugs(5, 5, HERE << "non-recoverable error:" << status() << " handler Subscription: " << theCallSub);
247 notify(flag, newConnDetails);
248 mustStop("Listener socket closed");
249 return;
250 }
251
252 debugs(5, 5, HERE << "Listener: " << conn <<
253 " accepted new connection " << newConnDetails <<
254 " handler Subscription: " << theCallSub);
255 notify(flag, newConnDetails);
256 }
257
258 void
259 Comm::TcpAcceptor::acceptNext()
260 {
261 Must(IsConnOpen(conn));
262 debugs(5, 2, HERE << "connection on " << conn);
263 acceptOne();
264 }
265
266 void
267 Comm::TcpAcceptor::notify(const comm_err_t flag, const Comm::ConnectionPointer &newConnDetails) const
268 {
269 // listener socket handlers just abandon the port with COMM_ERR_CLOSING
270 // it should only happen when this object is deleted...
271 if (flag == COMM_ERR_CLOSING) {
272 return;
273 }
274
275 if (theCallSub != NULL) {
276 AsyncCall::Pointer call = theCallSub->callback();
277 CommAcceptCbParams &params = GetCommParams<CommAcceptCbParams>(call);
278 params.fd = conn->fd;
279 params.conn = newConnDetails;
280 params.flag = flag;
281 params.xerrno = errcode;
282 ScheduleCallHere(call);
283 }
284 }
285
286 /**
287 * accept() and process
288 * Wait for an incoming connection on our listener socket.
289 *
290 * \retval COMM_OK success. details parameter filled.
291 * \retval COMM_NOMESSAGE attempted accept() but nothing useful came in.
292 * \retval COMM_ERROR an outright failure occured.
293 * Or if this client has too many connections already.
294 */
295 comm_err_t
296 Comm::TcpAcceptor::oldAccept(Comm::ConnectionPointer &details)
297 {
298 PROF_start(comm_accept);
299 statCounter.syscalls.sock.accepts++;
300 int sock;
301 struct addrinfo *gai = NULL;
302 details->local.InitAddrInfo(gai);
303
304 errcode = 0; // reset local errno copy.
305 if ((sock = accept(conn->fd, gai->ai_addr, &gai->ai_addrlen)) < 0) {
306 errcode = errno; // store last accept errno locally.
307
308 details->local.FreeAddrInfo(gai);
309
310 PROF_stop(comm_accept);
311
312 if (ignoreErrno(errno)) {
313 debugs(50, 5, HERE << status() << ": " << xstrerror());
314 return COMM_NOMESSAGE;
315 } else if (ENFILE == errno || EMFILE == errno) {
316 debugs(50, 3, HERE << status() << ": " << xstrerror());
317 return COMM_ERROR;
318 } else {
319 debugs(50, 1, HERE << status() << ": " << xstrerror());
320 return COMM_ERROR;
321 }
322 }
323
324 Must(sock >= 0);
325 details->fd = sock;
326 details->remote = *gai;
327
328 if ( Config.client_ip_max_connections >= 0) {
329 if (clientdbEstablished(details->remote, 0) > Config.client_ip_max_connections) {
330 debugs(50, DBG_IMPORTANT, "WARNING: " << details->remote << " attempting more than " << Config.client_ip_max_connections << " connections.");
331 details->local.FreeAddrInfo(gai);
332 return COMM_ERROR;
333 }
334 }
335
336 // lookup the local-end details of this new connection
337 details->local.InitAddrInfo(gai);
338 details->local.SetEmpty();
339 getsockname(sock, gai->ai_addr, &gai->ai_addrlen);
340 details->local = *gai;
341 details->local.FreeAddrInfo(gai);
342
343 /* fdstat update */
344 // XXX : these are not all HTTP requests. use a note about type and ip:port details->
345 // so we end up with a uniform "(HTTP|FTP-data|HTTPS|...) remote-ip:remote-port"
346 fd_open(sock, FD_SOCKET, "HTTP Request");
347
348 fdd_table[sock].close_file = NULL;
349 fdd_table[sock].close_line = 0;
350
351 fde *F = &fd_table[sock];
352 details->remote.NtoA(F->ipaddr,MAX_IPSTRLEN);
353 F->remote_port = details->remote.GetPort();
354 F->local_addr = details->local;
355 F->sock_family = details->local.IsIPv6()?AF_INET6:AF_INET;
356
357 // set socket flags
358 commSetCloseOnExec(sock);
359 commSetNonBlocking(sock);
360
361 /* IFF the socket is (tproxy) transparent, pass the flag down to allow spoofing */
362 F->flags.transparent = fd_table[conn->fd].flags.transparent; // XXX: can we remove this line yet?
363
364 // Perform NAT or TPROXY operations to retrieve the real client/dest IP addresses
365 if (conn->flags&(COMM_TRANSPARENT|COMM_INTERCEPTION) && !Ip::Interceptor.Lookup(details, conn)) {
366 // Failed.
367 return COMM_ERROR;
368 }
369
370 PROF_stop(comm_accept);
371 return COMM_OK;
372 }