2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 93 ICAP (RFC 3507) Client */
12 #include "acl/FilledChecklist.h"
13 #include "adaptation/icap/Config.h"
14 #include "adaptation/icap/Launcher.h"
15 #include "adaptation/icap/Xaction.h"
16 #include "base/TextException.h"
18 #include "comm/Connection.h"
19 #include "comm/ConnOpener.h"
20 #include "comm/Read.h"
21 #include "comm/Write.h"
22 #include "CommCalls.h"
23 #include "err_detail_type.h"
28 #include "HttpReply.h"
29 #include "HttpRequest.h"
33 #include "SquidConfig.h"
34 #include "SquidTime.h"
37 /// Gives Ssl::PeerConnector access to Answer in the PeerPoolMgr callback dialer.
38 class MyIcapAnswerDialer
: public UnaryMemFunT
<Adaptation::Icap::Xaction
, Security::EncryptorAnswer
, Security::EncryptorAnswer
&>,
39 public Ssl::PeerConnector::CbDialer
42 MyIcapAnswerDialer(const JobPointer
&aJob
, Method aMethod
):
43 UnaryMemFunT
<Adaptation::Icap::Xaction
, Security::EncryptorAnswer
, Security::EncryptorAnswer
&>(aJob
, aMethod
, Security::EncryptorAnswer()) {}
45 /* Ssl::PeerConnector::CbDialer API */
46 virtual Security::EncryptorAnswer
&answer() { return arg1
; }
51 /// A simple PeerConnector for Secure ICAP services. No SslBump capabilities.
52 class IcapPeerConnector
: public PeerConnector
{
53 CBDATA_CLASS(IcapPeerConnector
);
56 Adaptation::Icap::ServiceRep::Pointer
&service
,
57 const Comm::ConnectionPointer
&aServerConn
,
58 AsyncCall::Pointer
&aCallback
, const time_t timeout
= 0):
59 AsyncJob("Ssl::IcapPeerConnector"),
60 PeerConnector(aServerConn
, aCallback
, timeout
), icapService(service
) {}
62 /* PeerConnector API */
63 virtual SSL
*initializeSsl();
64 virtual void noteNegotiationDone(ErrorState
*error
);
65 virtual SSL_CTX
*getSslContext() {return icapService
->sslContext
; }
68 Adaptation::Icap::ServiceRep::Pointer icapService
;
72 CBDATA_NAMESPACED_CLASS_INIT(Ssl
, IcapPeerConnector
);
75 Adaptation::Icap::Xaction::Xaction(const char *aTypeName
, Adaptation::Icap::ServiceRep::Pointer
&aService
):
77 Adaptation::Initiate(aTypeName
),
84 reuseConnection(true),
87 ignoreLastWrite(false),
93 alep(new AccessLogEntry
),
97 debugs(93,3, typeName
<< " constructed, this=" << this <<
98 " [icapx" << id
<< ']'); // we should not call virtual status() here
99 icapRequest
= new HttpRequest
;
100 HTTPMSGLOCK(icapRequest
);
101 icap_tr_start
= current_time
;
102 memset(&icap_tio_start
, 0, sizeof(icap_tio_start
));
103 memset(&icap_tio_finish
, 0, sizeof(icap_tio_finish
));
106 Adaptation::Icap::Xaction::~Xaction()
108 debugs(93,3, typeName
<< " destructed, this=" << this <<
109 " [icapx" << id
<< ']'); // we should not call virtual status() here
110 HTTPMSGUNLOCK(icapRequest
);
113 Adaptation::Icap::ServiceRep
&
114 Adaptation::Icap::Xaction::service()
116 Must(theService
!= NULL
);
120 void Adaptation::Icap::Xaction::disableRetries()
122 debugs(93,5, typeName
<< (isRetriable
? " from now on" : " still") <<
123 " cannot be retried " << status());
127 void Adaptation::Icap::Xaction::disableRepeats(const char *reason
)
129 debugs(93,5, typeName
<< (isRepeatable
? " from now on" : " still") <<
130 " cannot be repeated because " << reason
<< status());
131 isRepeatable
= false;
134 void Adaptation::Icap::Xaction::start()
136 Adaptation::Initiate::start();
140 icapLookupDnsResults(const ipcache_addrs
*ia
, const Dns::LookupDetails
&, void *data
)
142 Adaptation::Icap::Xaction
*xa
= static_cast<Adaptation::Icap::Xaction
*>(data
);
143 xa
->dnsLookupDone(ia
);
146 // TODO: obey service-specific, OPTIONS-reported connection limit
148 Adaptation::Icap::Xaction::openConnection()
150 Must(!haveConnection());
152 Adaptation::Icap::ServiceRep
&s
= service();
154 if (!TheConfig
.reuse_connections
)
155 disableRetries(); // this will also safely drain pconn pool
157 bool wasReused
= false;
158 connection
= s
.getConnection(isRetriable
, wasReused
);
160 if (wasReused
&& Comm::IsConnOpen(connection
)) {
161 // Set comm Close handler
162 // fake the connect callback
163 // TODO: can we sync call Adaptation::Icap::Xaction::noteCommConnected here instead?
164 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommConnectCbParams
> Dialer
;
165 CbcPointer
<Xaction
> self(this);
166 Dialer
dialer(self
, &Adaptation::Icap::Xaction::noteCommConnected
);
167 dialer
.params
.conn
= connection
;
168 dialer
.params
.flag
= Comm::OK
;
169 // fake other parameters by copying from the existing connection
170 connector
= asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected", dialer
);
171 ScheduleCallHere(connector
);
175 disableRetries(); // we only retry pconn failures
177 // Attempt to open a new connection...
178 debugs(93,3, typeName
<< " opens connection to " << s
.cfg().host
.termedBuf() << ":" << s
.cfg().port
);
180 // Locate the Service IP(s) to open
181 ipcache_nbgethostbyname(s
.cfg().host
.termedBuf(), icapLookupDnsResults
, this);
185 Adaptation::Icap::Xaction::dnsLookupDone(const ipcache_addrs
*ia
)
187 Adaptation::Icap::ServiceRep
&s
= service();
190 debugs(44, DBG_IMPORTANT
, "ICAP: Unknown service host: " << s
.cfg().host
);
192 #if WHEN_IPCACHE_NBGETHOSTBYNAME_USES_ASYNC_CALLS
193 dieOnConnectionFailure(); // throws
194 #else // take a step back into protected Async call dialing.
195 // fake the connect callback
196 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommConnectCbParams
> Dialer
;
197 CbcPointer
<Xaction
> self(this);
198 Dialer
dialer(self
, &Adaptation::Icap::Xaction::noteCommConnected
);
199 dialer
.params
.conn
= connection
;
200 dialer
.params
.flag
= Comm::COMM_ERROR
;
201 // fake other parameters by copying from the existing connection
202 connector
= asyncCall(93,3, "Adaptation::Icap::Xaction::noteCommConnected", dialer
);
203 ScheduleCallHere(connector
);
208 assert(ia
->cur
< ia
->count
);
210 connection
= new Comm::Connection
;
211 connection
->remote
= ia
->in_addrs
[ia
->cur
];
212 connection
->remote
.port(s
.cfg().port
);
213 getOutgoingAddress(NULL
, connection
);
215 // TODO: service bypass status may differ from that of a transaction
216 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommConnectCbParams
> ConnectDialer
;
217 connector
= JobCallback(93,3, ConnectDialer
, this, Adaptation::Icap::Xaction::noteCommConnected
);
218 cs
= new Comm::ConnOpener(connection
, connector
, TheConfig
.connect_timeout(service().cfg().bypass
));
219 cs
->setHost(s
.cfg().host
.termedBuf());
224 * This event handler is necessary to work around the no-rentry policy
225 * of Adaptation::Icap::Xaction::callStart()
229 Adaptation::Icap::Xaction::reusedConnection(void *data
)
231 debugs(93, 5, HERE
<< "reused connection");
232 Adaptation::Icap::Xaction
*x
= (Adaptation::Icap::Xaction
*)data
;
233 x
->noteCommConnected(Comm::OK
);
237 void Adaptation::Icap::Xaction::closeConnection()
239 if (haveConnection()) {
241 if (closer
!= NULL
) {
242 comm_remove_close_handler(connection
->fd
, closer
);
246 cancelRead(); // may not work
248 if (reuseConnection
&& !doneWithIo()) {
249 //status() adds leading spaces.
250 debugs(93,5, HERE
<< "not reusing pconn due to pending I/O" << status());
251 reuseConnection
= false;
257 const bool reset
= !reuseConnection
&&
258 (al
.icap
.outcome
== xoGone
|| al
.icap
.outcome
== xoError
);
260 Adaptation::Icap::ServiceRep
&s
= service();
261 s
.putConnection(connection
, reuseConnection
, reset
, status());
270 // connection with the ICAP service established
271 void Adaptation::Icap::Xaction::noteCommConnected(const CommConnectCbParams
&io
)
275 if (io
.flag
== Comm::TIMEOUT
) {
276 handleCommTimedout();
280 Must(connector
!= NULL
);
283 if (io
.flag
!= Comm::OK
)
284 dieOnConnectionFailure(); // throws
286 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommTimeoutCbParams
> TimeoutDialer
;
287 AsyncCall::Pointer timeoutCall
= asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommTimedout",
288 TimeoutDialer(this,&Adaptation::Icap::Xaction::noteCommTimedout
));
289 commSetConnTimeout(io
.conn
, TheConfig
.connect_timeout(service().cfg().bypass
), timeoutCall
);
291 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommCloseCbParams
> CloseDialer
;
292 closer
= asyncCall(93, 5, "Adaptation::Icap::Xaction::noteCommClosed",
293 CloseDialer(this,&Adaptation::Icap::Xaction::noteCommClosed
));
294 comm_add_close_handler(io
.conn
->fd
, closer
);
297 // If it is a reused connection and the SSL object is build
298 // we should not negotiate new SSL session
299 SSL
*ssl
= fd_table
[io
.conn
->fd
].ssl
;
300 if (!ssl
&& service().cfg().secure
.encryptTransport
) {
301 CbcPointer
<Adaptation::Icap::Xaction
> me(this);
302 securer
= asyncCall(93, 4, "Adaptation::Icap::Xaction::handleSecuredPeer",
303 MyIcapAnswerDialer(me
, &Adaptation::Icap::Xaction::handleSecuredPeer
));
305 Ssl::PeerConnector::HttpRequestPointer
tmpReq(NULL
);
306 Ssl::IcapPeerConnector
*sslConnector
=
307 new Ssl::IcapPeerConnector(theService
, io
.conn
, securer
, TheConfig
.connect_timeout(service().cfg().bypass
));
308 AsyncJob::Start(sslConnector
); // will call our callback
313 // ?? fd_table[io.conn->fd].noteUse(icapPconnPool);
314 service().noteConnectionUse(connection
);
316 handleCommConnected();
319 void Adaptation::Icap::Xaction::dieOnConnectionFailure()
321 debugs(93, 2, HERE
<< typeName
<<
322 " failed to connect to " << service().cfg().uri
);
323 service().noteConnectionFailed("failure");
324 detailError(ERR_DETAIL_ICAP_XACT_START
);
325 throw TexcHere("cannot connect to the ICAP service");
328 void Adaptation::Icap::Xaction::scheduleWrite(MemBuf
&buf
)
330 Must(haveConnection());
332 // comm module will free the buffer
333 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommIoCbParams
> Dialer
;
334 writer
= JobCallback(93, 3,
335 Dialer
, this, Adaptation::Icap::Xaction::noteCommWrote
);
337 Comm::Write(connection
, &buf
, writer
);
341 void Adaptation::Icap::Xaction::noteCommWrote(const CommIoCbParams
&io
)
343 Must(writer
!= NULL
);
346 if (ignoreLastWrite
) {
347 // a hack due to comm inability to cancel a pending write
348 ignoreLastWrite
= false;
349 debugs(93, 7, HERE
<< "ignoring last write; status: " << io
.flag
);
351 Must(io
.flag
== Comm::OK
);
352 al
.icap
.bytesSent
+= io
.size
;
354 handleCommWrote(io
.size
);
358 // communication timeout with the ICAP service
359 void Adaptation::Icap::Xaction::noteCommTimedout(const CommTimeoutCbParams
&)
361 handleCommTimedout();
364 void Adaptation::Icap::Xaction::handleCommTimedout()
366 debugs(93, 2, HERE
<< typeName
<< " failed: timeout with " <<
367 theService
->cfg().methodStr() << " " <<
368 theService
->cfg().uri
<< status());
369 reuseConnection
= false;
370 const bool whileConnecting
= connector
!= NULL
;
371 if (whileConnecting
) {
372 assert(!haveConnection());
373 theService
->noteConnectionFailed("timedout");
375 closeConnection(); // so that late Comm callbacks do not disturb bypass
376 throw TexcHere(whileConnecting
?
377 "timed out while connecting to the ICAP service" :
378 "timed out while talking to the ICAP service");
381 // unexpected connection close while talking to the ICAP service
382 void Adaptation::Icap::Xaction::noteCommClosed(const CommCloseCbParams
&)
384 if (securer
!= NULL
) {
385 securer
->cancel("Connection closed before SSL negotiation finished");
392 void Adaptation::Icap::Xaction::handleCommClosed()
394 detailError(ERR_DETAIL_ICAP_XACT_CLOSE
);
395 mustStop("ICAP service connection externally closed");
398 void Adaptation::Icap::Xaction::callException(const std::exception
&e
)
401 service().noteFailure();
402 Adaptation::Initiate::callException(e
);
405 void Adaptation::Icap::Xaction::callEnd()
408 debugs(93, 5, HERE
<< typeName
<< " done with I/O" << status());
411 Adaptation::Initiate::callEnd(); // may destroy us
414 bool Adaptation::Icap::Xaction::doneAll() const
416 return !connector
&& !securer
&& !reader
&& !writer
&& Adaptation::Initiate::doneAll();
419 void Adaptation::Icap::Xaction::updateTimeout()
421 Must(haveConnection());
423 if (reader
!= NULL
|| writer
!= NULL
) {
424 // restart the timeout before each I/O
425 // XXX: why does Config.Timeout lacks a write timeout?
426 // TODO: service bypass status may differ from that of a transaction
427 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommTimeoutCbParams
> TimeoutDialer
;
428 AsyncCall::Pointer call
= JobCallback(93, 5, TimeoutDialer
, this, Adaptation::Icap::Xaction::noteCommTimedout
);
429 commSetConnTimeout(connection
, TheConfig
.io_timeout(service().cfg().bypass
), call
);
431 // clear timeout when there is no I/O
432 // Do we need a lifetime timeout?
433 commUnsetConnTimeout(connection
);
437 void Adaptation::Icap::Xaction::scheduleRead()
439 Must(haveConnection());
441 Must(readBuf
.length() < SQUID_TCP_SO_RCVBUF
); // will expand later if needed
443 typedef CommCbMemFunT
<Adaptation::Icap::Xaction
, CommIoCbParams
> Dialer
;
444 reader
= JobCallback(93, 3, Dialer
, this, Adaptation::Icap::Xaction::noteCommRead
);
445 Comm::Read(connection
, reader
);
449 // comm module read a portion of the ICAP response for us
450 void Adaptation::Icap::Xaction::noteCommRead(const CommIoCbParams
&io
)
452 Must(reader
!= NULL
);
455 Must(io
.flag
== Comm::OK
);
457 // TODO: tune this better to expected message sizes
458 readBuf
.reserveCapacity(SQUID_TCP_SO_RCVBUF
);
460 CommIoCbParams
rd(this); // will be expanded with ReadNow results
463 switch (Comm::ReadNow(rd
, readBuf
)) {
464 case Comm::INPROGRESS
:
465 if (readBuf
.isEmpty())
466 debugs(33, 2, io
.conn
<< ": no data to process, " << xstrerr(rd
.xerrno
));
471 al
.icap
.bytesRead
+= rd
.size
;
475 debugs(93, 3, "read " << rd
.size
<< " bytes");
477 disableRetries(); // because pconn did not fail
479 /* Continue to process previously read data */
482 case Comm::ENDFILE
: // close detected by 0-byte read
484 reuseConnection
= false;
486 // detect a pconn race condition: eof on the first pconn read
487 if (!al
.icap
.bytesRead
&& retriable()) {
489 mustStop("pconn race");
495 // case Comm::COMM_ERROR:
496 default: // no other flags should ever occur
497 debugs(11, 2, io
.conn
<< ": read failure: " << xstrerr(rd
.xerrno
));
498 mustStop("unknown ICAP I/O read error");
502 handleCommRead(io
.size
);
505 void Adaptation::Icap::Xaction::cancelRead()
507 if (reader
!= NULL
) {
508 Must(haveConnection());
509 Comm::ReadCancel(connection
->fd
, reader
);
514 bool Adaptation::Icap::Xaction::parseHttpMsg(HttpMsg
*msg
)
516 debugs(93, 5, "have " << readBuf
.length() << " head bytes to parse");
518 Http::StatusCode error
= Http::scNone
;
519 // XXX: performance regression c_str() data copies
520 const char *buf
= readBuf
.c_str();
521 const bool parsed
= msg
->parse(buf
, readBuf
.length(), commEof
, &error
);
522 Must(parsed
|| !error
); // success or need more data
524 if (!parsed
) { // need more data
530 readBuf
.consume(msg
->hdr_sz
);
534 bool Adaptation::Icap::Xaction::mayReadMore() const
536 return !doneReading() && // will read more data
537 readBuf
.spaceSize(); // have space for more data
540 bool Adaptation::Icap::Xaction::doneReading() const
545 bool Adaptation::Icap::Xaction::doneWriting() const
550 bool Adaptation::Icap::Xaction::doneWithIo() const
552 return haveConnection() &&
553 !connector
&& !reader
&& !writer
&& // fast checks, some redundant
554 doneReading() && doneWriting();
557 bool Adaptation::Icap::Xaction::haveConnection() const
559 return connection
!= NULL
&& connection
->isOpen();
563 void Adaptation::Icap::Xaction::noteInitiatorAborted()
566 if (theInitiator
.set()) {
567 debugs(93,4, HERE
<< "Initiator gone before ICAP transaction ended");
569 detailError(ERR_DETAIL_ICAP_INIT_GONE
);
571 mustStop("initiator aborted");
576 void Adaptation::Icap::Xaction::setOutcome(const Adaptation::Icap::XactOutcome
&xo
)
578 if (al
.icap
.outcome
!= xoUnknown
) {
579 debugs(93, 3, HERE
<< "Warning: reseting outcome: from " <<
580 al
.icap
.outcome
<< " to " << xo
);
582 debugs(93, 4, HERE
<< xo
);
584 al
.icap
.outcome
= xo
;
587 // This 'last chance' method is called before a 'done' transaction is deleted.
588 // It is wrong to call virtual methods from a destructor. Besides, this call
589 // indicates that the transaction will terminate as planned.
590 void Adaptation::Icap::Xaction::swanSong()
592 // kids should sing first and then call the parent method.
594 debugs(93,6, HERE
<< id
<< " about to notify ConnOpener!");
595 CallJobHere(93, 3, cs
, Comm::ConnOpener
, noteAbort
);
597 service().noteConnectionFailed("abort");
600 closeConnection(); // TODO: rename because we do not always close
608 Adaptation::Initiate::swanSong();
611 void Adaptation::Icap::Xaction::tellQueryAborted()
613 if (theInitiator
.set()) {
614 Adaptation::Icap::XactAbortInfo
abortInfo(icapRequest
, icapReply
.getRaw(),
615 retriable(), repeatable());
616 Launcher
*launcher
= dynamic_cast<Launcher
*>(theInitiator
.get());
617 // launcher may be nil if initiator is invalid
618 CallJobHere1(91,5, CbcPointer
<Launcher
>(launcher
),
619 Launcher
, noteXactAbort
, abortInfo
);
624 void Adaptation::Icap::Xaction::maybeLog()
626 if (IcapLogfileStatus
== LOG_ENABLE
) {
632 void Adaptation::Icap::Xaction::finalizeLogInfo()
635 al
.icp
.opcode
= ICP_INVALID
;
637 const Adaptation::Icap::ServiceRep
&s
= service();
638 al
.icap
.hostAddr
= s
.cfg().host
.termedBuf();
639 al
.icap
.serviceName
= s
.cfg().key
;
640 al
.icap
.reqUri
= s
.cfg().uri
;
642 tvSub(al
.icap
.ioTime
, icap_tio_start
, icap_tio_finish
);
643 tvSub(al
.icap
.trTime
, icap_tr_start
, current_time
);
645 al
.icap
.request
= icapRequest
;
646 HTTPMSGLOCK(al
.icap
.request
);
647 if (icapReply
!= NULL
) {
648 al
.icap
.reply
= icapReply
.getRaw();
649 HTTPMSGLOCK(al
.icap
.reply
);
650 al
.icap
.resStatus
= icapReply
->sline
.status();
654 // returns a temporary string depicting transaction status, for debugging
655 const char *Adaptation::Icap::Xaction::status() const
660 fillPendingStatus(buf
);
663 buf
.appendf(" %s%u]", id
.Prefix
, id
.value
);
666 return buf
.content();
669 void Adaptation::Icap::Xaction::fillPendingStatus(MemBuf
&buf
) const
671 if (haveConnection()) {
672 buf
.appendf("FD %d", connection
->fd
);
684 void Adaptation::Icap::Xaction::fillDoneStatus(MemBuf
&buf
) const
686 if (haveConnection() && commEof
)
687 buf
.appendf("Comm(%d)", connection
->fd
);
689 if (stopReason
!= NULL
)
690 buf
.append("Stopped", 7);
693 bool Adaptation::Icap::Xaction::fillVirginHttpHeader(MemBuf
&) const
700 Ssl::IcapPeerConnector::initializeSsl()
702 SSL
*ssl
= Ssl::PeerConnector::initializeSsl();
706 assert(!icapService
->cfg().secure
.sslDomain
.isEmpty());
707 SBuf
*host
= new SBuf(icapService
->cfg().secure
.sslDomain
);
708 SSL_set_ex_data(ssl
, ssl_ex_index_server
, host
);
710 ACLFilledChecklist
*check
= (ACLFilledChecklist
*)SSL_get_ex_data(ssl
, ssl_ex_index_cert_error_check
);
712 check
->dst_peer_name
= *host
;
714 if (icapService
->sslSession
)
715 SSL_set_session(ssl
, icapService
->sslSession
);
721 Ssl::IcapPeerConnector::noteNegotiationDone(ErrorState
*error
)
726 const int fd
= serverConnection()->fd
;
727 SSL
*ssl
= fd_table
[fd
].ssl
;
729 if (!SSL_session_reused(ssl
)) {
730 if (icapService
->sslSession
)
731 SSL_SESSION_free(icapService
->sslSession
);
732 icapService
->sslSession
= SSL_get1_session(ssl
);
737 Adaptation::Icap::Xaction::handleSecuredPeer(Security::EncryptorAnswer
&answer
)
739 Must(securer
!= NULL
);
742 if (closer
!= NULL
) {
743 if (answer
.conn
!= NULL
)
744 comm_remove_close_handler(answer
.conn
->fd
, closer
);
746 closer
->cancel("securing completed");
750 if (answer
.error
.get()) {
751 if (answer
.conn
!= NULL
)
752 answer
.conn
->close();
753 debugs(93, 2, typeName
<<
754 " SSL negotiation to " << service().cfg().uri
<< " failed");
755 service().noteConnectionFailed("failure");
756 detailError(ERR_DETAIL_ICAP_XACT_SSL_START
);
757 throw TexcHere("cannot connect to the SSL ICAP service");
760 debugs(93, 5, "SSL negotiation to " << service().cfg().uri
<< " complete");
762 service().noteConnectionUse(answer
.conn
);
764 handleCommConnected();