ConnOpener.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2021 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 #include "SquidTime.h"
26 
27 #include <cerrno>
28 
29 class CachePeer;
30 
32 
34  AsyncJob("Comm::ConnOpener"),
35  host_(NULL),
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_ == NULL) {
64  return AsyncJob::doneAll();
65  }
66 
67  // is the callback still to be called?
68  if (callback_ == NULL || 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_ != NULL) {
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_ != NULL)
105  safe_free(host_);
106 
107  // set the new one if given.
108  if (new_host != NULL)
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_ != NULL) {
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_ != NULL) {
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_ = NULL;
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 = NULL;
195  f.write_handler = NULL;
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, NULL, NULL, 0);
202 
203  if (calls_.timeout_ != NULL) {
204  calls_.timeout_->cancel("Comm::ConnOpener::cleanFd");
205  calls_.timeout_ = NULL;
206  }
207  // Comm checkTimeouts() and commCloseAllSockets() do not clear .timeout
208  // when calling timeoutHandler (XXX fix them), so we clear unconditionally.
209  f.timeoutHandler = NULL;
210  f.timeout = 0;
211 
212  if (calls_.earlyAbort_ != NULL) {
213  comm_remove_close_handler(temporaryFd_, calls_.earlyAbort_);
214  calls_.earlyAbort_ = NULL;
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, NULL, NULL, 0);
232 
233  comm_close(temporaryFd_);
234  temporaryFd_ = -1;
235 }
236 
238 void
240 {
241  Must(conn_ != NULL);
242  Must(temporaryFd_ >= 0);
243 
244  cleanFd();
245 
246  conn_->fd = temporaryFd_;
247  temporaryFd_ = -1;
248 }
249 
250 void
252 {
253  Must(conn_ != NULL);
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_ == NULL || callback_->canceled())
286  return false;
287 
288  temporaryFd_ = comm_openex(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_ != NULL);
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, HERE << conn_ << ": Comm::INPROGRESS");
369  break;
370 
371  case Comm::OK:
372  debugs(5, 5, HERE << 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, HERE << conn_ << ": * - try again");
385  retrySleep();
386  return;
387  } else {
388  // send ERROR back to the upper layer.
389  debugs(5, 5, HERE << 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 addrinfo *addr = NULL;
434  Ip::Address::InitAddr(addr);
435 
436  if (getsockname(conn_->fd, addr->ai_addr, &(addr->ai_addrlen)) != 0) {
437  int xerrno = errno;
438  debugs(50, DBG_IMPORTANT, "ERROR: Failed to retrieve TCP/UDP details for socket: " << conn_ << ": " << xstrerr(xerrno));
439  Ip::Address::FreeAddr(addr);
440  return;
441  }
442 
443  conn_->local = *addr;
444  Ip::Address::FreeAddr(addr);
445  debugs(5, 6, HERE << conn_);
446 }
447 
451 void
453 {
454  debugs(5, 3, HERE << io.conn);
455  calls_.earlyAbort_ = NULL;
456  // NP: is closing or shutdown better?
457  sendAnswer(Comm::ERR_CLOSING, io.xerrno, "Comm::ConnOpener::earlyAbort");
458 }
459 
464 void
466 {
467  debugs(5, 5, HERE << conn_ << ": * - ERR took too long to receive response.");
468  calls_.timeout_ = NULL;
469  sendAnswer(Comm::TIMEOUT, ETIMEDOUT, "Comm::ConnOpener::timeout");
470 }
471 
472 /* Legacy Wrapper for the retry event after Comm::INPROGRESS
473  * XXX: As soon as Comm::SetSelect() accepts Async calls we can use a ConnOpener::doConnect call
474  */
475 void
477 {
478  Pointer *ptr = static_cast<Pointer*>(data);
479  assert(ptr);
480  if (ConnOpener *cs = ptr->valid()) {
481  // Ew. we are now outside the all AsyncJob protections.
482  // get back inside by scheduling another call...
483  typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
485  ScheduleCallHere(call);
486  }
487  delete ptr;
488 }
489 
490 /* Legacy Wrapper for the retry event with small delay after errors.
491  * XXX: As soon as eventAdd() accepts Async calls we can use a ConnOpener::restart call
492  */
493 void
495 {
496  Pointer *ptr = static_cast<Pointer*>(data);
497  assert(ptr);
498  if (ConnOpener *cs = ptr->valid()) {
499  // Ew. we are now outside the all AsyncJob protections.
500  // get back inside by scheduling another call...
501  typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
502  AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::restart);
503  ScheduleCallHere(call);
504  }
505  delete ptr;
506 }
507 
void timeout(const CommTimeoutCbParams &)
Definition: ConnOpener.cc:465
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:935
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:601
virtual void swanSong()
Definition: AsyncJob.h:59
void closeFd()
cleans I/O state and ends I/O for temporaryFd_
Definition: ConnOpener.cc:220
static void handler(int signo)
Definition: purge.cc:854
int comm_connect_addr(int sock, const Ip::Address &address)
Definition: comm.cc:606
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
@ 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:251
const char * getHost() const
get the hostname noted for this connection
Definition: ConnOpener.cc:113
#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:295
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:60
CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener)
@ ERR_CLOSING
Definition: Flag.h:25
#define DBG_IMPORTANT
Definition: Debug.h:41
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:101
socklen_t ai_addrlen
void cancelSleep()
cleans up this job sleep state
Definition: ConnOpener.cc:410
static void DelayedConnectRetry(void *data)
Definition: ConnOpener.cc:494
struct sockaddr * ai_addr
int connect_retries
Definition: SquidConfig.h:359
struct CachePeer::@31::@37 flags
virtual void swanSong()
Definition: ConnOpener.cc:78
#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
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:123
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
std::ostream & HERE(std::ostream &s)
Definition: Debug.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:357
#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_time.cc:17
void ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
Definition: ipcache.cc:1066
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:452
struct SquidConfig::@111 onoff
@ ERR_CONNECT
Definition: Flag.h:23
void ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
Definition: ipcache.cc:1058
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
Definition: ModDevPoll.cc:225
void netdbDeleteAddrNetwork(Ip::Address &addr)
Definition: net_db.cc:1186
#define Must(condition)
Like assert() but throws an exception instead of aborting the process.
Definition: TextException.h:73
void restart()
called at the end of Comm::ConnOpener::DelayedConnectRetry event
Definition: ConnOpener.cc:267
int setSockTos(const Comm::ConnectionPointer &conn, tos_t tos)
Definition: QosConfig.cc:569
bool isOpen() const
Definition: Connection.h:99
AsyncCall::Pointer timeoutHandler
Definition: fde.h:153
static void InProgressConnectRetry(int fd, void *data)
Definition: ConnOpener.cc:476
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:239
#define COMM_SELECT_WRITE
Definition: defines.h:25
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:962
void eventAdd(const char *name, EVH *func, void *arg, double when, int weight, bool cbdata)
Definition: event.cc:109
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