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 
29 class CachePeer;
30 
32 
33 Comm::ConnOpener::ConnOpener(const Comm::ConnectionPointer &c, const AsyncCall::Pointer &handler, time_t ctimeout) :
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 
55 {
56  safe_free(host_);
57 }
58 
59 bool
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 
77 void
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 
100 void
101 Comm::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 
112 const char *
114 {
115  return host_;
116 }
117 
122 void
123 Comm::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 
173 void
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 
219 void
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 
238 void
240 {
241  Must(conn_ != nullptr);
242  Must(temporaryFd_ >= 0);
243 
244  cleanFd();
245 
246  conn_->fd = temporaryFd_;
247  temporaryFd_ = -1;
248 }
249 
250 void
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 
266 void
268 {
269  debugs(5, 5, conn_ << " restarting after sleep");
270  calls_.sleep_ = false;
271 
272  if (createFd())
273  doConnect();
274 }
275 
278 bool
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 
327 void
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 
356 void
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 
397 void
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 
409 void
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 
430 void
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 
448 void
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 
461 void
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  */
472 void
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...
480  typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
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  */
490 void
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...
498  typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
499  AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::restart);
500  ScheduleCallHere(call);
501  }
502  delete ptr;
503 }
504 
void timeout(const CommTimeoutCbParams &)
Definition: ConnOpener.cc:462
void retrySleep()
Close and wait a little before trying to open and connect again.
Definition: ConnOpener.cc:398
const char * xstrerr(int error)
Definition: xstrerror.cc:83
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:942
void sendAnswer(Comm::Flag errFlag, int xerrno, const char *why)
Definition: ConnOpener.cc:123
void lookupLocalAddress()
Definition: ConnOpener.cc:431
ConnOpener(const Comm::ConnectionPointer &, const AsyncCall::Pointer &handler, time_t connect_timeout)
Definition: ConnOpener.cc:33
int setSockNfmark(const Comm::ConnectionPointer &conn, nfmark_t mark)
Definition: QosConfig.cc:590
virtual void swanSong()
Definition: AsyncJob.h:61
void closeFd()
cleans I/O state and ends I/O for temporaryFd_
Definition: ConnOpener.cc:220
int comm_connect_addr(int sock, const Ip::Address &address)
Definition: comm.cc:631
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
@ INPROGRESS
Definition: Flag.h:21
Comm::ConnectionPointer conn_
single connection currently to be opened.
Definition: ConnOpener.h:70
const char * getHost() const
get the hostname noted for this connection
Definition: ConnOpener.cc:113
#define xstrdup
@ TIMEOUT
Definition: Flag.h:18
Abstraction layer for TCP, UDP, TLS, UDS and filedescriptor sockets.
Definition: AcceptLimiter.h:16
int test_reachability
Definition: SquidConfig.h:288
#define comm_close(x)
Definition: comm.h:36
void start() override
called by AsyncStart; do not call directly
Definition: ConnOpener.cc:251
@ OK
Definition: Flag.h:16
~ConnOpener() override
Definition: ConnOpener.cc:54
CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener)
@ ERR_CLOSING
Definition: Flag.h:24
int xerrno
The last errno to occur. non-zero if flag is Comm::COMM_ERROR.
Definition: CommCalls.h:83
Definition: fde.h:51
int socklen_t
Definition: types.h:137
void setHost(const char *)
set the hostname note for this connection
Definition: ConnOpener.cc:101
bool doneAll() const override
whether positive goal has been reached
Definition: ConnOpener.cc:60
void cancelSleep()
cleans up this job sleep state
Definition: ConnOpener.cc:410
static void DelayedConnectRetry(void *data)
Definition: ConnOpener.cc:491
int connect_retries
Definition: SquidConfig.h:352
int xgetsockname(int socketFd, struct sockaddr *sa, socklen_t *saLength)
POSIX getsockname(2) equivalent.
Definition: socket.h:80
virtual bool doneAll() const
whether positive goal has been reached
Definition: AsyncJob.cc:112
PF * write_handler
Definition: fde.h:151
time_t timeout
Definition: fde.h:154
#define IPV6_SPECIAL_V4MAPPING
Definition: tools.h:21
void swanSong() override
Definition: ConnOpener.cc:78
void * write_data
Definition: fde.h:152
Comm::ConnectionPointer conn
Definition: CommCalls.h:80
#define safe_free(x)
Definition: xalloc.h:73
void doConnect()
Make an FD connection attempt.
Definition: ConnOpener.cc:357
#define assert(EX)
Definition: assert.h:17
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:70
int comm_open(int sock_type, int proto, Ip::Address &addr, int flags, const char *note)
Definition: comm.cc:245
time_t squid_curtime
Definition: stub_libtime.cc:20
void ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
Definition: ipcache.cc:1077
int Squid_MaxFD
Flag
Definition: Flag.h:15
struct SquidConfig::@93 onoff
#define fd_table
Definition: fde.h:189
bool params
Definition: CachePeer.h:138
void earlyAbort(const CommCloseCbParams &)
Definition: ConnOpener.cc:449
@ ERR_CONNECT
Definition: Flag.h:22
void ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
Definition: ipcache.cc:1069
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
Definition: ModDevPoll.cc:220
void netdbDeleteAddrNetwork(Ip::Address &addr)
Definition: net_db.cc:1074
#define Must(condition)
Definition: TextException.h:75
void restart()
called at the end of Comm::ConnOpener::DelayedConnectRetry event
Definition: ConnOpener.cc:267
#define DBG_IMPORTANT
Definition: Stream.h:38
int setSockTos(const Comm::ConnectionPointer &conn, tos_t tos)
Definition: QosConfig.cc:558
bool isOpen() const
Definition: Connection.h:101
AsyncCall::Pointer timeoutHandler
Definition: fde.h:153
static void InProgressConnectRetry(int fd, void *data)
Definition: ConnOpener.cc:473
int EnableIpv6
Whether IPv6 is supported and type of support.
Definition: tools.h:25
struct CachePeer::@24::@30 flags
void keepFd()
cleans I/O state and moves temporaryFd_ to the conn_ for long-term use
Definition: ConnOpener.cc:239
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
#define COMM_SELECT_WRITE
Definition: defines.h:25
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:971
void eventAdd(const char *name, EVH *func, void *arg, double when, int weight, bool cbdata)
Definition: event.cc:107
class SquidConfig Config
Definition: SquidConfig.cc:12

 

Introduction

Documentation

Support

Miscellaneous