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