ConnOpener.cc
Go to the documentation of this file.
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 
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 
54 {
55  safe_free(host_);
56 }
57 
58 bool
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
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 
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 *
113 {
114  return host_;
115 }
116 
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
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 
172 void
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 
218 void
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 
237 void
239 {
240  Must(conn_ != nullptr);
241  Must(temporaryFd_ >= 0);
242 
243  cleanFd();
244 
245  conn_->fd = temporaryFd_;
246  temporaryFd_ = -1;
247 }
248 
249 void
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 
265 void
267 {
268  debugs(5, 5, conn_ << " restarting after sleep");
269  calls_.sleep_ = false;
270 
271  if (createFd())
272  doConnect();
273 }
274 
277 bool
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 
307  calls_.earlyAbort_ = JobCallback(5, 4, abortDialer, this, Comm::ConnOpener::earlyAbort);
308  comm_add_close_handler(temporaryFd_, calls_.earlyAbort_);
309 
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
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 
355 void
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");
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 
396 void
398 {
399  Must(!calls_.sleep_);
400  closeFd();
401  calls_.sleep_ = true;
402  eventAdd("Comm::ConnOpener::DelayedConnectRetry",
404  new Pointer(this), 0.05, 0, false);
405 }
406 
408 void
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 
429 void
431 {
432  struct sockaddr_storage addr = {};
433  socklen_t len = sizeof(addr);
434  if (getsockname(conn_->fd, reinterpret_cast<struct sockaddr *>(&addr), &len) != 0) {
435  int xerrno = errno;
436  debugs(50, DBG_IMPORTANT, "ERROR: Failed to retrieve TCP/UDP details for socket: " << conn_ << ": " << xstrerr(xerrno));
437  return;
438  }
439 
440  conn_->local = addr;
441  debugs(5, 6, conn_);
442 }
443 
447 void
449 {
450  debugs(5, 3, io.conn);
451  calls_.earlyAbort_ = nullptr;
452  // NP: is closing or shutdown better?
453  sendAnswer(Comm::ERR_CLOSING, io.xerrno, "Comm::ConnOpener::earlyAbort");
454 }
455 
460 void
462 {
463  debugs(5, 5, conn_ << ": * - ERR took too long to receive response.");
464  calls_.timeout_ = nullptr;
465  sendAnswer(Comm::TIMEOUT, ETIMEDOUT, "Comm::ConnOpener::timeout");
466 }
467 
468 /* Legacy Wrapper for the retry event after Comm::INPROGRESS
469  * XXX: As soon as Comm::SetSelect() accepts Async calls we can use a ConnOpener::doConnect call
470  */
471 void
473 {
474  Pointer *ptr = static_cast<Pointer*>(data);
475  assert(ptr);
476  if (ConnOpener *cs = ptr->valid()) {
477  // Ew. we are now outside the all AsyncJob protections.
478  // get back inside by scheduling another call...
479  typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
481  ScheduleCallHere(call);
482  }
483  delete ptr;
484 }
485 
486 /* Legacy Wrapper for the retry event with small delay after errors.
487  * XXX: As soon as eventAdd() accepts Async calls we can use a ConnOpener::restart call
488  */
489 void
491 {
492  Pointer *ptr = static_cast<Pointer*>(data);
493  assert(ptr);
494  if (ConnOpener *cs = ptr->valid()) {
495  // Ew. we are now outside the all AsyncJob protections.
496  // get back inside by scheduling another call...
497  typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
498  AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::restart);
499  ScheduleCallHere(call);
500  }
501  delete ptr;
502 }
503 
void timeout(const CommTimeoutCbParams &)
Definition: ConnOpener.cc:461
void retrySleep()
Close and wait a little before trying to open and connect again.
Definition: ConnOpener.cc:397
const char * xstrerr(int error)
Definition: xstrerror.cc:83
AsyncCall::Pointer comm_add_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:952
void sendAnswer(Comm::Flag errFlag, int xerrno, const char *why)
Definition: ConnOpener.cc:122
void lookupLocalAddress()
Definition: ConnOpener.cc:430
ConnOpener(const Comm::ConnectionPointer &, const AsyncCall::Pointer &handler, time_t connect_timeout)
Definition: ConnOpener.cc:32
int setSockNfmark(const Comm::ConnectionPointer &conn, nfmark_t mark)
Definition: QosConfig.cc:589
virtual void swanSong()
Definition: AsyncJob.h:61
void closeFd()
cleans I/O state and ends I/O for temporaryFd_
Definition: ConnOpener.cc:219
int comm_connect_addr(int sock, const Ip::Address &address)
Definition: comm.cc:629
#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:112
#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
struct SquidConfig::@97 onoff
#define comm_close(x)
Definition: comm.h:36
void start() override
called by AsyncStart; do not call directly
Definition: ConnOpener.cc:250
@ OK
Definition: Flag.h:16
~ConnOpener() override
Definition: ConnOpener.cc:53
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:100
bool doneAll() const override
whether positive goal has been reached
Definition: ConnOpener.cc:59
void cancelSleep()
cleans up this job sleep state
Definition: ConnOpener.cc:409
static void DelayedConnectRetry(void *data)
Definition: ConnOpener.cc:490
int connect_retries
Definition: SquidConfig.h:352
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:77
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:356
#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:243
struct CachePeer::@28::@34 flags
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
#define fd_table
Definition: fde.h:189
bool params
Definition: CachePeer.h:138
void earlyAbort(const CommCloseCbParams &)
Definition: ConnOpener.cc:448
@ 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:266
#define DBG_IMPORTANT
Definition: Stream.h:38
int setSockTos(const Comm::ConnectionPointer &conn, tos_t tos)
Definition: QosConfig.cc:557
bool isOpen() const
Definition: Connection.h:101
AsyncCall::Pointer timeoutHandler
Definition: fde.h:153
static void InProgressConnectRetry(int fd, void *data)
Definition: ConnOpener.cc:472
int EnableIpv6
Whether IPv6 is supported and type of support.
Definition: tools.h:25
void keepFd()
cleans I/O state and moves temporaryFd_ to the conn_ for long-term use
Definition: ConnOpener.cc:238
#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:981
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