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