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