2 * DEBUG: section 17 Request Forwarding
3 * AUTHOR: Duane Wessels
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
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.
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.
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.
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.
35 #include "acl/FilledChecklist.h"
36 #include "acl/Gadgets.h"
37 #include "comm/Connection.h"
38 #include "comm/ConnOpener.h"
39 #include "CommCalls.h"
41 #include "errorpage.h"
44 #include "hier_code.h"
45 #include "HttpReply.h"
46 #include "HttpRequest.h"
47 #include "ip/QosConfig.h"
48 #include "MemObject.h"
50 #include "PeerSelectState.h"
51 #include "SquidTime.h"
53 #include "icmp/net_db.h"
54 #include "ip/Intercept.h"
56 #include "mgr/Registration.h"
58 static PSC fwdPeerSelectionCompleteWrapper
;
59 static PF fwdServerClosedWrapper
;
61 static PF fwdNegotiateSSLWrapper
;
63 static CNCB fwdConnectDoneWrapper
;
67 #define MAX_FWD_STATS_IDX 9
68 static int FwdReplyCodes
[MAX_FWD_STATS_IDX
+ 1][HTTP_INVALID_HEADER
+ 1];
71 static void fwdLog(FwdState
* fwdState
);
72 static Logfile
*logfile
= NULL
;
75 static PconnPool
*fwdPconnPool
= new PconnPool("server-side");
76 CBDATA_CLASS_INIT(FwdState
);
79 FwdState::abort(void* d
)
81 FwdState
* fwd
= (FwdState
*)d
;
82 Pointer tmp
= fwd
; // Grab a temporary pointer to keep the object alive during our scope.
84 if (Comm::IsConnOpen(fwd
->serverConnection())) {
85 comm_remove_close_handler(fwd
->serverConnection()->fd
, fwdServerClosedWrapper
, fwd
);
87 fwd
->serverDestinations
.clean();
91 /**** PUBLIC INTERFACE ********************************************************/
93 FwdState::FwdState(const Comm::ConnectionPointer
&client
, StoreEntry
* e
, HttpRequest
* r
)
95 debugs(17, 1, HERE
<< "Forwarding client request " << client
<< ", url=" << e
->url() );
98 request
= HTTPMSGLOCK(r
);
99 start_t
= squid_curtime
;
100 serverDestinations
.reserve(Config
.forward_max_tries
);
102 EBIT_SET(e
->flags
, ENTRY_FWD_HDR_WAIT
);
105 // Called once, right after object creation, when it is safe to set self
106 void FwdState::start(Pointer aSelf
)
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
114 // We hope that either the store entry aborts or peer is selected.
115 // Otherwise we are going to leak our object.
117 entry
->registerAbort(FwdState::abort
, this);
118 peerSelect(&serverDestinations
, request
, entry
, fwdPeerSelectionCompleteWrapper
, this);
122 FwdState::completed()
124 if (flags
.forward_completed
== 1) {
125 debugs(17, 1, HERE
<< "FwdState::completed called on a completed request! Bad!");
129 flags
.forward_completed
= 1;
131 #if URL_CHECKSUM_DEBUG
133 entry
->mem_obj
->checkUrlChecksum();
140 if (entry
->store_status
== STORE_PENDING
) {
141 if (entry
->isEmpty()) {
143 errorAppendEntry(entry
, err
);
146 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
148 entry
->releaseRequest();
152 if (storePendingNClients(entry
) > 0)
153 assert(!EBIT_TEST(entry
->flags
, ENTRY_FWD_HDR_WAIT
));
157 FwdState::~FwdState()
159 debugs(17, 3, HERE
<< "FwdState destructor starting");
161 if (! flags
.forward_completed
)
166 HTTPMSGUNLOCK(request
);
171 entry
->unregisterAbort();
177 if (Comm::IsConnOpen(serverConn
)) {
178 comm_remove_close_handler(serverConnection()->fd
, fwdServerClosedWrapper
, this);
179 debugs(17, 3, HERE
<< "closing FD " << serverConnection()->fd
);
183 serverDestinations
.clean();
185 debugs(17, 3, HERE
<< "FwdState destructor done");
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.
194 FwdState::fwdStart(const Comm::ConnectionPointer
&clientConn
, StoreEntry
*entry
, HttpRequest
*request
)
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.
202 if ( Config
.accessList
.miss
&& !request
->client_addr
.IsNoAddr() &&
203 request
->protocol
!= PROTO_INTERNAL
&& request
->protocol
!= PROTO_CACHEOBJ
) {
205 * Check if this host is allowed to fetch MISSES from us (miss_access)
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();
214 page_id
= aclGetDenyInfoPage(&Config
.denyInfoList
, AclMatchedName
, 1);
216 if (page_id
== ERR_NONE
)
217 page_id
= ERR_FORWARDING_DENIED
;
219 ErrorState
*anErr
= errorCon(page_id
, HTTP_FORBIDDEN
, request
);
221 errorAppendEntry(entry
, anErr
); // frees anErr
227 debugs(17, 3, HERE
<< "'" << entry
->url() << "'");
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
232 entry
->mem_obj
->request
= HTTPMSGLOCK(request
);
233 #if URL_CHECKSUM_DEBUG
235 entry
->mem_obj
->checkUrlChecksum();
240 ErrorState
*anErr
= errorCon(ERR_SHUTTING_DOWN
, HTTP_SERVICE_UNAVAILABLE
, request
);
241 errorAppendEntry(entry
, anErr
); // frees anErr
245 switch (request
->protocol
) {
248 internalStart(request
, entry
);
252 CacheManager::GetInstance()->Start(clientConn
, request
, entry
);
256 urnStart(request
, entry
);
260 FwdState::Pointer fwd
= new FwdState(clientConn
, entry
, request
);
269 FwdState::startConnectionOrFail()
271 debugs(17, 3, HERE
<< entry
->url() );
273 if (serverDestinations
.size() > 0) {
276 debugs(17, 3, HERE
<< entry
->url() );
277 ErrorState
*anErr
= errorCon(ERR_CANNOT_FORWARD
, HTTP_SERVICE_UNAVAILABLE
, request
);
278 anErr
->xerrno
= errno
;
280 self
= NULL
; // refcounted
285 FwdState::fail(ErrorState
* errorState
)
287 debugs(17, 3, HERE
<< err_type_str
[errorState
->type
] << " \"" << httpStatusString(errorState
->httpStatus
) << "\"\n\t" << entry
->url() );
294 if (!errorState
->request
)
295 errorState
->request
= HTTPMSGLOCK(request
);
297 request
->detailError(errorState
->type
, errorState
->xerrno
);
301 * Frees fwdState without closing FD or generating an abort
304 FwdState::unregister(Comm::ConnectionPointer
&conn
)
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);
313 // Legacy method to be removed in favor of the above as soon as possible
315 FwdState::unregister(int fd
)
317 debugs(17, 3, HERE
<< entry
->url() );
318 assert(fd
== serverConnection()->fd
);
319 unregister(serverConn
);
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()
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
335 entry
->mem_obj
->checkUrlChecksum();
338 logReplyStatus(n_tries
, entry
->getReply()->sline
.status
);
341 assert(serverDestinations
.size() > 0);
342 debugs(17, 3, HERE
<< "re-forwarding " << entry
->getReply()->sline
.status
<< " " << entry
->url());
344 if (Comm::IsConnOpen(serverConn
))
345 unregister(serverConn
);
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.
354 if (Comm::IsConnOpen(serverConn
))
355 debugs(17, 3, HERE
<< "server FD " << serverConnection()->fd
<< " not re-forwarding status " << entry
->getReply()->sline
.status
);
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
);
361 if (!Comm::IsConnOpen(serverConn
))
364 self
= NULL
; // refcounted
369 /**** CALLBACK WRAPPERS ************************************************************/
372 fwdPeerSelectionCompleteWrapper(Comm::ConnectionList
* unused
, void *data
)
374 FwdState
*fwd
= (FwdState
*) data
;
375 fwd
->startConnectionOrFail();
379 fwdServerClosedWrapper(int fd
, void *data
)
381 FwdState
*fwd
= (FwdState
*) data
;
382 fwd
->serverClosed(fd
);
387 fwdConnectStartWrapper(void *data
)
389 FwdState
*fwd
= (FwdState
*) data
;
396 fwdNegotiateSSLWrapper(int fd
, void *data
)
398 FwdState
*fwd
= (FwdState
*) data
;
399 fwd
->negotiateSSL(fd
);
404 fwdConnectDoneWrapper(const Comm::ConnectionPointer
&conn
, comm_err_t status
, int xerrno
, void *data
)
406 FwdState
*fwd
= (FwdState
*) data
;
407 fwd
->connectDone(conn
, status
, xerrno
);
410 /**** PRIVATE *****************************************************************/
413 * FwdState::checkRetry
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.
420 FwdState::checkRetry()
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
431 if (entry
->store_status
!= STORE_PENDING
)
434 if (!entry
->isEmpty())
440 if (origin_tries
> 2)
443 if (squid_curtime
- start_t
> Config
.Timeout
.forward
)
446 if (flags
.dont_retry
)
449 if (!checkRetriable())
452 if (request
->bodyNibbled())
459 * FwdState::checkRetriable
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.
467 FwdState::checkRetriable()
469 /* If there is a request body then Squid can only try once
470 * even if the method is indempotent
473 if (request
->body_pipe
!= NULL
)
476 /* RFC2616 9.1 Safe and Idempotent Methods */
477 switch (request
->method
.id()) {
478 /* 9.1.1 Safe Methods */
483 /* 9.1.2 Idempotent Methods */
502 FwdState::serverClosed(int fd
)
504 debugs(17, 2, HERE
<< "FD " << fd
<< " " << entry
->url());
509 FwdState::retryOrBail()
512 debugs(17, 3, HERE
<< "re-forwarding (" << n_tries
<< " tries, " << (squid_curtime
- start_t
) << " secs)");
514 serverDestinations
.shift(); // last one failed. try another.
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 */
527 // else bail. no more serverDestinations possible to try.
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);
535 // TODO: should we call completed() here and move doneWithRetries there?
538 if (self
!= NULL
&& !err
&& shutting_down
) {
539 ErrorState
*anErr
= errorCon(ERR_SHUTTING_DOWN
, HTTP_SERVICE_UNAVAILABLE
, request
);
540 errorAppendEntry(entry
, anErr
);
543 self
= NULL
; // refcounted
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.
550 FwdState::doneWithRetries()
552 if (request
&& request
->body_pipe
!= NULL
)
553 request
->body_pipe
->expectNoConsumption();
556 // called by the server that failed after calling unregister()
558 FwdState::handleUnregisteredServerEnd()
560 debugs(17, 2, HERE
<< "self=" << self
<< " err=" << err
<< ' ' << entry
->url());
561 assert(!Comm::IsConnOpen(serverConn
));
567 FwdState::negotiateSSL(int fd
)
569 SSL
*ssl
= fd_table
[fd
].ssl
;
572 if ((ret
= SSL_connect(ssl
)) <= 0) {
573 int ssl_error
= SSL_get_error(ssl
, ret
);
577 case SSL_ERROR_WANT_READ
:
578 commSetSelect(fd
, COMM_SELECT_READ
, fwdNegotiateSSLWrapper
, this, 0);
581 case SSL_ERROR_WANT_WRITE
:
582 commSetSelect(fd
, COMM_SELECT_WRITE
, fwdNegotiateSSLWrapper
, this, 0);
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
);
592 anErr
->xerrno
= EPROTO
;
595 anErr
->xerrno
= EACCES
;
600 if (serverConnection()->getPeer()) {
601 peerConnectFailed(serverConnection()->getPeer());
609 if (serverConnection()->getPeer() && !SSL_session_reused(ssl
)) {
610 if (serverConnection()->getPeer()->sslSession
)
611 SSL_SESSION_free(serverConnection()->getPeer()->sslSession
);
613 serverConnection()->getPeer()->sslSession
= SSL_get1_session(ssl
);
620 FwdState::initiateSSL()
623 SSL_CTX
*sslContext
= NULL
;
624 const peer
*peer
= serverConnection()->getPeer();
625 int fd
= serverConnection()->fd
;
628 assert(peer
->use_ssl
);
629 sslContext
= peer
->sslContext
;
631 sslContext
= Config
.ssl_client
.sslContext
;
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
;
641 self
= NULL
; // refcounted
649 SSL_set_ex_data(ssl
, ssl_ex_index_server
, peer
->ssldomain
);
654 SSL_set_ex_data(ssl
, ssl_ex_index_server
, peer
->name
);
659 SSL_set_ex_data(ssl
, ssl_ex_index_server
, peer
->host
);
661 if (peer
->sslSession
)
662 SSL_set_session(ssl
, peer
->sslSession
);
665 SSL_set_ex_data(ssl
, ssl_ex_index_server
, (void*)request
->GetHost());
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
);
673 SSL_set_ex_data(ssl
, ssl_ex_index_cert_error_check
, check
);
676 fd_table
[fd
].ssl
= ssl
;
677 fd_table
[fd
].read_method
= &ssl_read_method
;
678 fd_table
[fd
].write_method
= &ssl_write_method
;
685 FwdState::connectDone(const Comm::ConnectionPointer
&conn
, comm_err_t status
, int xerrno
)
687 if (status
!= COMM_OK
) {
688 ErrorState
*const anErr
= makeConnectingError(ERR_CONNECT_FAIL
);
689 anErr
->xerrno
= xerrno
;
692 /* it might have been a timeout with a partially open link */
695 peerConnectFailed(conn
->getPeer());
706 if (Config
.onoff
.log_ip_on_direct
&& serverConnection()->peerType
== HIER_DIRECT
)
707 updateHierarchyInfo();
710 debugs(17, 3, HERE
<< serverConnection() << ": '" << entry
->url() << "'" );
712 comm_add_close_handler(serverConnection()->fd
, fwdServerClosedWrapper
, this);
714 if (serverConnection()->getPeer())
715 peerConnectSucceded(serverConnection()->getPeer());
717 updateHierarchyInfo();
720 if ((serverConnection()->getPeer() && serverConnection()->getPeer()->use_ssl
) ||
721 (!serverConnection()->getPeer() && request
->protocol
== PROTO_HTTPS
)) {
731 FwdState::connectTimeout(int fd
)
733 debugs(17, 2, "fwdConnectTimeout: FD " << fd
<< ": '" << entry
->url() << "'" );
734 assert(serverDestinations
[0] != NULL
);
735 assert(fd
== serverDestinations
[0]->fd
);
737 if (Config
.onoff
.log_ip_on_direct
&& serverDestinations
[0]->peerType
== HIER_DIRECT
)
738 updateHierarchyInfo();
740 if (entry
->isEmpty()) {
741 ErrorState
*anErr
= errorCon(ERR_CONNECT_FAIL
, HTTP_GATEWAY_TIMEOUT
, request
);
742 anErr
->xerrno
= ETIMEDOUT
;
745 /* This marks the peer DOWN ... */
746 if (serverDestinations
[0]->getPeer())
747 peerConnectFailed(serverDestinations
[0]->getPeer());
750 if (Comm::IsConnOpen(serverDestinations
[0])) {
751 serverDestinations
[0]->close();
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.
761 FwdState::connectStart()
763 assert(serverDestinations
.size() > 0);
765 debugs(17, 3, "fwdConnectStart: " << entry
->url());
767 if (n_tries
== 0) // first attempt
768 request
->hier
.first_conn_start
= current_time
;
770 /* connection timeout */
772 if (serverDestinations
[0]->getPeer()) {
773 ctimeout
= serverDestinations
[0]->getPeer()->connect_timeout
> 0 ?
774 serverDestinations
[0]->getPeer()->connect_timeout
: Config
.Timeout
.peer_connect
;
776 ctimeout
= Config
.Timeout
.connect
;
779 /* calculate total forwarding timeout ??? */
780 int ftimeout
= Config
.Timeout
.forward
- (squid_curtime
- start_t
);
784 if (ftimeout
< ctimeout
)
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();
796 if (!serverDestinations
[0]->getPeer())
797 serverDestinations
[0]->peerType
= HIER_DIRECT
;
800 request
->flags
.pinned
= 1;
801 if (pinned_connection
->pinnedAuth())
802 request
->flags
.auth
= 1;
803 updateHierarchyInfo();
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();
815 // Use pconn to avoid opening a new connection.
818 if (serverDestinations
[0]->getPeer()) {
819 host
= serverDestinations
[0]->getPeer()->host
;
820 port
= serverDestinations
[0]->getPeer()->http_port
;
822 host
= request
->GetHost();
823 port
= request
->port
;
825 serverDestinations
[0]->remote
.SetPort(port
);
826 Comm::ConnectionPointer temp
= fwdPconnPool
->pop(serverDestinations
[0], host
, checkRetriable());
828 // if we found an open persistent connection to use. use it.
829 if (temp
!= NULL
&& Comm::IsConnOpen(temp
)) {
831 debugs(17, 3, HERE
<< "reusing pconn " << serverConnection());
834 if (!serverConnection()->getPeer())
837 updateHierarchyInfo();
838 comm_add_close_handler(serverConnection()->fd
, fwdServerClosedWrapper
, this);
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
);
846 if (Ip::Qos::TheConfig
.isAclNfmarkActive()) {
847 temp
->nfmark
= GetNfmarkToServer(request
);
848 Ip::Qos::setSockNfmark(temp
, temp
->nfmark
);
856 #if URL_CHECKSUM_DEBUG
857 entry
->mem_obj
->checkUrlChecksum();
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
);
865 serverDestinations
[0]->nfmark
= GetNfmarkToServer(request
);
866 debugs(17, 3, "fwdConnectStart: got outgoing addr " << outgoing
<< ", tos " << int(tos
)
867 << ", netfilter mark " << serverDestinations
[0]->nfmark
);
869 serverDestinations
[0]->nfmark
= 0;
870 debugs(17, 3, "fwdConnectStart: got outgoing addr " << outgoing
<< ", tos " << int(tos
));
873 AsyncCall::Pointer call
= commCbCall(17,3, "fwdConnectDoneWrapper", CommConnectCbPtrFun(fwdConnectDoneWrapper
, this));
874 Comm::ConnOpener
*cs
= new Comm::ConnOpener(serverDestinations
[0], call
, ctimeout
);
882 debugs(17, 3, HERE
<< clientConn
<< ": Fetching '" << RequestMethodStr(request
->method
) << " " << entry
->url() << "'");
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
888 assert(Comm::IsConnOpen(serverConn
));
890 fd_note(serverConnection()->fd
, entry
->url());
892 fd_table
[serverConnection()->fd
].noteUse(fwdPconnPool
);
894 /*assert(!EBIT_TEST(entry->flags, ENTRY_DISPATCHED)); */
895 assert(entry
->ping_status
!= PING_WAITING
);
897 assert(entry
->lock_count
);
899 EBIT_SET(entry
->flags
, ENTRY_DISPATCHED
);
901 netdbPingSite(request
->GetHost());
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.
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
);
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
);
926 if (serverConnection()->getPeer() != NULL
) {
927 serverConnection()->getPeer()->stats
.fetches
++;
928 request
->peer_login
= serverConnection()->getPeer()->login
;
929 request
->peer_domain
= serverConnection()->getPeer()->domain
;
932 request
->peer_login
= NULL
;
933 request
->peer_domain
= NULL
;
935 switch (request
->protocol
) {
960 fatal_dump("Should never get here");
967 case PROTO_WAIS
: /* Not implemented */
970 debugs(17, 1, "fwdDispatch: Cannot retrieve '" << entry
->url() << "'" );
971 ErrorState
*anErr
= errorCon(ERR_UNSUP_REQ
, HTTP_BAD_REQUEST
, request
);
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.
978 request
->flags
.proxy_keepalive
= 0;
980 * Set the dont_retry flag because this is not a
981 * transient (network) error; its a bug.
983 flags
.dont_retry
= 1;
984 if (Comm::IsConnOpen(serverConn
)) {
993 * FwdState::reforward
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.
1001 FwdState::reforward()
1003 StoreEntry
*e
= entry
;
1005 assert(e
->store_status
== STORE_PENDING
);
1007 #if URL_CHECKSUM_DEBUG
1009 e
->mem_obj
->checkUrlChecksum();
1012 debugs(17, 3, HERE
<< e
->url() << "?" );
1014 if (!EBIT_TEST(e
->flags
, ENTRY_FWD_HDR_WAIT
)) {
1015 debugs(17, 3, HERE
<< "No, ENTRY_FWD_HDR_WAIT isn't set");
1019 if (n_tries
> Config
.forward_max_tries
)
1022 if (origin_tries
> 1)
1025 if (request
->bodyNibbled())
1028 serverDestinations
.shift();
1030 if (serverDestinations
.size() == 0) {
1031 debugs(17, 3, HERE
<< "No alternative forwarding paths left");
1035 s
= e
->getReply()->sline
.status
;
1036 debugs(17, 3, HERE
<< "status " << s
);
1037 return reforwardableStatus(s
);
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.
1047 FwdState::makeConnectingError(const err_type type
) const
1049 return errorCon(type
, request
->flags
.need_validation
?
1050 HTTP_GATEWAY_TIMEOUT
: HTTP_SERVICE_UNAVAILABLE
, request
);
1054 fwdStats(StoreEntry
* s
)
1058 storeAppendPrintf(s
, "Status");
1060 for (j
= 0; j
<= MAX_FWD_STATS_IDX
; j
++) {
1061 storeAppendPrintf(s
, "\ttry#%d", j
+ 1);
1064 storeAppendPrintf(s
, "\n");
1066 for (i
= 0; i
<= (int) HTTP_INVALID_HEADER
; i
++) {
1067 if (FwdReplyCodes
[0][i
] == 0)
1070 storeAppendPrintf(s
, "%3d", i
);
1072 for (j
= 0; j
<= MAX_FWD_STATS_IDX
; j
++) {
1073 storeAppendPrintf(s
, "\t%d", FwdReplyCodes
[j
][i
]);
1076 storeAppendPrintf(s
, "\n");
1081 /**** STATIC MEMBER FUNCTIONS *************************************************/
1084 FwdState::reforwardableStatus(http_status s
)
1088 case HTTP_BAD_GATEWAY
:
1090 case HTTP_GATEWAY_TIMEOUT
:
1093 case HTTP_FORBIDDEN
:
1095 case HTTP_INTERNAL_SERVER_ERROR
:
1097 case HTTP_NOT_IMPLEMENTED
:
1099 case HTTP_SERVICE_UNAVAILABLE
:
1100 return Config
.retry
.onerror
;
1110 * Decide where details need to be gathered to correctly describe a persistent connection.
1112 * - the address/port details about this link
1113 * - domain name of server at other end of this link (either peer or requested host)
1116 FwdState::pconnPush(Comm::ConnectionPointer
&conn
, const char *domain
)
1118 if (conn
->getPeer()) {
1119 fwdPconnPool
->push(conn
, conn
->getPeer()->name
);
1121 fwdPconnPool
->push(conn
, domain
);
1126 FwdState::initModule()
1132 else if (NULL
== Config
.Log
.forward
)
1135 logfile
= logfileOpen(Config
.Log
.forward
, 0, 1);
1139 RegisterWithCacheManager();
1143 FwdState::RegisterWithCacheManager(void)
1145 Mgr::RegisterAction("forward", "Request Forwarding Statistics", fwdStats
, 0, 1);
1149 FwdState::logReplyStatus(int tries
, http_status status
)
1151 if (status
> HTTP_INVALID_HEADER
)
1156 if (tries
> MAX_FWD_STATS_IDX
)
1157 tries
= MAX_FWD_STATS_IDX
;
1159 FwdReplyCodes
[tries
][status
]++;
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
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..
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.
1174 // updates HierarchyLogEntry, guessing nextHop and its format
1176 FwdState::updateHierarchyInfo()
1180 assert(serverDestinations
.size() > 0);
1184 if (serverConnection()->getPeer()) {
1185 // went to peer, log peer host name
1186 snprintf(nextHop
,256,"%s", serverConnection()->getPeer()->name
);
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
1192 serverConnection()->remote
.NtoA(nextHop
, 256);
1195 request
->hier
.peer_local_port
= serverConnection()->local
.GetPort();
1198 hierarchyNote(&request
->hier
, serverConnection()->peerType
, nextHop
);
1202 /**** PRIVATE NON-MEMBER FUNCTIONS ********************************************/
1206 * Formerly static, but now used by client_side_request.cc
1208 /// Checks for a TOS value to apply depending on the ACL
1210 aclMapTOS(acl_tos
* head
, ACLChecklist
* ch
)
1214 for (l
= head
; l
; l
= l
->next
) {
1215 if (!l
->aclList
|| ch
->matchAclListFast(l
->aclList
))
1222 /// Checks for a netfilter mark value to apply depending on the ACL
1224 aclMapNfmark(acl_nfmark
* head
, ACLChecklist
* ch
)
1228 for (l
= head
; l
; l
= l
->next
) {
1229 if (!l
->aclList
|| ch
->matchAclListFast(l
->aclList
))
1237 getOutgoingAddress(HttpRequest
* request
, Comm::ConnectionPointer conn
)
1239 /* skip if an outgoing address is already set. */
1240 if (!conn
->local
.IsAnyAddr()) return;
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
;
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
;
1256 // else no tproxy today ...
1259 if (!Config
.accessList
.outgoing_address
) {
1260 return; // anything will do.
1263 ACLFilledChecklist
ch(NULL
, request
, NULL
);
1264 ch
.dst_peer
= conn
->getPeer();
1265 ch
.dst_addr
= conn
->remote
;
1267 // TODO use the connection details in ACL.
1268 // needs a bit of rework in ACLFilledChecklist to use Comm::Connection instead of ConnStateData
1271 #if FOLLOW_X_FORWARDED_FOR
1272 if (Config
.onoff
.acl_uses_indirect_client
)
1273 ch
.src_addr
= request
->indirect_client_addr
;
1276 ch
.src_addr
= request
->client_addr
;
1277 ch
.my_addr
= request
->my_addr
;
1281 for (l
= Config
.accessList
.outgoing_address
; l
; l
= l
->next
) {
1283 /* check if the outgoing address is usable to the destination */
1284 if (conn
->remote
.IsIPv4() != l
->addr
.IsIPv4()) continue;
1286 /* check ACLs for this outgoing address */
1287 if (!l
->aclList
|| ch
.matchAclListFast(l
->aclList
)) {
1288 conn
->local
= l
->addr
;
1294 // XXX: convert this to accepting a serverConn and migrate to QosConfig.cc
1296 GetTosToServer(HttpRequest
* request
)
1298 ACLFilledChecklist
ch(NULL
, request
, NULL
);
1301 ch
.src_addr
= request
->client_addr
;
1302 ch
.my_addr
= request
->my_addr
;
1305 return aclMapTOS(Ip::Qos::TheConfig
.tosToServer
, &ch
);
1308 // XXX: convert this to accepting a serverConn and migrate to QosConfig.cc
1310 GetNfmarkToServer(HttpRequest
* request
)
1312 ACLFilledChecklist
ch(NULL
, request
, NULL
);
1315 ch
.src_addr
= request
->client_addr
;
1316 ch
.my_addr
= request
->my_addr
;
1319 return aclMapNfmark(Ip::Qos::TheConfig
.nfmarkToServer
, &ch
);
1323 /**** WIP_FWD_LOG *************************************************************/
1329 if (NULL
== logfile
)
1332 logfileClose(logfile
);
1341 logfileRotate(logfile
);
1347 if (NULL
== logfile
)
1350 logfilePrintf(logfile
, "%9d.%03d %03d %s %s\n",
1351 (int) current_time
.tv_sec
,
1352 (int) current_time
.tv_usec
/ 1000,
1354 RequestMethodStr(request
->method
),
1355 request
->canonical
);
1359 FwdState::status(http_status s
)