]> git.ipfire.org Git - thirdparty/squid.git/blob - src/comm/ConnOpener.cc
Removed inclusion of protos.h from most clients.
[thirdparty/squid.git] / src / comm / ConnOpener.cc
1 /*
2 * DEBUG: section 05 Socket Connection Opener
3 */
4
5 #include "squid.h"
6 #include "comm/ConnOpener.h"
7 #include "comm/Connection.h"
8 #include "comm/Loops.h"
9 #include "comm.h"
10 #include "fd.h"
11 #include "fde.h"
12 #include "globals.h"
13 #include "icmp/net_db.h"
14 #include "ipcache.h"
15 #include "SquidTime.h"
16
17 #if HAVE_ERRNO_H
18 #include <errno.h>
19 #endif
20
21 CBDATA_NAMESPACED_CLASS_INIT(Comm, ConnOpener);
22
23 Comm::ConnOpener::ConnOpener(Comm::ConnectionPointer &c, AsyncCall::Pointer &handler, time_t ctimeout) :
24 AsyncJob("Comm::ConnOpener"),
25 host_(NULL),
26 temporaryFd_(-1),
27 conn_(c),
28 callback_(handler),
29 totalTries_(0),
30 failRetries_(0),
31 connectTimeout_(ctimeout),
32 connectStart_(0)
33 {}
34
35 Comm::ConnOpener::~ConnOpener()
36 {
37 safe_free(host_);
38 }
39
40 bool
41 Comm::ConnOpener::doneAll() const
42 {
43 // is the conn_ to be opened still waiting?
44 if (conn_ == NULL) {
45 return AsyncJob::doneAll();
46 }
47
48 // is the callback still to be called?
49 if (callback_ == NULL || callback_->canceled()) {
50 return AsyncJob::doneAll();
51 }
52
53 return false;
54 }
55
56 void
57 Comm::ConnOpener::swanSong()
58 {
59 // cancel any event watchers
60 // done here to get the "swanSong" mention in cancel debugging.
61 if (calls_.earlyAbort_ != NULL) {
62 calls_.earlyAbort_->cancel("Comm::ConnOpener::swanSong");
63 calls_.earlyAbort_ = NULL;
64 }
65 if (calls_.timeout_ != NULL) {
66 calls_.timeout_->cancel("Comm::ConnOpener::swanSong");
67 calls_.timeout_ = NULL;
68 }
69
70 if (callback_ != NULL) {
71 if (callback_->canceled())
72 callback_ = NULL;
73 else
74 // inform the still-waiting caller we are dying
75 doneConnecting(COMM_ERR_CONNECT, 0);
76 }
77
78 // rollback what we can from the job state
79 if (temporaryFd_ >= 0) {
80 // doneConnecting() handles partial FD connection cleanup
81 doneConnecting(COMM_ERR_CONNECT, 0);
82 }
83
84 AsyncJob::swanSong();
85 }
86
87 void
88 Comm::ConnOpener::setHost(const char * new_host)
89 {
90 // unset and erase if already set.
91 if (host_ != NULL)
92 safe_free(host_);
93
94 // set the new one if given.
95 if (new_host != NULL)
96 host_ = xstrdup(new_host);
97 }
98
99 const char *
100 Comm::ConnOpener::getHost() const
101 {
102 return host_;
103 }
104
105 /**
106 * Connection attempt are completed. One way or the other.
107 * Pass the results back to the external handler.
108 * NP: on errors the earlyAbort call should be cancelled first with a reason.
109 */
110 void
111 Comm::ConnOpener::doneConnecting(comm_err_t status, int xerrno)
112 {
113 // only mark the address good/bad AFTER connect is finished.
114 if (host_ != NULL) {
115 if (xerrno == 0)
116 ipcacheMarkGoodAddr(host_, conn_->remote);
117 else {
118 ipcacheMarkBadAddr(host_, conn_->remote);
119 #if USE_ICMP
120 if (Config.onoff.test_reachability)
121 netdbDeleteAddrNetwork(conn_->remote);
122 #endif
123 }
124 }
125
126 if (callback_ != NULL) {
127 typedef CommConnectCbParams Params;
128 Params &params = GetCommParams<Params>(callback_);
129 params.conn = conn_;
130 params.flag = status;
131 params.xerrno = xerrno;
132 ScheduleCallHere(callback_);
133 callback_ = NULL;
134 }
135
136 if (temporaryFd_ >= 0) {
137 debugs(5, 4, HERE << conn_ << " closing temp FD " << temporaryFd_);
138 // it never reached fully open, so cleanup the FD handlers
139 // Note that comm_close() sequence does not happen for partially open FD
140 Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, NULL, NULL, 0);
141 calls_.earlyAbort_ = NULL;
142 if (calls_.timeout_ != NULL) {
143 calls_.timeout_->cancel("Comm::ConnOpener::doneConnecting");
144 calls_.timeout_ = NULL;
145 }
146 fd_table[temporaryFd_].timeoutHandler = NULL;
147 fd_table[temporaryFd_].timeout = 0;
148 close(temporaryFd_);
149 fd_close(temporaryFd_);
150 temporaryFd_ = -1;
151 }
152
153 /* ensure cleared local state, we are done. */
154 conn_ = NULL;
155 }
156
157 void
158 Comm::ConnOpener::start()
159 {
160 Must(conn_ != NULL);
161
162 /* get a socket open ready for connecting with */
163 if (temporaryFd_ < 0) {
164 #if USE_IPV6
165 /* outbound sockets have no need to be protocol agnostic. */
166 if (conn_->remote.IsIPv4()) {
167 conn_->local.SetIPv4();
168 }
169 #endif
170 temporaryFd_ = comm_openex(SOCK_STREAM, IPPROTO_TCP, conn_->local, conn_->flags, conn_->tos, conn_->nfmark, host_);
171 if (temporaryFd_ < 0) {
172 doneConnecting(COMM_ERR_CONNECT, 0);
173 return;
174 }
175 }
176
177 typedef CommCbMemFunT<Comm::ConnOpener, CommCloseCbParams> abortDialer;
178 calls_.earlyAbort_ = JobCallback(5, 4, abortDialer, this, Comm::ConnOpener::earlyAbort);
179 comm_add_close_handler(temporaryFd_, calls_.earlyAbort_);
180
181 typedef CommCbMemFunT<Comm::ConnOpener, CommTimeoutCbParams> timeoutDialer;
182 calls_.timeout_ = JobCallback(5, 4, timeoutDialer, this, Comm::ConnOpener::timeout);
183 debugs(5, 3, HERE << conn_ << " timeout " << connectTimeout_);
184
185 // Update the fd_table directly because conn_ is not yet storing the FD
186 assert(temporaryFd_ < Squid_MaxFD);
187 assert(fd_table[temporaryFd_].flags.open);
188 typedef CommTimeoutCbParams Params;
189 Params &params = GetCommParams<Params>(calls_.timeout_);
190 params.conn = conn_;
191 fd_table[temporaryFd_].timeoutHandler = calls_.timeout_;
192 fd_table[temporaryFd_].timeout = squid_curtime + (time_t) connectTimeout_;
193
194 connectStart_ = squid_curtime;
195 connect();
196 }
197
198 void
199 Comm::ConnOpener::connected()
200 {
201 conn_->fd = temporaryFd_;
202 temporaryFd_ = -1;
203
204 /*
205 * stats.conn_open is used to account for the number of
206 * connections that we have open to the peer, so we can limit
207 * based on the max-conn option. We need to increment here,
208 * even if the connection may fail.
209 */
210 if (peer *peer=(conn_->getPeer()))
211 ++peer->stats.conn_open;
212
213 lookupLocalAddress();
214
215 /* TODO: remove these fd_table accesses. But old code still depends on fd_table flags to
216 * indicate the state of a raw fd object being passed around.
217 * Also, legacy code still depends on comm_local_port() with no access to Comm::Connection
218 * when those are done comm_local_port can become one of our member functions to do the below.
219 */
220 fd_table[conn_->fd].flags.open = 1;
221 fd_table[conn_->fd].local_addr = conn_->local;
222 }
223
224 /** Make an FD connection attempt.
225 * Handles the case(s) when a partially setup connection gets closed early.
226 */
227 void
228 Comm::ConnOpener::connect()
229 {
230 Must(conn_ != NULL);
231
232 // our parent Jobs signal abort by cancelling their callbacks.
233 if (callback_ == NULL || callback_->canceled())
234 return;
235
236 ++ totalTries_;
237
238 switch (comm_connect_addr(temporaryFd_, conn_->remote) ) {
239
240 case COMM_INPROGRESS:
241 // check for timeout FIRST.
242 if (squid_curtime - connectStart_ > connectTimeout_) {
243 debugs(5, 5, HERE << conn_ << ": * - ERR took too long already.");
244 calls_.earlyAbort_->cancel("Comm::ConnOpener::connect timed out");
245 doneConnecting(COMM_TIMEOUT, errno);
246 return;
247 } else {
248 debugs(5, 5, HERE << conn_ << ": COMM_INPROGRESS");
249 Comm::SetSelect(temporaryFd_, COMM_SELECT_WRITE, Comm::ConnOpener::InProgressConnectRetry, new Pointer(this), 0);
250 }
251 break;
252
253 case COMM_OK:
254 debugs(5, 5, HERE << conn_ << ": COMM_OK - connected");
255 connected();
256 doneConnecting(COMM_OK, 0);
257 break;
258
259 default:
260 ++failRetries_;
261
262 // check for timeout FIRST.
263 if (squid_curtime - connectStart_ > connectTimeout_) {
264 debugs(5, 5, HERE << conn_ << ": * - ERR took too long to receive response.");
265 calls_.earlyAbort_->cancel("Comm::ConnOpener::connect timed out");
266 doneConnecting(COMM_TIMEOUT, errno);
267 } else if (failRetries_ < Config.connect_retries) {
268 debugs(5, 5, HERE << conn_ << ": * - try again");
269 eventAdd("Comm::ConnOpener::DelayedConnectRetry", Comm::ConnOpener::DelayedConnectRetry, new Pointer(this), 0.05, 0, false);
270 return;
271 } else {
272 // send ERROR back to the upper layer.
273 debugs(5, 5, HERE << conn_ << ": * - ERR tried too many times already.");
274 calls_.earlyAbort_->cancel("Comm::ConnOpener::connect failed");
275 doneConnecting(COMM_ERR_CONNECT, errno);
276 }
277 }
278 }
279
280 /**
281 * Lookup local-end address and port of the TCP link just opened.
282 * This ensure the connection local details are set correctly
283 */
284 void
285 Comm::ConnOpener::lookupLocalAddress()
286 {
287 struct addrinfo *addr = NULL;
288 conn_->local.InitAddrInfo(addr);
289
290 if (getsockname(conn_->fd, addr->ai_addr, &(addr->ai_addrlen)) != 0) {
291 debugs(50, DBG_IMPORTANT, "ERROR: Failed to retrieve TCP/UDP details for socket: " << conn_ << ": " << xstrerror());
292 conn_->local.FreeAddrInfo(addr);
293 return;
294 }
295
296 conn_->local = *addr;
297 conn_->local.FreeAddrInfo(addr);
298 debugs(5, 6, HERE << conn_);
299 }
300
301 /** Abort connection attempt.
302 * Handles the case(s) when a partially setup connection gets closed early.
303 */
304 void
305 Comm::ConnOpener::earlyAbort(const CommCloseCbParams &io)
306 {
307 debugs(5, 3, HERE << io.conn);
308 doneConnecting(COMM_ERR_CLOSING, io.xerrno); // NP: is closing or shutdown better?
309 }
310
311 /**
312 * Handles the case(s) when a partially setup connection gets timed out.
313 * NP: When commSetConnTimeout accepts generic CommCommonCbParams this can die.
314 */
315 void
316 Comm::ConnOpener::timeout(const CommTimeoutCbParams &)
317 {
318 connect();
319 }
320
321 /* Legacy Wrapper for the retry event after COMM_INPROGRESS
322 * XXX: As soon as Comm::SetSelect() accepts Async calls we can use a ConnOpener::connect call
323 */
324 void
325 Comm::ConnOpener::InProgressConnectRetry(int fd, void *data)
326 {
327 Pointer *ptr = static_cast<Pointer*>(data);
328 assert(ptr);
329 if (ConnOpener *cs = ptr->valid()) {
330 // Ew. we are now outside the all AsyncJob protections.
331 // get back inside by scheduling another call...
332 typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
333 AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::connect);
334 ScheduleCallHere(call);
335 }
336 delete ptr;
337 }
338
339 /* Legacy Wrapper for the retry event with small delay after errors.
340 * XXX: As soon as eventAdd() accepts Async calls we can use a ConnOpener::connect call
341 */
342 void
343 Comm::ConnOpener::DelayedConnectRetry(void *data)
344 {
345 Pointer *ptr = static_cast<Pointer*>(data);
346 assert(ptr);
347 if (ConnOpener *cs = ptr->valid()) {
348 // Ew. we are now outside the all AsyncJob protections.
349 // get back inside by scheduling another call...
350 typedef NullaryMemFunT<Comm::ConnOpener> Dialer;
351 AsyncCall::Pointer call = JobCallback(5, 4, Dialer, cs, Comm::ConnOpener::connect);
352 ScheduleCallHere(call);
353 }
354 delete ptr;
355 }