ConnOpener.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2022 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 
33  AsyncJob("Comm::ConnOpener"),
34  host_(NULL),
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_ == NULL) {
63  return AsyncJob::doneAll();
64  }
65 
66  // is the callback still to be called?
67  if (callback_ == NULL || 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_ != NULL) {
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_ != NULL)
104  safe_free(host_);
105 
106  // set the new one if given.
107  if (new_host != NULL)
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_ != NULL) {
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_ != NULL) {
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_ = NULL;
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 = NULL;
194  f.write_handler = NULL;
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, NULL, NULL, 0);
201 
202  if (calls_.timeout_ != NULL) {
203  calls_.timeout_->cancel("Comm::ConnOpener::cleanFd");
204  calls_.timeout_ = NULL;
205  }
206  // Comm checkTimeouts() and commCloseAllSockets() do not clear .timeout
207  // when calling timeoutHandler (XXX fix them), so we clear unconditionally.
208  f.timeoutHandler = NULL;
209  f.timeout = 0;
210 
211  if (calls_.earlyAbort_ != NULL) {
212  comm_remove_close_handler(temporaryFd_, calls_.earlyAbort_);
213  calls_.earlyAbort_ = NULL;
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, NULL, NULL, 0);
231 
232  comm_close(temporaryFd_);
233  temporaryFd_ = -1;
234 }
235 
237 void
239 {
240  Must(conn_ != NULL);
241  Must(temporaryFd_ >= 0);
242 
243  cleanFd();
244 
245  conn_->fd = temporaryFd_;
246  temporaryFd_ = -1;
247 }
248 
249 void
251 {
252  Must(conn_ != NULL);
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_ == NULL || callback_->canceled())
285  return false;
286 
287  temporaryFd_ = comm_openex(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_ != NULL);
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 addrinfo *addr = NULL;
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 
450 void
452 {
453  debugs(5, 3, io.conn);
454  calls_.earlyAbort_ = NULL;
455  // NP: is closing or shutdown better?
456  sendAnswer(Comm::ERR_CLOSING, io.xerrno, "Comm::ConnOpener::earlyAbort");
457 }
458 
463 void
465 {
466  debugs(5, 5, conn_ << ": * - ERR took too long to receive response.");
467  calls_.timeout_ = NULL;
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
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;
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
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 
void timeout(const CommTimeoutCbParams &)
Definition: ConnOpener.cc:464
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:921
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:602
virtual void swanSong()
Definition: AsyncJob.h:59
void closeFd()
cleans I/O state and ends I/O for temporaryFd_
Definition: ConnOpener.cc:219
static void handler(int signo)
Definition: purge.cc:854
int comm_connect_addr(int sock, const Ip::Address &address)
Definition: comm.cc:602
#define ScheduleCallHere(call)
Definition: AsyncCall.h:164
@ INPROGRESS
Definition: Flag.h:22
Comm::ConnectionPointer conn_
single connection currently to be opened.
Definition: ConnOpener.h:70
virtual void start()
called by AsyncStart; do not call directly
Definition: ConnOpener.cc:250
const char * getHost() const
get the hostname noted for this connection
Definition: ConnOpener.cc:112
#define xstrdup
@ TIMEOUT
Definition: Flag.h:19
Abstraction layer for TCP, UDP, TLS, UDS and filedescriptor sockets.
Definition: AcceptLimiter.h:17
int test_reachability
Definition: SquidConfig.h:294
static void FreeAddr(struct addrinfo *&ai)
Definition: Address.cc:686
#define comm_close(x)
Definition: comm.h:27
@ OK
Definition: Flag.h:16
virtual bool doneAll() const
whether positive goal has been reached
Definition: ConnOpener.cc:59
CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener)
@ ERR_CLOSING
Definition: Flag.h:25
int xerrno
The last errno to occur. non-zero if flag is Comm::COMM_ERROR.
Definition: CommCalls.h:88
Definition: fde.h:52
void setHost(const char *)
set the hostname note for this connection
Definition: ConnOpener.cc:100
socklen_t ai_addrlen
void cancelSleep()
cleans up this job sleep state
Definition: ConnOpener.cc:409
static void DelayedConnectRetry(void *data)
Definition: ConnOpener.cc:493
struct sockaddr * ai_addr
int connect_retries
Definition: SquidConfig.h:358
struct CachePeer::@31::@37 flags
virtual void swanSong()
Definition: ConnOpener.cc:77
#define NULL
Definition: types.h:166
virtual bool doneAll() const
whether positive goal has been reached
Definition: AsyncJob.cc:97
PF * write_handler
Definition: fde.h:151
time_t timeout
Definition: fde.h:154
#define IPV6_SPECIAL_V4MAPPING
Definition: tools.h:21
int comm_openex(int sock_type, int proto, Ip::Address &addr, int flags, const char *note)
Definition: comm.cc:331
void * write_data
Definition: fde.h:152
Comm::ConnectionPointer conn
Definition: CommCalls.h:85
#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:19
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:69
time_t squid_curtime
Definition: stub_libtime.cc:20
void ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
Definition: ipcache.cc:1065
int Squid_MaxFD
Flag
Definition: Flag.h:15
#define fd_table
Definition: fde.h:189
bool params
Definition: CachePeer.h:111
void earlyAbort(const CommCloseCbParams &)
Definition: ConnOpener.cc:451
@ ERR_CONNECT
Definition: Flag.h:23
struct SquidConfig::@110 onoff
void ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
Definition: ipcache.cc:1057
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
Definition: ModDevPoll.cc:223
void netdbDeleteAddrNetwork(Ip::Address &addr)
Definition: net_db.cc:1185
#define Must(condition)
Definition: TextException.h:71
void restart()
called at the end of Comm::ConnOpener::DelayedConnectRetry event
Definition: ConnOpener.cc:266
#define DBG_IMPORTANT
Definition: Stream.h:41
int setSockTos(const Comm::ConnectionPointer &conn, tos_t tos)
Definition: QosConfig.cc:570
bool isOpen() const
Definition: Connection.h:99
AsyncCall::Pointer timeoutHandler
Definition: fde.h:153
static void InProgressConnectRetry(int fd, void *data)
Definition: ConnOpener.cc:475
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:196
#define COMM_SELECT_WRITE
Definition: defines.h:25
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:950
void eventAdd(const char *name, EVH *func, void *arg, double when, int weight, bool cbdata)
Definition: event.cc:107
static void InitAddr(struct addrinfo *&ai)
Definition: Address.cc:668
class SquidConfig Config
Definition: SquidConfig.cc:12

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors