]> git.ipfire.org Git - thirdparty/squid.git/blob - src/comm/ConnOpener.cc
Windows: rename ConnOpener::connect
[thirdparty/squid.git] / src / comm / ConnOpener.cc
1 /*
2 * DEBUG: section 05 Socket Connection Opener
3 */
4
5 #include "squid.h"
6 #include "CachePeer.h"
7 #include "comm.h"
8 #include "comm/Connection.h"
9 #include "comm/ConnOpener.h"
10 #include "comm/Loops.h"
11 #include "fd.h"
12 #include "fde.h"
13 #include "globals.h"
14 #include "icmp/net_db.h"
15 #include "ip/tools.h"
16 #include "ipcache.h"
17 #include "SquidConfig.h"
18 #include "SquidTime.h"
19
20 #include <cerrno>
21
22 class CachePeer;
23
24 CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener);
25
26 Comm::ConnOpener::ConnOpener(Comm::ConnectionPointer &c, AsyncCall::Pointer &handler, time_t ctimeout) :
27 AsyncJob("Comm::ConnOpener"),
28 host_(NULL),
29 temporaryFd_(-1),
30 conn_(c),
31 callback_(handler),
32 totalTries_(0),
33 failRetries_(0),
34 deadline_(squid_curtime + static_cast<time_t>(ctimeout))
35 {}
36
37 Comm::ConnOpener::~ConnOpener()
38 {
39 safe_free(host_);
40 }
41
42 bool
43 Comm::ConnOpener::doneAll() const
44 {
45 // is the conn_ to be opened still waiting?
46 if (conn_ == NULL) {
47 return AsyncJob::doneAll();
48 }
49
50 // is the callback still to be called?
51 if (callback_ == NULL || callback_->canceled()) {
52 return AsyncJob::doneAll();
53 }
54
55 // otherwise, we must be waiting for something
56 Must(temporaryFd_ >= 0 || calls_.sleep_);
57 return false;
58 }
59
60 void
61 Comm::ConnOpener::swanSong()
62 {
63 if (callback_ != NULL) {
64 // inform the still-waiting caller we are dying
65 sendAnswer(Comm::ERR_CONNECT, 0, "Comm::ConnOpener::swanSong");
66 }
67
68 // did we abort with a temporary FD assigned?
69 if (temporaryFd_ >= 0)
70 closeFd();
71
72 // did we abort while waiting between retries?
73 if (calls_.sleep_)
74 cancelSleep();
75
76 AsyncJob::swanSong();
77 }
78
79 void
80 Comm::ConnOpener::setHost(const char * new_host)
81 {
82 // unset and erase if already set.
83 if (host_ != NULL)
84 safe_free(host_);
85
86 // set the new one if given.
87 if (new_host != NULL)
88 host_ = xstrdup(new_host);
89 }
90
91 const char *
92 Comm::ConnOpener::getHost() const
93 {
94 return host_;
95 }
96
97 /**
98 * Connection attempt are completed. One way or the other.
99 * Pass the results back to the external handler.
100 */
101 void
102 Comm::ConnOpener::sendAnswer(Comm::Flag errFlag, int xerrno, const char *why)
103 {
104 // only mark the address good/bad AFTER connect is finished.
105 if (host_ != NULL) {
106 if (xerrno == 0) // XXX: should not we use errFlag instead?
107 ipcacheMarkGoodAddr(host_, conn_->remote);
108 else {
109 ipcacheMarkBadAddr(host_, conn_->remote);
110 #if USE_ICMP
111 if (Config.onoff.test_reachability)
112 netdbDeleteAddrNetwork(conn_->remote);
113 #endif
114 }
115 }
116
117 if (callback_ != NULL) {
118 // avoid scheduling cancelled callbacks, assuming they are common
119 // enough to make this extra check an optimization
120 if (callback_->canceled()) {
121 debugs(5, 4, conn_ << " not calling canceled " << *callback_ <<
122 " [" << callback_->id << ']' );
123 // TODO save the pconn to the pconnPool ?
124 } else {
125 typedef CommConnectCbParams Params;
126 Params &params = GetCommParams<Params>(callback_);
127 params.conn = conn_;
128 params.flag = errFlag;
129 params.xerrno = xerrno;
130 ScheduleCallHere(callback_);
131 }
132 callback_ = NULL;
133 }
134
135 // The job will stop without this call because nil callback_ makes
136 // doneAll() true, but this explicit call creates nicer debugging.
137 mustStop(why);
138 }
139
140 /// cleans up this job I/O state without closing temporaryFd
141 /// required before closing temporaryFd or keeping it in conn_
142 /// leaves FD bare so must only be called via closeFd() or keepFd()
143 void
144 Comm::ConnOpener::cleanFd()
145 {
146 debugs(5, 4, HERE << conn_ << " closing temp FD " << temporaryFd_);
147
148 Must(temporaryFd_ >= 0);
149 fde &f = fd_table[temporaryFd_];
150
151 // Our write_handler was set without using Comm::Write API, so we cannot
152 // use a cancellable Pointer-free job callback and simply cancel it here.
153 if (f.write_handler) {
154
155 /* XXX: We are about to remove write_handler, which was responsible
156 * for deleting write_data, so we have to delete write_data
157 * ourselves. Comm currently calls SetSelect handlers synchronously
158 * so if write_handler is set, we know it has not been called yet.
159 * ConnOpener converts that sync call into an async one, but only
160 * after deleting ptr, so that is not a problem.
161 */
162
163 delete static_cast<Pointer*>(f.write_data);
164 f.write_data = NULL;
165 f.write_handler = NULL;
166 }
167 // Comm::DoSelect does not do this when calling and resetting write_handler
168 // (because it expects more writes to come?). We could mimic that
169 // optimization by resetting Comm "Select" state only when the FD is
170 // actually closed.
171 Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, NULL, NULL, 0);
172
173 if (calls_.timeout_ != NULL) {
174 calls_.timeout_->cancel("Comm::ConnOpener::cleanFd");
175 calls_.timeout_ = NULL;
176 }
177 // Comm checkTimeouts() and commCloseAllSockets() do not clear .timeout
178 // when calling timeoutHandler (XXX fix them), so we clear unconditionally.
179 f.timeoutHandler = NULL;
180 f.timeout = 0;
181
182 if (calls_.earlyAbort_ != NULL) {
183 comm_remove_close_handler(temporaryFd_, calls_.earlyAbort_);
184 calls_.earlyAbort_ = NULL;
185 }
186 }
187
188 /// cleans I/O state and ends I/O for temporaryFd_
189 void
190 Comm::ConnOpener::closeFd()
191 {
192 if (temporaryFd_ < 0)
193 return;
194
195 cleanFd();
196
197 // comm_close() below uses COMMIO_FD_WRITECB(fd)->active() to clear Comm
198 // "Select" state. It will not clear ours. XXX: It should always clear
199 // because a callback may have been active but was called before comm_close
200 // Update: we now do this in cleanFd()
201 // Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, NULL, NULL, 0);
202
203 comm_close(temporaryFd_);
204 temporaryFd_ = -1;
205 }
206
207 /// cleans I/O state and moves temporaryFd_ to the conn_ for long-term use
208 void
209 Comm::ConnOpener::keepFd()
210 {
211 Must(conn_ != NULL);
212 Must(temporaryFd_ >= 0);
213
214 cleanFd();
215
216 conn_->fd = temporaryFd_;
217 temporaryFd_ = -1;
218 }
219
220 void
221 Comm::ConnOpener::start()
222 {
223 Must(conn_ != NULL);
224
225 /* outbound sockets have no need to be protocol agnostic. */
226 if (!(Ip::EnableIpv6&IPV6_SPECIAL_V4MAPPING) && conn_->remote.isIPv4()) {
227 conn_->local.setIPv4();
228 }
229
230 if (createFd())
231 doConnect();
232 }
233
234 /// called at the end of Comm::ConnOpener::DelayedConnectRetry event
235 void
236 Comm::ConnOpener::restart()
237 {
238 debugs(5, 5, conn_ << " restarting after sleep");
239 calls_.sleep_ = false;
240
241 if (createFd())
242 doConnect();
243 }
244
245 /// Create a socket for the future connection or return false.
246 /// If false is returned, done() is guaranteed to return true and end the job.
247 bool
248 Comm::ConnOpener::createFd()
249 {
250 Must(temporaryFd_ < 0);
251
252 // our initators signal abort by cancelling their callbacks
253 if (callback_ == NULL || callback_->canceled())
254 return false;
255
256 temporaryFd_ = comm_openex(SOCK_STREAM, IPPROTO_TCP, conn_->local, conn_->flags, conn_->tos, conn_->nfmark, host_);
257 if (temporaryFd_ < 0) {
258 sendAnswer(Comm::ERR_CONNECT, 0, "Comm::ConnOpener::createFd");
259 return false;
260 }
261
262 typedef CommCbMemFunT<Comm::ConnOpener, CommCloseCbParams> abortDialer;
263 calls_.earlyAbort_ = JobCallback(5, 4, abortDialer, this, Comm::ConnOpener::earlyAbort);
264 comm_add_close_handler(temporaryFd_, calls_.earlyAbort_);
265
266 typedef CommCbMemFunT<Comm::ConnOpener, CommTimeoutCbParams> timeoutDialer;
267 calls_.timeout_ = JobCallback(5, 4, timeoutDialer, this, Comm::ConnOpener::timeout);
268 debugs(5, 3, conn_ << " will timeout in " << (deadline_ - squid_curtime));
269
270 // Update the fd_table directly because commSetConnTimeout() needs open conn_
271 assert(temporaryFd_ < Squid_MaxFD);
272 assert(fd_table[temporaryFd_].flags.open);
273 typedef CommTimeoutCbParams Params;
274 Params &params = GetCommParams<Params>(calls_.timeout_);
275 params.conn = conn_;
276 fd_table[temporaryFd_].timeoutHandler = calls_.timeout_;
277 fd_table[temporaryFd_].timeout = deadline_;
278
279 return true;
280 }
281
282 void
283 Comm::ConnOpener::connected()
284 {
285 Must(temporaryFd_ >= 0);
286 keepFd();
287
288 /*
289 * stats.conn_open is used to account for the number of
290 * connections that we have open to the CachePeer, so we can limit
291 * based on the max-conn option. We need to increment here,
292 * even if the connection may fail.
293 */
294 if (CachePeer *peer=(conn_->getPeer()))
295 ++peer->stats.conn_open;
296
297 lookupLocalAddress();
298
299 /* TODO: remove these fd_table accesses. But old code still depends on fd_table flags to
300 * indicate the state of a raw fd object being passed around.
301 * Also, legacy code still depends on comm_local_port() with no access to Comm::Connection
302 * when those are done comm_local_port can become one of our member functions to do the below.
303 */
304 Must(fd_table[conn_->fd].flags.open);
305 fd_table[conn_->fd].local_addr = conn_->local;
306
307 sendAnswer(Comm::OK, 0, "Comm::ConnOpener::connected");
308 }
309
310 /// Make an FD connection attempt.
311 void
312 Comm::ConnOpener::doConnect()
313 {
314 Must(conn_ != NULL);
315 Must(temporaryFd_ >= 0);
316
317 ++ totalTries_;
318
319 switch (comm_connect_addr(temporaryFd_, conn_->remote) ) {
320
321 case Comm::INPROGRESS:
322 debugs(5, 5, HERE << conn_ << ": Comm::INPROGRESS");
323 Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, Comm::ConnOpener::InProgressConnectRetry, new Pointer(this), 0);
324 break;
325
326 case Comm::OK:
327 debugs(5, 5, HERE << conn_ << ": Comm::OK - connected");
328 connected();
329 break;
330
331 default: {
332 const int xerrno = errno;
333
334 ++failRetries_;
335 debugs(5, 7, conn_ << ": failure #" << failRetries_ << " <= " <<
336 Config.connect_retries << ": " << xstrerr(xerrno));
337
338 if (failRetries_ < Config.connect_retries) {
339 debugs(5, 5, HERE << conn_ << ": * - try again");
340 retrySleep();
341 return;
342 } else {
343 // send ERROR back to the upper layer.
344 debugs(5, 5, HERE << conn_ << ": * - ERR tried too many times already.");
345 sendAnswer(Comm::ERR_CONNECT, xerrno, "Comm::ConnOpener::connect");
346 }
347 }
348 }
349 }
350
351 /// Close and wait a little before trying to open and connect again.
352 void
353 Comm::ConnOpener::retrySleep()
354 {
355 Must(!calls_.sleep_);
356 closeFd();
357 calls_.sleep_ = true;
358 eventAdd("Comm::ConnOpener::DelayedConnectRetry",
359 Comm::ConnOpener::DelayedConnectRetry,
360 new Pointer(this), 0.05, 0, false);
361 }
362
363 /// cleans up this job sleep state
364 void
365 Comm::ConnOpener::cancelSleep()
366 {
367 if (calls_.sleep_) {
368 // It would be nice to delete the sleep event, but it might be out of
369 // the event queue and in the async queue already, so (a) we do not know
370 // whether we can safely delete the call ptr here and (b) eventDelete()
371 // will assert if the event went async. Thus, we let the event run so
372 // that it deletes the call ptr [after this job is gone]. Note that we
373 // are called only when the job ends so this "hanging event" will do
374 // nothing but deleting the call ptr. TODO: Revise eventDelete() API.
375 // eventDelete(Comm::ConnOpener::DelayedConnectRetry, calls_.sleep);
376 calls_.sleep_ = false;
377 debugs(5, 9, conn_ << " stops sleeping");
378 }
379 }
380
381 /**
382 * Lookup local-end address and port of the TCP link just opened.
383 * This ensure the connection local details are set correctly
384 */
385 void
386 Comm::ConnOpener::lookupLocalAddress()
387 {
388 struct addrinfo *addr = NULL;
389 Ip::Address::InitAddrInfo(addr);
390
391 if (getsockname(conn_->fd, addr->ai_addr, &(addr->ai_addrlen)) != 0) {
392 debugs(50, DBG_IMPORTANT, "ERROR: Failed to retrieve TCP/UDP details for socket: " << conn_ << ": " << xstrerror());
393 Ip::Address::FreeAddrInfo(addr);
394 return;
395 }
396
397 conn_->local = *addr;
398 Ip::Address::FreeAddrInfo(addr);
399 debugs(5, 6, HERE << conn_);
400 }
401
402 /** Abort connection attempt.
403 * Handles the case(s) when a partially setup connection gets closed early.
404 */
405 void
406 Comm::ConnOpener::earlyAbort(const CommCloseCbParams &io)
407 {
408 debugs(5, 3, HERE << io.conn);
409 calls_.earlyAbort_ = NULL;
410 // NP: is closing or shutdown better?
411 sendAnswer(Comm::ERR_CLOSING, io.xerrno, "Comm::ConnOpener::earlyAbort");
412 }
413
414 /**
415 * Handles the case(s) when a partially setup connection gets timed out.
416 * NP: When commSetConnTimeout accepts generic CommCommonCbParams this can die.
417 */
418 void
419 Comm::ConnOpener::timeout(const CommTimeoutCbParams &)
420 {
421 debugs(5, 5, HERE << conn_ << ": * - ERR took too long to receive response.");
422 calls_.timeout_ = NULL;
423 sendAnswer(Comm::TIMEOUT, ETIMEDOUT, "Comm::ConnOpener::timeout");
424 }
425
426 /* Legacy Wrapper for the retry event after Comm::INPROGRESS
427 * XXX: As soon as Comm::SetSelect() accepts Async calls we can use a ConnOpener::connect call
428 */
429 void
430 Comm::ConnOpener::InProgressConnectRetry(int fd, void *data)
431 {
432 Pointer *ptr = static_cast<Pointer*>(data);
433 assert(ptr);
434 if (ConnOpener *cs = ptr->valid()) {
435 // Ew. we are now outside the all AsyncJob protections.
436 // get back inside by scheduling another call...
437 typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
438 AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::connect);
439 ScheduleCallHere(call);
440 }
441 delete ptr;
442 }
443
444 /* Legacy Wrapper for the retry event with small delay after errors.
445 * XXX: As soon as eventAdd() accepts Async calls we can use a ConnOpener::restart call
446 */
447 void
448 Comm::ConnOpener::DelayedConnectRetry(void *data)
449 {
450 Pointer *ptr = static_cast<Pointer*>(data);
451 assert(ptr);
452 if (ConnOpener *cs = ptr->valid()) {
453 // Ew. we are now outside the all AsyncJob protections.
454 // get back inside by scheduling another call...
455 typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
456 AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::restart);
457 ScheduleCallHere(call);
458 }
459 delete ptr;
460 }