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