3 * $Id: forward.cc,v 1.175 2008/02/11 22:26:39 rousskov Exp $
5 * DEBUG: section 17 Request Forwarding
6 * AUTHOR: Duane Wessels
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
39 #include "ACLChecklist.h"
41 #include "CacheManager.h"
43 #include "errorpage.h"
45 #include "HttpReply.h"
46 #include "HttpRequest.h"
47 #include "MemObject.h"
49 #include "SquidTime.h"
51 #include "icmp/net_db.h"
52 #include "IPInterception.h"
54 static PSC fwdStartCompleteWrapper
;
55 static PF fwdServerClosedWrapper
;
57 static PF fwdNegotiateSSLWrapper
;
59 static PF fwdConnectTimeoutWrapper
;
60 static EVH fwdConnectStartWrapper
;
61 static CNCB fwdConnectDoneWrapper
;
64 static void fwdServerFree(FwdServer
* fs
);
66 #define MAX_FWD_STATS_IDX 9
67 static int FwdReplyCodes
[MAX_FWD_STATS_IDX
+ 1][HTTP_INVALID_HEADER
+ 1];
70 static void fwdLog(FwdState
* fwdState
);
71 static Logfile
*logfile
= NULL
;
74 static PconnPool
*fwdPconnPool
= new PconnPool("server-side");
75 CBDATA_CLASS_INIT(FwdState
);
78 FwdState::abort(void* d
)
80 FwdState
* fwd
= (FwdState
*)d
;
81 Pointer tmp
= fwd
; // Grab a temporary pointer to keep the object alive during our scope.
83 if (fwd
->server_fd
>= 0) {
84 comm_close(fwd
->server_fd
);
91 /**** PUBLIC INTERFACE ********************************************************/
93 FwdState::FwdState(int fd
, StoreEntry
* e
, HttpRequest
* r
)
98 request
= HTTPMSGLOCK(r
);
99 start_t
= squid_curtime
;
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(request
, entry
, fwdStartCompleteWrapper
, this);
120 // TODO: set self _after_ the peer is selected because we do not need
121 // self until we start talking to some Server.
125 FwdState::completed()
127 if (flags
.forward_completed
== 1) {
128 debugs(17, 1, HERE
<< "FwdState::completed called on a completed request! Bad!");
132 flags
.forward_completed
= 1;
134 #if URL_CHECKSUM_DEBUG
136 entry
->mem_obj
->checkUrlChecksum();
143 if (entry
->store_status
== STORE_PENDING
) {
144 if (entry
->isEmpty()) {
146 errorAppendEntry(entry
, err
);
149 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
151 entry
->releaseRequest();
155 if (storePendingNClients(entry
) > 0)
156 assert(!EBIT_TEST(entry
->flags
, ENTRY_FWD_HDR_WAIT
));
160 FwdState::~FwdState()
162 debugs(17, 3, HERE
<< "FwdState destructor starting");
164 if (! flags
.forward_completed
)
167 serversFree(&servers
);
169 HTTPMSGUNLOCK(request
);
174 entry
->unregisterAbort();
184 comm_remove_close_handler(fd
, fwdServerClosedWrapper
, this);
185 debugs(17, 3, "fwdStateFree: closing FD " << fd
);
189 debugs(17, 3, HERE
<< "FwdState destructor done");
193 * This is the entry point for client-side to start forwarding
194 * a transaction. It is a static method that may or may not
195 * allocate a FwdState.
198 FwdState::fwdStart(int client_fd
, StoreEntry
*entry
, HttpRequest
*request
)
201 * client_addr == no_addr indicates this is an "internal" request
202 * from peer_digest.c, asn.c, netdb.c, etc and should always
203 * be allowed. yuck, I know.
206 if ( !request
->client_addr
.IsNoAddr() && request
->protocol
!= PROTO_INTERNAL
&& request
->protocol
!= PROTO_CACHEOBJ
) {
208 * Check if this host is allowed to fetch MISSES from us (miss_access)
211 ch
.src_addr
= request
->client_addr
;
212 ch
.my_addr
= request
->my_addr
;
213 ch
.request
= HTTPMSGLOCK(request
);
214 ch
.accessList
= cbdataReference(Config
.accessList
.miss
);
215 /* cbdataReferenceDone() happens in either fastCheck() or ~ACLCheckList */
216 int answer
= ch
.fastCheck();
220 page_id
= aclGetDenyInfoPage(&Config
.denyInfoList
, AclMatchedName
, 1);
222 if (page_id
== ERR_NONE
)
223 page_id
= ERR_FORWARDING_DENIED
;
225 ErrorState
*anErr
= errorCon(page_id
, HTTP_FORBIDDEN
, request
);
227 errorAppendEntry(entry
, anErr
); // frees anErr
233 debugs(17, 3, "FwdState::start() '" << entry
->url() << "'");
235 * This seems like an odd place to bind mem_obj and request.
236 * Might want to assert that request is NULL at this point
238 entry
->mem_obj
->request
= HTTPMSGLOCK(request
);
239 #if URL_CHECKSUM_DEBUG
241 entry
->mem_obj
->checkUrlChecksum();
246 ErrorState
*anErr
= errorCon(ERR_SHUTTING_DOWN
, HTTP_SERVICE_UNAVAILABLE
, request
);
247 errorAppendEntry(entry
, anErr
); // frees anErr
251 switch (request
->protocol
) {
254 internalStart(request
, entry
);
258 CacheManager::GetInstance()->Start(client_fd
, request
, entry
);
262 urnStart(request
, entry
);
266 FwdState::Pointer fwd
= new FwdState(client_fd
, entry
, request
);
268 /* If we need to transparently proxy the request
269 * then we need the client source protocol, address and port */
270 if (request
->flags
.spoof_client_ip
) {
271 fwd
->src
= request
->client_addr
;
272 // AYJ: do we need to pass on the transparent flag also?
283 FwdState::fail(ErrorState
* errorState
)
285 debugs(17, 3, HERE
<< err_type_str
[errorState
->type
] << " \"" << httpStatusString(errorState
->httpStatus
) << "\"\n\t" << entry
->url() );
292 if (!errorState
->request
)
293 errorState
->request
= HTTPMSGLOCK(request
);
297 * Frees fwdState without closing FD or generating an abort
300 FwdState::unregister(int fd
)
302 debugs(17, 3, HERE
<< entry
->url() );
303 assert(fd
== server_fd
);
305 comm_remove_close_handler(fd
, fwdServerClosedWrapper
, this);
310 * server-side modules call fwdComplete() when they are done
311 * downloading an object. Then, we either 1) re-forward the
312 * request somewhere else if needed, or 2) call storeComplete()
318 StoreEntry
*e
= entry
;
319 assert(entry
->store_status
== STORE_PENDING
);
320 debugs(17, 3, HERE
<< e
->url() << "\n\tstatus " << entry
->getReply()->sline
.status
);
321 #if URL_CHECKSUM_DEBUG
323 entry
->mem_obj
->checkUrlChecksum();
326 logReplyStatus(n_tries
, entry
->getReply()->sline
.status
);
329 debugs(17, 3, "fwdComplete: re-forwarding " << entry
->getReply()->sline
.status
<< " " << e
->url());
332 unregister(server_fd
);
336 startComplete(servers
);
338 debugs(17, 3, "fwdComplete: server FD " << server_fd
<< " not re-forwarding status " << entry
->getReply()->sline
.status
);
339 if (entry
->isEmpty() && !err
) {
340 EBIT_CLR(entry
->flags
, ENTRY_FWD_HDR_WAIT
);
347 self
= NULL
; // refcounted
352 /**** CALLBACK WRAPPERS ************************************************************/
355 fwdStartCompleteWrapper(FwdServer
* servers
, void *data
)
357 FwdState
*fwd
= (FwdState
*) data
;
358 fwd
->startComplete(servers
);
362 fwdServerClosedWrapper(int fd
, void *data
)
364 FwdState
*fwd
= (FwdState
*) data
;
365 fwd
->serverClosed(fd
);
369 fwdConnectStartWrapper(void *data
)
371 FwdState
*fwd
= (FwdState
*) data
;
377 fwdNegotiateSSLWrapper(int fd
, void *data
)
379 FwdState
*fwd
= (FwdState
*) data
;
380 fwd
->negotiateSSL(fd
);
386 fwdConnectDoneWrapper(int server_fd
, comm_err_t status
, int xerrno
, void *data
)
388 FwdState
*fwd
= (FwdState
*) data
;
389 fwd
->connectDone(server_fd
, status
, xerrno
);
393 fwdConnectTimeoutWrapper(int fd
, void *data
)
395 FwdState
*fwd
= (FwdState
*) data
;
396 fwd
->connectTimeout(fd
);
400 * Accounts for closed persistent connections
403 fwdPeerClosed(int fd
, void *data
)
405 peer
*p
= (peer
*)data
;
406 p
->stats
.conn_open
--;
409 /**** PRIVATE *****************************************************************/
412 * FwdState::checkRetry
414 * Return TRUE if the request SHOULD be retried. This method is
415 * called when the HTTP connection fails, or when the connection
416 * is closed before server-side read the end of HTTP headers.
419 FwdState::checkRetry()
424 if (entry
->store_status
!= STORE_PENDING
)
427 if (!entry
->isEmpty())
433 if (origin_tries
> 2)
436 if (squid_curtime
- start_t
> Config
.Timeout
.forward
)
439 if (flags
.dont_retry
)
442 if (!checkRetriable())
445 if (request
->bodyNibbled())
452 * FwdState::checkRetriable
454 * Return TRUE if this is the kind of request that can be retried
455 * after a failure. If the request is not retriable then we don't
456 * want to risk sending it on a persistent connection. Instead we'll
457 * force it to go on a new HTTP connection.
460 FwdState::checkRetriable()
462 /* If there is a request body then Squid can only try once
463 * even if the method is indempotent
466 if (request
->body_pipe
!= NULL
)
469 /* RFC2616 9.1 Safe and Idempotent Methods */
470 switch (request
->method
.id()) {
471 /* 9.1.1 Safe Methods */
476 /* 9.1.2 Idempotent Methods */
495 FwdState::serverClosed(int fd
)
497 debugs(17, 2, "fwdServerClosed: FD " << fd
<< " " << entry
->url());
498 assert(server_fd
== fd
);
505 FwdState::retryOrBail()
507 if (!self
) { // we have aborted before the server called us back
508 debugs(17, 5, HERE
<< "not retrying because of earlier abort");
509 // we will be destroyed when the server clears its Pointer to us
514 int originserver
= (servers
->_peer
== NULL
);
515 debugs(17, 3, "fwdServerClosed: re-forwarding (" << n_tries
<< " tries, " << (squid_curtime
- start_t
) << " secs)");
518 /* use next, or cycle if origin server isn't last */
519 FwdServer
*fs
= servers
;
520 FwdServer
**T
, *T2
= NULL
;
523 for (T
= &servers
; *T
; T2
= *T
, T
= &(*T
)->next
);
524 if (T2
&& T2
->_peer
) {
529 /* Use next. The last "direct" entry is retried multiple times */
536 /* use eventAdd to break potential call sequence loops and to slow things down a little */
537 eventAdd("fwdConnectStart", fwdConnectStartWrapper
, this, originserver
? 0.05 : 0.005, 0);
542 if (!err
&& shutting_down
) {
543 errorCon(ERR_SHUTTING_DOWN
, HTTP_SERVICE_UNAVAILABLE
, request
);
546 self
= NULL
; // refcounted
549 // called by the server that failed after calling unregister()
551 FwdState::handleUnregisteredServerEnd()
553 debugs(17, 2, "handleUnregisteredServerEnd: self=" << self
<<
554 " err=" << err
<< ' ' << entry
->url());
555 assert(server_fd
< 0);
561 FwdState::negotiateSSL(int fd
)
563 FwdServer
*fs
= servers
;
564 SSL
*ssl
= fd_table
[fd
].ssl
;
567 if ((ret
= SSL_connect(ssl
)) <= 0) {
568 int ssl_error
= SSL_get_error(ssl
, ret
);
572 case SSL_ERROR_WANT_READ
:
573 commSetSelect(fd
, COMM_SELECT_READ
, fwdNegotiateSSLWrapper
, this, 0);
576 case SSL_ERROR_WANT_WRITE
:
577 commSetSelect(fd
, COMM_SELECT_WRITE
, fwdNegotiateSSLWrapper
, this, 0);
581 debugs(81, 1, "fwdNegotiateSSL: Error negotiating SSL connection on FD " << fd
<<
582 ": " << ERR_error_string(ERR_get_error(), NULL
) << " (" << ssl_error
<<
583 "/" << ret
<< "/" << errno
<< ")");
584 ErrorState
*anErr
= errorCon(ERR_SECURE_CONNECT_FAIL
, HTTP_SERVICE_UNAVAILABLE
, request
);
587 anErr
->xerrno
= EPROTO
;
590 anErr
->xerrno
= EACCES
;
596 peerConnectFailed(fs
->_peer
);
597 fs
->_peer
->stats
.conn_open
--;
605 if (fs
->_peer
&& !SSL_session_reused(ssl
)) {
606 if (fs
->_peer
->sslSession
)
607 SSL_SESSION_free(fs
->_peer
->sslSession
);
609 fs
->_peer
->sslSession
= SSL_get1_session(ssl
);
616 FwdState::initiateSSL()
618 FwdServer
*fs
= servers
;
621 SSL_CTX
*sslContext
= NULL
;
622 peer
*peer
= fs
->_peer
;
625 assert(peer
->use_ssl
);
626 sslContext
= peer
->sslContext
;
628 sslContext
= Config
.ssl_client
.sslContext
;
633 if ((ssl
= SSL_new(sslContext
)) == NULL
) {
634 debugs(83, 1, "fwdInitiateSSL: Error allocating handle: " << ERR_error_string(ERR_get_error(), NULL
) );
635 ErrorState
*anErr
= errorCon(ERR_SOCKET_FAILURE
, HTTP_INTERNAL_SERVER_ERROR
, request
);
636 anErr
->xerrno
= errno
;
638 self
= NULL
; // refcounted
646 SSL_set_ex_data(ssl
, ssl_ex_index_server
, peer
->ssldomain
);
651 SSL_set_ex_data(ssl
, ssl_ex_index_server
, peer
->name
);
656 SSL_set_ex_data(ssl
, ssl_ex_index_server
, peer
->host
);
658 if (peer
->sslSession
)
659 SSL_set_session(ssl
, peer
->sslSession
);
662 SSL_set_ex_data(ssl
, ssl_ex_index_server
, (void*)request
->GetHost());
665 // Create the ACL check list now, while we have access to more info.
666 // The list is used in ssl_verify_cb() and is freed in ssl_free().
667 if (acl_access
*acl
= Config
.ssl_client
.cert_error
) {
668 ACLChecklist
*check
= aclChecklistCreate(acl
, request
, dash_str
);
670 SSL_set_ex_data(ssl
, ssl_ex_index_cert_error_check
, check
);
673 fd_table
[fd
].ssl
= ssl
;
674 fd_table
[fd
].read_method
= &ssl_read_method
;
675 fd_table
[fd
].write_method
= &ssl_write_method
;
682 FwdState::connectDone(int aServerFD
, comm_err_t status
, int xerrno
)
684 FwdServer
*fs
= servers
;
685 assert(server_fd
== aServerFD
);
687 if (Config
.onoff
.log_ip_on_direct
&& status
!= COMM_ERR_DNS
&& fs
->code
== HIER_DIRECT
)
688 updateHierarchyInfo();
690 if (status
== COMM_ERR_DNS
) {
692 * Only set the dont_retry flag if the DNS lookup fails on
693 * a direct connection. If DNS lookup fails when trying
694 * a neighbor cache, we may want to retry another option.
697 if (NULL
== fs
->_peer
)
698 flags
.dont_retry
= 1;
700 debugs(17, 4, "fwdConnectDone: Unknown host: " << request
->GetHost());
702 ErrorState
*anErr
= errorCon(ERR_DNS_FAIL
, HTTP_SERVICE_UNAVAILABLE
, request
);
704 anErr
->dnsserver_msg
= xstrdup(dns_error_message_safe());
708 comm_close(server_fd
);
709 } else if (status
!= COMM_OK
) {
711 ErrorState
*anErr
= errorCon(ERR_CONNECT_FAIL
, HTTP_SERVICE_UNAVAILABLE
, request
);
712 anErr
->xerrno
= xerrno
;
717 peerConnectFailed(fs
->_peer
);
719 comm_close(server_fd
);
721 debugs(17, 3, "fwdConnectDone: FD " << server_fd
<< ": '" << entry
->url() << "'" );
724 peerConnectSucceded(fs
->_peer
);
728 if ((fs
->_peer
&& fs
->_peer
->use_ssl
) ||
729 (!fs
->_peer
&& request
->protocol
== PROTO_HTTPS
)) {
740 FwdState::connectTimeout(int fd
)
742 FwdServer
*fs
= servers
;
744 debugs(17, 2, "fwdConnectTimeout: FD " << fd
<< ": '" << entry
->url() << "'" );
745 assert(fd
== server_fd
);
747 if (Config
.onoff
.log_ip_on_direct
&& fs
->code
== HIER_DIRECT
&& fd_table
[fd
].ipaddr
[0])
748 updateHierarchyInfo();
750 if (entry
->isEmpty()) {
751 ErrorState
*anErr
= errorCon(ERR_CONNECT_FAIL
, HTTP_GATEWAY_TIMEOUT
, request
);
752 anErr
->xerrno
= ETIMEDOUT
;
755 * This marks the peer DOWN ...
760 peerConnectFailed(servers
->_peer
);
767 FwdState::connectStart()
769 const char *url
= entry
->url();
771 FwdServer
*fs
= servers
;
774 const char *domain
= NULL
;
776 int ftimeout
= Config
.Timeout
.forward
- (squid_curtime
- start_t
);
781 IPAddress client_addr
;
783 assert(server_fd
== -1);
784 debugs(17, 3, "fwdConnectStart: " << url
);
787 host
= fs
->_peer
->host
;
788 port
= fs
->_peer
->http_port
;
789 ctimeout
= fs
->_peer
->connect_timeout
> 0 ? fs
->_peer
->connect_timeout
790 : Config
.Timeout
.peer_connect
;
792 if (fs
->_peer
->options
.originserver
)
793 domain
= request
->GetHost();
795 host
= request
->GetHost();
796 port
= request
->port
;
797 ctimeout
= Config
.Timeout
.connect
;
800 if (request
->flags
.spoof_client_ip
)
801 client_addr
= request
->client_addr
;
806 if (ftimeout
< ctimeout
)
810 request
->flags
.pinned
= 0;
811 if (fs
->code
== PINNED
) {
812 ConnStateData
*pinned_connection
= request
->pinnedConnection();
813 assert(pinned_connection
);
814 fd
= pinned_connection
->validatePinnedConnection(request
, fs
->_peer
);
816 pinned_connection
->unpinConnection();
819 fs
->code
= HIER_DIRECT
;
823 request
->flags
.pinned
= 1;
824 if (pinned_connection
->pinnedAuth())
825 request
->flags
.auth
= 1;
826 comm_add_close_handler(fd
, fwdServerClosedWrapper
, this);
827 connectDone(fd
, COMM_OK
, 0);
830 /* Failure. Fall back on next path */
831 request
->releasePinnedConnection();
838 fd
= fwdPconnPool
->pop(host
, port
, domain
, client_addr
, checkRetriable());
840 debugs(17, 3, "fwdConnectStart: reusing pconn FD " << fd
);
847 updateHierarchyInfo();
849 comm_add_close_handler(fd
, fwdServerClosedWrapper
, this);
856 #if URL_CHECKSUM_DEBUG
857 entry
->mem_obj
->checkUrlChecksum();
861 outgoing
= getOutgoingAddr(request
, fs
->_peer
);
863 tos
= getOutgoingTOS(request
);
865 debugs(17, 3, "fwdConnectStart: got outgoing addr " << outgoing
<< ", tos " << tos
);
867 if (request
->flags
.spoof_client_ip
) {
868 fd
= comm_openex(SOCK_STREAM
, IPPROTO_TCP
, outgoing
, (COMM_NONBLOCKING
|COMM_TRANSPARENT
), tos
, url
);
870 fd
= comm_openex(SOCK_STREAM
, IPPROTO_TCP
, outgoing
, COMM_NONBLOCKING
, tos
, url
);
873 debugs(17, 3, "fwdConnectStart: got TCP FD " << fd
);
876 debugs(50, 4, "fwdConnectStart: " << xstrerror());
877 ErrorState
*anErr
= errorCon(ERR_SOCKET_FAILURE
, HTTP_INTERNAL_SERVER_ERROR
, request
);
878 anErr
->xerrno
= errno
;
880 self
= NULL
; // refcounted
891 * stats.conn_open is used to account for the number of
892 * connections that we have open to the peer, so we can limit
893 * based on the max-conn option. We need to increment here,
894 * even if the connection may fail.
898 fs
->_peer
->stats
.conn_open
++;
899 comm_add_close_handler(fd
, fwdPeerClosed
, fs
->_peer
);
902 comm_add_close_handler(fd
, fwdServerClosedWrapper
, this);
904 commSetTimeout(fd
, ctimeout
, fwdConnectTimeoutWrapper
, this);
907 if (!fs
->_peer
&& request
->flags
.spoof_client_ip
) {
908 // try to set the outgoing address using TPROXY v2
909 // if it fails we abort any further TPROXY actions on this connection
910 if (IPInterceptor
.SetTproxy2OutgoingAddr(int fd
, const IPAddress
&src
) == -1) {
911 request
->flags
.spoof_client_ip
= 0;
916 updateHierarchyInfo();
917 commConnectStart(fd
, host
, port
, fwdConnectDoneWrapper
, this);
921 FwdState::startComplete(FwdServer
* theServers
)
923 debugs(17, 3, "fwdStartComplete: " << entry
->url() );
925 if (theServers
!= NULL
) {
926 servers
= theServers
;
934 FwdState::startFail()
936 debugs(17, 3, "fwdStartFail: " << entry
->url() );
937 ErrorState
*anErr
= errorCon(ERR_CANNOT_FORWARD
, HTTP_SERVICE_UNAVAILABLE
, request
);
938 anErr
->xerrno
= errno
;
940 self
= NULL
; // refcounted
947 debugs(17, 3, "fwdDispatch: FD " << client_fd
<< ": Fetching '" << RequestMethodStr(request
->method
) << " " << entry
->url() << "'" );
949 * Assert that server_fd is set. This is to guarantee that fwdState
950 * is attached to something and will be deallocated when server_fd
953 assert(server_fd
> -1);
955 fd_note(server_fd
, entry
->url());
957 fd_table
[server_fd
].noteUse(fwdPconnPool
);
959 /*assert(!EBIT_TEST(entry->flags, ENTRY_DISPATCHED)); */
960 assert(entry
->ping_status
!= PING_WAITING
);
962 assert(entry
->lock_count
);
964 EBIT_SET(entry
->flags
, ENTRY_DISPATCHED
);
966 netdbPingSite(request
->GetHost());
968 #if USE_ZPH_QOS && _SQUID_LINUX_
969 /* Bug 2537: This part of ZPH only applies to patched Linux kernels. */
971 /* Retrieves remote server TOS value, and stores it as part of the
972 * original client request FD object. It is later used to forward
973 * remote server's TOS in the response to the client in case of a MISS.
975 fde
* clientFde
= &fd_table
[client_fd
];
978 int tos_len
= sizeof(tos
);
979 clientFde
->upstreamTOS
= 0;
980 if (setsockopt(server_fd
,SOL_IP
,IP_RECVTOS
,&tos
,tos_len
)==0) {
981 unsigned char buf
[512];
983 if (getsockopt(server_fd
,SOL_IP
,IP_PKTOPTIONS
,buf
,(socklen_t
*)&len
) == 0) {
984 /* Parse the PKTOPTIONS structure to locate the TOS data message
985 * prepared in the kernel by the ZPH incoming TCP TOS preserving
988 unsigned char * p
= buf
;
989 while (p
-buf
< len
) {
990 struct cmsghdr
*o
= (struct cmsghdr
*)p
;
994 if (o
->cmsg_level
== SOL_IP
&& o
->cmsg_type
== IP_TOS
) {
995 clientFde
->upstreamTOS
= (unsigned char)(*(int*)CMSG_DATA(o
));
998 p
+= CMSG_LEN(o
->cmsg_len
);
1001 debugs(33, 1, "ZPH: error in getsockopt(IP_PKTOPTIONS) on FD "<<server_fd
<<" "<<xstrerror());
1004 debugs(33, 1, "ZPH: error in setsockopt(IP_RECVTOS) on FD "<<server_fd
<<" "<<xstrerror());
1009 if (servers
&& (p
= servers
->_peer
)) {
1011 request
->peer_login
= p
->login
;
1012 request
->peer_domain
= p
->domain
;
1015 request
->peer_login
= NULL
;
1016 request
->peer_domain
= NULL
;
1018 switch (request
->protocol
) {
1038 case PROTO_CACHEOBJ
:
1040 case PROTO_INTERNAL
:
1043 fatal_dump("Should never get here");
1050 case PROTO_WAIS
: /* Not implemented */
1053 debugs(17, 1, "fwdDispatch: Cannot retrieve '" << entry
->url() << "'" );
1054 ErrorState
*anErr
= errorCon(ERR_UNSUP_REQ
, HTTP_BAD_REQUEST
, request
);
1057 * Force a persistent connection to be closed because
1058 * some Netscape browsers have a bug that sends CONNECT
1059 * requests as GET's over persistent connections.
1061 request
->flags
.proxy_keepalive
= 0;
1063 * Set the dont_retry flag becuase this is not a
1064 * transient (network) error; its a bug.
1066 flags
.dont_retry
= 1;
1067 comm_close(server_fd
);
1074 * FwdState::reforward
1076 * returns TRUE if the transaction SHOULD be re-forwarded to the
1077 * next choice in the FwdServers list. This method is called when
1078 * server-side communication completes normally, or experiences
1079 * some error after receiving the end of HTTP headers.
1082 FwdState::reforward()
1084 StoreEntry
*e
= entry
;
1085 FwdServer
*fs
= servers
;
1087 assert(e
->store_status
== STORE_PENDING
);
1089 #if URL_CHECKSUM_DEBUG
1091 e
->mem_obj
->checkUrlChecksum();
1094 debugs(17, 3, "fwdReforward: " << e
->url() << "?" );
1096 if (!EBIT_TEST(e
->flags
, ENTRY_FWD_HDR_WAIT
)) {
1097 debugs(17, 3, "fwdReforward: No, ENTRY_FWD_HDR_WAIT isn't set");
1104 if (origin_tries
> 1)
1107 if (request
->bodyNibbled())
1116 if (servers
== NULL
) {
1117 debugs(17, 3, "fwdReforward: No forward-servers left");
1121 s
= e
->getReply()->sline
.status
;
1122 debugs(17, 3, "fwdReforward: status " << s
);
1123 return reforwardableStatus(s
);
1127 fwdStats(StoreEntry
* s
)
1131 storeAppendPrintf(s
, "Status");
1133 for (j
= 0; j
<= MAX_FWD_STATS_IDX
; j
++) {
1134 storeAppendPrintf(s
, "\ttry#%d", j
+ 1);
1137 storeAppendPrintf(s
, "\n");
1139 for (i
= 0; i
<= (int) HTTP_INVALID_HEADER
; i
++) {
1140 if (FwdReplyCodes
[0][i
] == 0)
1143 storeAppendPrintf(s
, "%3d", i
);
1145 for (j
= 0; j
<= MAX_FWD_STATS_IDX
; j
++) {
1146 storeAppendPrintf(s
, "\t%d", FwdReplyCodes
[j
][i
]);
1149 storeAppendPrintf(s
, "\n");
1154 /**** STATIC MEMBER FUNCTIONS *************************************************/
1157 FwdState::reforwardableStatus(http_status s
)
1161 case HTTP_BAD_GATEWAY
:
1163 case HTTP_GATEWAY_TIMEOUT
:
1166 case HTTP_FORBIDDEN
:
1168 case HTTP_INTERNAL_SERVER_ERROR
:
1170 case HTTP_NOT_IMPLEMENTED
:
1172 case HTTP_SERVICE_UNAVAILABLE
:
1173 return Config
.retry
.onerror
;
1184 FwdState::pconnPush(int fd
, const char *host
, int port
, const char *domain
, IPAddress
&client_addr
)
1186 fwdPconnPool
->push(fd
, host
, port
, domain
, client_addr
);
1190 FwdState::initModule()
1192 memDataInit(MEM_FWD_SERVER
, "FwdServer", sizeof(FwdServer
), 0);
1198 else if (NULL
== Config
.Log
.forward
)
1201 logfile
= logfileOpen(Config
.Log
.forward
, 0, 1);
1205 RegisterWithCacheManager();
1209 FwdState::RegisterWithCacheManager(void)
1211 CacheManager::GetInstance()->
1212 registerAction("forward", "Request Forwarding Statistics", fwdStats
, 0, 1);
1216 FwdState::logReplyStatus(int tries
, http_status status
)
1218 if (status
> HTTP_INVALID_HEADER
)
1225 if (tries
> MAX_FWD_STATS_IDX
)
1226 tries
= MAX_FWD_STATS_IDX
;
1228 FwdReplyCodes
[tries
][status
]++;
1232 FwdState::serversFree(FwdServer
** FSVR
)
1236 while ((fs
= *FSVR
)) {
1242 /** From Comment #5 by Henrik Nordstrom made at
1243 http://www.squid-cache.org/bugs/show_bug.cgi?id=2391 on 2008-09-19
1245 updateHierarchyInfo should be called each time a new path has been
1246 selected or when more information about the path is available (i.e. the
1247 server IP), and when it's called it needs to be given reasonable
1248 arguments describing the now selected path..
1250 It does not matter from a functional perspective if it gets called a few
1251 times more than what is really needed, but calling it too often may
1252 obviously hurt performance.
1254 \todo Current code looks fine, even if using !fs->_peer as condition
1255 instead of HIER_DIRECT would be clearer.
1257 // updates HierarchyLogEntry, guessing nextHop and its format
1259 FwdState::updateHierarchyInfo()
1263 FwdServer
*fs
= servers
;
1266 const char *nextHop
= NULL
;
1269 // went to peer, log peer host name
1270 nextHop
= fs
->_peer
->name
;
1272 // went DIRECT, must honor log_ip_on_direct
1274 // XXX: or should we use request->host_addr here? how?
1275 assert(server_fd
>= 0);
1276 nextHop
= fd_table
[server_fd
].ipaddr
;
1277 if (!Config
.onoff
.log_ip_on_direct
|| !nextHop
[0])
1278 nextHop
= request
->GetHost(); // domain name
1282 hierarchyNote(&request
->hier
, fs
->code
, nextHop
);
1286 /**** PRIVATE NON-MEMBER FUNCTIONS ********************************************/
1289 fwdServerFree(FwdServer
* fs
)
1291 cbdataReferenceDone(fs
->_peer
);
1292 memFree(fs
, MEM_FWD_SERVER
);
1296 aclMapAddr(acl_address
* head
, ACLChecklist
* ch
)
1302 for (l
= head
; l
; l
= l
->next
) {
1303 if (ch
->matchAclListFast(l
->aclList
))
1313 * Formerly static, but now used by client_side_request.cc
1316 aclMapTOS(acl_tos
* head
, ACLChecklist
* ch
)
1320 for (l
= head
; l
; l
= l
->next
) {
1321 if (ch
->matchAclListFast(l
->aclList
))
1329 getOutgoingAddr(HttpRequest
* request
, struct peer
*dst_peer
)
1333 if (request
&& request
->flags
.spoof_client_ip
)
1334 return request
->client_addr
;
1336 ch
.dst_peer
= dst_peer
;
1339 ch
.src_addr
= request
->client_addr
;
1340 ch
.my_addr
= request
->my_addr
;
1341 ch
.request
= HTTPMSGLOCK(request
);
1344 return aclMapAddr(Config
.accessList
.outgoing_address
, &ch
);
1348 getOutgoingTOS(HttpRequest
* request
)
1353 ch
.src_addr
= request
->client_addr
;
1354 ch
.my_addr
= request
->my_addr
;
1355 ch
.request
= HTTPMSGLOCK(request
);
1358 return aclMapTOS(Config
.accessList
.outgoing_tos
, &ch
);
1362 /**** WIP_FWD_LOG *************************************************************/
1368 if (NULL
== logfile
)
1371 logfileClose(logfile
);
1380 logfileRotate(logfile
);
1386 if (NULL
== logfile
)
1389 logfilePrintf(logfile
, "%9d.%03d %03d %s %s\n",
1390 (int) current_time
.tv_sec
,
1391 (int) current_time
.tv_usec
/ 1000,
1393 RequestMethodStr(request
->method
),
1394 request
->canonical
);
1398 FwdState::status(http_status s
)