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