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