Squid Web Cache master
Loading...
Searching...
No Matches
ConnOpener.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2025 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 "compat/socket.h"
18#include "fd.h"
19#include "fde.h"
20#include "globals.h"
21#include "icmp/net_db.h"
22#include "ip/QosConfig.h"
23#include "ip/tools.h"
24#include "ipcache.h"
25#include "SquidConfig.h"
26
27#include <cerrno>
28
29class CachePeer;
30
32
34 AsyncJob("Comm::ConnOpener"),
35 host_(nullptr),
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 debugs(5, 3, "will connect to " << c << " with " << ctimeout << " timeout");
44 assert(conn_); // we know where to go
45
46 // Sharing a being-modified Connection object with the caller is dangerous,
47 // but we cannot ban (or even check for) that using existing APIs. We do not
48 // want to clone "just in case" because cloning is a bit expensive, and most
49 // callers already have a non-owned Connection object to give us. Until the
50 // APIs improve, we can only check that the connection is not open.
51 assert(!conn_->isOpen());
52}
53
58
59bool
61{
62 // is the conn_ to be opened still waiting?
63 if (conn_ == nullptr) {
64 return AsyncJob::doneAll();
65 }
66
67 // is the callback still to be called?
68 if (callback_ == nullptr || callback_->canceled()) {
69 return AsyncJob::doneAll();
70 }
71
72 // otherwise, we must be waiting for something
73 Must(temporaryFd_ >= 0 || calls_.sleep_);
74 return false;
75}
76
77void
79{
80 if (callback_ != nullptr) {
81 // inform the still-waiting caller we are dying
82 sendAnswer(Comm::ERR_CONNECT, 0, "Comm::ConnOpener::swanSong");
83 }
84
85 // did we abort with a temporary FD assigned?
86 if (temporaryFd_ >= 0)
87 closeFd();
88
89 // did we abort while owning an open connection?
90 if (conn_ && conn_->isOpen())
91 conn_->close();
92
93 // did we abort while waiting between retries?
94 if (calls_.sleep_)
95 cancelSleep();
96
98}
99
100void
101Comm::ConnOpener::setHost(const char * new_host)
102{
103 // unset and erase if already set.
104 if (host_ != nullptr)
105 safe_free(host_);
106
107 // set the new one if given.
108 if (new_host != nullptr)
109 host_ = xstrdup(new_host);
110}
111
112const char *
114{
115 return host_;
116}
117
122void
123Comm::ConnOpener::sendAnswer(Comm::Flag errFlag, int xerrno, const char *why)
124{
125 // only mark the address good/bad AFTER connect is finished.
126 if (host_ != nullptr) {
127 if (xerrno == 0) // XXX: should not we use errFlag instead?
128 ipcacheMarkGoodAddr(host_, conn_->remote);
129 else {
130 ipcacheMarkBadAddr(host_, conn_->remote);
131#if USE_ICMP
133 netdbDeleteAddrNetwork(conn_->remote);
134#endif
135 }
136 }
137
138 if (callback_ != nullptr) {
139 // avoid scheduling cancelled callbacks, assuming they are common
140 // enough to make this extra check an optimization
141 if (callback_->canceled()) {
142 debugs(5, 4, conn_ << " not calling canceled " << *callback_ <<
143 " [" << callback_->id << ']' );
144 // TODO save the pconn to the pconnPool ?
145 } else {
146 assert(conn_);
147
148 // free resources earlier and simplify recipients
149 if (errFlag != Comm::OK)
150 conn_->close(); // may not be opened
151 else
152 assert(conn_->isOpen());
153
154 typedef CommConnectCbParams Params;
155 Params &params = GetCommParams<Params>(callback_);
156 params.conn = conn_;
157 conn_ = nullptr; // release ownership; prevent closure by us
158 params.flag = errFlag;
159 params.xerrno = xerrno;
160 ScheduleCallHere(callback_);
161 }
162 callback_ = nullptr;
163 }
164
165 // The job will stop without this call because nil callback_ makes
166 // doneAll() true, but this explicit call creates nicer debugging.
167 mustStop(why);
168}
169
173void
175{
176 debugs(5, 4, conn_ << "; temp FD " << temporaryFd_);
177
178 Must(temporaryFd_ >= 0);
179 fde &f = fd_table[temporaryFd_];
180
181 // Our write_handler was set without using Comm::Write API, so we cannot
182 // use a cancellable Pointer-free job callback and simply cancel it here.
183 if (f.write_handler) {
184
185 /* XXX: We are about to remove write_handler, which was responsible
186 * for deleting write_data, so we have to delete write_data
187 * ourselves. Comm currently calls SetSelect handlers synchronously
188 * so if write_handler is set, we know it has not been called yet.
189 * ConnOpener converts that sync call into an async one, but only
190 * after deleting ptr, so that is not a problem.
191 */
192
193 delete static_cast<Pointer*>(f.write_data);
194 f.write_data = nullptr;
195 f.write_handler = nullptr;
196 }
197 // Comm::DoSelect does not do this when calling and resetting write_handler
198 // (because it expects more writes to come?). We could mimic that
199 // optimization by resetting Comm "Select" state only when the FD is
200 // actually closed.
201 Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, nullptr, nullptr, 0);
202
203 if (calls_.timeout_ != nullptr) {
204 calls_.timeout_->cancel("Comm::ConnOpener::cleanFd");
205 calls_.timeout_ = nullptr;
206 }
207 // Comm checkTimeouts() and commCloseAllSockets() do not clear .timeout
208 // when calling timeoutHandler (XXX fix them), so we clear unconditionally.
209 f.timeoutHandler = nullptr;
210 f.timeout = 0;
211
212 if (calls_.earlyAbort_ != nullptr) {
213 comm_remove_close_handler(temporaryFd_, calls_.earlyAbort_);
214 calls_.earlyAbort_ = nullptr;
215 }
216}
217
219void
221{
222 if (temporaryFd_ < 0)
223 return;
224
225 cleanFd();
226
227 // comm_close() below uses COMMIO_FD_WRITECB(fd)->active() to clear Comm
228 // "Select" state. It will not clear ours. XXX: It should always clear
229 // because a callback may have been active but was called before comm_close
230 // Update: we now do this in cleanFd()
231 // Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, nullptr, nullptr, 0);
232
233 comm_close(temporaryFd_);
234 temporaryFd_ = -1;
235}
236
238void
240{
241 Must(conn_ != nullptr);
242 Must(temporaryFd_ >= 0);
243
244 cleanFd();
245
246 conn_->fd = temporaryFd_;
247 temporaryFd_ = -1;
248}
249
250void
252{
253 Must(conn_ != nullptr);
254
255 /* outbound sockets have no need to be protocol agnostic. */
256 if (!(Ip::EnableIpv6&IPV6_SPECIAL_V4MAPPING) && conn_->remote.isIPv4()) {
257 conn_->local.setIPv4();
258 }
259
260 conn_->noteStart();
261 if (createFd())
262 doConnect();
263}
264
266void
268{
269 debugs(5, 5, conn_ << " restarting after sleep");
270 calls_.sleep_ = false;
271
272 if (createFd())
273 doConnect();
274}
275
278bool
280{
281 Must(temporaryFd_ < 0);
282 assert(conn_);
283
284 // our initiators signal abort by cancelling their callbacks
285 if (callback_ == nullptr || callback_->canceled())
286 return false;
287
288 temporaryFd_ = comm_open(SOCK_STREAM, IPPROTO_TCP, conn_->local, conn_->flags, host_);
289 if (temporaryFd_ < 0) {
290 sendAnswer(Comm::ERR_CONNECT, 0, "Comm::ConnOpener::createFd");
291 return false;
292 }
293
294 // Set TOS if needed.
295 if (conn_->tos &&
296 Ip::Qos::setSockTos(temporaryFd_, conn_->tos, conn_->remote.isIPv4() ? AF_INET : AF_INET6) < 0)
297 conn_->tos = 0;
298#if SO_MARK
299 if (conn_->nfmark &&
300 Ip::Qos::setSockNfmark(temporaryFd_, conn_->nfmark) < 0)
301 conn_->nfmark = 0;
302#endif
303
304 fd_table[temporaryFd_].tosToServer = conn_->tos;
305 fd_table[temporaryFd_].nfmarkToServer = conn_->nfmark;
306
308 calls_.earlyAbort_ = JobCallback(5, 4, abortDialer, this, Comm::ConnOpener::earlyAbort);
309 comm_add_close_handler(temporaryFd_, calls_.earlyAbort_);
310
312 calls_.timeout_ = JobCallback(5, 4, timeoutDialer, this, Comm::ConnOpener::timeout);
313 debugs(5, 3, conn_ << " will timeout in " << (deadline_ - squid_curtime));
314
315 // Update the fd_table directly because commSetConnTimeout() needs open conn_
316 assert(temporaryFd_ < Squid_MaxFD);
317 assert(fd_table[temporaryFd_].flags.open);
318 typedef CommTimeoutCbParams Params;
319 Params &params = GetCommParams<Params>(calls_.timeout_);
320 params.conn = conn_;
321 fd_table[temporaryFd_].timeoutHandler = calls_.timeout_;
322 fd_table[temporaryFd_].timeout = deadline_;
323
324 return true;
325}
326
327void
329{
330 Must(temporaryFd_ >= 0);
331 keepFd();
332
333 /*
334 * stats.conn_open is used to account for the number of
335 * connections that we have open to the CachePeer, so we can limit
336 * based on the max-conn option. We need to increment here,
337 * even if the connection may fail.
338 */
339 if (CachePeer *peer=(conn_->getPeer()))
340 ++peer->stats.conn_open;
341
342 lookupLocalAddress();
343
344 /* TODO: remove these fd_table accesses. But old code still depends on fd_table flags to
345 * indicate the state of a raw fd object being passed around.
346 * Also, legacy code still depends on comm_local_port() with no access to Comm::Connection
347 * when those are done comm_local_port can become one of our member functions to do the below.
348 */
349 Must(fd_table[conn_->fd].flags.open);
350 fd_table[conn_->fd].local_addr = conn_->local;
351
352 sendAnswer(Comm::OK, 0, "Comm::ConnOpener::connected");
353}
354
356void
358{
359 Must(conn_ != nullptr);
360 Must(temporaryFd_ >= 0);
361
362 ++ totalTries_;
363
364 switch (comm_connect_addr(temporaryFd_, conn_->remote) ) {
365
366 case Comm::INPROGRESS:
367 debugs(5, 5, conn_ << ": Comm::INPROGRESS");
369 break;
370
371 case Comm::OK:
372 debugs(5, 5, conn_ << ": Comm::OK - connected");
373 connected();
374 break;
375
376 default: {
377 const int xerrno = errno;
378
379 ++failRetries_;
380 debugs(5, 7, conn_ << ": failure #" << failRetries_ << " <= " <<
381 Config.connect_retries << ": " << xstrerr(xerrno));
382
383 if (failRetries_ < Config.connect_retries) {
384 debugs(5, 5, conn_ << ": * - try again");
385 retrySleep();
386 return;
387 } else {
388 // send ERROR back to the upper layer.
389 debugs(5, 5, conn_ << ": * - ERR tried too many times already.");
390 sendAnswer(Comm::ERR_CONNECT, xerrno, "Comm::ConnOpener::doConnect");
391 }
392 }
393 }
394}
395
397void
399{
400 Must(!calls_.sleep_);
401 closeFd();
402 calls_.sleep_ = true;
403 eventAdd("Comm::ConnOpener::DelayedConnectRetry",
405 new Pointer(this), 0.05, 0, false);
406}
407
409void
411{
412 if (calls_.sleep_) {
413 // It would be nice to delete the sleep event, but it might be out of
414 // the event queue and in the async queue already, so (a) we do not know
415 // whether we can safely delete the call ptr here and (b) eventDelete()
416 // will assert if the event went async. Thus, we let the event run so
417 // that it deletes the call ptr [after this job is gone]. Note that we
418 // are called only when the job ends so this "hanging event" will do
419 // nothing but deleting the call ptr. TODO: Revise eventDelete() API.
420 // eventDelete(Comm::ConnOpener::DelayedConnectRetry, calls_.sleep);
421 calls_.sleep_ = false;
422 debugs(5, 9, conn_ << " stops sleeping");
423 }
424}
425
430void
432{
433 struct sockaddr_storage addr = {};
434 socklen_t len = sizeof(addr);
435 if (xgetsockname(conn_->fd, reinterpret_cast<struct sockaddr *>(&addr), &len) != 0) {
436 int xerrno = errno;
437 debugs(50, DBG_IMPORTANT, "ERROR: Failed to retrieve TCP/UDP details for socket: " << conn_ << ": " << xstrerr(xerrno));
438 return;
439 }
440
441 conn_->local = addr;
442 debugs(5, 6, conn_);
443}
444
448void
450{
451 debugs(5, 3, io.conn);
452 calls_.earlyAbort_ = nullptr;
453 // NP: is closing or shutdown better?
454 sendAnswer(Comm::ERR_CLOSING, io.xerrno, "Comm::ConnOpener::earlyAbort");
455}
456
461void
463{
464 debugs(5, 5, conn_ << ": * - ERR took too long to receive response.");
465 calls_.timeout_ = nullptr;
466 sendAnswer(Comm::TIMEOUT, ETIMEDOUT, "Comm::ConnOpener::timeout");
467}
468
469/* Legacy Wrapper for the retry event after Comm::INPROGRESS
470 * XXX: As soon as Comm::SetSelect() accepts Async calls we can use a ConnOpener::doConnect call
471 */
472void
474{
475 Pointer *ptr = static_cast<Pointer*>(data);
476 assert(ptr);
477 if (ConnOpener *cs = ptr->valid()) {
478 // Ew. we are now outside the all AsyncJob protections.
479 // get back inside by scheduling another call...
482 ScheduleCallHere(call);
483 }
484 delete ptr;
485}
486
487/* Legacy Wrapper for the retry event with small delay after errors.
488 * XXX: As soon as eventAdd() accepts Async calls we can use a ConnOpener::restart call
489 */
490void
492{
493 Pointer *ptr = static_cast<Pointer*>(data);
494 assert(ptr);
495 if (ConnOpener *cs = ptr->valid()) {
496 // Ew. we are now outside the all AsyncJob protections.
497 // get back inside by scheduling another call...
500 ScheduleCallHere(call);
501 }
502 delete ptr;
503}
504
#define ScheduleCallHere(call)
Definition AsyncCall.h:166
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
time_t squid_curtime
class SquidConfig Config
#define Must(condition)
#define assert(EX)
Definition assert.h:17
#define CBDATA_NAMESPACED_CLASS_INIT(namespace, type)
Definition cbdata.h:333
virtual bool doneAll() const
whether positive goal has been reached
Definition AsyncJob.cc:112
virtual void swanSong()
Definition AsyncJob.h:61
Cbc * valid() const
was set and is valid
Definition CbcPointer.h:41
int xerrno
The last errno to occur. non-zero if flag is Comm::COMM_ERROR.
Definition CommCalls.h:83
Comm::ConnectionPointer conn
Definition CommCalls.h:80
void timeout(const CommTimeoutCbParams &)
Comm::ConnectionPointer conn_
single connection currently to be opened.
Definition ConnOpener.h:70
void restart()
called at the end of Comm::ConnOpener::DelayedConnectRetry event
static void InProgressConnectRetry(int fd, void *data)
void retrySleep()
Close and wait a little before trying to open and connect again.
void cancelSleep()
cleans up this job sleep state
~ConnOpener() override
Definition ConnOpener.cc:54
static void DelayedConnectRetry(void *data)
void setHost(const char *)
set the hostname note for this connection
void earlyAbort(const CommCloseCbParams &)
void closeFd()
cleans I/O state and ends I/O for temporaryFd_
void sendAnswer(Comm::Flag errFlag, int xerrno, const char *why)
ConnOpener(const Comm::ConnectionPointer &, const AsyncCall::Pointer &handler, time_t connect_timeout)
Definition ConnOpener.cc:33
void start() override
called by AsyncStart; do not call directly
void lookupLocalAddress()
void swanSong() override
Definition ConnOpener.cc:78
bool doneAll() const override
whether positive goal has been reached
Definition ConnOpener.cc:60
void keepFd()
cleans I/O state and moves temporaryFd_ to the conn_ for long-term use
void doConnect()
Make an FD connection attempt.
const char * getHost() const
get the hostname noted for this connection
bool isOpen() const
Definition Connection.h:101
int connect_retries
int test_reachability
struct SquidConfig::@90 onoff
Definition fde.h:52
AsyncCall::Pointer timeoutHandler
Definition fde.h:153
void * write_data
Definition fde.h:152
time_t timeout
Definition fde.h:154
PF * write_handler
Definition fde.h:151
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition comm.cc:942
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition comm.cc:971
int comm_open(int sock_type, int proto, Ip::Address &addr, int flags, const char *note)
Definition comm.cc:245
int comm_connect_addr(int sock, const Ip::Address &address)
Definition comm.cc:631
#define comm_close(x)
Definition comm.h:36
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define COMM_SELECT_WRITE
Definition defines.h:25
void eventAdd(const char *name, EVH *func, void *arg, double when, int weight, bool cbdata)
Definition event.cc:107
#define fd_table
Definition fde.h:189
int Squid_MaxFD
void ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
Definition ipcache.cc:1077
#define IPV6_SPECIAL_V4MAPPING
Definition tools.h:21
void ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
Definition ipcache.cc:1069
Abstraction layer for TCP, UDP, TLS, UDS and filedescriptor sockets.
Flag
Definition Flag.h:15
@ ERR_CONNECT
Definition Flag.h:22
@ OK
Definition Flag.h:16
@ ERR_CLOSING
Definition Flag.h:24
@ TIMEOUT
Definition Flag.h:18
@ INPROGRESS
Definition Flag.h:21
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
int setSockTos(const Comm::ConnectionPointer &conn, tos_t tos)
Definition QosConfig.cc:558
int setSockNfmark(const Comm::ConnectionPointer &conn, nfmark_t mark)
Definition QosConfig.cc:590
#define xstrdup
void netdbDeleteAddrNetwork(Ip::Address &addr)
Definition net_db.cc:1074
int xgetsockname(int socketFd, struct sockaddr *sa, socklen_t *saLength)
POSIX getsockname(2) equivalent.
Definition socket.h:80
int socklen_t
Definition types.h:137
#define safe_free(x)
Definition xalloc.h:73
const char * xstrerr(int error)
Definition xstrerror.cc:83