]> git.ipfire.org Git - thirdparty/squid.git/blob - src/forward.cc
ff141ec8ceeaf2ef34894ee74eb9ef280f5fc109
[thirdparty/squid.git] / src / forward.cc
1 /*
2 * DEBUG: section 17 Request Forwarding
3 * AUTHOR: Duane Wessels
4 *
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
7 *
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
30 *
31 */
32
33
34 #include "squid.h"
35 #include "acl/FilledChecklist.h"
36 #include "acl/Gadgets.h"
37 #include "comm/Connection.h"
38 #include "comm/ConnOpener.h"
39 #include "CommCalls.h"
40 #include "event.h"
41 #include "errorpage.h"
42 #include "fde.h"
43 #include "forward.h"
44 #include "hier_code.h"
45 #include "HttpReply.h"
46 #include "HttpRequest.h"
47 #include "ip/QosConfig.h"
48 #include "MemObject.h"
49 #include "pconn.h"
50 #include "PeerSelectState.h"
51 #include "SquidTime.h"
52 #include "Store.h"
53 #include "icmp/net_db.h"
54 #include "ip/Intercept.h"
55 #include "ip/tools.h"
56 #include "mgr/Registration.h"
57
58 static PSC fwdPeerSelectionCompleteWrapper;
59 static PF fwdServerClosedWrapper;
60 #if USE_SSL
61 static PF fwdNegotiateSSLWrapper;
62 #endif
63 static CNCB fwdConnectDoneWrapper;
64
65 static OBJH fwdStats;
66
67 #define MAX_FWD_STATS_IDX 9
68 static int FwdReplyCodes[MAX_FWD_STATS_IDX + 1][HTTP_INVALID_HEADER + 1];
69
70 #if WIP_FWD_LOG
71 static void fwdLog(FwdState * fwdState);
72 static Logfile *logfile = NULL;
73 #endif
74
75 static PconnPool *fwdPconnPool = new PconnPool("server-side");
76 CBDATA_CLASS_INIT(FwdState);
77
78 void
79 FwdState::abort(void* d)
80 {
81 FwdState* fwd = (FwdState*)d;
82 Pointer tmp = fwd; // Grab a temporary pointer to keep the object alive during our scope.
83
84 if (Comm::IsConnOpen(fwd->serverConnection())) {
85 comm_remove_close_handler(fwd->serverConnection()->fd, fwdServerClosedWrapper, fwd);
86 }
87 fwd->serverDestinations.clean();
88 fwd->self = NULL;
89 }
90
91 /**** PUBLIC INTERFACE ********************************************************/
92
93 FwdState::FwdState(const Comm::ConnectionPointer &client, StoreEntry * e, HttpRequest * r)
94 {
95 debugs(17, 1, HERE << "Forwarding client request " << client << ", url=" << e->url() );
96 entry = e;
97 clientConn = client;
98 request = HTTPMSGLOCK(r);
99 start_t = squid_curtime;
100 serverDestinations.reserve(Config.forward_max_tries);
101 e->lock();
102 EBIT_SET(e->flags, ENTRY_FWD_HDR_WAIT);
103 }
104
105 // Called once, right after object creation, when it is safe to set self
106 void FwdState::start(Pointer aSelf)
107 {
108 // Protect ourselves from being destroyed when the only Server pointing
109 // to us is gone (while we expect to talk to more Servers later).
110 // Once we set self, we are responsible for clearing it when we do not
111 // expect to talk to any servers.
112 self = aSelf; // refcounted
113
114 // We hope that either the store entry aborts or peer is selected.
115 // Otherwise we are going to leak our object.
116
117 entry->registerAbort(FwdState::abort, this);
118 peerSelect(&serverDestinations, request, entry, fwdPeerSelectionCompleteWrapper, this);
119 }
120
121 void
122 FwdState::completed()
123 {
124 if (flags.forward_completed == 1) {
125 debugs(17, 1, HERE << "FwdState::completed called on a completed request! Bad!");
126 return;
127 }
128
129 flags.forward_completed = 1;
130
131 #if URL_CHECKSUM_DEBUG
132
133 entry->mem_obj->checkUrlChecksum();
134 #endif
135 #if WIP_FWD_LOG
136
137 log();
138 #endif
139
140 if (entry->store_status == STORE_PENDING) {
141 if (entry->isEmpty()) {
142 assert(err);
143 errorAppendEntry(entry, err);
144 err = NULL;
145 } else {
146 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
147 entry->complete();
148 entry->releaseRequest();
149 }
150 }
151
152 if (storePendingNClients(entry) > 0)
153 assert(!EBIT_TEST(entry->flags, ENTRY_FWD_HDR_WAIT));
154
155 }
156
157 FwdState::~FwdState()
158 {
159 debugs(17, 3, HERE << "FwdState destructor starting");
160
161 if (! flags.forward_completed)
162 completed();
163
164 doneWithRetries();
165
166 HTTPMSGUNLOCK(request);
167
168 if (err)
169 errorStateFree(err);
170
171 entry->unregisterAbort();
172
173 entry->unlock();
174
175 entry = NULL;
176
177 if (Comm::IsConnOpen(serverConn)) {
178 comm_remove_close_handler(serverConnection()->fd, fwdServerClosedWrapper, this);
179 debugs(17, 3, HERE << "closing FD " << serverConnection()->fd);
180 serverConn->close();
181 }
182
183 serverDestinations.clean();
184
185 debugs(17, 3, HERE << "FwdState destructor done");
186 }
187
188 /**
189 * This is the entry point for client-side to start forwarding
190 * a transaction. It is a static method that may or may not
191 * allocate a FwdState.
192 */
193 void
194 FwdState::fwdStart(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, HttpRequest *request)
195 {
196 /** \note
197 * client_addr == no_addr indicates this is an "internal" request
198 * from peer_digest.c, asn.c, netdb.c, etc and should always
199 * be allowed. yuck, I know.
200 */
201
202 if ( Config.accessList.miss && !request->client_addr.IsNoAddr() &&
203 request->protocol != PROTO_INTERNAL && request->protocol != PROTO_CACHEOBJ) {
204 /**
205 * Check if this host is allowed to fetch MISSES from us (miss_access)
206 */
207 ACLFilledChecklist ch(Config.accessList.miss, request, NULL);
208 ch.src_addr = request->client_addr;
209 ch.my_addr = request->my_addr;
210 int answer = ch.fastCheck();
211
212 if (answer == 0) {
213 err_type page_id;
214 page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName, 1);
215
216 if (page_id == ERR_NONE)
217 page_id = ERR_FORWARDING_DENIED;
218
219 ErrorState *anErr = errorCon(page_id, HTTP_FORBIDDEN, request);
220
221 errorAppendEntry(entry, anErr); // frees anErr
222
223 return;
224 }
225 }
226
227 debugs(17, 3, HERE << "'" << entry->url() << "'");
228 /*
229 * This seems like an odd place to bind mem_obj and request.
230 * Might want to assert that request is NULL at this point
231 */
232 entry->mem_obj->request = HTTPMSGLOCK(request);
233 #if URL_CHECKSUM_DEBUG
234
235 entry->mem_obj->checkUrlChecksum();
236 #endif
237
238 if (shutting_down) {
239 /* more yuck */
240 ErrorState *anErr = errorCon(ERR_SHUTTING_DOWN, HTTP_SERVICE_UNAVAILABLE, request);
241 errorAppendEntry(entry, anErr); // frees anErr
242 return;
243 }
244
245 switch (request->protocol) {
246
247 case PROTO_INTERNAL:
248 internalStart(request, entry);
249 return;
250
251 case PROTO_CACHEOBJ:
252 CacheManager::GetInstance()->Start(clientConn, request, entry);
253 return;
254
255 case PROTO_URN:
256 urnStart(request, entry);
257 return;
258
259 default:
260 FwdState::Pointer fwd = new FwdState(clientConn, entry, request);
261 fwd->start(fwd);
262 return;
263 }
264
265 /* NOTREACHED */
266 }
267
268 void
269 FwdState::startConnectionOrFail()
270 {
271 debugs(17, 3, HERE << entry->url() );
272
273 if (serverDestinations.size() > 0) {
274 connectStart();
275 } else {
276 debugs(17, 3, HERE << entry->url() );
277 ErrorState *anErr = errorCon(ERR_CANNOT_FORWARD, HTTP_SERVICE_UNAVAILABLE, request);
278 anErr->xerrno = errno;
279 fail(anErr);
280 self = NULL; // refcounted
281 }
282 }
283
284 void
285 FwdState::fail(ErrorState * errorState)
286 {
287 debugs(17, 3, HERE << err_type_str[errorState->type] << " \"" << httpStatusString(errorState->httpStatus) << "\"\n\t" << entry->url() );
288
289 if (err)
290 errorStateFree(err);
291
292 err = errorState;
293
294 if (!errorState->request)
295 errorState->request = HTTPMSGLOCK(request);
296
297 request->detailError(errorState->type, errorState->xerrno);
298 }
299
300 /**
301 * Frees fwdState without closing FD or generating an abort
302 */
303 void
304 FwdState::unregister(Comm::ConnectionPointer &conn)
305 {
306 debugs(17, 3, HERE << entry->url() );
307 assert(serverConnection() == conn);
308 assert(Comm::IsConnOpen(conn));
309 comm_remove_close_handler(conn->fd, fwdServerClosedWrapper, this);
310 serverConn = NULL;
311 }
312
313 // Legacy method to be removed in favor of the above as soon as possible
314 void
315 FwdState::unregister(int fd)
316 {
317 debugs(17, 3, HERE << entry->url() );
318 assert(fd == serverConnection()->fd);
319 unregister(serverConn);
320 }
321
322 /**
323 * server-side modules call fwdComplete() when they are done
324 * downloading an object. Then, we either 1) re-forward the
325 * request somewhere else if needed, or 2) call storeComplete()
326 * to finish it off
327 */
328 void
329 FwdState::complete()
330 {
331 assert(entry->store_status == STORE_PENDING);
332 debugs(17, 3, HERE << entry->url() << "\n\tstatus " << entry->getReply()->sline.status );
333 #if URL_CHECKSUM_DEBUG
334
335 entry->mem_obj->checkUrlChecksum();
336 #endif
337
338 logReplyStatus(n_tries, entry->getReply()->sline.status);
339
340 if (reforward()) {
341 assert(serverDestinations.size() > 0);
342 debugs(17, 3, HERE << "re-forwarding " << entry->getReply()->sline.status << " " << entry->url());
343
344 if (Comm::IsConnOpen(serverConn))
345 unregister(serverConn);
346
347 entry->reset();
348
349 /* the call to reforward() has already dropped the last path off the
350 * selection list. all we have now are the next path(s) to be tried.
351 */
352 connectStart();
353 } else {
354 if (Comm::IsConnOpen(serverConn))
355 debugs(17, 3, HERE << "server FD " << serverConnection()->fd << " not re-forwarding status " << entry->getReply()->sline.status);
356 else
357 debugs(17, 3, HERE << "server (FD closed) not re-forwarding status " << entry->getReply()->sline.status);
358 EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
359 entry->complete();
360
361 if (!Comm::IsConnOpen(serverConn))
362 completed();
363
364 self = NULL; // refcounted
365 }
366 }
367
368
369 /**** CALLBACK WRAPPERS ************************************************************/
370
371 static void
372 fwdPeerSelectionCompleteWrapper(Comm::ConnectionList * unused, void *data)
373 {
374 FwdState *fwd = (FwdState *) data;
375 fwd->startConnectionOrFail();
376 }
377
378 static void
379 fwdServerClosedWrapper(int fd, void *data)
380 {
381 FwdState *fwd = (FwdState *) data;
382 fwd->serverClosed(fd);
383 }
384
385 #if 0
386 static void
387 fwdConnectStartWrapper(void *data)
388 {
389 FwdState *fwd = (FwdState *) data;
390 fwd->connectStart();
391 }
392 #endif
393
394 #if USE_SSL
395 static void
396 fwdNegotiateSSLWrapper(int fd, void *data)
397 {
398 FwdState *fwd = (FwdState *) data;
399 fwd->negotiateSSL(fd);
400 }
401 #endif
402
403 void
404 fwdConnectDoneWrapper(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
405 {
406 FwdState *fwd = (FwdState *) data;
407 fwd->connectDone(conn, status, xerrno);
408 }
409
410 /**** PRIVATE *****************************************************************/
411
412 /*
413 * FwdState::checkRetry
414 *
415 * Return TRUE if the request SHOULD be retried. This method is
416 * called when the HTTP connection fails, or when the connection
417 * is closed before server-side read the end of HTTP headers.
418 */
419 bool
420 FwdState::checkRetry()
421 {
422 if (shutting_down)
423 return false;
424
425 if (!self) { // we have aborted before the server called us back
426 debugs(17, 5, HERE << "not retrying because of earlier abort");
427 // we will be destroyed when the server clears its Pointer to us
428 return false;
429 }
430
431 if (entry->store_status != STORE_PENDING)
432 return false;
433
434 if (!entry->isEmpty())
435 return false;
436
437 if (n_tries > 10)
438 return false;
439
440 if (origin_tries > 2)
441 return false;
442
443 if (squid_curtime - start_t > Config.Timeout.forward)
444 return false;
445
446 if (flags.dont_retry)
447 return false;
448
449 if (!checkRetriable())
450 return false;
451
452 if (request->bodyNibbled())
453 return false;
454
455 return true;
456 }
457
458 /*
459 * FwdState::checkRetriable
460 *
461 * Return TRUE if this is the kind of request that can be retried
462 * after a failure. If the request is not retriable then we don't
463 * want to risk sending it on a persistent connection. Instead we'll
464 * force it to go on a new HTTP connection.
465 */
466 bool
467 FwdState::checkRetriable()
468 {
469 /* If there is a request body then Squid can only try once
470 * even if the method is indempotent
471 */
472
473 if (request->body_pipe != NULL)
474 return false;
475
476 /* RFC2616 9.1 Safe and Idempotent Methods */
477 switch (request->method.id()) {
478 /* 9.1.1 Safe Methods */
479
480 case METHOD_GET:
481
482 case METHOD_HEAD:
483 /* 9.1.2 Idempotent Methods */
484
485 case METHOD_PUT:
486
487 case METHOD_DELETE:
488
489 case METHOD_OPTIONS:
490
491 case METHOD_TRACE:
492 break;
493
494 default:
495 return false;
496 }
497
498 return true;
499 }
500
501 void
502 FwdState::serverClosed(int fd)
503 {
504 debugs(17, 2, HERE << "FD " << fd << " " << entry->url());
505 retryOrBail();
506 }
507
508 void
509 FwdState::retryOrBail()
510 {
511 if (checkRetry()) {
512 debugs(17, 3, HERE << "re-forwarding (" << n_tries << " tries, " << (squid_curtime - start_t) << " secs)");
513
514 serverDestinations.shift(); // last one failed. try another.
515
516 if (serverDestinations.size() > 0) {
517 /* Ditch error page if it was created before.
518 * A new one will be created if there's another problem */
519 if (err) {
520 errorStateFree(err);
521 err = NULL;
522 }
523
524 connectStart();
525 return;
526 }
527 // else bail. no more serverDestinations possible to try.
528
529 // AYJ: cannot-forward error ??
530 // is this hack needed since we now have doneWithRetries() below?
531 // ErrorState *anErr = errorCon(ERR_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
532 // errorAppendEntry(entry, anErr);
533 }
534
535 // TODO: should we call completed() here and move doneWithRetries there?
536 doneWithRetries();
537
538 if (self != NULL && !err && shutting_down) {
539 ErrorState *anErr = errorCon(ERR_SHUTTING_DOWN, HTTP_SERVICE_UNAVAILABLE, request);
540 errorAppendEntry(entry, anErr);
541 }
542
543 self = NULL; // refcounted
544 }
545
546 // If the Server quits before nibbling at the request body, the body sender
547 // will not know (so that we can retry). Call this if we will not retry. We
548 // will notify the sender so that it does not get stuck waiting for space.
549 void
550 FwdState::doneWithRetries()
551 {
552 if (request && request->body_pipe != NULL)
553 request->body_pipe->expectNoConsumption();
554 }
555
556 // called by the server that failed after calling unregister()
557 void
558 FwdState::handleUnregisteredServerEnd()
559 {
560 debugs(17, 2, HERE << "self=" << self << " err=" << err << ' ' << entry->url());
561 assert(!Comm::IsConnOpen(serverConn));
562 retryOrBail();
563 }
564
565 #if USE_SSL
566 void
567 FwdState::negotiateSSL(int fd)
568 {
569 SSL *ssl = fd_table[fd].ssl;
570 int ret;
571
572 if ((ret = SSL_connect(ssl)) <= 0) {
573 int ssl_error = SSL_get_error(ssl, ret);
574
575 switch (ssl_error) {
576
577 case SSL_ERROR_WANT_READ:
578 commSetSelect(fd, COMM_SELECT_READ, fwdNegotiateSSLWrapper, this, 0);
579 return;
580
581 case SSL_ERROR_WANT_WRITE:
582 commSetSelect(fd, COMM_SELECT_WRITE, fwdNegotiateSSLWrapper, this, 0);
583 return;
584
585 default:
586 debugs(81, 1, "fwdNegotiateSSL: Error negotiating SSL connection on FD " << fd <<
587 ": " << ERR_error_string(ERR_get_error(), NULL) << " (" << ssl_error <<
588 "/" << ret << "/" << errno << ")");
589 ErrorState *const anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL);
590 #ifdef EPROTO
591
592 anErr->xerrno = EPROTO;
593 #else
594
595 anErr->xerrno = EACCES;
596 #endif
597
598 fail(anErr);
599
600 if (serverConnection()->getPeer()) {
601 peerConnectFailed(serverConnection()->getPeer());
602 }
603
604 serverConn->close();
605 return;
606 }
607 }
608
609 if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) {
610 if (serverConnection()->getPeer()->sslSession)
611 SSL_SESSION_free(serverConnection()->getPeer()->sslSession);
612
613 serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
614 }
615
616 dispatch();
617 }
618
619 void
620 FwdState::initiateSSL()
621 {
622 SSL *ssl;
623 SSL_CTX *sslContext = NULL;
624 const peer *peer = serverConnection()->getPeer();
625 int fd = serverConnection()->fd;
626
627 if (peer) {
628 assert(peer->use_ssl);
629 sslContext = peer->sslContext;
630 } else {
631 sslContext = Config.ssl_client.sslContext;
632 }
633
634 assert(sslContext);
635
636 if ((ssl = SSL_new(sslContext)) == NULL) {
637 debugs(83, 1, "fwdInitiateSSL: Error allocating handle: " << ERR_error_string(ERR_get_error(), NULL) );
638 ErrorState *anErr = errorCon(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
639 anErr->xerrno = errno;
640 fail(anErr);
641 self = NULL; // refcounted
642 return;
643 }
644
645 SSL_set_fd(ssl, fd);
646
647 if (peer) {
648 if (peer->ssldomain)
649 SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain);
650
651 #if NOT_YET
652
653 else if (peer->name)
654 SSL_set_ex_data(ssl, ssl_ex_index_server, peer->name);
655
656 #endif
657
658 else
659 SSL_set_ex_data(ssl, ssl_ex_index_server, peer->host);
660
661 if (peer->sslSession)
662 SSL_set_session(ssl, peer->sslSession);
663
664 } else {
665 SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)request->GetHost());
666 }
667
668 // Create the ACL check list now, while we have access to more info.
669 // The list is used in ssl_verify_cb() and is freed in ssl_free().
670 if (acl_access *acl = Config.ssl_client.cert_error) {
671 ACLFilledChecklist *check = new ACLFilledChecklist(acl, request, dash_str);
672 check->fd(fd);
673 SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check);
674 }
675
676 fd_table[fd].ssl = ssl;
677 fd_table[fd].read_method = &ssl_read_method;
678 fd_table[fd].write_method = &ssl_write_method;
679 negotiateSSL(fd);
680 }
681
682 #endif
683
684 void
685 FwdState::connectDone(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno)
686 {
687 if (status != COMM_OK) {
688 ErrorState *const anErr = makeConnectingError(ERR_CONNECT_FAIL);
689 anErr->xerrno = xerrno;
690 fail(anErr);
691
692 /* it might have been a timeout with a partially open link */
693 if (conn != NULL) {
694 if (conn->getPeer())
695 peerConnectFailed(conn->getPeer());
696
697 conn->close();
698 }
699 retryOrBail();
700 return;
701 }
702
703 serverConn = conn;
704
705 #if REDUNDANT_NOW
706 if (Config.onoff.log_ip_on_direct && serverConnection()->peerType == HIER_DIRECT)
707 updateHierarchyInfo();
708 #endif
709
710 debugs(17, 3, HERE << serverConnection() << ": '" << entry->url() << "'" );
711
712 comm_add_close_handler(serverConnection()->fd, fwdServerClosedWrapper, this);
713
714 if (serverConnection()->getPeer())
715 peerConnectSucceded(serverConnection()->getPeer());
716
717 updateHierarchyInfo();
718
719 #if USE_SSL
720 if ((serverConnection()->getPeer() && serverConnection()->getPeer()->use_ssl) ||
721 (!serverConnection()->getPeer() && request->protocol == PROTO_HTTPS)) {
722 initiateSSL();
723 return;
724 }
725 #endif
726
727 dispatch();
728 }
729
730 void
731 FwdState::connectTimeout(int fd)
732 {
733 debugs(17, 2, "fwdConnectTimeout: FD " << fd << ": '" << entry->url() << "'" );
734 assert(serverDestinations[0] != NULL);
735 assert(fd == serverDestinations[0]->fd);
736
737 if (Config.onoff.log_ip_on_direct && serverDestinations[0]->peerType == HIER_DIRECT)
738 updateHierarchyInfo();
739
740 if (entry->isEmpty()) {
741 ErrorState *anErr = errorCon(ERR_CONNECT_FAIL, HTTP_GATEWAY_TIMEOUT, request);
742 anErr->xerrno = ETIMEDOUT;
743 fail(anErr);
744
745 /* This marks the peer DOWN ... */
746 if (serverDestinations[0]->getPeer())
747 peerConnectFailed(serverDestinations[0]->getPeer());
748 }
749
750 if (Comm::IsConnOpen(serverDestinations[0])) {
751 serverDestinations[0]->close();
752 }
753 }
754
755 /**
756 * Called after Forwarding path selection (via peer select) has taken place.
757 * And whenever forwarding needs to attempt a new connection (routing failover)
758 * We have a vector of possible localIP->remoteIP paths now ready to start being connected.
759 */
760 void
761 FwdState::connectStart()
762 {
763 assert(serverDestinations.size() > 0);
764
765 debugs(17, 3, "fwdConnectStart: " << entry->url());
766
767 if (n_tries == 0) // first attempt
768 request->hier.first_conn_start = current_time;
769
770 /* connection timeout */
771 int ctimeout;
772 if (serverDestinations[0]->getPeer()) {
773 ctimeout = serverDestinations[0]->getPeer()->connect_timeout > 0 ?
774 serverDestinations[0]->getPeer()->connect_timeout : Config.Timeout.peer_connect;
775 } else {
776 ctimeout = Config.Timeout.connect;
777 }
778
779 /* calculate total forwarding timeout ??? */
780 int ftimeout = Config.Timeout.forward - (squid_curtime - start_t);
781 if (ftimeout < 0)
782 ftimeout = 5;
783
784 if (ftimeout < ctimeout)
785 ctimeout = ftimeout;
786
787 request->flags.pinned = 0;
788 if (serverDestinations[0]->peerType == PINNED) {
789 ConnStateData *pinned_connection = request->pinnedConnection();
790 assert(pinned_connection);
791 serverDestinations[0]->fd = pinned_connection->validatePinnedConnection(request, serverDestinations[0]->getPeer());
792 if (Comm::IsConnOpen(serverDestinations[0])) {
793 serverConn = serverDestinations[0];
794 pinned_connection->unpinConnection();
795 #if 0
796 if (!serverDestinations[0]->getPeer())
797 serverDestinations[0]->peerType = HIER_DIRECT;
798 #endif
799 n_tries++;
800 request->flags.pinned = 1;
801 if (pinned_connection->pinnedAuth())
802 request->flags.auth = 1;
803 updateHierarchyInfo();
804 dispatch();
805 return;
806 }
807 /* Failure. Fall back on next path */
808 debugs(17,2,HERE << " Pinned connection " << pinned_connection << " not valid. Releasing.");
809 request->releasePinnedConnection();
810 serverDestinations.shift();
811 startConnectionOrFail();
812 return;
813 }
814
815 // Use pconn to avoid opening a new connection.
816 const char *host;
817 int port;
818 if (serverDestinations[0]->getPeer()) {
819 host = serverDestinations[0]->getPeer()->host;
820 port = serverDestinations[0]->getPeer()->http_port;
821 } else {
822 host = request->GetHost();
823 port = request->port;
824 }
825 serverDestinations[0]->remote.SetPort(port);
826 Comm::ConnectionPointer temp = fwdPconnPool->pop(serverDestinations[0], host, checkRetriable());
827
828 // if we found an open persistent connection to use. use it.
829 if (temp != NULL && Comm::IsConnOpen(temp)) {
830 serverConn = temp;
831 debugs(17, 3, HERE << "reusing pconn " << serverConnection());
832 n_tries++;
833
834 if (!serverConnection()->getPeer())
835 origin_tries++;
836
837 updateHierarchyInfo();
838 comm_add_close_handler(serverConnection()->fd, fwdServerClosedWrapper, this);
839
840 /* Update server side TOS and Netfilter mark on the connection. */
841 if (Ip::Qos::TheConfig.isAclTosActive()) {
842 temp->tos = GetTosToServer(request);
843 Ip::Qos::setSockTos(temp, temp->tos);
844 }
845 #if SO_MARK
846 if (Ip::Qos::TheConfig.isAclNfmarkActive()) {
847 temp->nfmark = GetNfmarkToServer(request);
848 Ip::Qos::setSockNfmark(temp, temp->nfmark);
849 }
850 #endif
851
852 dispatch();
853 return;
854 }
855
856 #if URL_CHECKSUM_DEBUG
857 entry->mem_obj->checkUrlChecksum();
858 #endif
859
860 /* Get the server side TOS and Netfilter mark to be set on the connection. */
861 if (Ip::Qos::TheConfig.isAclTosActive()) {
862 serverDestinations[0]->tos = GetTosToServer(request);
863 }
864 #if SO_MARK
865 serverDestinations[0]->nfmark = GetNfmarkToServer(request);
866 debugs(17, 3, "fwdConnectStart: got outgoing addr " << outgoing << ", tos " << int(tos)
867 << ", netfilter mark " << serverDestinations[0]->nfmark);
868 #else
869 serverDestinations[0]->nfmark = 0;
870 debugs(17, 3, "fwdConnectStart: got outgoing addr " << outgoing << ", tos " << int(tos));
871 #endif
872
873 AsyncCall::Pointer call = commCbCall(17,3, "fwdConnectDoneWrapper", CommConnectCbPtrFun(fwdConnectDoneWrapper, this));
874 Comm::ConnOpener *cs = new Comm::ConnOpener(serverDestinations[0], call, ctimeout);
875 cs->setHost(host);
876 AsyncJob::Start(cs);
877 }
878
879 void
880 FwdState::dispatch()
881 {
882 debugs(17, 3, HERE << clientConn << ": Fetching '" << RequestMethodStr(request->method) << " " << entry->url() << "'");
883 /*
884 * Assert that server_fd is set. This is to guarantee that fwdState
885 * is attached to something and will be deallocated when server_fd
886 * is closed.
887 */
888 assert(Comm::IsConnOpen(serverConn));
889
890 fd_note(serverConnection()->fd, entry->url());
891
892 fd_table[serverConnection()->fd].noteUse(fwdPconnPool);
893
894 /*assert(!EBIT_TEST(entry->flags, ENTRY_DISPATCHED)); */
895 assert(entry->ping_status != PING_WAITING);
896
897 assert(entry->lock_count);
898
899 EBIT_SET(entry->flags, ENTRY_DISPATCHED);
900
901 netdbPingSite(request->GetHost());
902
903 /* Retrieves remote server TOS or MARK value, and stores it as part of the
904 * original client request FD object. It is later used to forward
905 * remote server's TOS/MARK in the response to the client in case of a MISS.
906 */
907 if (Ip::Qos::TheConfig.isHitNfmarkActive()) {
908 if (Comm::IsConnOpen(clientConn) && Comm::IsConnOpen(serverConnection())) {
909 fde * clientFde = &fd_table[clientConn->fd]; // XXX: move the fd_table access into Ip::Qos
910 /* Get the netfilter mark for the connection */
911 Ip::Qos::getNfmarkFromServer(serverConnection(), clientFde);
912 }
913 }
914
915 #if _SQUID_LINUX_
916 /* Bug 2537: The TOS forward part of QOS only applies to patched Linux kernels. */
917 if (Ip::Qos::TheConfig.isHitTosActive()) {
918 if (Comm::IsConnOpen(clientConn)) {
919 fde * clientFde = &fd_table[clientConn->fd]; // XXX: move the fd_table access into Ip::Qos
920 /* Get the TOS value for the packet */
921 Ip::Qos::getTosFromServer(serverConnection(), clientFde);
922 }
923 }
924 #endif
925
926 if (serverConnection()->getPeer() != NULL) {
927 serverConnection()->getPeer()->stats.fetches++;
928 request->peer_login = serverConnection()->getPeer()->login;
929 request->peer_domain = serverConnection()->getPeer()->domain;
930 httpStart(this);
931 } else {
932 request->peer_login = NULL;
933 request->peer_domain = NULL;
934
935 switch (request->protocol) {
936 #if USE_SSL
937
938 case PROTO_HTTPS:
939 httpStart(this);
940 break;
941 #endif
942
943 case PROTO_HTTP:
944 httpStart(this);
945 break;
946
947 case PROTO_GOPHER:
948 gopherStart(this);
949 break;
950
951 case PROTO_FTP:
952 ftpStart(this);
953 break;
954
955 case PROTO_CACHEOBJ:
956
957 case PROTO_INTERNAL:
958
959 case PROTO_URN:
960 fatal_dump("Should never get here");
961 break;
962
963 case PROTO_WHOIS:
964 whoisStart(this);
965 break;
966
967 case PROTO_WAIS: /* Not implemented */
968
969 default:
970 debugs(17, 1, "fwdDispatch: Cannot retrieve '" << entry->url() << "'" );
971 ErrorState *anErr = errorCon(ERR_UNSUP_REQ, HTTP_BAD_REQUEST, request);
972 fail(anErr);
973 /*
974 * Force a persistent connection to be closed because
975 * some Netscape browsers have a bug that sends CONNECT
976 * requests as GET's over persistent connections.
977 */
978 request->flags.proxy_keepalive = 0;
979 /*
980 * Set the dont_retry flag because this is not a
981 * transient (network) error; its a bug.
982 */
983 flags.dont_retry = 1;
984 if (Comm::IsConnOpen(serverConn)) {
985 serverConn->close();
986 }
987 break;
988 }
989 }
990 }
991
992 /*
993 * FwdState::reforward
994 *
995 * returns TRUE if the transaction SHOULD be re-forwarded to the
996 * next choice in the FwdServers list. This method is called when
997 * server-side communication completes normally, or experiences
998 * some error after receiving the end of HTTP headers.
999 */
1000 int
1001 FwdState::reforward()
1002 {
1003 StoreEntry *e = entry;
1004 http_status s;
1005 assert(e->store_status == STORE_PENDING);
1006 assert(e->mem_obj);
1007 #if URL_CHECKSUM_DEBUG
1008
1009 e->mem_obj->checkUrlChecksum();
1010 #endif
1011
1012 debugs(17, 3, HERE << e->url() << "?" );
1013
1014 if (!EBIT_TEST(e->flags, ENTRY_FWD_HDR_WAIT)) {
1015 debugs(17, 3, HERE << "No, ENTRY_FWD_HDR_WAIT isn't set");
1016 return 0;
1017 }
1018
1019 if (n_tries > Config.forward_max_tries)
1020 return 0;
1021
1022 if (origin_tries > 1)
1023 return 0;
1024
1025 if (request->bodyNibbled())
1026 return 0;
1027
1028 serverDestinations.shift();
1029
1030 if (serverDestinations.size() == 0) {
1031 debugs(17, 3, HERE << "No alternative forwarding paths left");
1032 return 0;
1033 }
1034
1035 s = e->getReply()->sline.status;
1036 debugs(17, 3, HERE << "status " << s);
1037 return reforwardableStatus(s);
1038 }
1039
1040 /**
1041 * Create "503 Service Unavailable" or "504 Gateway Timeout" error depending
1042 * on whether this is a validation request. RFC 2616 says that we MUST reply
1043 * with "504 Gateway Timeout" if validation fails and cached reply has
1044 * proxy-revalidate, must-revalidate or s-maxage Cache-Control directive.
1045 */
1046 ErrorState *
1047 FwdState::makeConnectingError(const err_type type) const
1048 {
1049 return errorCon(type, request->flags.need_validation ?
1050 HTTP_GATEWAY_TIMEOUT : HTTP_SERVICE_UNAVAILABLE, request);
1051 }
1052
1053 static void
1054 fwdStats(StoreEntry * s)
1055 {
1056 int i;
1057 int j;
1058 storeAppendPrintf(s, "Status");
1059
1060 for (j = 0; j <= MAX_FWD_STATS_IDX; j++) {
1061 storeAppendPrintf(s, "\ttry#%d", j + 1);
1062 }
1063
1064 storeAppendPrintf(s, "\n");
1065
1066 for (i = 0; i <= (int) HTTP_INVALID_HEADER; i++) {
1067 if (FwdReplyCodes[0][i] == 0)
1068 continue;
1069
1070 storeAppendPrintf(s, "%3d", i);
1071
1072 for (j = 0; j <= MAX_FWD_STATS_IDX; j++) {
1073 storeAppendPrintf(s, "\t%d", FwdReplyCodes[j][i]);
1074 }
1075
1076 storeAppendPrintf(s, "\n");
1077 }
1078 }
1079
1080
1081 /**** STATIC MEMBER FUNCTIONS *************************************************/
1082
1083 bool
1084 FwdState::reforwardableStatus(http_status s)
1085 {
1086 switch (s) {
1087
1088 case HTTP_BAD_GATEWAY:
1089
1090 case HTTP_GATEWAY_TIMEOUT:
1091 return true;
1092
1093 case HTTP_FORBIDDEN:
1094
1095 case HTTP_INTERNAL_SERVER_ERROR:
1096
1097 case HTTP_NOT_IMPLEMENTED:
1098
1099 case HTTP_SERVICE_UNAVAILABLE:
1100 return Config.retry.onerror;
1101
1102 default:
1103 return false;
1104 }
1105
1106 /* NOTREACHED */
1107 }
1108
1109 /**
1110 * Decide where details need to be gathered to correctly describe a persistent connection.
1111 * What is needed:
1112 * - the address/port details about this link
1113 * - domain name of server at other end of this link (either peer or requested host)
1114 */
1115 void
1116 FwdState::pconnPush(Comm::ConnectionPointer &conn, const char *domain)
1117 {
1118 if (conn->getPeer()) {
1119 fwdPconnPool->push(conn, conn->getPeer()->name);
1120 } else {
1121 fwdPconnPool->push(conn, domain);
1122 }
1123 }
1124
1125 void
1126 FwdState::initModule()
1127 {
1128 #if WIP_FWD_LOG
1129
1130 if (logfile)
1131 (void) 0;
1132 else if (NULL == Config.Log.forward)
1133 (void) 0;
1134 else
1135 logfile = logfileOpen(Config.Log.forward, 0, 1);
1136
1137 #endif
1138
1139 RegisterWithCacheManager();
1140 }
1141
1142 void
1143 FwdState::RegisterWithCacheManager(void)
1144 {
1145 Mgr::RegisterAction("forward", "Request Forwarding Statistics", fwdStats, 0, 1);
1146 }
1147
1148 void
1149 FwdState::logReplyStatus(int tries, http_status status)
1150 {
1151 if (status > HTTP_INVALID_HEADER)
1152 return;
1153
1154 assert(tries >= 0);
1155
1156 if (tries > MAX_FWD_STATS_IDX)
1157 tries = MAX_FWD_STATS_IDX;
1158
1159 FwdReplyCodes[tries][status]++;
1160 }
1161
1162 /** From Comment #5 by Henrik Nordstrom made at
1163 http://www.squid-cache.org/bugs/show_bug.cgi?id=2391 on 2008-09-19
1164
1165 updateHierarchyInfo should be called each time a new path has been
1166 selected or when more information about the path is available (i.e. the
1167 server IP), and when it's called it needs to be given reasonable
1168 arguments describing the now selected path..
1169
1170 It does not matter from a functional perspective if it gets called a few
1171 times more than what is really needed, but calling it too often may
1172 obviously hurt performance.
1173 */
1174 // updates HierarchyLogEntry, guessing nextHop and its format
1175 void
1176 FwdState::updateHierarchyInfo()
1177 {
1178 assert(request);
1179
1180 assert(serverDestinations.size() > 0);
1181
1182 char nextHop[256];
1183
1184 if (serverConnection()->getPeer()) {
1185 // went to peer, log peer host name
1186 snprintf(nextHop,256,"%s", serverConnection()->getPeer()->name);
1187 } else {
1188 // went DIRECT, must honor log_ip_on_direct
1189 if (!Config.onoff.log_ip_on_direct)
1190 snprintf(nextHop,256,"%s",request->GetHost()); // domain name
1191 else
1192 serverConnection()->remote.NtoA(nextHop, 256);
1193 }
1194
1195 request->hier.peer_local_port = serverConnection()->local.GetPort();
1196
1197 assert(nextHop[0]);
1198 hierarchyNote(&request->hier, serverConnection()->peerType, nextHop);
1199 }
1200
1201
1202 /**** PRIVATE NON-MEMBER FUNCTIONS ********************************************/
1203
1204 /*
1205 * DPW 2007-05-19
1206 * Formerly static, but now used by client_side_request.cc
1207 */
1208 /// Checks for a TOS value to apply depending on the ACL
1209 tos_t
1210 aclMapTOS(acl_tos * head, ACLChecklist * ch)
1211 {
1212 acl_tos *l;
1213
1214 for (l = head; l; l = l->next) {
1215 if (!l->aclList || ch->matchAclListFast(l->aclList))
1216 return l->tos;
1217 }
1218
1219 return 0;
1220 }
1221
1222 /// Checks for a netfilter mark value to apply depending on the ACL
1223 nfmark_t
1224 aclMapNfmark(acl_nfmark * head, ACLChecklist * ch)
1225 {
1226 acl_nfmark *l;
1227
1228 for (l = head; l; l = l->next) {
1229 if (!l->aclList || ch->matchAclListFast(l->aclList))
1230 return l->nfmark;
1231 }
1232
1233 return 0;
1234 }
1235
1236 void
1237 getOutgoingAddress(HttpRequest * request, Comm::ConnectionPointer conn)
1238 {
1239 /* skip if an outgoing address is already set. */
1240 if (!conn->local.IsAnyAddr()) return;
1241
1242 // maybe use TPROXY client address
1243 if (request && request->flags.spoof_client_ip) {
1244 if (!conn->getPeer() || !conn->getPeer()->options.no_tproxy) {
1245 #if FOLLOW_X_FORWARDED_FOR && LINUX_NETFILTER
1246 if (Config.onoff.tproxy_uses_indirect_client)
1247 conn->local = request->indirect_client_addr;
1248 else
1249 #endif
1250 conn->local = request->client_addr;
1251 // some flags need setting on the socket to use this address
1252 conn->flags |= COMM_DOBIND;
1253 conn->flags |= COMM_TRANSPARENT;
1254 return;
1255 }
1256 // else no tproxy today ...
1257 }
1258
1259 if (!Config.accessList.outgoing_address) {
1260 return; // anything will do.
1261 }
1262
1263 ACLFilledChecklist ch(NULL, request, NULL);
1264 ch.dst_peer = conn->getPeer();
1265 ch.dst_addr = conn->remote;
1266
1267 // TODO use the connection details in ACL.
1268 // needs a bit of rework in ACLFilledChecklist to use Comm::Connection instead of ConnStateData
1269
1270 if (request) {
1271 #if FOLLOW_X_FORWARDED_FOR
1272 if (Config.onoff.acl_uses_indirect_client)
1273 ch.src_addr = request->indirect_client_addr;
1274 else
1275 #endif
1276 ch.src_addr = request->client_addr;
1277 ch.my_addr = request->my_addr;
1278 }
1279
1280 acl_address *l;
1281 for (l = Config.accessList.outgoing_address; l; l = l->next) {
1282
1283 /* check if the outgoing address is usable to the destination */
1284 if (conn->remote.IsIPv4() != l->addr.IsIPv4()) continue;
1285
1286 /* check ACLs for this outgoing address */
1287 if (!l->aclList || ch.matchAclListFast(l->aclList)) {
1288 conn->local = l->addr;
1289 return;
1290 }
1291 }
1292 }
1293
1294 // XXX: convert this to accepting a serverConn and migrate to QosConfig.cc
1295 tos_t
1296 GetTosToServer(HttpRequest * request)
1297 {
1298 ACLFilledChecklist ch(NULL, request, NULL);
1299
1300 if (request) {
1301 ch.src_addr = request->client_addr;
1302 ch.my_addr = request->my_addr;
1303 }
1304
1305 return aclMapTOS(Ip::Qos::TheConfig.tosToServer, &ch);
1306 }
1307
1308 // XXX: convert this to accepting a serverConn and migrate to QosConfig.cc
1309 nfmark_t
1310 GetNfmarkToServer(HttpRequest * request)
1311 {
1312 ACLFilledChecklist ch(NULL, request, NULL);
1313
1314 if (request) {
1315 ch.src_addr = request->client_addr;
1316 ch.my_addr = request->my_addr;
1317 }
1318
1319 return aclMapNfmark(Ip::Qos::TheConfig.nfmarkToServer, &ch);
1320 }
1321
1322
1323 /**** WIP_FWD_LOG *************************************************************/
1324
1325 #if WIP_FWD_LOG
1326 void
1327 fwdUninit(void)
1328 {
1329 if (NULL == logfile)
1330 return;
1331
1332 logfileClose(logfile);
1333
1334 logfile = NULL;
1335 }
1336
1337 void
1338 fwdLogRotate(void)
1339 {
1340 if (logfile)
1341 logfileRotate(logfile);
1342 }
1343
1344 static void
1345 FwdState::log()
1346 {
1347 if (NULL == logfile)
1348 return;
1349
1350 logfilePrintf(logfile, "%9d.%03d %03d %s %s\n",
1351 (int) current_time.tv_sec,
1352 (int) current_time.tv_usec / 1000,
1353 last_status,
1354 RequestMethodStr(request->method),
1355 request->canonical);
1356 }
1357
1358 void
1359 FwdState::status(http_status s)
1360 {
1361 last_status = s;
1362 }
1363
1364 #endif