HttpRequest::Pointer requestPointer = request;
AsyncCall::Pointer callback = asyncCall(17,4,
- "FwdState::ConnectedToPeer",
- FwdStatePeerAnswerDialer(&FwdState::connectedToPeer, this));
+ "FwdState::ConnectedToPeer",
+ FwdStatePeerAnswerDialer(&FwdState::connectedToPeer, this));
Ssl::PeerConnector *connector =
- new Ssl::PeerConnector(requestPointer, serverConnection(), callback);
+ new Ssl::PeerConnector(requestPointer, serverConnection(), clientConn, callback);
AsyncJob::Start(connector); // will call our callback
return;
}
--- /dev/null
- new Ssl::PeerConnector(request, params.conn, securer);
+ #include "squid.h"
+ #include "base/AsyncJobCalls.h"
+ #include "base/RunnersRegistry.h"
+ #include "CachePeer.h"
+ #include "comm/Connection.h"
+ #include "comm/ConnOpener.h"
+ #include "Debug.h"
+ #include "fd.h"
+ #include "FwdState.h"
+ #include "globals.h"
+ #include "HttpRequest.h"
+ #include "neighbors.h"
+ #include "pconn.h"
+ #include "PeerPoolMgr.h"
+ #include "SquidConfig.h"
+ #if USE_OPENSSL
+ #include "ssl/PeerConnector.h"
+ #endif
+
+ CBDATA_CLASS_INIT(PeerPoolMgr);
+
+ #if USE_OPENSSL
+ /// Gives Ssl::PeerConnector access to Answer in the PeerPoolMgr callback dialer.
+ class MyAnswerDialer: public UnaryMemFunT<PeerPoolMgr, Ssl::PeerConnectorAnswer, Ssl::PeerConnectorAnswer&>,
+ public Ssl::PeerConnector::CbDialer
+ {
+ public:
+ MyAnswerDialer(const JobPointer &aJob, Method aMethod):
+ UnaryMemFunT<PeerPoolMgr, Ssl::PeerConnectorAnswer, Ssl::PeerConnectorAnswer&>(aJob, aMethod, Ssl::PeerConnectorAnswer()) {}
+
+ /* Ssl::PeerConnector::CbDialer API */
+ virtual Ssl::PeerConnectorAnswer &answer() { return arg1; }
+ };
+ #endif
+
+ PeerPoolMgr::PeerPoolMgr(CachePeer *aPeer): AsyncJob("PeerPoolMgr"),
+ peer(cbdataReference(aPeer)),
+ request(),
+ opener(),
+ securer(),
+ closer(),
+ addrUsed(0)
+ {
+ }
+
+ PeerPoolMgr::~PeerPoolMgr()
+ {
+ cbdataReferenceDone(peer);
+ }
+
+ void
+ PeerPoolMgr::start()
+ {
+ AsyncJob::start();
+
+ // ErrorState, getOutgoingAddress(), and other APIs may require a request.
+ // We fake one. TODO: Optionally send this request to peers?
+ request = new HttpRequest(Http::METHOD_OPTIONS, AnyP::PROTO_HTTP, "*");
+ request->SetHost(peer->host);
+
+ checkpoint("peer initialized");
+ }
+
+ void
+ PeerPoolMgr::swanSong()
+ {
+ AsyncJob::swanSong();
+ }
+
+ bool
+ PeerPoolMgr::validPeer() const
+ {
+ return peer && cbdataReferenceValid(peer) && peer->standby.pool;
+ }
+
+ bool
+ PeerPoolMgr::doneAll() const
+ {
+ return !(validPeer() && peer->standby.limit) && AsyncJob::doneAll();
+ }
+
+ void
+ PeerPoolMgr::handleOpenedConnection(const CommConnectCbParams ¶ms)
+ {
+ opener = NULL;
+
+ if (!validPeer()) {
+ debugs(48, 3, "peer gone");
+ if (params.conn != NULL)
+ params.conn->close();
+ return;
+ }
+
+ if (params.flag != COMM_OK) {
+ /* it might have been a timeout with a partially open link */
+ if (params.conn != NULL)
+ params.conn->close();
+ peerConnectFailed(peer);
+ checkpoint("conn opening failure"); // may retry
+ return;
+ }
+
+ Must(params.conn != NULL);
+
+ #if USE_OPENSSL
+ // Handle SSL peers.
+ if (peer->use_ssl) {
+ typedef CommCbMemFunT<PeerPoolMgr, CommCloseCbParams> CloserDialer;
+ closer = JobCallback(48, 3, CloserDialer, this,
+ PeerPoolMgr::handleSecureClosure);
+ comm_add_close_handler(params.conn->fd, closer);
+
+ securer = asyncCall(48, 4, "PeerPoolMgr::handleSecuredPeer",
+ MyAnswerDialer(this, &PeerPoolMgr::handleSecuredPeer));
+ Ssl::PeerConnector *connector =
++ new Ssl::PeerConnector(request, params.conn, NULL, securer);
+ AsyncJob::Start(connector); // will call our callback
+ return;
+ }
+ #endif
+
+ pushNewConnection(params.conn);
+ }
+
+ void
+ PeerPoolMgr::pushNewConnection(const Comm::ConnectionPointer &conn)
+ {
+ Must(validPeer());
+ Must(Comm::IsConnOpen(conn));
+ peer->standby.pool->push(conn, NULL /* domain */);
+ // push() will trigger a checkpoint()
+ }
+
+ #if USE_OPENSSL
+ void
+ PeerPoolMgr::handleSecuredPeer(Ssl::PeerConnectorAnswer &answer)
+ {
+ Must(securer != NULL);
+ securer = NULL;
+
+ if (closer != NULL) {
+ if (answer.conn != NULL)
+ comm_remove_close_handler(answer.conn->fd, closer);
+ else
+ closer->cancel("securing completed");
+ closer = NULL;
+ }
+
+ if (!validPeer()) {
+ debugs(48, 3, "peer gone");
+ if (answer.conn != NULL)
+ answer.conn->close();
+ return;
+ }
+
+ if (answer.error.get()) {
+ if (answer.conn != NULL)
+ answer.conn->close();
+ // PeerConnector calls peerConnectFailed() for us;
+ checkpoint("conn securing failure"); // may retry
+ return;
+ }
+
+ pushNewConnection(answer.conn);
+ }
+
+ void
+ PeerPoolMgr::handleSecureClosure(const CommCloseCbParams ¶ms)
+ {
+ Must(closer != NULL);
+ Must(securer != NULL);
+ securer->cancel("conn closed by a 3rd party");
+ securer = NULL;
+ closer = NULL;
+ // allow the closing connection to fully close before we check again
+ Checkpoint(this, "conn closure while securing");
+ }
+ #endif
+
+ void
+ PeerPoolMgr::openNewConnection()
+ {
+ // KISS: Do nothing else when we are already doing something.
+ if (opener != NULL || securer != NULL || shutting_down) {
+ debugs(48, 7, "busy: " << opener << '|' << securer << '|' << shutting_down);
+ return; // there will be another checkpoint when we are done opening/securing
+ }
+
+ // Do not talk to a peer until it is ready.
+ if (!neighborUp(peer)) // provides debugging
+ return; // there will be another checkpoint when peer is up
+
+ // Do not violate peer limits.
+ if (!peerCanOpenMore(peer)) { // provides debugging
+ peer->standby.waitingForClose = true; // may already be true
+ return; // there will be another checkpoint when a peer conn closes
+ }
+
+ // Do not violate global restrictions.
+ if (fdUsageHigh()) {
+ debugs(48, 7, "overwhelmed");
+ peer->standby.waitingForClose = true; // may already be true
+ // There will be another checkpoint when a peer conn closes OR when
+ // a future pop() fails due to an empty pool. See PconnPool::pop().
+ return;
+ }
+
+ peer->standby.waitingForClose = false;
+
+ Comm::ConnectionPointer conn = new Comm::Connection;
+ Must(peer->n_addresses); // guaranteed by neighborUp() above
+ // cycle through all available IP addresses
+ conn->remote = peer->addresses[addrUsed++ % peer->n_addresses];
+ conn->remote.port(peer->http_port);
+ conn->peerType = STANDBY_POOL; // should be reset by peerSelect()
+ conn->setPeer(peer);
+ getOutgoingAddress(request.getRaw(), conn);
+ GetMarkingsToServer(request.getRaw(), *conn);
+
+ const int ctimeout = peer->connect_timeout > 0 ?
+ peer->connect_timeout : Config.Timeout.peer_connect;
+ typedef CommCbMemFunT<PeerPoolMgr, CommConnectCbParams> Dialer;
+ opener = JobCallback(48, 5, Dialer, this, PeerPoolMgr::handleOpenedConnection);
+ Comm::ConnOpener *cs = new Comm::ConnOpener(conn, opener, ctimeout);
+ AsyncJob::Start(cs);
+ }
+
+ void
+ PeerPoolMgr::closeOldConnections(const int howMany)
+ {
+ debugs(48, 8, howMany);
+ peer->standby.pool->closeN(howMany);
+ }
+
+ void
+ PeerPoolMgr::checkpoint(const char *reason)
+ {
+ if (!validPeer()) {
+ debugs(48, 3, reason << " and peer gone");
+ return; // nothing to do after our owner dies; the job will quit
+ }
+
+ const int count = peer->standby.pool->count();
+ const int limit = peer->standby.limit;
+ debugs(48, 7, reason << " with " << count << " ? " << limit);
+
+ if (count < limit)
+ openNewConnection();
+ else if (count > limit)
+ closeOldConnections(count - limit);
+ }
+
+ void
+ PeerPoolMgr::Checkpoint(const Pointer &mgr, const char *reason)
+ {
+ CallJobHere1(48, 5, mgr, PeerPoolMgr, checkpoint, reason);
+ }
+
+ /// launches PeerPoolMgrs for peers configured with standby.limit
+ class PeerPoolMgrsRr: public RegisteredRunner
+ {
+ public:
+ /* RegisteredRunner API */
+ virtual void useConfig() { syncConfig(); }
+ virtual void syncConfig();
+ };
+
+ RunnerRegistrationEntry(PeerPoolMgrsRr);
+
+ void
+ PeerPoolMgrsRr::syncConfig()
+ {
+ for (CachePeer *p = Config.peers; p; p = p->next) {
+ // On reconfigure, Squid deletes the old config (and old peers in it),
+ // so should always be dealing with a brand new configuration.
+ assert(!p->standby.mgr);
+ assert(!p->standby.pool);
+ if (p->standby.limit) {
+ p->standby.mgr = new PeerPoolMgr(p);
+ p->standby.pool = new PconnPool(p->name, p->standby.mgr);
+ AsyncJob::Start(p->standby.mgr.get());
+ }
+ }
+ }
Ssl::PeerConnector::PeerConnector(
HttpRequestPointer &aRequest,
const Comm::ConnectionPointer &aServerConn,
+ const Comm::ConnectionPointer &aClientConn,
AsyncCall::Pointer &aCallback):
- AsyncJob("Ssl::PeerConnector"),
- request(aRequest),
- serverConn(aServerConn),
- clientConn(aClientConn),
- callback(aCallback)
+ AsyncJob("Ssl::PeerConnector"),
+ request(aRequest),
+ serverConn(aServerConn),
++ clientConn(aClientConn),
+ callback(aCallback)
{
// if this throws, the caller's cb dialer is not our CbDialer
Must(dynamic_cast<CbDialer*>(callback->getDialer()));
if (peer->sslSession)
SSL_set_session(ssl, peer->sslSession);
+ } else if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) {
++ // client connection is required for Peek or Stare mode in the case we need to splice
++ // or terminate client and server connections
++ assert(clientConn != NULL);
+ SSL *clientSsl = fd_table[request->clientConnectionManager->clientConnection->fd].ssl;
+ BIO *b = SSL_get_rbio(clientSsl);
+ Ssl::ClientBio *clnBio = static_cast<Ssl::ClientBio *>(b->ptr);
+ const Ssl::Bio::sslFeatures &features = clnBio->getFeatures();
+ if (features.sslVersion != -1) {
+ SSL_set_ssl_method(ssl, Ssl::method(features.toSquidSSLVersion()));
+ if (!features.serverName.empty())
+ SSL_set_tlsext_host_name(ssl, features.serverName.c_str());
+ if (!features.clientRequestedCiphers.empty())
+ SSL_set_cipher_list(ssl, features.clientRequestedCiphers.c_str());
+#ifdef SSL_OP_NO_COMPRESSION /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */
+ if (features.compressMethod == 0)
+ SSL_set_options(ssl, SSL_OP_NO_COMPRESSION);
+#endif
+ // Should we allow it for all protocols?
+ if (features.sslVersion >= 3) {
+ b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ srvBio->setClientFeatures(features);
+ srvBio->recordInput(true);
+ srvBio->mode(request->clientConnectionManager->sslBumpMode);
+ }
+ }
} else {
// While we are peeking at the certificate, we may not know the server
// name that the client will request (after interception or CONNECT)
unsigned long ssl_lib_error = SSL_ERROR_NONE;
SSL *ssl = fd_table[fd].ssl;
int ssl_error = SSL_get_error(ssl, ret);
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
#ifdef EPROTO
- int sysErrNo = EPROTO;
+ int sysErrNo = EPROTO;
#else
- int sysErrNo = EACCES;
+ int sysErrNo = EACCES;
#endif
- switch (ssl_error) {
+ switch (ssl_error) {
- case SSL_ERROR_WANT_READ:
- Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
- return;
+ case SSL_ERROR_WANT_READ:
+ Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
+ return;
- case SSL_ERROR_WANT_WRITE:
- if ((request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) && srvBio->holdWrite()) {
- debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
- checkForPeekAndSplice(false, Ssl::bumpNone);
- return;
- }
- Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
+ case SSL_ERROR_WANT_WRITE:
++ if ((request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) && srvBio->holdWrite()) {
++ debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
++ checkForPeekAndSplice(false, Ssl::bumpNone);
+ return;
++ }
+ Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
+ return;
- case SSL_ERROR_SSL:
- case SSL_ERROR_SYSCALL:
- ssl_lib_error = ERR_get_error();
+ case SSL_ERROR_SSL:
+ case SSL_ERROR_SYSCALL:
+ ssl_lib_error = ERR_get_error();
- // If we are in peek-and-splice mode and still we did not write to
- // server yet, try to see if we should splice.
- // In this case the connection can be saved.
- // If the checklist decision is do not splice a new error will
- // occure in the next SSL_connect call, and we will fail again.
++ // If we are in peek-and-splice mode and still we did not write to
++ // server yet, try to see if we should splice.
++ // In this case the connection can be saved.
++ // If the checklist decision is do not splice a new error will
++ // occure in the next SSL_connect call, and we will fail again.
+#if 1
- if ((request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) && srvBio->holdWrite()) {
- debugs(81, DBG_IMPORTANT, "fwdNegotiateSSL: Error but, hold write on SSL connection on FD " << fd);
- checkForPeekAndSplice(false, Ssl::bumpNone);
- return;
- }
++ if ((request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) && srvBio->holdWrite()) {
++ debugs(81, DBG_IMPORTANT, "fwdNegotiateSSL: Error but, hold write on SSL connection on FD " << fd);
++ checkForPeekAndSplice(false, Ssl::bumpNone);
++ return;
++ }
+#endif
+
- // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1
- if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0)
- sysErrNo = errno;
+ // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1
+ if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0)
+ sysErrNo = errno;
- debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
- ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
- ssl_error << "/" << ret << "/" << errno << ")");
+ debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
+ ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
+ ssl_error << "/" << ret << "/" << errno << ")");
- break; // proceed to the general error handling code
+ break; // proceed to the general error handling code
- default:
- break; // no special error handling for all other errors
- }
+ default:
+ break; // no special error handling for all other errors
+ }
ErrorState *const anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
anErr->xerrno = sysErrNo;
#ifndef SQUID_SSL_PEER_CONNECTOR_H
#define SQUID_SSL_PEER_CONNECTOR_H
- #include "base/AsyncJob.h"
++#include "acl/Acl.h"
#include "base/AsyncCbdataCalls.h"
+ #include "base/AsyncJob.h"
#include "ssl/support.h"
#include <iosfwd>
}
void
- TunnelStateData::connectToPeer() {
+ TunnelStateData::connectToPeer()
+ {
const Comm::ConnectionPointer &srv = server.conn;
+ const Comm::ConnectionPointer &cln = client.conn;
#if USE_OPENSSL
if (CachePeer *p = srv->getPeer()) {
if (p->use_ssl) {
AsyncCall::Pointer callback = asyncCall(5,4,
- "TunnelStateData::ConnectedToPeer",
- MyAnswerDialer(&TunnelStateData::connectedToPeer, this));
+ "TunnelStateData::ConnectedToPeer",
+ MyAnswerDialer(&TunnelStateData::connectedToPeer, this));
Ssl::PeerConnector *connector =
- new Ssl::PeerConnector(request, srv, callback);
+ new Ssl::PeerConnector(request, srv, cln, callback);
AsyncJob::Start(connector); // will call our callback
return;
}