]> git.ipfire.org Git - thirdparty/squid.git/blob - src/comm/ConnOpener.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / comm / ConnOpener.cc
1 /*
2 * Copyright (C) 1996-2017 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 Socket Connection Opener */
10
11 #include "squid.h"
12 #include "CachePeer.h"
13 #include "comm.h"
14 #include "comm/Connection.h"
15 #include "comm/ConnOpener.h"
16 #include "comm/Loops.h"
17 #include "fd.h"
18 #include "fde.h"
19 #include "globals.h"
20 #include "icmp/net_db.h"
21 #include "ip/QosConfig.h"
22 #include "ip/tools.h"
23 #include "ipcache.h"
24 #include "SquidConfig.h"
25 #include "SquidTime.h"
26
27 #include <cerrno>
28
29 class CachePeer;
30
31 CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener);
32
33 Comm::ConnOpener::ConnOpener(Comm::ConnectionPointer &c, AsyncCall::Pointer &handler, time_t ctimeout) :
34 AsyncJob("Comm::ConnOpener"),
35 host_(NULL),
36 temporaryFd_(-1),
37 conn_(c),
38 callback_(handler),
39 totalTries_(0),
40 failRetries_(0),
41 deadline_(squid_curtime + static_cast<time_t>(ctimeout))
42 {}
43
44 Comm::ConnOpener::~ConnOpener()
45 {
46 safe_free(host_);
47 }
48
49 bool
50 Comm::ConnOpener::doneAll() const
51 {
52 // is the conn_ to be opened still waiting?
53 if (conn_ == NULL) {
54 return AsyncJob::doneAll();
55 }
56
57 // is the callback still to be called?
58 if (callback_ == NULL || callback_->canceled()) {
59 return AsyncJob::doneAll();
60 }
61
62 // otherwise, we must be waiting for something
63 Must(temporaryFd_ >= 0 || calls_.sleep_);
64 return false;
65 }
66
67 void
68 Comm::ConnOpener::swanSong()
69 {
70 if (callback_ != NULL) {
71 // inform the still-waiting caller we are dying
72 sendAnswer(Comm::ERR_CONNECT, 0, "Comm::ConnOpener::swanSong");
73 }
74
75 // did we abort with a temporary FD assigned?
76 if (temporaryFd_ >= 0)
77 closeFd();
78
79 // did we abort while waiting between retries?
80 if (calls_.sleep_)
81 cancelSleep();
82
83 AsyncJob::swanSong();
84 }
85
86 void
87 Comm::ConnOpener::setHost(const char * new_host)
88 {
89 // unset and erase if already set.
90 if (host_ != NULL)
91 safe_free(host_);
92
93 // set the new one if given.
94 if (new_host != NULL)
95 host_ = xstrdup(new_host);
96 }
97
98 const char *
99 Comm::ConnOpener::getHost() const
100 {
101 return host_;
102 }
103
104 /**
105 * Connection attempt are completed. One way or the other.
106 * Pass the results back to the external handler.
107 */
108 void
109 Comm::ConnOpener::sendAnswer(Comm::Flag errFlag, int xerrno, const char *why)
110 {
111 // only mark the address good/bad AFTER connect is finished.
112 if (host_ != NULL) {
113 if (xerrno == 0) // XXX: should not we use errFlag instead?
114 ipcacheMarkGoodAddr(host_, conn_->remote);
115 else {
116 ipcacheMarkBadAddr(host_, conn_->remote);
117 #if USE_ICMP
118 if (Config.onoff.test_reachability)
119 netdbDeleteAddrNetwork(conn_->remote);
120 #endif
121 }
122 }
123
124 if (callback_ != NULL) {
125 // avoid scheduling cancelled callbacks, assuming they are common
126 // enough to make this extra check an optimization
127 if (callback_->canceled()) {
128 debugs(5, 4, conn_ << " not calling canceled " << *callback_ <<
129 " [" << callback_->id << ']' );
130 // TODO save the pconn to the pconnPool ?
131 } else {
132 typedef CommConnectCbParams Params;
133 Params &params = GetCommParams<Params>(callback_);
134 params.conn = conn_;
135 params.flag = errFlag;
136 params.xerrno = xerrno;
137 ScheduleCallHere(callback_);
138 }
139 callback_ = NULL;
140 }
141
142 // The job will stop without this call because nil callback_ makes
143 // doneAll() true, but this explicit call creates nicer debugging.
144 mustStop(why);
145 }
146
147 /// cleans up this job I/O state without closing temporaryFd
148 /// required before closing temporaryFd or keeping it in conn_
149 /// leaves FD bare so must only be called via closeFd() or keepFd()
150 void
151 Comm::ConnOpener::cleanFd()
152 {
153 debugs(5, 4, HERE << conn_ << " closing temp FD " << temporaryFd_);
154
155 Must(temporaryFd_ >= 0);
156 fde &f = fd_table[temporaryFd_];
157
158 // Our write_handler was set without using Comm::Write API, so we cannot
159 // use a cancellable Pointer-free job callback and simply cancel it here.
160 if (f.write_handler) {
161
162 /* XXX: We are about to remove write_handler, which was responsible
163 * for deleting write_data, so we have to delete write_data
164 * ourselves. Comm currently calls SetSelect handlers synchronously
165 * so if write_handler is set, we know it has not been called yet.
166 * ConnOpener converts that sync call into an async one, but only
167 * after deleting ptr, so that is not a problem.
168 */
169
170 delete static_cast<Pointer*>(f.write_data);
171 f.write_data = NULL;
172 f.write_handler = NULL;
173 }
174 // Comm::DoSelect does not do this when calling and resetting write_handler
175 // (because it expects more writes to come?). We could mimic that
176 // optimization by resetting Comm "Select" state only when the FD is
177 // actually closed.
178 Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, NULL, NULL, 0);
179
180 if (calls_.timeout_ != NULL) {
181 calls_.timeout_->cancel("Comm::ConnOpener::cleanFd");
182 calls_.timeout_ = NULL;
183 }
184 // Comm checkTimeouts() and commCloseAllSockets() do not clear .timeout
185 // when calling timeoutHandler (XXX fix them), so we clear unconditionally.
186 f.timeoutHandler = NULL;
187 f.timeout = 0;
188
189 if (calls_.earlyAbort_ != NULL) {
190 comm_remove_close_handler(temporaryFd_, calls_.earlyAbort_);
191 calls_.earlyAbort_ = NULL;
192 }
193 }
194
195 /// cleans I/O state and ends I/O for temporaryFd_
196 void
197 Comm::ConnOpener::closeFd()
198 {
199 if (temporaryFd_ < 0)
200 return;
201
202 cleanFd();
203
204 // comm_close() below uses COMMIO_FD_WRITECB(fd)->active() to clear Comm
205 // "Select" state. It will not clear ours. XXX: It should always clear
206 // because a callback may have been active but was called before comm_close
207 // Update: we now do this in cleanFd()
208 // Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, NULL, NULL, 0);
209
210 comm_close(temporaryFd_);
211 temporaryFd_ = -1;
212 }
213
214 /// cleans I/O state and moves temporaryFd_ to the conn_ for long-term use
215 void
216 Comm::ConnOpener::keepFd()
217 {
218 Must(conn_ != NULL);
219 Must(temporaryFd_ >= 0);
220
221 cleanFd();
222
223 conn_->fd = temporaryFd_;
224 temporaryFd_ = -1;
225 }
226
227 void
228 Comm::ConnOpener::start()
229 {
230 Must(conn_ != NULL);
231
232 /* outbound sockets have no need to be protocol agnostic. */
233 if (!(Ip::EnableIpv6&IPV6_SPECIAL_V4MAPPING) && conn_->remote.isIPv4()) {
234 conn_->local.setIPv4();
235 }
236
237 conn_->noteStart();
238 if (createFd())
239 doConnect();
240 }
241
242 /// called at the end of Comm::ConnOpener::DelayedConnectRetry event
243 void
244 Comm::ConnOpener::restart()
245 {
246 debugs(5, 5, conn_ << " restarting after sleep");
247 calls_.sleep_ = false;
248
249 if (createFd())
250 doConnect();
251 }
252
253 /// Create a socket for the future connection or return false.
254 /// If false is returned, done() is guaranteed to return true and end the job.
255 bool
256 Comm::ConnOpener::createFd()
257 {
258 Must(temporaryFd_ < 0);
259
260 // our initators signal abort by cancelling their callbacks
261 if (callback_ == NULL || callback_->canceled())
262 return false;
263
264 temporaryFd_ = comm_openex(SOCK_STREAM, IPPROTO_TCP, conn_->local, conn_->flags, host_);
265 if (temporaryFd_ < 0) {
266 sendAnswer(Comm::ERR_CONNECT, 0, "Comm::ConnOpener::createFd");
267 return false;
268 }
269
270 // Set TOS if needed.
271 if (conn_->tos &&
272 Ip::Qos::setSockTos(temporaryFd_, conn_->tos, conn_->remote.isIPv4() ? AF_INET : AF_INET6) < 0)
273 conn_->tos = 0;
274 #if SO_MARK
275 if (conn_->nfmark &&
276 Ip::Qos::setSockNfmark(temporaryFd_, conn_->nfmark) < 0)
277 conn_->nfmark = 0;
278 #endif
279
280 fd_table[temporaryFd_].tosToServer = conn_->tos;
281 fd_table[temporaryFd_].nfmarkToServer = conn_->nfmark;
282
283 typedef CommCbMemFunT<Comm::ConnOpener, CommCloseCbParams> abortDialer;
284 calls_.earlyAbort_ = JobCallback(5, 4, abortDialer, this, Comm::ConnOpener::earlyAbort);
285 comm_add_close_handler(temporaryFd_, calls_.earlyAbort_);
286
287 typedef CommCbMemFunT<Comm::ConnOpener, CommTimeoutCbParams> timeoutDialer;
288 calls_.timeout_ = JobCallback(5, 4, timeoutDialer, this, Comm::ConnOpener::timeout);
289 debugs(5, 3, conn_ << " will timeout in " << (deadline_ - squid_curtime));
290
291 // Update the fd_table directly because commSetConnTimeout() needs open conn_
292 assert(temporaryFd_ < Squid_MaxFD);
293 assert(fd_table[temporaryFd_].flags.open);
294 typedef CommTimeoutCbParams Params;
295 Params &params = GetCommParams<Params>(calls_.timeout_);
296 params.conn = conn_;
297 fd_table[temporaryFd_].timeoutHandler = calls_.timeout_;
298 fd_table[temporaryFd_].timeout = deadline_;
299
300 return true;
301 }
302
303 void
304 Comm::ConnOpener::connected()
305 {
306 Must(temporaryFd_ >= 0);
307 keepFd();
308
309 /*
310 * stats.conn_open is used to account for the number of
311 * connections that we have open to the CachePeer, so we can limit
312 * based on the max-conn option. We need to increment here,
313 * even if the connection may fail.
314 */
315 if (CachePeer *peer=(conn_->getPeer()))
316 ++peer->stats.conn_open;
317
318 lookupLocalAddress();
319
320 /* TODO: remove these fd_table accesses. But old code still depends on fd_table flags to
321 * indicate the state of a raw fd object being passed around.
322 * Also, legacy code still depends on comm_local_port() with no access to Comm::Connection
323 * when those are done comm_local_port can become one of our member functions to do the below.
324 */
325 Must(fd_table[conn_->fd].flags.open);
326 fd_table[conn_->fd].local_addr = conn_->local;
327
328 sendAnswer(Comm::OK, 0, "Comm::ConnOpener::connected");
329 }
330
331 /// Make an FD connection attempt.
332 void
333 Comm::ConnOpener::doConnect()
334 {
335 Must(conn_ != NULL);
336 Must(temporaryFd_ >= 0);
337
338 ++ totalTries_;
339
340 switch (comm_connect_addr(temporaryFd_, conn_->remote) ) {
341
342 case Comm::INPROGRESS:
343 debugs(5, 5, HERE << conn_ << ": Comm::INPROGRESS");
344 Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, Comm::ConnOpener::InProgressConnectRetry, new Pointer(this), 0);
345 break;
346
347 case Comm::OK:
348 debugs(5, 5, HERE << conn_ << ": Comm::OK - connected");
349 connected();
350 break;
351
352 default: {
353 const int xerrno = errno;
354
355 ++failRetries_;
356 debugs(5, 7, conn_ << ": failure #" << failRetries_ << " <= " <<
357 Config.connect_retries << ": " << xstrerr(xerrno));
358
359 if (failRetries_ < Config.connect_retries) {
360 debugs(5, 5, HERE << conn_ << ": * - try again");
361 retrySleep();
362 return;
363 } else {
364 // send ERROR back to the upper layer.
365 debugs(5, 5, HERE << conn_ << ": * - ERR tried too many times already.");
366 sendAnswer(Comm::ERR_CONNECT, xerrno, "Comm::ConnOpener::doConnect");
367 }
368 }
369 }
370 }
371
372 /// Close and wait a little before trying to open and connect again.
373 void
374 Comm::ConnOpener::retrySleep()
375 {
376 Must(!calls_.sleep_);
377 closeFd();
378 calls_.sleep_ = true;
379 eventAdd("Comm::ConnOpener::DelayedConnectRetry",
380 Comm::ConnOpener::DelayedConnectRetry,
381 new Pointer(this), 0.05, 0, false);
382 }
383
384 /// cleans up this job sleep state
385 void
386 Comm::ConnOpener::cancelSleep()
387 {
388 if (calls_.sleep_) {
389 // It would be nice to delete the sleep event, but it might be out of
390 // the event queue and in the async queue already, so (a) we do not know
391 // whether we can safely delete the call ptr here and (b) eventDelete()
392 // will assert if the event went async. Thus, we let the event run so
393 // that it deletes the call ptr [after this job is gone]. Note that we
394 // are called only when the job ends so this "hanging event" will do
395 // nothing but deleting the call ptr. TODO: Revise eventDelete() API.
396 // eventDelete(Comm::ConnOpener::DelayedConnectRetry, calls_.sleep);
397 calls_.sleep_ = false;
398 debugs(5, 9, conn_ << " stops sleeping");
399 }
400 }
401
402 /**
403 * Lookup local-end address and port of the TCP link just opened.
404 * This ensure the connection local details are set correctly
405 */
406 void
407 Comm::ConnOpener::lookupLocalAddress()
408 {
409 struct addrinfo *addr = NULL;
410 Ip::Address::InitAddr(addr);
411
412 if (getsockname(conn_->fd, addr->ai_addr, &(addr->ai_addrlen)) != 0) {
413 int xerrno = errno;
414 debugs(50, DBG_IMPORTANT, "ERROR: Failed to retrieve TCP/UDP details for socket: " << conn_ << ": " << xstrerr(xerrno));
415 Ip::Address::FreeAddr(addr);
416 return;
417 }
418
419 conn_->local = *addr;
420 Ip::Address::FreeAddr(addr);
421 debugs(5, 6, HERE << conn_);
422 }
423
424 /** Abort connection attempt.
425 * Handles the case(s) when a partially setup connection gets closed early.
426 */
427 void
428 Comm::ConnOpener::earlyAbort(const CommCloseCbParams &io)
429 {
430 debugs(5, 3, HERE << io.conn);
431 calls_.earlyAbort_ = NULL;
432 // NP: is closing or shutdown better?
433 sendAnswer(Comm::ERR_CLOSING, io.xerrno, "Comm::ConnOpener::earlyAbort");
434 }
435
436 /**
437 * Handles the case(s) when a partially setup connection gets timed out.
438 * NP: When commSetConnTimeout accepts generic CommCommonCbParams this can die.
439 */
440 void
441 Comm::ConnOpener::timeout(const CommTimeoutCbParams &)
442 {
443 debugs(5, 5, HERE << conn_ << ": * - ERR took too long to receive response.");
444 calls_.timeout_ = NULL;
445 sendAnswer(Comm::TIMEOUT, ETIMEDOUT, "Comm::ConnOpener::timeout");
446 }
447
448 /* Legacy Wrapper for the retry event after Comm::INPROGRESS
449 * XXX: As soon as Comm::SetSelect() accepts Async calls we can use a ConnOpener::doConnect call
450 */
451 void
452 Comm::ConnOpener::InProgressConnectRetry(int, void *data)
453 {
454 Pointer *ptr = static_cast<Pointer*>(data);
455 assert(ptr);
456 if (ConnOpener *cs = ptr->valid()) {
457 // Ew. we are now outside the all AsyncJob protections.
458 // get back inside by scheduling another call...
459 typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
460 AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::doConnect);
461 ScheduleCallHere(call);
462 }
463 delete ptr;
464 }
465
466 /* Legacy Wrapper for the retry event with small delay after errors.
467 * XXX: As soon as eventAdd() accepts Async calls we can use a ConnOpener::restart call
468 */
469 void
470 Comm::ConnOpener::DelayedConnectRetry(void *data)
471 {
472 Pointer *ptr = static_cast<Pointer*>(data);
473 assert(ptr);
474 if (ConnOpener *cs = ptr->valid()) {
475 // Ew. we are now outside the all AsyncJob protections.
476 // get back inside by scheduling another call...
477 typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
478 AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::restart);
479 ScheduleCallHere(call);
480 }
481 delete ptr;
482 }
483