]>
Commit | Line | Data |
---|---|---|
41462d93 | 1 | /* |
f70aedc4 | 2 | * Copyright (C) 1996-2021 The Squid Software Foundation and contributors |
e25c139f | 3 | * |
bbc27441 AJ |
4 | * Squid software is distributed under GPLv2+ license and includes |
5 | * contributions from numerous individuals and organizations. | |
6 | * Please see the COPYING and CONTRIBUTORS files for details. | |
41462d93 | 7 | */ |
8 | ||
bbc27441 AJ |
9 | /* DEBUG: section 17 Request Forwarding */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
4bf68cfa | 12 | #include "AccessLogEntry.h" |
289848ca | 13 | #include "acl/Address.h" |
c0941a6a AR |
14 | #include "acl/FilledChecklist.h" |
15 | #include "acl/Gadgets.h" | |
5ae65581 | 16 | #include "anyp/PortCfg.h" |
25b481e6 | 17 | #include "CacheManager.h" |
602d9612 | 18 | #include "CachePeer.h" |
582c2af2 | 19 | #include "client_side.h" |
92ae4c86 | 20 | #include "clients/forward.h" |
f5e17947 | 21 | #include "clients/HttpTunneler.h" |
f9b72e0c | 22 | #include "comm/Connection.h" |
aed188fd | 23 | #include "comm/ConnOpener.h" |
d841c88d | 24 | #include "comm/Loops.h" |
582c2af2 | 25 | #include "CommCalls.h" |
aa839030 | 26 | #include "errorpage.h" |
582c2af2 | 27 | #include "event.h" |
fc54b8d2 | 28 | #include "fd.h" |
aa839030 | 29 | #include "fde.h" |
eb13c21e | 30 | #include "FwdState.h" |
582c2af2 | 31 | #include "globals.h" |
fc54b8d2 | 32 | #include "gopher.h" |
55622953 | 33 | #include "HappyConnOpener.h" |
bbaf2685 | 34 | #include "hier_code.h" |
fc54b8d2 | 35 | #include "http.h" |
d3dddfb5 | 36 | #include "http/Stream.h" |
924f73bc | 37 | #include "HttpReply.h" |
aa839030 | 38 | #include "HttpRequest.h" |
582c2af2 | 39 | #include "icmp/net_db.h" |
fc54b8d2 | 40 | #include "internal.h" |
582c2af2 | 41 | #include "ip/Intercept.h" |
244da4ad | 42 | #include "ip/NfMarkConfig.h" |
425de4c8 | 43 | #include "ip/QosConfig.h" |
582c2af2 | 44 | #include "ip/tools.h" |
aa839030 | 45 | #include "MemObject.h" |
582c2af2 | 46 | #include "mgr/Registration.h" |
fc54b8d2 | 47 | #include "neighbors.h" |
781ce8ff | 48 | #include "pconn.h" |
e8dca475 | 49 | #include "PeerPoolMgr.h" |
55622953 | 50 | #include "ResolvedPeers.h" |
a72b6e88 | 51 | #include "security/BlindPeerConnector.h" |
4d5904f7 | 52 | #include "SquidConfig.h" |
aa839030 | 53 | #include "SquidTime.h" |
32f1ca3f | 54 | #include "ssl/PeekingPeerConnector.h" |
aa839030 | 55 | #include "Store.h" |
e87137f1 | 56 | #include "StoreClient.h" |
5eb529cb | 57 | #include "urn.h" |
fc54b8d2 | 58 | #include "whois.h" |
cb4f4424 | 59 | #if USE_OPENSSL |
2cef0ca6 AR |
60 | #include "ssl/cert_validate_message.h" |
61 | #include "ssl/Config.h" | |
602d9612 | 62 | #include "ssl/helper.h" |
fd4624d7 | 63 | #include "ssl/ServerBump.h" |
602d9612 | 64 | #include "ssl/support.h" |
fcfdf7f9 AJ |
65 | #else |
66 | #include "security/EncryptorAnswer.h" | |
4db984be | 67 | #endif |
1a30fdf5 AJ |
68 | |
69 | #include <cerrno> | |
cfd66529 | 70 | |
575d05c4 | 71 | static CLCB fwdServerClosedWrapper; |
b6b6f466 | 72 | |
8ddcc35d | 73 | static OBJH fwdStats; |
74 | ||
75 | #define MAX_FWD_STATS_IDX 9 | |
955394ce | 76 | static int FwdReplyCodes[MAX_FWD_STATS_IDX + 1][Http::scInvalidHeader + 1]; |
db1cd23c | 77 | |
55622953 CT |
78 | PconnPool *fwdPconnPool = new PconnPool("server-peers", nullptr); |
79 | ||
b6b6f466 | 80 | CBDATA_CLASS_INIT(FwdState); |
781ce8ff | 81 | |
a72b6e88 | 82 | class FwdStatePeerAnswerDialer: public CallDialer, public Security::PeerConnector::CbDialer |
a23223bf CT |
83 | { |
84 | public: | |
fcfdf7f9 | 85 | typedef void (FwdState::*Method)(Security::EncryptorAnswer &); |
a23223bf CT |
86 | |
87 | FwdStatePeerAnswerDialer(Method method, FwdState *fwd): | |
f53969cc | 88 | method_(method), fwd_(fwd), answer_() {} |
a23223bf CT |
89 | |
90 | /* CallDialer API */ | |
91 | virtual bool canDial(AsyncCall &call) { return fwd_.valid(); } | |
92 | void dial(AsyncCall &call) { ((&(*fwd_))->*method_)(answer_); } | |
93 | virtual void print(std::ostream &os) const { | |
e2849af8 A |
94 | os << '(' << fwd_.get() << ", " << answer_ << ')'; |
95 | } | |
a23223bf | 96 | |
a72b6e88 | 97 | /* Security::PeerConnector::CbDialer API */ |
fcfdf7f9 | 98 | virtual Security::EncryptorAnswer &answer() { return answer_; } |
a23223bf CT |
99 | |
100 | private: | |
101 | Method method_; | |
102 | CbcPointer<FwdState> fwd_; | |
fcfdf7f9 | 103 | Security::EncryptorAnswer answer_; |
a23223bf | 104 | }; |
a23223bf | 105 | |
429871db | 106 | void |
7e9f330d | 107 | FwdState::HandleStoreAbort(FwdState *fwd) |
429871db | 108 | { |
6ecaf21a | 109 | Pointer tmp = fwd; // Grab a temporary pointer to keep the object alive during our scope. |
429871db | 110 | |
6b679a01 | 111 | if (Comm::IsConnOpen(fwd->serverConnection())) { |
e8dca475 | 112 | fwd->closeServerConnection("store entry aborted"); |
6dd9a2e4 AJ |
113 | } else { |
114 | debugs(17, 7, HERE << "store entry aborted; no connection to close"); | |
cff02fa6 | 115 | } |
6043e368 | 116 | fwd->stopAndDestroy("store entry aborted"); |
429871db | 117 | } |
118 | ||
25b0ce45 CT |
119 | void |
120 | FwdState::closePendingConnection(const Comm::ConnectionPointer &conn, const char *reason) | |
121 | { | |
122 | debugs(17, 3, "because " << reason << "; " << conn); | |
123 | assert(!serverConn); | |
124 | assert(!closeHandler); | |
125 | if (IsConnOpen(conn)) { | |
126 | fwdPconnPool->noteUses(fd_table[conn->fd].pconn.uses); | |
127 | conn->close(); | |
128 | } | |
129 | } | |
130 | ||
e8dca475 CT |
131 | void |
132 | FwdState::closeServerConnection(const char *reason) | |
133 | { | |
134 | debugs(17, 3, "because " << reason << "; " << serverConn); | |
236b41e8 | 135 | assert(Comm::IsConnOpen(serverConn)); |
398bc066 CT |
136 | comm_remove_close_handler(serverConn->fd, closeHandler); |
137 | closeHandler = NULL; | |
e8dca475 CT |
138 | fwdPconnPool->noteUses(fd_table[serverConn->fd].pconn.uses); |
139 | serverConn->close(); | |
140 | } | |
141 | ||
b6b6f466 | 142 | /**** PUBLIC INTERFACE ********************************************************/ |
c7f9eb6d | 143 | |
4bf68cfa | 144 | FwdState::FwdState(const Comm::ConnectionPointer &client, StoreEntry * e, HttpRequest * r, const AccessLogEntryPointer &alp): |
cc8c4af2 AJ |
145 | entry(e), |
146 | request(r), | |
147 | al(alp), | |
148 | err(NULL), | |
149 | clientConn(client), | |
150 | start_t(squid_curtime), | |
151 | n_tries(0), | |
55622953 | 152 | destinations(new ResolvedPeers()), |
cc8c4af2 | 153 | pconnRace(raceImpossible) |
db1cd23c | 154 | { |
cc8c4af2 | 155 | debugs(17, 2, "Forwarding client request " << client << ", url=" << e->url()); |
b248c2a3 | 156 | HTTPMSGLOCK(request); |
1bfe9ade | 157 | e->lock("FwdState"); |
cc8c4af2 AJ |
158 | flags.connected_okay = false; |
159 | flags.dont_retry = false; | |
160 | flags.forward_completed = false; | |
55622953 | 161 | flags.destinationsFound = false; |
cc8c4af2 | 162 | debugs(17, 3, "FwdState constructed, this=" << this); |
7a0fb323 | 163 | } |
164 | ||
165 | // Called once, right after object creation, when it is safe to set self | |
fc68f6b1 | 166 | void FwdState::start(Pointer aSelf) |
167 | { | |
7a0fb323 | 168 | // Protect ourselves from being destroyed when the only Server pointing |
169 | // to us is gone (while we expect to talk to more Servers later). | |
170 | // Once we set self, we are responsible for clearing it when we do not | |
171 | // expect to talk to any servers. | |
172 | self = aSelf; // refcounted | |
173 | ||
174 | // We hope that either the store entry aborts or peer is selected. | |
175 | // Otherwise we are going to leak our object. | |
34266cde | 176 | |
92cfc72f CT |
177 | // Ftp::Relay needs to preserve control connection on data aborts |
178 | // so it registers its own abort handler that calls ours when needed. | |
7e9f330d EB |
179 | if (!request->flags.ftpNative) { |
180 | AsyncCall::Pointer call = asyncCall(17, 4, "FwdState::Abort", cbdataDialer(&FwdState::HandleStoreAbort, this)); | |
181 | entry->registerAbortCallback(call); | |
182 | } | |
bfe4e2fe | 183 | |
3dde9e52 CT |
184 | // just in case; should already be initialized to false |
185 | request->flags.pinned = false; | |
186 | ||
32c32865 | 187 | #if STRICT_ORIGINAL_DST |
bfe4e2fe AJ |
188 | // Bug 3243: CVE 2009-0801 |
189 | // Bypass of browser same-origin access control in intercepted communication | |
190 | // To resolve this we must force DIRECT and only to the original client destination. | |
0d901ef4 | 191 | const bool isIntercepted = request && !request->flags.redirected && (request->flags.intercepted || request->flags.interceptTproxy); |
2962f8b8 AJ |
192 | const bool useOriginalDst = Config.onoff.client_dst_passthru || (request && !request->flags.hostVerified); |
193 | if (isIntercepted && useOriginalDst) { | |
d7ce0bcd | 194 | selectPeerForIntercepted(); |
7177edfb | 195 | return; |
bfe4e2fe | 196 | } |
32c32865 AJ |
197 | #endif |
198 | ||
7177edfb | 199 | // do full route options selection |
6043e368 AR |
200 | startSelectingDestinations(request, al, entry); |
201 | } | |
202 | ||
203 | /// ends forwarding; relies on refcounting so the effect may not be immediate | |
204 | void | |
205 | FwdState::stopAndDestroy(const char *reason) | |
206 | { | |
207 | debugs(17, 3, "for " << reason); | |
55622953 CT |
208 | |
209 | if (opening()) | |
210 | cancelOpening(reason); | |
211 | ||
6043e368 AR |
212 | PeerSelectionInitiator::subscribed = false; // may already be false |
213 | self = nullptr; // we hope refcounting destroys us soon; may already be nil | |
214 | /* do not place any code here as this object may be gone by now */ | |
db1cd23c | 215 | } |
216 | ||
55622953 CT |
217 | /// Notify connOpener that we no longer need connections. We do not have to do |
218 | /// this -- connOpener would eventually notice on its own, but notifying reduces | |
219 | /// waste and speeds up spare connection opening for other transactions (that | |
220 | /// could otherwise wait for this transaction to use its spare allowance). | |
221 | void | |
222 | FwdState::cancelOpening(const char *reason) | |
223 | { | |
224 | assert(calls.connector); | |
225 | calls.connector->cancel(reason); | |
226 | calls.connector = nullptr; | |
227 | notifyConnOpener(); | |
228 | connOpener.clear(); | |
229 | } | |
230 | ||
32c32865 | 231 | #if STRICT_ORIGINAL_DST |
d7ce0bcd AR |
232 | /// bypasses peerSelect() when dealing with intercepted requests |
233 | void | |
234 | FwdState::selectPeerForIntercepted() | |
235 | { | |
3dde9e52 CT |
236 | // We do not support re-wrapping inside CONNECT. |
237 | // Our only alternative is to fake a noteDestination() call. | |
238 | ||
d7ce0bcd | 239 | // use pinned connection if available |
693cb033 | 240 | if (ConnStateData *client = request->pinnedConnection()) { |
3dde9e52 CT |
241 | // emulate the PeerSelector::selectPinned() "Skip ICP" effect |
242 | entry->ping_status = PING_DONE; | |
243 | ||
55622953 | 244 | usePinned(); |
693cb033 | 245 | return; |
d7ce0bcd AR |
246 | } |
247 | ||
7177edfb | 248 | // use client original destination as second preferred choice |
3dde9e52 | 249 | const auto p = new Comm::Connection(); |
7177edfb AJ |
250 | p->peerType = ORIGINAL_DST; |
251 | p->remote = clientConn->local; | |
252 | getOutgoingAddress(request, p); | |
253 | ||
254 | debugs(17, 3, HERE << "using client original destination: " << *p); | |
55622953 CT |
255 | destinations->addPath(p); |
256 | destinations->destinationsFinalized = true; | |
257 | PeerSelectionInitiator::subscribed = false; | |
258 | useDestinations(); | |
d7ce0bcd | 259 | } |
32c32865 | 260 | #endif |
d7ce0bcd | 261 | |
802a8c1d | 262 | void |
263 | FwdState::completed() | |
41462d93 | 264 | { |
e857372a | 265 | if (flags.forward_completed) { |
e0236918 | 266 | debugs(17, DBG_IMPORTANT, HERE << "FwdState::completed called on a completed request! Bad!"); |
fc68f6b1 | 267 | return; |
268 | } | |
269 | ||
e857372a | 270 | flags.forward_completed = true; |
802a8c1d | 271 | |
16b70e2a CT |
272 | request->hier.stopPeerClock(false); |
273 | ||
c4a88a3e CT |
274 | if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { |
275 | debugs(17, 3, HERE << "entry aborted"); | |
276 | return ; | |
277 | } | |
278 | ||
bc87dc25 | 279 | #if URL_CHECKSUM_DEBUG |
62e76326 | 280 | |
b6b6f466 | 281 | entry->mem_obj->checkUrlChecksum(); |
225644d7 | 282 | #endif |
62e76326 | 283 | |
b6b6f466 | 284 | if (entry->store_status == STORE_PENDING) { |
285 | if (entry->isEmpty()) { | |
a04b7e6e | 286 | if (!err) // we quit (e.g., fd closed) before an error or content |
7e6eabbc | 287 | fail(new ErrorState(ERR_READ_ERROR, Http::scBadGateway, request, al)); |
b6b6f466 | 288 | assert(err); |
289 | errorAppendEntry(entry, err); | |
290 | err = NULL; | |
cb4f4424 | 291 | #if USE_OPENSSL |
2cef0ca6 AR |
292 | if (request->flags.sslPeek && request->clientConnectionManager.valid()) { |
293 | CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData, | |
801cfc26 | 294 | ConnStateData::httpsPeeked, ConnStateData::PinnedIdleContext(Comm::ConnectionPointer(nullptr), request)); |
2cef0ca6 AR |
295 | } |
296 | #endif | |
62e76326 | 297 | } else { |
b6b6f466 | 298 | entry->complete(); |
d88e3c49 | 299 | entry->releaseRequest(); |
62e76326 | 300 | } |
f563eea9 | 301 | } |
62e76326 | 302 | |
b6b6f466 | 303 | if (storePendingNClients(entry) > 0) |
304 | assert(!EBIT_TEST(entry->flags, ENTRY_FWD_HDR_WAIT)); | |
62e76326 | 305 | |
802a8c1d | 306 | } |
307 | ||
308 | FwdState::~FwdState() | |
309 | { | |
cc8c4af2 | 310 | debugs(17, 3, "FwdState destructor start"); |
fc68f6b1 | 311 | |
802a8c1d | 312 | if (! flags.forward_completed) |
fc68f6b1 | 313 | completed(); |
802a8c1d | 314 | |
9d2760b6 AR |
315 | doneWithRetries(); |
316 | ||
6dd9f4bd | 317 | HTTPMSGUNLOCK(request); |
62e76326 | 318 | |
913524f0 | 319 | delete err; |
62e76326 | 320 | |
7e9f330d | 321 | entry->unregisterAbortCallback("FwdState object destructed"); |
429871db | 322 | |
1bfe9ade | 323 | entry->unlock("FwdState"); |
62e76326 | 324 | |
b6b6f466 | 325 | entry = NULL; |
62e76326 | 326 | |
55622953 CT |
327 | if (opening()) |
328 | cancelOpening("~FwdState"); | |
329 | ||
e8dca475 CT |
330 | if (Comm::IsConnOpen(serverConn)) |
331 | closeServerConnection("~FwdState"); | |
fc68f6b1 | 332 | |
cc8c4af2 | 333 | debugs(17, 3, "FwdState destructed, this=" << this); |
b6b6f466 | 334 | } |
62e76326 | 335 | |
38413773 | 336 | /** |
b6b6f466 | 337 | * This is the entry point for client-side to start forwarding |
338 | * a transaction. It is a static method that may or may not | |
339 | * allocate a FwdState. | |
340 | */ | |
be0c6690 | 341 | void |
4bf68cfa | 342 | FwdState::Start(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, HttpRequest *request, const AccessLogEntryPointer &al) |
b6b6f466 | 343 | { |
b50e327b | 344 | /** \note |
b6b6f466 | 345 | * client_addr == no_addr indicates this is an "internal" request |
346 | * from peer_digest.c, asn.c, netdb.c, etc and should always | |
347 | * be allowed. yuck, I know. | |
348 | */ | |
62e76326 | 349 | |
4dd643d5 | 350 | if ( Config.accessList.miss && !request->client_addr.isNoAddr() && |
4e3f4dc7 | 351 | !request->flags.internal && request->url.getScheme() != AnyP::PROTO_CACHE_OBJECT) { |
b50e327b | 352 | /** |
fbd3636b NR |
353 | * Check if this host is allowed to fetch MISSES from us (miss_access). |
354 | * Intentionally replace the src_addr automatically selected by the checklist code | |
355 | * we do NOT want the indirect client address to be tested here. | |
b6b6f466 | 356 | */ |
c0941a6a | 357 | ACLFilledChecklist ch(Config.accessList.miss, request, NULL); |
cb365059 | 358 | ch.al = al; |
b6b6f466 | 359 | ch.src_addr = request->client_addr; |
cb365059 | 360 | ch.syncAle(request, nullptr); |
06bf5384 | 361 | if (ch.fastCheck().denied()) { |
b6b6f466 | 362 | err_type page_id; |
9ce7856a | 363 | page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName, 1); |
b6b6f466 | 364 | |
365 | if (page_id == ERR_NONE) | |
366 | page_id = ERR_FORWARDING_DENIED; | |
367 | ||
7e6eabbc | 368 | const auto anErr = new ErrorState(page_id, Http::scForbidden, request, al); |
f53969cc | 369 | errorAppendEntry(entry, anErr); // frees anErr |
be0c6690 | 370 | return; |
b6b6f466 | 371 | } |
372 | } | |
373 | ||
cfd66529 | 374 | debugs(17, 3, HERE << "'" << entry->url() << "'"); |
f4ef658f | 375 | /* |
376 | * This seems like an odd place to bind mem_obj and request. | |
377 | * Might want to assert that request is NULL at this point | |
378 | */ | |
b248c2a3 | 379 | entry->mem_obj->request = request; |
b6b6f466 | 380 | #if URL_CHECKSUM_DEBUG |
381 | ||
382 | entry->mem_obj->checkUrlChecksum(); | |
383 | #endif | |
384 | ||
385 | if (shutting_down) { | |
386 | /* more yuck */ | |
7e6eabbc | 387 | const auto anErr = new ErrorState(ERR_SHUTTING_DOWN, Http::scServiceUnavailable, request, al); |
f53969cc | 388 | errorAppendEntry(entry, anErr); // frees anErr |
be0c6690 | 389 | return; |
6801f8a8 | 390 | } |
62e76326 | 391 | |
4e3f4dc7 AJ |
392 | if (request->flags.internal) { |
393 | debugs(17, 2, "calling internalStart() due to request flag"); | |
7e6eabbc | 394 | internalStart(clientConn, request, entry, al); |
be0c6690 | 395 | return; |
4e3f4dc7 AJ |
396 | } |
397 | ||
398 | switch (request->url.getScheme()) { | |
b6b6f466 | 399 | |
39a19cb7 | 400 | case AnyP::PROTO_CACHE_OBJECT: |
4e3f4dc7 | 401 | debugs(17, 2, "calling CacheManager due to request scheme " << request->url.getScheme()); |
7e6eabbc | 402 | CacheManager::GetInstance()->start(clientConn, request, entry, al); |
be0c6690 | 403 | return; |
b6b6f466 | 404 | |
0c3d3f65 | 405 | case AnyP::PROTO_URN: |
d2a6dcba | 406 | urnStart(request, entry, al); |
be0c6690 | 407 | return; |
b6b6f466 | 408 | |
409 | default: | |
4bf68cfa | 410 | FwdState::Pointer fwd = new FwdState(clientConn, entry, request, al); |
7a0fb323 | 411 | fwd->start(fwd); |
be0c6690 | 412 | return; |
b6b6f466 | 413 | } |
414 | ||
415 | /* NOTREACHED */ | |
41462d93 | 416 | } |
417 | ||
4bf68cfa AR |
418 | void |
419 | FwdState::fwdStart(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, HttpRequest *request) | |
420 | { | |
421 | // Hides AccessLogEntry.h from code that does not supply ALE anyway. | |
422 | Start(clientConn, entry, request, NULL); | |
423 | } | |
424 | ||
0ce8e93b EB |
425 | /// subtracts time_t values, returning zero if smaller exceeds the larger value |
426 | /// time_t might be unsigned so we need to be careful when subtracting times... | |
427 | static inline time_t | |
428 | diffOrZero(const time_t larger, const time_t smaller) | |
429 | { | |
430 | return (larger > smaller) ? (larger - smaller) : 0; | |
431 | } | |
432 | ||
433 | /// time left to finish the whole forwarding process (which started at fwdStart) | |
434 | time_t | |
435 | FwdState::ForwardTimeout(const time_t fwdStart) | |
436 | { | |
437 | // time already spent on forwarding (0 if clock went backwards) | |
438 | const time_t timeSpent = diffOrZero(squid_curtime, fwdStart); | |
439 | return diffOrZero(Config.Timeout.forward, timeSpent); | |
440 | } | |
441 | ||
442 | bool | |
443 | FwdState::EnoughTimeToReForward(const time_t fwdStart) | |
444 | { | |
445 | return ForwardTimeout(fwdStart) > 0; | |
446 | } | |
447 | ||
cfd66529 | 448 | void |
3dde9e52 | 449 | FwdState::useDestinations() |
cfd66529 | 450 | { |
55622953 CT |
451 | if (!destinations->empty()) { |
452 | connectStart(); | |
cfd66529 | 453 | } else { |
6043e368 AR |
454 | if (PeerSelectionInitiator::subscribed) { |
455 | debugs(17, 4, "wait for more destinations to try"); | |
456 | return; // expect a noteDestination*() call | |
457 | } | |
458 | ||
95b83010 CT |
459 | debugs(17, 3, HERE << "Connection failed: " << entry->url()); |
460 | if (!err) { | |
7e6eabbc | 461 | const auto anErr = new ErrorState(ERR_CANNOT_FORWARD, Http::scInternalServerError, request, al); |
95b83010 | 462 | fail(anErr); |
8652f8e7 | 463 | } // else use actual error from last connection attempt |
6043e368 AR |
464 | |
465 | stopAndDestroy("tried all destinations"); | |
cfd66529 AJ |
466 | } |
467 | } | |
468 | ||
b6b6f466 | 469 | void |
470 | FwdState::fail(ErrorState * errorState) | |
471 | { | |
9b769c67 | 472 | debugs(17, 3, err_type_str[errorState->type] << " \"" << Http::StatusCodeString(errorState->httpStatus) << "\"\n\t" << entry->url()); |
b6b6f466 | 473 | |
913524f0 | 474 | delete err; |
b6b6f466 | 475 | err = errorState; |
476 | ||
e2cc8c07 | 477 | if (!errorState->request) |
b248c2a3 | 478 | errorState->request = request; |
64b66b76 | 479 | |
693cb033 CT |
480 | if (err->type != ERR_ZERO_SIZE_OBJECT) |
481 | return; | |
482 | ||
483 | if (pconnRace == racePossible) { | |
bc81ee68 AR |
484 | debugs(17, 5, HERE << "pconn race happened"); |
485 | pconnRace = raceHappened; | |
9b7992d9 EB |
486 | if (destinationReceipt) { |
487 | destinations->reinstatePath(destinationReceipt); | |
488 | destinationReceipt = nullptr; | |
489 | } | |
bc81ee68 | 490 | } |
693cb033 CT |
491 | |
492 | if (ConnStateData *pinned_connection = request->pinnedConnection()) { | |
493 | pinned_connection->pinning.zeroReply = true; | |
693cb033 CT |
494 | debugs(17, 4, "zero reply on pinned connection"); |
495 | } | |
b6b6f466 | 496 | } |
497 | ||
38413773 | 498 | /** |
b6b6f466 | 499 | * Frees fwdState without closing FD or generating an abort |
500 | */ | |
501 | void | |
00ae51e4 | 502 | FwdState::unregister(Comm::ConnectionPointer &conn) |
5229395c AJ |
503 | { |
504 | debugs(17, 3, HERE << entry->url() ); | |
505 | assert(serverConnection() == conn); | |
6b679a01 | 506 | assert(Comm::IsConnOpen(conn)); |
398bc066 CT |
507 | comm_remove_close_handler(conn->fd, closeHandler); |
508 | closeHandler = NULL; | |
00ae51e4 | 509 | serverConn = NULL; |
9b7992d9 | 510 | destinationReceipt = nullptr; |
5229395c AJ |
511 | } |
512 | ||
d5430dc8 | 513 | // \deprecated use unregister(Comm::ConnectionPointer &conn) instead |
5229395c | 514 | void |
b6b6f466 | 515 | FwdState::unregister(int fd) |
516 | { | |
5229395c AJ |
517 | debugs(17, 3, HERE << entry->url() ); |
518 | assert(fd == serverConnection()->fd); | |
00ae51e4 | 519 | unregister(serverConn); |
b6b6f466 | 520 | } |
521 | ||
38413773 | 522 | /** |
d5430dc8 | 523 | * FooClient modules call fwdComplete() when they are done |
b6b6f466 | 524 | * downloading an object. Then, we either 1) re-forward the |
525 | * request somewhere else if needed, or 2) call storeComplete() | |
526 | * to finish it off | |
527 | */ | |
528 | void | |
529 | FwdState::complete() | |
530 | { | |
66d51f4f AR |
531 | const auto replyStatus = entry->mem().baseReply().sline.status(); |
532 | debugs(17, 3, *entry << " status " << replyStatus << ' ' << entry->url()); | |
b6b6f466 | 533 | #if URL_CHECKSUM_DEBUG |
534 | ||
535 | entry->mem_obj->checkUrlChecksum(); | |
536 | #endif | |
537 | ||
66d51f4f | 538 | logReplyStatus(n_tries, replyStatus); |
b6b6f466 | 539 | |
540 | if (reforward()) { | |
66d51f4f | 541 | debugs(17, 3, "re-forwarding " << replyStatus << " " << entry->url()); |
b6b6f466 | 542 | |
6b679a01 | 543 | if (Comm::IsConnOpen(serverConn)) |
00ae51e4 | 544 | unregister(serverConn); |
b6b6f466 | 545 | |
cfd66529 AJ |
546 | entry->reset(); |
547 | ||
3dde9e52 | 548 | useDestinations(); |
8652f8e7 | 549 | |
b6b6f466 | 550 | } else { |
6b679a01 | 551 | if (Comm::IsConnOpen(serverConn)) |
66d51f4f | 552 | debugs(17, 3, "server FD " << serverConnection()->fd << " not re-forwarding status " << replyStatus); |
6b679a01 | 553 | else |
66d51f4f | 554 | debugs(17, 3, "server (FD closed) not re-forwarding status " << replyStatus); |
9e5c22cf | 555 | entry->complete(); |
fc68f6b1 | 556 | |
6b679a01 | 557 | if (!Comm::IsConnOpen(serverConn)) |
fc68f6b1 | 558 | completed(); |
559 | ||
6043e368 | 560 | stopAndDestroy("forwarding completed"); |
b6b6f466 | 561 | } |
562 | } | |
563 | ||
6043e368 AR |
564 | void |
565 | FwdState::noteDestination(Comm::ConnectionPointer path) | |
566 | { | |
55622953 CT |
567 | flags.destinationsFound = true; |
568 | ||
569 | if (!path) { | |
570 | // We can call usePinned() without fear of clashing with an earlier | |
571 | // forwarding attempt because PINNED must be the first destination. | |
572 | assert(destinations->empty()); | |
573 | usePinned(); | |
574 | return; | |
575 | } | |
576 | ||
577 | debugs(17, 3, path); | |
578 | ||
55622953 CT |
579 | destinations->addPath(path); |
580 | ||
581 | if (Comm::IsConnOpen(serverConn)) { | |
582 | // We are already using a previously opened connection, so we cannot be | |
583 | // waiting for connOpener. We still receive destinations for backup. | |
584 | Must(!opening()); | |
585 | return; | |
586 | } | |
587 | ||
588 | if (opening()) { | |
589 | notifyConnOpener(); | |
590 | return; // and continue to wait for FwdState::noteConnection() callback | |
591 | } | |
592 | ||
593 | // This is the first path candidate we have seen. Create connOpener. | |
594 | useDestinations(); | |
6043e368 | 595 | } |
b6b6f466 | 596 | |
6043e368 AR |
597 | void |
598 | FwdState::noteDestinationsEnd(ErrorState *selectionError) | |
b6b6f466 | 599 | { |
6043e368 | 600 | PeerSelectionInitiator::subscribed = false; |
55622953 | 601 | destinations->destinationsFinalized = true; |
6043e368 | 602 | |
55622953 | 603 | if (!flags.destinationsFound) { |
6043e368 AR |
604 | if (selectionError) { |
605 | debugs(17, 3, "Will abort forwarding because path selection has failed."); | |
606 | Must(!err); // if we tried to connect, then path selection succeeded | |
607 | fail(selectionError); | |
608 | } | |
609 | else if (err) | |
610 | debugs(17, 3, "Will abort forwarding because all found paths have failed."); | |
611 | else | |
612 | debugs(17, 3, "Will abort forwarding because path selection found no paths."); | |
613 | ||
3dde9e52 | 614 | useDestinations(); // will detect and handle the lack of paths |
6043e368 AR |
615 | return; |
616 | } | |
617 | // else continue to use one of the previously noted destinations; | |
618 | // if all of them fail, forwarding as whole will fail | |
619 | Must(!selectionError); // finding at least one path means selection succeeded | |
55622953 CT |
620 | |
621 | if (Comm::IsConnOpen(serverConn)) { | |
622 | // We are already using a previously opened connection, so we cannot be | |
623 | // waiting for connOpener. We were receiving destinations for backup. | |
624 | Must(!opening()); | |
625 | return; | |
626 | } | |
627 | ||
628 | Must(opening()); // or we would be stuck with nothing to do or wait for | |
629 | notifyConnOpener(); | |
630 | // and continue to wait for FwdState::noteConnection() callback | |
631 | } | |
632 | ||
633 | /// makes sure connOpener knows that destinations have changed | |
634 | void | |
635 | FwdState::notifyConnOpener() | |
636 | { | |
637 | if (destinations->notificationPending) { | |
638 | debugs(17, 7, "reusing pending notification about " << *destinations); | |
639 | } else { | |
640 | debugs(17, 7, "notifying about " << *destinations); | |
641 | destinations->notificationPending = true; | |
642 | CallJobHere(17, 5, connOpener, HappyConnOpener, noteCandidatesChange); | |
643 | } | |
b6b6f466 | 644 | } |
645 | ||
6043e368 AR |
646 | /**** CALLBACK WRAPPERS ************************************************************/ |
647 | ||
b6b6f466 | 648 | static void |
575d05c4 | 649 | fwdServerClosedWrapper(const CommCloseCbParams ¶ms) |
b6b6f466 | 650 | { |
575d05c4 AJ |
651 | FwdState *fwd = (FwdState *)params.data; |
652 | fwd->serverClosed(params.fd); | |
b6b6f466 | 653 | } |
654 | ||
b6b6f466 | 655 | /**** PRIVATE *****************************************************************/ |
656 | ||
545782b8 | 657 | /* |
658 | * FwdState::checkRetry | |
26ac0430 | 659 | * |
545782b8 | 660 | * Return TRUE if the request SHOULD be retried. This method is |
661 | * called when the HTTP connection fails, or when the connection | |
d5430dc8 | 662 | * is closed before reading the end of HTTP headers from the server. |
545782b8 | 663 | */ |
b6b6f466 | 664 | bool |
665 | FwdState::checkRetry() | |
68bd6892 | 666 | { |
d8fd0f18 | 667 | if (shutting_down) |
b6b6f466 | 668 | return false; |
62e76326 | 669 | |
9d2760b6 AR |
670 | if (!self) { // we have aborted before the server called us back |
671 | debugs(17, 5, HERE << "not retrying because of earlier abort"); | |
672 | // we will be destroyed when the server clears its Pointer to us | |
673 | return false; | |
674 | } | |
675 | ||
b6b6f466 | 676 | if (entry->store_status != STORE_PENDING) |
677 | return false; | |
62e76326 | 678 | |
b6b6f466 | 679 | if (!entry->isEmpty()) |
680 | return false; | |
62e76326 | 681 | |
3eebd267 | 682 | if (exhaustedTries()) |
b6b6f466 | 683 | return false; |
4ed0e075 | 684 | |
3dde9e52 CT |
685 | if (request->flags.pinned && !pinnedCanRetry()) |
686 | return false; | |
687 | ||
0ce8e93b | 688 | if (!EnoughTimeToReForward(start_t)) |
b6b6f466 | 689 | return false; |
62e76326 | 690 | |
b6b6f466 | 691 | if (flags.dont_retry) |
692 | return false; | |
62e76326 | 693 | |
3b9899f7 AR |
694 | if (request->bodyNibbled()) |
695 | return false; | |
696 | ||
0bbd5532 AJ |
697 | // NP: not yet actually connected anywhere. retry is safe. |
698 | if (!flags.connected_okay) | |
699 | return true; | |
700 | ||
5d4989a8 | 701 | if (!checkRetriable()) |
702 | return false; | |
703 | ||
b6b6f466 | 704 | return true; |
68bd6892 | 705 | } |
706 | ||
afc753f3 | 707 | /// Whether we may try sending this request again after a failure. |
b6b6f466 | 708 | bool |
709 | FwdState::checkRetriable() | |
cb928909 | 710 | { |
ccbcff0e AR |
711 | // Optimize: A compliant proxy may retry PUTs, but Squid lacks the [rather |
712 | // complicated] code required to protect the PUT request body from being | |
713 | // nibbled during the first try. Thus, Squid cannot retry some PUTs today. | |
714 | if (request->body_pipe != NULL) | |
715 | return false; | |
716 | ||
c2a7cefd AJ |
717 | // RFC2616 9.1 Safe and Idempotent Methods |
718 | return (request->method.isHttpSafe() || request->method.isIdempotent()); | |
cb928909 | 719 | } |
720 | ||
b6b6f466 | 721 | void |
722 | FwdState::serverClosed(int fd) | |
910169e5 | 723 | { |
8ed21336 | 724 | // XXX: fd is often -1 here |
e8dca475 | 725 | debugs(17, 2, "FD " << fd << " " << entry->url() << " after " << |
8ed21336 AR |
726 | (fd >= 0 ? fd_table[fd].pconn.uses : -1) << " requests"); |
727 | if (fd >= 0 && serverConnection()->fd == fd) | |
e8dca475 | 728 | fwdPconnPool->noteUses(fd_table[fd].pconn.uses); |
3e8c047e | 729 | retryOrBail(); |
730 | } | |
731 | ||
732 | void | |
26ac0430 AJ |
733 | FwdState::retryOrBail() |
734 | { | |
b6b6f466 | 735 | if (checkRetry()) { |
cfd66529 | 736 | debugs(17, 3, HERE << "re-forwarding (" << n_tries << " tries, " << (squid_curtime - start_t) << " secs)"); |
3dde9e52 | 737 | useDestinations(); |
8652f8e7 | 738 | return; |
d8fd0f18 | 739 | } |
62e76326 | 740 | |
9d2760b6 AR |
741 | // TODO: should we call completed() here and move doneWithRetries there? |
742 | doneWithRetries(); | |
743 | ||
16b70e2a CT |
744 | request->hier.stopPeerClock(false); |
745 | ||
8f01bdfb | 746 | if (self != NULL && !err && shutting_down && entry->isEmpty()) { |
7e6eabbc | 747 | const auto anErr = new ErrorState(ERR_SHUTTING_DOWN, Http::scServiceUnavailable, request, al); |
f01d4b80 | 748 | errorAppendEntry(entry, anErr); |
f563eea9 | 749 | } |
62e76326 | 750 | |
6043e368 | 751 | stopAndDestroy("cannot retry"); |
910169e5 | 752 | } |
753 | ||
9d2760b6 AR |
754 | // If the Server quits before nibbling at the request body, the body sender |
755 | // will not know (so that we can retry). Call this if we will not retry. We | |
756 | // will notify the sender so that it does not get stuck waiting for space. | |
757 | void | |
758 | FwdState::doneWithRetries() | |
759 | { | |
760 | if (request && request->body_pipe != NULL) | |
761 | request->body_pipe->expectNoConsumption(); | |
762 | } | |
763 | ||
3e8c047e | 764 | // called by the server that failed after calling unregister() |
765 | void | |
766 | FwdState::handleUnregisteredServerEnd() | |
767 | { | |
cfd66529 | 768 | debugs(17, 2, HERE << "self=" << self << " err=" << err << ' ' << entry->url()); |
6b679a01 | 769 | assert(!Comm::IsConnOpen(serverConn)); |
3e8c047e | 770 | retryOrBail(); |
771 | } | |
772 | ||
25b0ce45 CT |
773 | /// starts a preparation step for an established connection; retries on failures |
774 | template <typename StepStart> | |
775 | void | |
776 | FwdState::advanceDestination(const char *stepDescription, const Comm::ConnectionPointer &conn, const StepStart &startStep) | |
777 | { | |
778 | // TODO: Extract destination-specific handling from FwdState so that all the | |
779 | // awkward, limited-scope advanceDestination() calls can be replaced with a | |
780 | // single simple try/catch,retry block. | |
781 | try { | |
782 | startStep(); | |
783 | // now wait for the step callback | |
784 | } catch (...) { | |
785 | debugs (17, 2, "exception while trying to " << stepDescription << ": " << CurrentException); | |
786 | closePendingConnection(conn, "connection preparation exception"); | |
787 | retryOrBail(); | |
788 | } | |
789 | } | |
790 | ||
55622953 CT |
791 | /// called when a to-peer connection has been successfully obtained or |
792 | /// when all candidate destinations have been tried and all have failed | |
b6b6f466 | 793 | void |
55622953 | 794 | FwdState::noteConnection(HappyConnOpener::Answer &answer) |
41462d93 | 795 | { |
9b7992d9 EB |
796 | assert(!destinationReceipt); |
797 | ||
55622953 CT |
798 | calls.connector = nullptr; |
799 | connOpener.clear(); | |
62e76326 | 800 | |
55622953 CT |
801 | Must(n_tries <= answer.n_tries); // n_tries cannot decrease |
802 | n_tries = answer.n_tries; | |
62e76326 | 803 | |
25b0ce45 CT |
804 | ErrorState *error = nullptr; |
805 | if ((error = answer.error.get())) { | |
55622953 CT |
806 | flags.dont_retry = true; // or HappyConnOpener would not have given up |
807 | syncHierNote(answer.conn, request->url.host()); | |
25b0ce45 | 808 | Must(!Comm::IsConnOpen(answer.conn)); |
55622953 | 809 | answer.error.clear(); // preserve error for errorSendComplete() |
25b0ce45 | 810 | } else if (!Comm::IsConnOpen(answer.conn) || fd_table[answer.conn->fd].closing()) { |
9b7992d9 EB |
811 | // We do not know exactly why the connection got closed, so we play it |
812 | // safe, allowing retries only for persistent (reused) connections | |
813 | if (answer.reused) { | |
814 | destinationReceipt = answer.conn; | |
815 | assert(destinationReceipt); | |
816 | } | |
25b0ce45 CT |
817 | syncHierNote(answer.conn, request->url.host()); |
818 | closePendingConnection(answer.conn, "conn was closed while waiting for noteConnection"); | |
819 | error = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request, al); | |
9b7992d9 EB |
820 | } else { |
821 | assert(!error); | |
822 | destinationReceipt = answer.conn; | |
823 | assert(destinationReceipt); | |
824 | // serverConn remains nil until syncWithServerConn() | |
25b0ce45 CT |
825 | } |
826 | ||
827 | if (error) { | |
828 | fail(error); | |
9b7992d9 | 829 | retryOrBail(); |
2f538b78 AJ |
830 | return; |
831 | } | |
62e76326 | 832 | |
25b0ce45 CT |
833 | if (answer.reused) { |
834 | syncWithServerConn(answer.conn, request->url.host(), answer.reused); | |
55622953 | 835 | return dispatch(); |
25b0ce45 | 836 | } |
3dde9e52 | 837 | |
55622953 | 838 | // Check if we need to TLS before use |
25b0ce45 | 839 | if (const auto *peer = answer.conn->getPeer()) { |
f5e17947 CT |
840 | // Assume that it is only possible for the client-first from the |
841 | // bumping modes to try connect to a remote server. The bumped | |
842 | // requests with other modes are using pinned connections or fails. | |
843 | const bool clientFirstBump = request->flags.sslBumped; | |
844 | // We need a CONNECT tunnel to send encrypted traffic through a proxy, | |
845 | // but we do not support TLS inside TLS, so we exclude HTTPS proxies. | |
846 | const bool originWantsEncryptedTraffic = | |
847 | request->method == Http::METHOD_CONNECT || | |
848 | request->flags.sslPeek || | |
849 | clientFirstBump; | |
850 | if (originWantsEncryptedTraffic && // the "encrypted traffic" part | |
851 | !peer->options.originserver && // the "through a proxy" part | |
852 | !peer->secure.encryptTransport) // the "exclude HTTPS proxies" part | |
25b0ce45 | 853 | return advanceDestination("establish tunnel through proxy", answer.conn, [this,&answer] { |
70ac5b29 | 854 | establishTunnelThruProxy(answer.conn); |
855 | }); | |
f5e17947 CT |
856 | } |
857 | ||
25b0ce45 | 858 | secureConnectionToPeerIfNeeded(answer.conn); |
f5e17947 CT |
859 | } |
860 | ||
861 | void | |
25b0ce45 | 862 | FwdState::establishTunnelThruProxy(const Comm::ConnectionPointer &conn) |
f5e17947 CT |
863 | { |
864 | AsyncCall::Pointer callback = asyncCall(17,4, | |
865 | "FwdState::tunnelEstablishmentDone", | |
866 | Http::Tunneler::CbDialer<FwdState>(&FwdState::tunnelEstablishmentDone, this)); | |
867 | HttpRequest::Pointer requestPointer = request; | |
236b41e8 | 868 | const auto tunneler = new Http::Tunneler(conn, requestPointer, callback, connectingTimeout(conn), al); |
25b0ce45 CT |
869 | |
870 | // TODO: Replace this hack with proper Comm::Connection-Pool association | |
871 | // that is not tied to fwdPconnPool and can handle disappearing pools. | |
872 | tunneler->noteFwdPconnUse = true; | |
873 | ||
f5e17947 | 874 | #if USE_DELAY_POOLS |
236b41e8 AR |
875 | Must(conn); |
876 | Must(conn->getPeer()); | |
877 | if (!conn->getPeer()->options.no_delay) | |
f5e17947 CT |
878 | tunneler->setDelayId(entry->mem_obj->mostBytesAllowed()); |
879 | #endif | |
880 | AsyncJob::Start(tunneler); | |
881 | // and wait for the tunnelEstablishmentDone() call | |
882 | } | |
883 | ||
884 | /// resumes operations after the (possibly failed) HTTP CONNECT exchange | |
885 | void | |
886 | FwdState::tunnelEstablishmentDone(Http::TunnelerAnswer &answer) | |
887 | { | |
25b0ce45 CT |
888 | ErrorState *error = nullptr; |
889 | if (!answer.positive()) { | |
890 | Must(!Comm::IsConnOpen(answer.conn)); | |
891 | error = answer.squidError.get(); | |
892 | Must(error); | |
893 | answer.squidError.clear(); // preserve error for fail() | |
894 | } else if (!Comm::IsConnOpen(answer.conn) || fd_table[answer.conn->fd].closing()) { | |
895 | // The socket could get closed while our callback was queued. | |
896 | // We close Connection here to sync Connection::fd. | |
897 | closePendingConnection(answer.conn, "conn was closed while waiting for tunnelEstablishmentDone"); | |
898 | error = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request, al); | |
899 | } else if (!answer.leftovers.isEmpty()) { | |
f5e17947 CT |
900 | // This should not happen because TLS servers do not speak first. If we |
901 | // have to handle this, then pass answer.leftovers via a PeerConnector | |
902 | // to ServerBio. See ClientBio::setReadBufData(). | |
903 | static int occurrences = 0; | |
904 | const auto level = (occurrences++ < 100) ? DBG_IMPORTANT : 2; | |
905 | debugs(17, level, "ERROR: Early data after CONNECT response. " << | |
906 | "Found " << answer.leftovers.length() << " bytes. " << | |
25b0ce45 CT |
907 | "Closing " << answer.conn); |
908 | error = new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request, al); | |
909 | closePendingConnection(answer.conn, "server spoke before tunnelEstablishmentDone"); | |
910 | } | |
911 | if (error) { | |
912 | fail(error); | |
f5e17947 CT |
913 | retryOrBail(); |
914 | return; | |
915 | } | |
916 | ||
25b0ce45 | 917 | secureConnectionToPeerIfNeeded(answer.conn); |
f5e17947 CT |
918 | } |
919 | ||
920 | /// handles an established TCP connection to peer (including origin servers) | |
921 | void | |
25b0ce45 | 922 | FwdState::secureConnectionToPeerIfNeeded(const Comm::ConnectionPointer &conn) |
f5e17947 CT |
923 | { |
924 | assert(!request->flags.pinned); | |
925 | ||
25b0ce45 | 926 | const auto p = conn->getPeer(); |
3dde9e52 CT |
927 | const bool peerWantsTls = p && p->secure.encryptTransport; |
928 | // userWillTlsToPeerForUs assumes CONNECT == HTTPS | |
929 | const bool userWillTlsToPeerForUs = p && p->options.originserver && | |
930 | request->method == Http::METHOD_CONNECT; | |
931 | const bool needTlsToPeer = peerWantsTls && !userWillTlsToPeerForUs; | |
f5e17947 CT |
932 | const bool clientFirstBump = request->flags.sslBumped; // client-first (already) bumped connection |
933 | const bool needsBump = request->flags.sslPeek || clientFirstBump; | |
934 | ||
935 | // 'GET https://...' requests. If a peer is used the request is forwarded | |
936 | // as is | |
937 | const bool needTlsToOrigin = !p && request->url.getScheme() == AnyP::PROTO_HTTPS && !clientFirstBump; | |
938 | ||
939 | if (needTlsToPeer || needTlsToOrigin || needsBump) { | |
25b0ce45 CT |
940 | return advanceDestination("secure connection to peer", conn, [this,&conn] { |
941 | secureConnectionToPeer(conn); | |
942 | }); | |
41462d93 | 943 | } |
2f538b78 | 944 | |
fcfdf7f9 | 945 | // if not encrypting just run the post-connect actions |
25b0ce45 CT |
946 | successfullyConnectedToPeer(conn); |
947 | } | |
948 | ||
949 | /// encrypts an established TCP connection to peer (including origin servers) | |
950 | void | |
951 | FwdState::secureConnectionToPeer(const Comm::ConnectionPointer &conn) | |
952 | { | |
953 | HttpRequest::Pointer requestPointer = request; | |
954 | AsyncCall::Pointer callback = asyncCall(17,4, | |
955 | "FwdState::ConnectedToPeer", | |
956 | FwdStatePeerAnswerDialer(&FwdState::connectedToPeer, this)); | |
957 | const auto sslNegotiationTimeout = connectingTimeout(conn); | |
958 | Security::PeerConnector *connector = nullptr; | |
959 | #if USE_OPENSSL | |
960 | if (request->flags.sslPeek) | |
961 | connector = new Ssl::PeekingPeerConnector(requestPointer, conn, clientConn, callback, al, sslNegotiationTimeout); | |
962 | else | |
963 | #endif | |
964 | connector = new Security::BlindPeerConnector(requestPointer, conn, callback, al, sslNegotiationTimeout); | |
965 | connector->noteFwdPconnUse = true; | |
966 | AsyncJob::Start(connector); // will call our callback | |
41462d93 | 967 | } |
968 | ||
f5e17947 | 969 | /// called when all negotiations with the TLS-speaking peer have been completed |
a23223bf | 970 | void |
fcfdf7f9 | 971 | FwdState::connectedToPeer(Security::EncryptorAnswer &answer) |
a23223bf | 972 | { |
25b0ce45 CT |
973 | ErrorState *error = nullptr; |
974 | if ((error = answer.error.get())) { | |
975 | Must(!Comm::IsConnOpen(answer.conn)); | |
a23223bf | 976 | answer.error.clear(); // preserve error for errorSendComplete() |
25b0ce45 | 977 | } else if (answer.tunneled) { |
56753478 CT |
978 | // TODO: When ConnStateData establishes tunnels, its state changes |
979 | // [in ways that may affect logging?]. Consider informing | |
980 | // ConnStateData about our tunnel or otherwise unifying tunnel | |
981 | // establishment [side effects]. | |
56753478 CT |
982 | complete(); // destroys us |
983 | return; | |
25b0ce45 CT |
984 | } else if (!Comm::IsConnOpen(answer.conn) || fd_table[answer.conn->fd].closing()) { |
985 | closePendingConnection(answer.conn, "conn was closed while waiting for connectedToPeer"); | |
986 | error = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request, al); | |
56753478 CT |
987 | } |
988 | ||
25b0ce45 CT |
989 | if (error) { |
990 | fail(error); | |
991 | retryOrBail(); | |
992 | return; | |
993 | } | |
994 | ||
995 | successfullyConnectedToPeer(answer.conn); | |
f5e17947 CT |
996 | } |
997 | ||
998 | /// called when all negotiations with the peer have been completed | |
999 | void | |
25b0ce45 | 1000 | FwdState::successfullyConnectedToPeer(const Comm::ConnectionPointer &conn) |
f5e17947 | 1001 | { |
25b0ce45 CT |
1002 | syncWithServerConn(conn, request->url.host(), false); |
1003 | ||
fcfdf7f9 AJ |
1004 | // should reach ConnStateData before the dispatched Client job starts |
1005 | CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData, | |
1006 | ConnStateData::notePeerConnection, serverConnection()); | |
1007 | ||
89d7efa5 CT |
1008 | if (serverConnection()->getPeer()) |
1009 | peerConnectSucceded(serverConnection()->getPeer()); | |
1010 | ||
a23223bf CT |
1011 | dispatch(); |
1012 | } | |
a23223bf | 1013 | |
55622953 | 1014 | /// commits to using the given open to-peer connection |
b6b6f466 | 1015 | void |
55622953 | 1016 | FwdState::syncWithServerConn(const Comm::ConnectionPointer &conn, const char *host, const bool reused) |
41462d93 | 1017 | { |
55622953 CT |
1018 | Must(IsConnOpen(conn)); |
1019 | serverConn = conn; | |
9b7992d9 | 1020 | // no effect on destinationReceipt (which may even be nil here) |
62e76326 | 1021 | |
55622953 | 1022 | closeHandler = comm_add_close_handler(serverConn->fd, fwdServerClosedWrapper, this); |
62e76326 | 1023 | |
55622953 CT |
1024 | if (reused) { |
1025 | pconnRace = racePossible; | |
1026 | ResetMarkingsToServer(request, *serverConn); | |
1027 | } else { | |
1028 | pconnRace = raceImpossible; | |
1029 | // Comm::ConnOpener already applied proper/current markings | |
746beefe | 1030 | } |
4b77ea6b | 1031 | |
7d1dac79 EB |
1032 | syncHierNote(serverConn, host); |
1033 | } | |
1034 | ||
1035 | void | |
1036 | FwdState::syncHierNote(const Comm::ConnectionPointer &server, const char *host) | |
1037 | { | |
1ce66e29 | 1038 | if (request) |
d8165775 | 1039 | request->hier.resetPeerNotes(server, host); |
1ce66e29 | 1040 | if (al) |
d8165775 | 1041 | al->hier.resetPeerNotes(server, host); |
4b77ea6b AR |
1042 | } |
1043 | ||
8aec3e1b CT |
1044 | /** |
1045 | * Called after forwarding path selection (via peer select) has taken place | |
1046 | * and whenever forwarding needs to attempt a new connection (routing failover). | |
1047 | * We have a vector of possible localIP->remoteIP paths now ready to start being connected. | |
1048 | */ | |
1049 | void | |
1050 | FwdState::connectStart() | |
1051 | { | |
55622953 | 1052 | debugs(17, 3, *destinations << " to " << entry->url()); |
8aec3e1b | 1053 | |
daf80700 CT |
1054 | Must(!request->pinnedConnection()); |
1055 | ||
55622953 CT |
1056 | assert(!destinations->empty()); |
1057 | assert(!opening()); | |
8aec3e1b | 1058 | |
55622953 CT |
1059 | // Ditch error page if it was created before. |
1060 | // A new one will be created if there's another problem | |
3dde9e52 CT |
1061 | delete err; |
1062 | err = nullptr; | |
1063 | request->clearError(); | |
55622953 | 1064 | serverConn = nullptr; |
9b7992d9 | 1065 | destinationReceipt = nullptr; |
3dde9e52 | 1066 | |
16b70e2a | 1067 | request->hier.startPeerClock(); |
777831e0 | 1068 | |
55622953 | 1069 | calls.connector = asyncCall(17, 5, "FwdState::noteConnection", HappyConnOpener::CbDialer<FwdState>(&FwdState::noteConnection, this)); |
bc81ee68 | 1070 | |
55622953 CT |
1071 | HttpRequest::Pointer cause = request; |
1072 | const auto cs = new HappyConnOpener(destinations, calls.connector, cause, start_t, n_tries, al); | |
1073 | cs->setHost(request->url.host()); | |
1074 | bool retriable = checkRetriable(); | |
1075 | if (!retriable && Config.accessList.serverPconnForNonretriable) { | |
1076 | ACLFilledChecklist ch(Config.accessList.serverPconnForNonretriable, request, nullptr); | |
1077 | ch.al = al; | |
1078 | ch.syncAle(request, nullptr); | |
1079 | retriable = ch.fastCheck().allowed(); | |
41462d93 | 1080 | } |
55622953 CT |
1081 | cs->setRetriable(retriable); |
1082 | cs->allowPersistent(pconnRace != raceHappened); | |
1083 | destinations->notificationPending = true; // start() is async | |
1084 | connOpener = cs; | |
855150a4 | 1085 | AsyncJob::Start(cs); |
41462d93 | 1086 | } |
1087 | ||
3dde9e52 CT |
1088 | /// send request on an existing connection dedicated to the requesting client |
1089 | void | |
1090 | FwdState::usePinned() | |
1091 | { | |
3dde9e52 CT |
1092 | const auto connManager = request->pinnedConnection(); |
1093 | debugs(17, 7, "connection manager: " << connManager); | |
1094 | ||
daf80700 | 1095 | try { |
9b7992d9 EB |
1096 | // TODO: Refactor syncWithServerConn() and callers to always set |
1097 | // serverConn inside that method. | |
daf80700 CT |
1098 | serverConn = ConnStateData::BorrowPinnedConnection(request, al); |
1099 | debugs(17, 5, "connection: " << serverConn); | |
1100 | } catch (ErrorState * const anErr) { | |
1101 | syncHierNote(nullptr, connManager ? connManager->pinning.host : request->url.host()); | |
3dde9e52 | 1102 | serverConn = nullptr; |
3dde9e52 CT |
1103 | fail(anErr); |
1104 | // Connection managers monitor their idle pinned to-server | |
1105 | // connections and close from-client connections upon seeing | |
1106 | // a to-server connection closure. Retrying here is futile. | |
1107 | stopAndDestroy("pinned connection failure"); | |
1108 | return; | |
1109 | } | |
1110 | ||
3dde9e52 CT |
1111 | ++n_tries; |
1112 | request->flags.pinned = true; | |
1113 | ||
e7d3e24e | 1114 | assert(connManager); |
3dde9e52 CT |
1115 | if (connManager->pinnedAuth()) |
1116 | request->flags.auth = true; | |
1117 | ||
3dde9e52 | 1118 | // the server may close the pinned connection before this request |
55622953 | 1119 | const auto reused = true; |
daf80700 | 1120 | syncWithServerConn(serverConn, connManager->pinning.host, reused); |
55622953 | 1121 | |
3dde9e52 CT |
1122 | dispatch(); |
1123 | } | |
1124 | ||
b6b6f466 | 1125 | void |
1126 | FwdState::dispatch() | |
41462d93 | 1127 | { |
7f06a3d8 | 1128 | debugs(17, 3, clientConn << ": Fetching " << request->method << ' ' << entry->url()); |
e0ebe27c | 1129 | /* |
1130 | * Assert that server_fd is set. This is to guarantee that fwdState | |
1131 | * is attached to something and will be deallocated when server_fd | |
1132 | * is closed. | |
1133 | */ | |
6b679a01 | 1134 | assert(Comm::IsConnOpen(serverConn)); |
62e76326 | 1135 | |
5229395c | 1136 | fd_note(serverConnection()->fd, entry->url()); |
62e76326 | 1137 | |
e8dca475 | 1138 | fd_table[serverConnection()->fd].noteUse(); |
62e76326 | 1139 | |
a7ad6e4e | 1140 | /*assert(!EBIT_TEST(entry->flags, ENTRY_DISPATCHED)); */ |
1141 | assert(entry->ping_status != PING_WAITING); | |
62e76326 | 1142 | |
1bfe9ade | 1143 | assert(entry->locked()); |
62e76326 | 1144 | |
a7ad6e4e | 1145 | EBIT_SET(entry->flags, ENTRY_DISPATCHED); |
62e76326 | 1146 | |
55622953 CT |
1147 | flags.connected_okay = true; |
1148 | ||
5c51bffb | 1149 | netdbPingSite(request->url.host()); |
62e76326 | 1150 | |
425de4c8 | 1151 | /* Retrieves remote server TOS or MARK value, and stores it as part of the |
7172612f | 1152 | * original client request FD object. It is later used to forward |
425de4c8 | 1153 | * remote server's TOS/MARK in the response to the client in case of a MISS. |
7172612f | 1154 | */ |
425de4c8 | 1155 | if (Ip::Qos::TheConfig.isHitNfmarkActive()) { |
b5523edc AJ |
1156 | if (Comm::IsConnOpen(clientConn) && Comm::IsConnOpen(serverConnection())) { |
1157 | fde * clientFde = &fd_table[clientConn->fd]; // XXX: move the fd_table access into Ip::Qos | |
653d9927 | 1158 | /* Get the netfilter CONNMARK */ |
244da4ad | 1159 | clientFde->nfConnmarkFromServer = Ip::Qos::getNfConnmark(serverConnection(), Ip::Qos::dirOpened); |
425de4c8 AJ |
1160 | } |
1161 | } | |
1162 | ||
1163 | #if _SQUID_LINUX_ | |
1164 | /* Bug 2537: The TOS forward part of QOS only applies to patched Linux kernels. */ | |
1165 | if (Ip::Qos::TheConfig.isHitTosActive()) { | |
b5523edc AJ |
1166 | if (Comm::IsConnOpen(clientConn)) { |
1167 | fde * clientFde = &fd_table[clientConn->fd]; // XXX: move the fd_table access into Ip::Qos | |
425de4c8 | 1168 | /* Get the TOS value for the packet */ |
b5523edc | 1169 | Ip::Qos::getTosFromServer(serverConnection(), clientFde); |
7172612f | 1170 | } |
26ac0430 | 1171 | } |
7172612f AJ |
1172 | #endif |
1173 | ||
cb4f4424 | 1174 | #if USE_OPENSSL |
2c065fc8 | 1175 | if (request->flags.sslPeek) { |
d7ce0bcd | 1176 | CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData, |
801cfc26 | 1177 | ConnStateData::httpsPeeked, ConnStateData::PinnedIdleContext(serverConnection(), request)); |
d7ce0bcd AR |
1178 | unregister(serverConn); // async call owns it now |
1179 | complete(); // destroys us | |
1180 | return; | |
061bbdec | 1181 | } |
d7ce0bcd AR |
1182 | #endif |
1183 | ||
f5e17947 CT |
1184 | if (const auto peer = serverConnection()->getPeer()) { |
1185 | ++peer->stats.fetches; | |
1186 | request->prepForPeering(*peer); | |
b6b6f466 | 1187 | httpStart(this); |
41462d93 | 1188 | } else { |
2c065fc8 | 1189 | assert(!request->flags.sslPeek); |
f5e17947 | 1190 | request->prepForDirect(); |
62e76326 | 1191 | |
4e3f4dc7 | 1192 | switch (request->url.getScheme()) { |
62e76326 | 1193 | |
0c3d3f65 | 1194 | case AnyP::PROTO_HTTPS: |
b6b6f466 | 1195 | httpStart(this); |
62e76326 | 1196 | break; |
62e76326 | 1197 | |
0c3d3f65 | 1198 | case AnyP::PROTO_HTTP: |
b6b6f466 | 1199 | httpStart(this); |
62e76326 | 1200 | break; |
1201 | ||
0c3d3f65 | 1202 | case AnyP::PROTO_GOPHER: |
b6b6f466 | 1203 | gopherStart(this); |
62e76326 | 1204 | break; |
1205 | ||
0c3d3f65 | 1206 | case AnyP::PROTO_FTP: |
92ae4c86 | 1207 | if (request->flags.ftpNative) |
5517260a | 1208 | Ftp::StartRelay(this); |
434a79b0 | 1209 | else |
5517260a | 1210 | Ftp::StartGateway(this); |
62e76326 | 1211 | break; |
1212 | ||
39a19cb7 | 1213 | case AnyP::PROTO_CACHE_OBJECT: |
62e76326 | 1214 | |
0c3d3f65 | 1215 | case AnyP::PROTO_URN: |
62e76326 | 1216 | fatal_dump("Should never get here"); |
1217 | break; | |
1218 | ||
0c3d3f65 | 1219 | case AnyP::PROTO_WHOIS: |
b6b6f466 | 1220 | whoisStart(this); |
62e76326 | 1221 | break; |
1222 | ||
f53969cc | 1223 | case AnyP::PROTO_WAIS: /* Not implemented */ |
fc68f6b1 | 1224 | |
62e76326 | 1225 | default: |
7be06178 | 1226 | debugs(17, DBG_IMPORTANT, "WARNING: Cannot retrieve '" << entry->url() << "'."); |
7e6eabbc | 1227 | const auto anErr = new ErrorState(ERR_UNSUP_REQ, Http::scBadRequest, request, al); |
b6b6f466 | 1228 | fail(anErr); |
7be06178 | 1229 | // Set the dont_retry flag because this is not a transient (network) error. |
e857372a | 1230 | flags.dont_retry = true; |
6b679a01 | 1231 | if (Comm::IsConnOpen(serverConn)) { |
25b0ce45 | 1232 | serverConn->close(); // trigger cleanup |
746beefe | 1233 | } |
62e76326 | 1234 | break; |
1235 | } | |
41462d93 | 1236 | } |
1237 | } | |
1238 | ||
545782b8 | 1239 | /* |
1240 | * FwdState::reforward | |
1241 | * | |
1242 | * returns TRUE if the transaction SHOULD be re-forwarded to the | |
8bbb16e3 | 1243 | * next choice in the serverDestinations list. This method is called when |
d5430dc8 | 1244 | * peer communication completes normally, or experiences |
545782b8 | 1245 | * some error after receiving the end of HTTP headers. |
1246 | */ | |
b6b6f466 | 1247 | int |
1248 | FwdState::reforward() | |
db1cd23c | 1249 | { |
b6b6f466 | 1250 | StoreEntry *e = entry; |
c4a88a3e CT |
1251 | |
1252 | if (EBIT_TEST(e->flags, ENTRY_ABORTED)) { | |
1253 | debugs(17, 3, HERE << "entry aborted"); | |
1254 | return 0; | |
1255 | } | |
1256 | ||
db1cd23c | 1257 | assert(e->store_status == STORE_PENDING); |
1258 | assert(e->mem_obj); | |
bc87dc25 | 1259 | #if URL_CHECKSUM_DEBUG |
62e76326 | 1260 | |
528b2c61 | 1261 | e->mem_obj->checkUrlChecksum(); |
bc87dc25 | 1262 | #endif |
62e76326 | 1263 | |
cfd66529 | 1264 | debugs(17, 3, HERE << e->url() << "?" ); |
62e76326 | 1265 | |
3dde9e52 CT |
1266 | if (request->flags.pinned && !pinnedCanRetry()) { |
1267 | debugs(17, 3, "pinned connection; cannot retry"); | |
1268 | return 0; | |
1269 | } | |
1270 | ||
d6eb18d6 | 1271 | if (!EBIT_TEST(e->flags, ENTRY_FWD_HDR_WAIT)) { |
cfd66529 | 1272 | debugs(17, 3, HERE << "No, ENTRY_FWD_HDR_WAIT isn't set"); |
62e76326 | 1273 | return 0; |
d6eb18d6 | 1274 | } |
62e76326 | 1275 | |
3eebd267 | 1276 | if (exhaustedTries()) |
62e76326 | 1277 | return 0; |
1278 | ||
58217e94 | 1279 | if (request->bodyNibbled()) |
62e76326 | 1280 | return 0; |
1281 | ||
55622953 | 1282 | if (destinations->empty() && !PeerSelectionInitiator::subscribed) { |
cfd66529 | 1283 | debugs(17, 3, HERE << "No alternative forwarding paths left"); |
62e76326 | 1284 | return 0; |
db1cd23c | 1285 | } |
62e76326 | 1286 | |
66d51f4f | 1287 | const auto s = entry->mem().baseReply().sline.status(); |
cfd66529 | 1288 | debugs(17, 3, HERE << "status " << s); |
b6b6f466 | 1289 | return reforwardableStatus(s); |
db1cd23c | 1290 | } |
1291 | ||
b6b6f466 | 1292 | static void |
1293 | fwdStats(StoreEntry * s) | |
64d8034e | 1294 | { |
b6b6f466 | 1295 | int i; |
1296 | int j; | |
1297 | storeAppendPrintf(s, "Status"); | |
62e76326 | 1298 | |
95dc7ff4 FC |
1299 | for (j = 1; j < MAX_FWD_STATS_IDX; ++j) { |
1300 | storeAppendPrintf(s, "\ttry#%d", j); | |
64d8034e | 1301 | } |
64d8034e | 1302 | |
b6b6f466 | 1303 | storeAppendPrintf(s, "\n"); |
0185bd6f | 1304 | |
955394ce | 1305 | for (i = 0; i <= (int) Http::scInvalidHeader; ++i) { |
b6b6f466 | 1306 | if (FwdReplyCodes[0][i] == 0) |
1307 | continue; | |
0185bd6f | 1308 | |
b6b6f466 | 1309 | storeAppendPrintf(s, "%3d", i); |
0185bd6f | 1310 | |
95dc7ff4 | 1311 | for (j = 0; j <= MAX_FWD_STATS_IDX; ++j) { |
b6b6f466 | 1312 | storeAppendPrintf(s, "\t%d", FwdReplyCodes[j][i]); |
62e76326 | 1313 | } |
62e76326 | 1314 | |
b6b6f466 | 1315 | storeAppendPrintf(s, "\n"); |
e0ebe27c | 1316 | } |
7197b20d | 1317 | } |
1318 | ||
b6b6f466 | 1319 | /**** STATIC MEMBER FUNCTIONS *************************************************/ |
db1cd23c | 1320 | |
b6b6f466 | 1321 | bool |
955394ce | 1322 | FwdState::reforwardableStatus(const Http::StatusCode s) const |
db1cd23c | 1323 | { |
b6b6f466 | 1324 | switch (s) { |
62e76326 | 1325 | |
955394ce | 1326 | case Http::scBadGateway: |
62e76326 | 1327 | |
f11c8e2f | 1328 | case Http::scGatewayTimeout: |
b6b6f466 | 1329 | return true; |
62e76326 | 1330 | |
955394ce | 1331 | case Http::scForbidden: |
62e76326 | 1332 | |
955394ce | 1333 | case Http::scInternalServerError: |
62e76326 | 1334 | |
955394ce | 1335 | case Http::scNotImplemented: |
62e76326 | 1336 | |
955394ce | 1337 | case Http::scServiceUnavailable: |
b6b6f466 | 1338 | return Config.retry.onerror; |
62e76326 | 1339 | |
b6b6f466 | 1340 | default: |
1341 | return false; | |
db1cd23c | 1342 | } |
b6b6f466 | 1343 | |
1344 | /* NOTREACHED */ | |
db1cd23c | 1345 | } |
8ddcc35d | 1346 | |
1347 | void | |
b6b6f466 | 1348 | FwdState::initModule() |
8ddcc35d | 1349 | { |
6852be71 | 1350 | RegisterWithCacheManager(); |
8ddcc35d | 1351 | } |
1352 | ||
62ee09ca | 1353 | void |
84f50787 | 1354 | FwdState::RegisterWithCacheManager(void) |
62ee09ca | 1355 | { |
8822ebee | 1356 | Mgr::RegisterAction("forward", "Request Forwarding Statistics", fwdStats, 0, 1); |
62ee09ca | 1357 | } |
1358 | ||
b6b6f466 | 1359 | void |
955394ce | 1360 | FwdState::logReplyStatus(int tries, const Http::StatusCode status) |
8ddcc35d | 1361 | { |
955394ce | 1362 | if (status > Http::scInvalidHeader) |
62e76326 | 1363 | return; |
1364 | ||
75eb730e | 1365 | assert(tries >= 0); |
62e76326 | 1366 | |
8ddcc35d | 1367 | if (tries > MAX_FWD_STATS_IDX) |
62e76326 | 1368 | tries = MAX_FWD_STATS_IDX; |
1369 | ||
95dc7ff4 | 1370 | ++ FwdReplyCodes[tries][status]; |
8ddcc35d | 1371 | } |
1372 | ||
3eebd267 EB |
1373 | bool |
1374 | FwdState::exhaustedTries() const | |
1375 | { | |
1376 | return n_tries >= Config.forward_max_tries; | |
1377 | } | |
1378 | ||
3dde9e52 CT |
1379 | bool |
1380 | FwdState::pinnedCanRetry() const | |
1381 | { | |
1382 | assert(request->flags.pinned); | |
1383 | ||
1384 | // pconn race on pinned connection: Currently we do not have any mechanism | |
1385 | // to retry current pinned connection path. | |
1386 | if (pconnRace == raceHappened) | |
1387 | return false; | |
1388 | ||
1389 | // If a bumped connection was pinned, then the TLS client was given our peer | |
1390 | // details. Do not retry because we do not ensure that those details stay | |
1391 | // constant. Step1-bumped connections do not get our TLS peer details, are | |
1392 | // never pinned, and, hence, never reach this method. | |
1393 | if (request->flags.sslBumped) | |
1394 | return false; | |
1395 | ||
1396 | // The other pinned cases are FTP proxying and connection-based HTTP | |
1397 | // authentication. TODO: Do these cases have restrictions? | |
1398 | return true; | |
1399 | } | |
1400 | ||
f5e17947 CT |
1401 | time_t |
1402 | FwdState::connectingTimeout(const Comm::ConnectionPointer &conn) const | |
1403 | { | |
1404 | const auto connTimeout = conn->connectTimeout(start_t); | |
1405 | return positiveTimeout(connTimeout); | |
1406 | } | |
1407 | ||
b6b6f466 | 1408 | /**** PRIVATE NON-MEMBER FUNCTIONS ********************************************/ |
62e76326 | 1409 | |
057f5854 | 1410 | /* |
1411 | * DPW 2007-05-19 | |
1412 | * Formerly static, but now used by client_side_request.cc | |
1413 | */ | |
425de4c8 AJ |
1414 | /// Checks for a TOS value to apply depending on the ACL |
1415 | tos_t | |
b6b6f466 | 1416 | aclMapTOS(acl_tos * head, ACLChecklist * ch) |
b6a2f15e | 1417 | { |
60019fea | 1418 | for (acl_tos *l = head; l; l = l->next) { |
06bf5384 | 1419 | if (!l->aclList || ch->fastCheck(l->aclList).allowed()) |
b6b6f466 | 1420 | return l->tos; |
1421 | } | |
5894ad28 | 1422 | |
b6b6f466 | 1423 | return 0; |
1424 | } | |
5894ad28 | 1425 | |
425de4c8 | 1426 | /// Checks for a netfilter mark value to apply depending on the ACL |
244da4ad AG |
1427 | Ip::NfMarkConfig |
1428 | aclFindNfMarkConfig(acl_nfmark * head, ACLChecklist * ch) | |
425de4c8 | 1429 | { |
60019fea | 1430 | for (acl_nfmark *l = head; l; l = l->next) { |
06bf5384 | 1431 | if (!l->aclList || ch->fastCheck(l->aclList).allowed()) |
244da4ad | 1432 | return l->markConfig; |
425de4c8 AJ |
1433 | } |
1434 | ||
244da4ad | 1435 | return {}; |
425de4c8 AJ |
1436 | } |
1437 | ||
cfd66529 | 1438 | void |
7d74b4b8 | 1439 | getOutgoingAddress(HttpRequest * request, const Comm::ConnectionPointer &conn) |
b6b6f466 | 1440 | { |
07f889c1 | 1441 | // skip if an outgoing address is already set. |
4dd643d5 | 1442 | if (!conn->local.isAnyAddr()) return; |
cfd66529 | 1443 | |
07f889c1 | 1444 | // ensure that at minimum the wildcard local matches remote protocol |
4dd643d5 AJ |
1445 | if (conn->remote.isIPv4()) |
1446 | conn->local.setIPv4(); | |
07f889c1 | 1447 | |
cfd66529 | 1448 | // maybe use TPROXY client address |
450fe1cb | 1449 | if (request && request->flags.spoofClientIp) { |
739b352a | 1450 | if (!conn->getPeer() || !conn->getPeer()->options.no_tproxy) { |
96d64448 AJ |
1451 | #if FOLLOW_X_FORWARDED_FOR && LINUX_NETFILTER |
1452 | if (Config.onoff.tproxy_uses_indirect_client) | |
45906573 | 1453 | conn->local = request->indirect_client_addr; |
96d64448 AJ |
1454 | else |
1455 | #endif | |
45906573 | 1456 | conn->local = request->client_addr; |
3b659606 | 1457 | conn->local.port(0); // let OS pick the source port to prevent address clashes |
cfd66529 AJ |
1458 | // some flags need setting on the socket to use this address |
1459 | conn->flags |= COMM_DOBIND; | |
1460 | conn->flags |= COMM_TRANSPARENT; | |
1461 | return; | |
1462 | } | |
b0758e04 AJ |
1463 | // else no tproxy today ... |
1464 | } | |
c303f6e3 | 1465 | |
b50e327b | 1466 | if (!Config.accessList.outgoing_address) { |
cfd66529 | 1467 | return; // anything will do. |
b50e327b AJ |
1468 | } |
1469 | ||
c0941a6a | 1470 | ACLFilledChecklist ch(NULL, request, NULL); |
1b091aec | 1471 | ch.dst_peer_name = conn->getPeer() ? conn->getPeer()->name : NULL; |
cfd66529 AJ |
1472 | ch.dst_addr = conn->remote; |
1473 | ||
1474 | // TODO use the connection details in ACL. | |
1475 | // needs a bit of rework in ACLFilledChecklist to use Comm::Connection instead of ConnStateData | |
6db78a1a | 1476 | |
289848ca | 1477 | for (Acl::Address *l = Config.accessList.outgoing_address; l; l = l->next) { |
cfd66529 AJ |
1478 | |
1479 | /* check if the outgoing address is usable to the destination */ | |
4dd643d5 | 1480 | if (conn->remote.isIPv4() != l->addr.isIPv4()) continue; |
cfd66529 AJ |
1481 | |
1482 | /* check ACLs for this outgoing address */ | |
06bf5384 | 1483 | if (!l->aclList || ch.fastCheck(l->aclList).allowed()) { |
cfd66529 AJ |
1484 | conn->local = l->addr; |
1485 | return; | |
1486 | } | |
1487 | } | |
b6b6f466 | 1488 | } |
62e76326 | 1489 | |
55622953 CT |
1490 | /// \returns the TOS value that should be set on the to-peer connection |
1491 | static tos_t | |
1492 | GetTosToServer(HttpRequest * request, Comm::Connection &conn) | |
425de4c8 | 1493 | { |
55622953 CT |
1494 | if (!Ip::Qos::TheConfig.tosToServer) |
1495 | return 0; | |
1496 | ||
425de4c8 | 1497 | ACLFilledChecklist ch(NULL, request, NULL); |
55622953 CT |
1498 | ch.dst_peer_name = conn.getPeer() ? conn.getPeer()->name : nullptr; |
1499 | ch.dst_addr = conn.remote; | |
425de4c8 AJ |
1500 | return aclMapTOS(Ip::Qos::TheConfig.tosToServer, &ch); |
1501 | } | |
1502 | ||
55622953 CT |
1503 | /// \returns the Netfilter mark that should be set on the to-peer connection |
1504 | static nfmark_t | |
1505 | GetNfmarkToServer(HttpRequest * request, Comm::Connection &conn) | |
b6b6f466 | 1506 | { |
55622953 CT |
1507 | if (!Ip::Qos::TheConfig.nfmarkToServer) |
1508 | return 0; | |
1509 | ||
c0941a6a | 1510 | ACLFilledChecklist ch(NULL, request, NULL); |
55622953 CT |
1511 | ch.dst_peer_name = conn.getPeer() ? conn.getPeer()->name : nullptr; |
1512 | ch.dst_addr = conn.remote; | |
244da4ad AG |
1513 | const auto mc = aclFindNfMarkConfig(Ip::Qos::TheConfig.nfmarkToServer, &ch); |
1514 | return mc.mark; | |
b6a2f15e | 1515 | } |
6ee88490 CT |
1516 | |
1517 | void | |
1518 | GetMarkingsToServer(HttpRequest * request, Comm::Connection &conn) | |
1519 | { | |
1520 | // Get the server side TOS and Netfilter mark to be set on the connection. | |
55622953 CT |
1521 | conn.tos = GetTosToServer(request, conn); |
1522 | conn.nfmark = GetNfmarkToServer(request, conn); | |
1523 | debugs(17, 3, "from " << conn.local << " tos " << int(conn.tos) << " netfilter mark " << conn.nfmark); | |
1524 | } | |
6ee88490 | 1525 | |
55622953 CT |
1526 | void |
1527 | ResetMarkingsToServer(HttpRequest * request, Comm::Connection &conn) | |
1528 | { | |
1529 | GetMarkingsToServer(request, conn); | |
1530 | ||
1531 | // TODO: Avoid these calls if markings has not changed. | |
1532 | if (conn.tos) | |
1533 | Ip::Qos::setSockTos(&conn, conn.tos); | |
1534 | if (conn.nfmark) | |
1535 | Ip::Qos::setSockNfmark(&conn, conn.nfmark); | |
6ee88490 | 1536 | } |
f53969cc | 1537 |