/* Web Polygraph http://www.web-polygraph.org/
* (C) 2003-2006 The Measurement Factory
* Licensed under the Apache License, Version 2.0 */
#include "base/polygraph.h"
#include "xstd/h/iomanip.h"
#include "xstd/Rnd.h"
#include "xstd/Ssl.h"
#include "base/RndPermut.h"
#include "base/polyLogCats.h"
#include "runtime/globals.h"
#include "runtime/AddrSubsts.h"
#include "runtime/HostMap.h"
#include "runtime/SslWrap.h"
#include "pgl/RobotSym.h"
#include "client/CltConnMgr.h"
CltConnMgr::CltConnMgr(): thePortMgr(0), thePipeDepth(0),
theSslResumpProb(-1), theSslCacheLimit(-1),
theMinNewConnProb(-1), theConnLvlLmt(-1) {
}
CltConnMgr::~CltConnMgr() {
closeAllIdle();
while (theSslSessionCache.count()) delete theSslSessionCache.pop();
}
void CltConnMgr::configure(const SockOpt &anOpt, const RobotSym *cfg, int srvCnt) {
ConnMgr::configure(anOpt, cfg->pconnUseLmt());
cfg->openConnLimit(theConnLvlLmt);
cfg->minimizeNewConn(theMinNewConnProb);
if (theMinNewConnProb > 0 && !TheAddrSubsts->count()) {
cerr << cfg->loc()
<< "Robot's minimize_new_conn must be used with "
<< "address substitutes to make sense " << endl << xexit;
}
thePipeDepth = cfg->pipelineDepth();
// determine the maximum number of addresses (for concurrent idle conns)
const int max = theConnLvlLmt >= 0 ? Min(theConnLvlLmt, srvCnt) : srvCnt;
theIdleHash.ccAddrMax(max);
}
void CltConnMgr::portMgr(PortMgr *aPortMgr) {
Assert(aPortMgr && !thePortMgr);
thePortMgr = aPortMgr;
}
void CltConnMgr::configureSsl(SslCtx *aCtx, const SslWrap *wrap) {
ConnMgr::configureSsl(aCtx, wrap);
if (!theSslCtx)
return;
// we maintain our own cache, if any
aCtx->sessionCacheMode(SSL_SESS_CACHE_OFF);
theSslResumpProb = wrap->resumpProb();
theSslCacheLimit = wrap->sessionCacheSize();
}
// note: idle connections can be closed on-demand
bool CltConnMgr::atHardConnLimit() const {
return theConnLvlLmt >= 0 && (theConnLvl-theIdleQueue.count()) >= theConnLvlLmt;
}
Connection *CltConnMgr::get(const NetAddr &hopAddr, const NetAddr &destAddr) {
static const NetAddr none;
Connection *conn = 0;
bool needsTunnel = false;
bool needsSsl = needSsl(hopAddr, destAddr, needsTunnel);
const NetAddr &tunnelAddr = needsTunnel ? destAddr : none;
ConnHashPos pos;
if (findIdle(hopAddr, tunnelAddr, pos)) {
conn = theIdleHash.delAt(pos);
theIdleQueue.dequeue(conn);
conn->theRd.stop(this);
TheFileScanner->clearTimeout(conn->sock().fd());
} else {
// check if we should close some idle conn first
if (theConnLvlLmt >= 0 && theConnLvl >= theConnLvlLmt)
closeIdle(theIdleQueue.firstOut(), ConnCloseStat::ckIdleLocal);
conn = open(hopAddr, needsSsl);
if (needsTunnel)
conn->tunnelEnd(tunnelAddr);
}
if (conn)
conn->startUse();
Assert(theConnLvlLmt < 0 || theConnLvl <= theConnLvlLmt);
Assert(!conn || (conn->sock() && !conn->bad()));
return conn;
}
bool CltConnMgr::findIdle(const NetAddr &hopAddr, const NetAddr &tunnelAddr, ConnHashPos &pos) {
return theIdleHash.find(hopAddr, tunnelAddr, pos) ||
findIdleSubst(hopAddr, tunnelAddr, pos);
}
void CltConnMgr::closeAllIdle() {
while (theIdleQueue.count())
closeIdle(theIdleQueue.firstOut(), ConnCloseStat::ckIdleLocal);
}
Connection *CltConnMgr::open(const NetAddr &hopAddr, bool needsSsl) {
Connection *conn = TheConnFarm.get();
conn->logCat(lgcCltSide);
if (needsSsl) {
SslSession *sess = 0;
static RndGen rng(LclPermut(rndSslSessionCache));
if (theSslSessionCache.count() && rng.event(theSslResumpProb))
sess = theSslSessionCache.pop();
conn->useSsl(theSslCtx, sess); // eventually
}
if (conn->connect(hopAddr, theSockOpt, thePortMgr)) {
int limit = 1; // no pipeling by default
if (thePipeDepth)
limit = (int)MiniMax(1.1, thePipeDepth->trial(), (double)INT_MAX);
conn->useLevelLimit(limit);
opened(conn);
return conn;
} else {
// connect failed
TheConnFarm.put(conn);
return 0;
}
}
bool CltConnMgr::needSsl(const NetAddr &hopAddr, const NetAddr &tunnelAddr, bool &needTunnel) const {
// need SSL encryption if both source and at least one of the distinations
// (hop or tunnel) need it
if (!theSslCtx)
return false; // source cannot do SSL
if (TheHostMap->findSslWrap(hopAddr))
needTunnel = false; // the next hop does SSL
else
if (TheHostMap->findSslWrap(tunnelAddr))
needTunnel = hopAddr != tunnelAddr; // hop does not do SSL, final does
else
return false; // neither hop nor final do SSL
return true;
}
// called on an idle connection closed by the other side
void CltConnMgr::noteReadReady(int fd) {
Connection *conn = TheConnIdx[fd];
Assert(conn && conn->sock() && !conn->bad());
if (conn->closing()) {
ConnMgr::noteReadReady(fd);
} else {
// delete connection from the idle queue
closeIdle(conn, ConnCloseStat::ckIdleForeign);
}
}
bool CltConnMgr::findIdleSubst(const NetAddr &hopAddr, const NetAddr &tunnelAddr, ConnHashPos &pos) {
static RndGen rng;
if (!rng.event(theMinNewConnProb))
return false;
Assert(TheAddrSubsts);
for (AddrSubsts::Iterator i = TheAddrSubsts->iterator(hopAddr); i; ++i) {
if (theIdleHash.find(i.addr(), tunnelAddr, pos))
return true;
}
return false;
}
void CltConnMgr::putIdle(Connection *conn) {
Assert(conn && conn->sock() && !conn->bad());
theIdleQueue.enqueue(conn);
theIdleHash.add(conn);
ConnMgr::putIdle(conn);
}
void CltConnMgr::delIdle(Connection *conn) {
Assert(conn);
theIdleQueue.dequeue(conn);
theIdleHash.del(conn);
conn->theRd.stop(this);
TheFileScanner->clearTimeout(conn->sock().fd());
}
void CltConnMgr::closePrep(Connection *conn) {
if (const Ssl *ssl = conn->sslActive()) {
SslSession *sess = conn->sslSession();
Should(!sess || theSslResumpProb > 0);
if (sess && (conn->bad() || !ssl->reusedSession())) {
// delete sessions of bad connections
delete sess;
sess = 0;
conn->sslSessionForget();
} else
if (!sess && !conn->bad() && needMoreSslSessions()) {
// remember sessions of new successful connections
sess = ssl->refCountedSession();
}
if (sess)
theSslSessionCache.push(sess);
}
ConnMgr::closePrep(conn);
}
bool CltConnMgr::needMoreSslSessions() const {
return theSslResumpProb > 0 && (theSslCacheLimit < 0 ||
theSslSessionCache.count() < theSslCacheLimit);
}
syntax highlighted by Code2HTML, v. 0.9.1