2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 05 Socket Connection Opener */
12 #include "CachePeer.h"
14 #include "comm/Connection.h"
15 #include "comm/ConnOpener.h"
16 #include "comm/Loops.h"
20 #include "icmp/net_db.h"
21 #include "ip/QosConfig.h"
24 #include "SquidConfig.h"
30 CBDATA_NAMESPACED_CLASS_INIT(Comm
, ConnOpener
);
32 Comm::ConnOpener::ConnOpener(const Comm::ConnectionPointer
&c
, const AsyncCall::Pointer
&handler
, time_t ctimeout
) :
33 AsyncJob("Comm::ConnOpener"),
40 deadline_(squid_curtime
+ static_cast<time_t>(ctimeout
))
42 debugs(5, 3, "will connect to " << c
<< " with " << ctimeout
<< " timeout");
43 assert(conn_
); // we know where to go
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());
53 Comm::ConnOpener::~ConnOpener()
59 Comm::ConnOpener::doneAll() const
61 // is the conn_ to be opened still waiting?
62 if (conn_
== nullptr) {
63 return AsyncJob::doneAll();
66 // is the callback still to be called?
67 if (callback_
== nullptr || callback_
->canceled()) {
68 return AsyncJob::doneAll();
71 // otherwise, we must be waiting for something
72 Must(temporaryFd_
>= 0 || calls_
.sleep_
);
77 Comm::ConnOpener::swanSong()
79 if (callback_
!= nullptr) {
80 // inform the still-waiting caller we are dying
81 sendAnswer(Comm::ERR_CONNECT
, 0, "Comm::ConnOpener::swanSong");
84 // did we abort with a temporary FD assigned?
85 if (temporaryFd_
>= 0)
88 // did we abort while owning an open connection?
89 if (conn_
&& conn_
->isOpen())
92 // did we abort while waiting between retries?
100 Comm::ConnOpener::setHost(const char * new_host
)
102 // unset and erase if already set.
103 if (host_
!= nullptr)
106 // set the new one if given.
107 if (new_host
!= nullptr)
108 host_
= xstrdup(new_host
);
112 Comm::ConnOpener::getHost() const
118 * Connection attempt are completed. One way or the other.
119 * Pass the results back to the external handler.
122 Comm::ConnOpener::sendAnswer(Comm::Flag errFlag
, int xerrno
, const char *why
)
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
);
129 ipcacheMarkBadAddr(host_
, conn_
->remote
);
131 if (Config
.onoff
.test_reachability
)
132 netdbDeleteAddrNetwork(conn_
->remote
);
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 ?
147 // free resources earlier and simplify recipients
148 if (errFlag
!= Comm::OK
)
149 conn_
->close(); // may not be opened
151 assert(conn_
->isOpen());
153 typedef CommConnectCbParams Params
;
154 Params
¶ms
= GetCommParams
<Params
>(callback_
);
156 conn_
= nullptr; // release ownership; prevent closure by us
157 params
.flag
= errFlag
;
158 params
.xerrno
= xerrno
;
159 ScheduleCallHere(callback_
);
164 // The job will stop without this call because nil callback_ makes
165 // doneAll() true, but this explicit call creates nicer debugging.
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()
173 Comm::ConnOpener::cleanFd()
175 debugs(5, 4, conn_
<< "; temp FD " << temporaryFd_
);
177 Must(temporaryFd_
>= 0);
178 fde
&f
= fd_table
[temporaryFd_
];
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
) {
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.
192 delete static_cast<Pointer
*>(f
.write_data
);
193 f
.write_data
= nullptr;
194 f
.write_handler
= nullptr;
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
200 Comm::SetSelect(temporaryFd_
, COMM_SELECT_WRITE
, nullptr, nullptr, 0);
202 if (calls_
.timeout_
!= nullptr) {
203 calls_
.timeout_
->cancel("Comm::ConnOpener::cleanFd");
204 calls_
.timeout_
= nullptr;
206 // Comm checkTimeouts() and commCloseAllSockets() do not clear .timeout
207 // when calling timeoutHandler (XXX fix them), so we clear unconditionally.
208 f
.timeoutHandler
= nullptr;
211 if (calls_
.earlyAbort_
!= nullptr) {
212 comm_remove_close_handler(temporaryFd_
, calls_
.earlyAbort_
);
213 calls_
.earlyAbort_
= nullptr;
217 /// cleans I/O state and ends I/O for temporaryFd_
219 Comm::ConnOpener::closeFd()
221 if (temporaryFd_
< 0)
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);
232 comm_close(temporaryFd_
);
236 /// cleans I/O state and moves temporaryFd_ to the conn_ for long-term use
238 Comm::ConnOpener::keepFd()
240 Must(conn_
!= nullptr);
241 Must(temporaryFd_
>= 0);
245 conn_
->fd
= temporaryFd_
;
250 Comm::ConnOpener::start()
252 Must(conn_
!= nullptr);
254 /* outbound sockets have no need to be protocol agnostic. */
255 if (!(Ip::EnableIpv6
&IPV6_SPECIAL_V4MAPPING
) && conn_
->remote
.isIPv4()) {
256 conn_
->local
.setIPv4();
264 /// called at the end of Comm::ConnOpener::DelayedConnectRetry event
266 Comm::ConnOpener::restart()
268 debugs(5, 5, conn_
<< " restarting after sleep");
269 calls_
.sleep_
= false;
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.
278 Comm::ConnOpener::createFd()
280 Must(temporaryFd_
< 0);
283 // our initiators signal abort by cancelling their callbacks
284 if (callback_
== nullptr || callback_
->canceled())
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");
293 // Set TOS if needed.
295 Ip::Qos::setSockTos(temporaryFd_
, conn_
->tos
, conn_
->remote
.isIPv4() ? AF_INET
: AF_INET6
) < 0)
299 Ip::Qos::setSockNfmark(temporaryFd_
, conn_
->nfmark
) < 0)
303 fd_table
[temporaryFd_
].tosToServer
= conn_
->tos
;
304 fd_table
[temporaryFd_
].nfmarkToServer
= conn_
->nfmark
;
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_
);
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
));
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
¶ms
= GetCommParams
<Params
>(calls_
.timeout_
);
320 fd_table
[temporaryFd_
].timeoutHandler
= calls_
.timeout_
;
321 fd_table
[temporaryFd_
].timeout
= deadline_
;
327 Comm::ConnOpener::connected()
329 Must(temporaryFd_
>= 0);
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.
338 if (CachePeer
*peer
=(conn_
->getPeer()))
339 ++peer
->stats
.conn_open
;
341 lookupLocalAddress();
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.
348 Must(fd_table
[conn_
->fd
].flags
.open
);
349 fd_table
[conn_
->fd
].local_addr
= conn_
->local
;
351 sendAnswer(Comm::OK
, 0, "Comm::ConnOpener::connected");
354 /// Make an FD connection attempt.
356 Comm::ConnOpener::doConnect()
358 Must(conn_
!= nullptr);
359 Must(temporaryFd_
>= 0);
363 switch (comm_connect_addr(temporaryFd_
, conn_
->remote
) ) {
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);
371 debugs(5, 5, conn_
<< ": Comm::OK - connected");
376 const int xerrno
= errno
;
379 debugs(5, 7, conn_
<< ": failure #" << failRetries_
<< " <= " <<
380 Config
.connect_retries
<< ": " << xstrerr(xerrno
));
382 if (failRetries_
< Config
.connect_retries
) {
383 debugs(5, 5, conn_
<< ": * - try again");
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");
395 /// Close and wait a little before trying to open and connect again.
397 Comm::ConnOpener::retrySleep()
399 Must(!calls_
.sleep_
);
401 calls_
.sleep_
= true;
402 eventAdd("Comm::ConnOpener::DelayedConnectRetry",
403 Comm::ConnOpener::DelayedConnectRetry
,
404 new Pointer(this), 0.05, 0, false);
407 /// cleans up this job sleep state
409 Comm::ConnOpener::cancelSleep()
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");
426 * Lookup local-end address and port of the TCP link just opened.
427 * This ensure the connection local details are set correctly
430 Comm::ConnOpener::lookupLocalAddress()
432 struct addrinfo
*addr
= nullptr;
433 Ip::Address::InitAddr(addr
);
435 if (getsockname(conn_
->fd
, addr
->ai_addr
, &(addr
->ai_addrlen
)) != 0) {
437 debugs(50, DBG_IMPORTANT
, "ERROR: Failed to retrieve TCP/UDP details for socket: " << conn_
<< ": " << xstrerr(xerrno
));
438 Ip::Address::FreeAddr(addr
);
442 conn_
->local
= *addr
;
443 Ip::Address::FreeAddr(addr
);
447 /** Abort connection attempt.
448 * Handles the case(s) when a partially setup connection gets closed early.
451 Comm::ConnOpener::earlyAbort(const CommCloseCbParams
&io
)
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");
460 * Handles the case(s) when a partially setup connection gets timed out.
461 * NP: When commSetConnTimeout accepts generic CommCommonCbParams this can die.
464 Comm::ConnOpener::timeout(const CommTimeoutCbParams
&)
466 debugs(5, 5, conn_
<< ": * - ERR took too long to receive response.");
467 calls_
.timeout_
= nullptr;
468 sendAnswer(Comm::TIMEOUT
, ETIMEDOUT
, "Comm::ConnOpener::timeout");
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
475 Comm::ConnOpener::InProgressConnectRetry(int, void *data
)
477 Pointer
*ptr
= static_cast<Pointer
*>(data
);
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
);
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
493 Comm::ConnOpener::DelayedConnectRetry(void *data
)
495 Pointer
*ptr
= static_cast<Pointer
*>(data
);
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
);