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