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
28class CachePeer;
29
31
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
58bool
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
76void
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
99void
100Comm::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
111const char *
113{
114 return host_;
115}
116
121void
122Comm::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
172void
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
218void
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
237void
239{
240 Must(conn_ != nullptr);
241 Must(temporaryFd_ >= 0);
242
243 cleanFd();
244
245 conn_->fd = temporaryFd_;
246 temporaryFd_ = -1;
247}
248
249void
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
265void
267{
268 debugs(5, 5, conn_ << " restarting after sleep");
269 calls_.sleep_ = false;
270
271 if (createFd())
272 doConnect();
273}
274
277bool
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
326void
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
355void
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
396void
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
408void
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
429void
431{
432 struct addrinfo *addr = nullptr;
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));
439 return;
440 }
441
442 conn_->local = *addr;
444 debugs(5, 6, conn_);
445}
446
450void
452{
453 debugs(5, 3, io.conn);
454 calls_.earlyAbort_ = nullptr;
455 // NP: is closing or shutdown better?
456 sendAnswer(Comm::ERR_CLOSING, io.xerrno, "Comm::ConnOpener::earlyAbort");
457}
458
463void
465{
466 debugs(5, 5, conn_ << ": * - ERR took too long to receive response.");
467 calls_.timeout_ = nullptr;
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 */
474void
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...
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 */
492void
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...
502 ScheduleCallHere(call);
503 }
504 delete ptr;
505}
506
#define ScheduleCallHere(call)
Definition: AsyncCall.h:166
#define JobCallback(dbgSection, dbgLevel, Dialer, job, method)
Convenience macro to create a Dialer-based job callback.
Definition: AsyncJobCalls.h:69
CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener)
time_t squid_curtime
Definition: stub_libtime.cc:20
class SquidConfig Config
Definition: SquidConfig.cc:12
#define Must(condition)
Definition: TextException.h:75
#define assert(EX)
Definition: assert.h:17
virtual bool doneAll() const
whether positive goal has been reached
Definition: AsyncJob.cc:112
virtual void swanSong()
Definition: AsyncJob.h:61
bool params
Definition: CachePeer.h:137
struct CachePeer::@29::@35 flags
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 &)
Definition: ConnOpener.cc:464
Comm::ConnectionPointer conn_
single connection currently to be opened.
Definition: ConnOpener.h:70
void restart()
called at the end of Comm::ConnOpener::DelayedConnectRetry event
Definition: ConnOpener.cc:266
static void InProgressConnectRetry(int fd, void *data)
Definition: ConnOpener.cc:475
void retrySleep()
Close and wait a little before trying to open and connect again.
Definition: ConnOpener.cc:397
void cancelSleep()
cleans up this job sleep state
Definition: ConnOpener.cc:409
~ConnOpener() override
Definition: ConnOpener.cc:53
static void DelayedConnectRetry(void *data)
Definition: ConnOpener.cc:493
void setHost(const char *)
set the hostname note for this connection
Definition: ConnOpener.cc:100
void earlyAbort(const CommCloseCbParams &)
Definition: ConnOpener.cc:451
void closeFd()
cleans I/O state and ends I/O for temporaryFd_
Definition: ConnOpener.cc:219
void sendAnswer(Comm::Flag errFlag, int xerrno, const char *why)
Definition: ConnOpener.cc:122
ConnOpener(const Comm::ConnectionPointer &, const AsyncCall::Pointer &handler, time_t connect_timeout)
Definition: ConnOpener.cc:32
void start() override
called by AsyncStart; do not call directly
Definition: ConnOpener.cc:250
void lookupLocalAddress()
Definition: ConnOpener.cc:430
void swanSong() override
Definition: ConnOpener.cc:77
bool doneAll() const override
whether positive goal has been reached
Definition: ConnOpener.cc:59
void keepFd()
cleans I/O state and moves temporaryFd_ to the conn_ for long-term use
Definition: ConnOpener.cc:238
void doConnect()
Make an FD connection attempt.
Definition: ConnOpener.cc:356
const char * getHost() const
get the hostname noted for this connection
Definition: ConnOpener.cc:112
bool isOpen() const
Definition: Connection.h:101
static void InitAddr(struct addrinfo *&ai)
Definition: Address.cc:668
static void FreeAddr(struct addrinfo *&ai)
Definition: Address.cc:686
int connect_retries
Definition: SquidConfig.h:352
struct SquidConfig::@106 onoff
int test_reachability
Definition: SquidConfig.h:288
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:949
void comm_remove_close_handler(int fd, CLCB *handler, void *data)
Definition: comm.cc:978
int comm_open(int sock_type, int proto, Ip::Address &addr, int flags, const char *note)
Definition: comm.cc:242
int comm_connect_addr(int sock, const Ip::Address &address)
Definition: comm.cc:634
#define comm_close(x)
Definition: comm.h:27
#define DBG_IMPORTANT
Definition: Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:194
#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:1074
#define IPV6_SPECIAL_V4MAPPING
Definition: tools.h:21
void ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
Definition: ipcache.cc:1066
Abstraction layer for TCP, UDP, TLS, UDS and filedescriptor sockets.
Definition: AcceptLimiter.h:17
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.
Definition: ModDevPoll.cc:223
int setSockTos(const Comm::ConnectionPointer &conn, tos_t tos)
Definition: QosConfig.cc:570
int setSockNfmark(const Comm::ConnectionPointer &conn, nfmark_t mark)
Definition: QosConfig.cc:602
#define xstrdup
void netdbDeleteAddrNetwork(Ip::Address &addr)
Definition: net_db.cc:1090
static void handler(int signo)
Definition: purge.cc:858
int EnableIpv6
Whether IPv6 is supported and type of support.
Definition: tools.h:25
#define safe_free(x)
Definition: xalloc.h:73
const char * xstrerr(int error)
Definition: xstrerror.cc:83

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors