/*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "comm/Read.h"
#include "comm/Write.h"
#include "errorpage.h"
+#include "fd.h"
#include "fde.h"
#include "FwdState.h"
#include "globals.h"
#include "http.h"
+#include "http/Stream.h"
#include "HttpRequest.h"
-#include "HttpStateFlags.h"
#include "ip/QosConfig.h"
#include "LogTags.h"
#include "MemBuf.h"
+#include "neighbors.h"
#include "PeerSelectState.h"
-#include "SBuf.h"
+#include "sbuf/SBuf.h"
+#include "security/BlindPeerConnector.h"
#include "SquidConfig.h"
#include "SquidTime.h"
#include "StatCounters.h"
#if USE_OPENSSL
#include "ssl/bio.h"
-#include "ssl/PeerConnector.h"
#include "ssl/ServerBump.h"
-#else
-#include "security/EncryptorAnswer.h"
#endif
#include "tools.h"
#if USE_DELAY_POOLS
*
* TODO 2: then convert this into a AsyncJob, possibly a child of 'Server'
*/
-class TunnelStateData
+class TunnelStateData: public PeerSelectionInitiator
{
- CBDATA_CLASS(TunnelStateData);
+ CBDATA_CHILD(TunnelStateData);
public:
- TunnelStateData();
- ~TunnelStateData();
+ TunnelStateData(ClientHttpRequest *);
+ virtual ~TunnelStateData();
TunnelStateData(const TunnelStateData &); // do not implement
TunnelStateData &operator =(const TunnelStateData &); // do not implement
Comm::ConnectionList serverDestinations;
const char * getHost() const {
- return (server.conn != NULL && server.conn->getPeer() ? server.conn->getPeer()->host : request->GetHost());
+ return (server.conn != NULL && server.conn->getPeer() ? server.conn->getPeer()->host : request->url.host());
};
/// Whether we are writing a CONNECT request to a peer.
/// Whether the client sent a CONNECT request to us.
bool clientExpectsConnectResponse() const {
+ // If we are forcing a tunnel after receiving a client CONNECT, then we
+ // have already responded to that CONNECT before tunnel.cc started.
+ if (request && request->flags.forceTunnel)
+ return false;
#if USE_OPENSSL
// We are bumping and we had already send "OK CONNECTED"
if (http.valid() && http->getConn() && http->getConn()->serverBump() && http->getConn()->serverBump()->step > Ssl::bumpStep1)
(request->flags.interceptTproxy || request->flags.intercepted));
}
+ /// Sends "502 Bad Gateway" error response to the client,
+ /// if it is waiting for Squid CONNECT response, closing connections.
+ void informUserOfPeerError(const char *errMsg, size_t);
+
+ /// starts connecting to the next hop, either for the first time or while
+ /// recovering from the previous connect failure
+ void startConnecting();
+
+ void noteConnectFailure(const Comm::ConnectionPointer &conn);
+
class Connection
{
void error(int const xerrno);
int debugLevelForError(int const xerrno) const;
- /// handles a non-I/O error associated with this Connection
- void logicError(const char *errMsg);
void closeIfOpen();
void dataSent (size_t amount);
+ /// writes 'b' buffer, setting the 'writer' member to 'callback'.
+ void write(const char *b, int size, AsyncCall::Pointer &callback, FREE * free_func);
int len;
char *buf;
- int64_t *size_ptr; /* pointer to size in an ConnStateData for logging */
+ AsyncCall::Pointer writer; ///< pending Comm::Write callback
+ uint64_t *size_ptr; /* pointer to size in an ConnStateData for logging */
Comm::ConnectionPointer conn; ///< The currently connected connection.
uint8_t delayedLoops; ///< how many times a read on this connection has been postponed.
MemBuf *connectRespBuf; ///< accumulates peer CONNECT response when we need it
bool connectReqWriting; ///< whether we are writing a CONNECT request to a peer
SBuf preReadClientData;
+ SBuf preReadServerData;
+ time_t startTime; ///< object creation time, before any peer selection/connection attempts
void copyRead(Connection &from, IOCB *completion);
/// continue to set up connection to a peer, going async for SSL peers
void connectToPeer();
+ /* PeerSelectionInitiator API */
+ virtual void noteDestination(Comm::ConnectionPointer conn) override;
+ virtual void noteDestinationsEnd(ErrorState *selectionError) override;
+
+ void saveError(ErrorState *finalError);
+ void sendError(ErrorState *finalError, const char *reason);
+
private:
-#if USE_OPENSSL
- /// Gives PeerConnector access to Answer in the TunnelStateData callback dialer.
- class MyAnswerDialer: public CallDialer, public Ssl::PeerConnector::CbDialer
+ /// Gives Security::PeerConnector access to Answer in the TunnelStateData callback dialer.
+ class MyAnswerDialer: public CallDialer, public Security::PeerConnector::CbDialer
{
public:
typedef void (TunnelStateData::*Method)(Security::EncryptorAnswer &);
os << '(' << tunnel_.get() << ", " << answer_ << ')';
}
- /* Ssl::PeerConnector::CbDialer API */
+ /* Security::PeerConnector::CbDialer API */
virtual Security::EncryptorAnswer &answer() { return answer_; }
private:
CbcPointer<TunnelStateData> tunnel_;
Security::EncryptorAnswer answer_;
};
-#endif
/// callback handler after connection setup (including any encryption)
void connectedToPeer(Security::EncryptorAnswer &answer);
+ /// details of the "last tunneling attempt" failure (if it failed)
+ ErrorState *savedError = nullptr;
+
public:
bool keepGoingAfterRead(size_t len, Comm::Flag errcode, int xerrno, Connection &from, Connection &to);
void copy(size_t len, Connection &from, Connection &to, IOCB *);
static void ReadConnectResponseDone(const Comm::ConnectionPointer &, char *buf, size_t len, Comm::Flag errcode, int xerrno, void *data);
void readConnectResponseDone(char *buf, size_t len, Comm::Flag errcode, int xerrno);
void copyClientBytes();
+ void copyServerBytes();
};
static const char *const conn_established = "HTTP/1.1 200 Connection established\r\n\r\n";
static CLCB tunnelServerClosed;
static CLCB tunnelClientClosed;
static CTCB tunnelTimeout;
-static PSC tunnelPeerSelectComplete;
static EVH tunnelDelayedClientRead;
static EVH tunnelDelayedServerRead;
static void tunnelConnected(const Comm::ConnectionPointer &server, void *);
TunnelStateData *tunnelState = (TunnelStateData *)params.data;
debugs(26, 3, HERE << tunnelState->server.conn);
tunnelState->server.conn = NULL;
+ tunnelState->server.writer = NULL;
if (tunnelState->request != NULL)
tunnelState->request->hier.stopPeerClock(false);
if (tunnelState->noConnections()) {
+ // ConnStateData pipeline should contain the CONNECT we are performing
+ // but it may be invalid already (bug 4392)
+ if (tunnelState->http.valid() && tunnelState->http->getConn()) {
+ auto ctx = tunnelState->http->getConn()->pipeline.front();
+ if (ctx != nullptr)
+ ctx->finished();
+ }
delete tunnelState;
return;
}
- if (!tunnelState->server.len) {
+ if (!tunnelState->client.writer) {
tunnelState->client.conn->close();
return;
}
TunnelStateData *tunnelState = (TunnelStateData *)params.data;
debugs(26, 3, HERE << tunnelState->client.conn);
tunnelState->client.conn = NULL;
+ tunnelState->client.writer = NULL;
if (tunnelState->noConnections()) {
+ // ConnStateData pipeline should contain the CONNECT we are performing
+ // but it may be invalid already (bug 4392)
+ if (tunnelState->http.valid() && tunnelState->http->getConn()) {
+ auto ctx = tunnelState->http->getConn()->pipeline.front();
+ if (ctx != nullptr)
+ ctx->finished();
+ }
delete tunnelState;
return;
}
- if (!tunnelState->client.len) {
+ if (!tunnelState->server.writer) {
tunnelState->server.conn->close();
return;
}
}
-TunnelStateData::TunnelStateData() :
- url(NULL),
- http(),
- request(NULL),
- status_ptr(NULL),
- logTag_ptr(NULL),
+TunnelStateData::TunnelStateData(ClientHttpRequest *clientRequest) :
connectRespBuf(NULL),
- connectReqWriting(false)
+ connectReqWriting(false),
+ startTime(squid_curtime)
{
debugs(26, 3, "TunnelStateData constructed this=" << this);
client.readPendingFunc = &tunnelDelayedClientRead;
server.readPendingFunc = &tunnelDelayedServerRead;
+
+ assert(clientRequest);
+ url = xstrdup(clientRequest->uri);
+ request = clientRequest->request;
+ server.size_ptr = &clientRequest->out.size;
+ client.size_ptr = &clientRequest->al->http.clientRequestSz.payloadData;
+ status_ptr = &clientRequest->al->http.code;
+ logTag_ptr = &clientRequest->logType;
+ al = clientRequest->al;
+ http = clientRequest;
+
+ client.conn = clientRequest->getConn()->clientConnection;
+ comm_add_close_handler(client.conn->fd, tunnelClientClosed, this);
+
+ AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
+ CommTimeoutCbPtrFun(tunnelTimeout, this));
+ commSetConnTimeout(client.conn, Config.Timeout.lifetime, timeoutCall);
}
TunnelStateData::~TunnelStateData()
xfree(url);
serverDestinations.clear();
delete connectRespBuf;
+ delete savedError;
}
TunnelStateData::Connection::~Connection()
if (len > 0) {
server.bytesIn(len);
- kb_incr(&(statCounter.server.all.kbytes_in), len);
- kb_incr(&(statCounter.server.other.kbytes_in), len);
+ statCounter.server.all.kbytes_in += len;
+ statCounter.server.other.kbytes_in += len;
+ request->hier.notePeerRead();
}
if (keepGoingAfterRead(len, errcode, xerrno, server, client))
if (len > 0) {
connectRespBuf->appended(len);
server.bytesIn(len);
- kb_incr(&(statCounter.server.all.kbytes_in), len);
- kb_incr(&(statCounter.server.other.kbytes_in), len);
+ statCounter.server.all.kbytes_in += len;
+ statCounter.server.other.kbytes_in += len;
+ request->hier.notePeerRead();
}
if (keepGoingAfterRead(len, errcode, xerrno, server, client))
handleConnectResponse(len);
}
+void
+TunnelStateData::informUserOfPeerError(const char *errMsg, const size_t sz)
+{
+ server.len = 0;
+
+ if (logTag_ptr)
+ *logTag_ptr = LOG_TCP_TUNNEL;
+
+ if (!clientExpectsConnectResponse()) {
+ // closing the connection is the best we can do here
+ debugs(50, 3, server.conn << " closing on error: " << errMsg);
+ server.conn->close();
+ return;
+ }
+
+ // if we have no reply suitable to relay, use 502 Bad Gateway
+ if (!sz || sz > static_cast<size_t>(connectRespBuf->contentSize()))
+ return sendError(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw()),
+ "peer error without reply");
+
+ // if we need to send back the server response. write its headers to the client
+ server.len = sz;
+ memcpy(server.buf, connectRespBuf->content(), server.len);
+ copy(server.len, server, client, TunnelStateData::WriteClientDone);
+ // then close the server FD to prevent any relayed keep-alive causing CVE-2015-5400
+ server.closeIfOpen();
+}
+
/* Read from client side and queue it for writing to the server */
void
TunnelStateData::ReadConnectResponseDone(const Comm::ConnectionPointer &, char *buf, size_t len, Comm::Flag errcode, int xerrno, void *data)
HttpReply rep;
Http::StatusCode parseErr = Http::scNone;
const bool eof = !chunkSize;
- connectRespBuf->terminate(); // HttpMsg::parse requires terminated string
+ connectRespBuf->terminate(); // Http::Message::parse requires terminated string
const bool parsed = rep.parse(connectRespBuf->content(), connectRespBuf->contentSize(), eof, &parseErr);
if (!parsed) {
if (parseErr > 0) { // unrecoverable parsing error
- server.logicError("malformed CONNECT response from peer");
+ informUserOfPeerError("malformed CONNECT response from peer", 0);
return;
}
assert(!parseErr);
if (!connectRespBuf->hasSpace()) {
- server.logicError("huge CONNECT response from peer");
+ informUserOfPeerError("huge CONNECT response from peer", 0);
return;
}
}
// CONNECT response was successfully parsed
- *status_ptr = rep.sline.status();
+ request->hier.peer_reply_status = rep.sline.status();
// bail if we did not get an HTTP 200 (Connection Established) response
if (rep.sline.status() != Http::scOkay) {
- server.logicError("unsupported CONNECT response status code");
+ // if we ever decide to reuse the peer connection, we must extract the error response first
+ *status_ptr = rep.sline.status(); // we are relaying peer response
+ informUserOfPeerError("unsupported CONNECT response status code", rep.hdr_sz);
return;
}
connectExchangeCheckpoint();
}
-void
-TunnelStateData::Connection::logicError(const char *errMsg)
-{
- debugs(50, 3, conn << " closing on error: " << errMsg);
- conn->close();
-}
-
void
TunnelStateData::Connection::error(int const xerrno)
{
- /* XXX fixme xstrerror and xerrno... */
- errno = xerrno;
-
- debugs(50, debugLevelForError(xerrno), HERE << conn << ": read/write failure: " << xstrerror());
+ debugs(50, debugLevelForError(xerrno), HERE << conn << ": read/write failure: " << xstrerr(xerrno));
if (!ignoreErrno(xerrno))
conn->close();
if (len > 0) {
client.bytesIn(len);
- kb_incr(&(statCounter.client_http.kbytes_in), len);
+ statCounter.client_http.kbytes_in += len;
}
if (keepGoingAfterRead(len, errcode, xerrno, client, server))
debugs(26, 3, HERE << "Schedule Write");
AsyncCall::Pointer call = commCbCall(5,5, "TunnelBlindCopyWriteHandler",
CommIoCbPtrFun(completion, this));
- Comm::Write(to.conn, from.buf, len, call, NULL);
+ to.write(from.buf, len, call, NULL);
}
/* Writes data from the client buffer to the server side */
{
TunnelStateData *tunnelState = (TunnelStateData *)data;
assert (cbdataReferenceValid (tunnelState));
+ tunnelState->server.writer = NULL;
tunnelState->writeServerDone(buf, len, flag, xerrno);
}
{
debugs(26, 3, HERE << server.conn << ", " << len << " bytes written, flag=" << flag);
+ if (flag == Comm::ERR_CLOSING)
+ return;
+
+ request->hier.notePeerWrite();
+
/* Error? */
if (flag != Comm::OK) {
- if (flag != Comm::ERR_CLOSING) {
- debugs(26, 4, HERE << "calling TunnelStateData::server.error(" << xerrno <<")");
- server.error(xerrno); // may call comm_close
- }
+ debugs(26, 4, "to-server write failed: " << xerrno);
+ server.error(xerrno); // may call comm_close
return;
}
}
/* Valid data */
- kb_incr(&(statCounter.server.all.kbytes_out), len);
- kb_incr(&(statCounter.server.other.kbytes_out), len);
+ statCounter.server.all.kbytes_out += len;
+ statCounter.server.other.kbytes_out += len;
client.dataSent(len);
/* If the other end has closed, so should we */
{
TunnelStateData *tunnelState = (TunnelStateData *)data;
assert (cbdataReferenceValid (tunnelState));
+ tunnelState->client.writer = NULL;
tunnelState->writeClientDone(buf, len, flag, xerrno);
}
if (size_ptr)
*size_ptr += amount;
+
+}
+
+void
+TunnelStateData::Connection::write(const char *b, int size, AsyncCall::Pointer &callback, FREE * free_func)
+{
+ writer = callback;
+ Comm::Write(conn, b, size, callback, free_func);
}
void
{
debugs(26, 3, HERE << client.conn << ", " << len << " bytes written, flag=" << flag);
+ if (flag == Comm::ERR_CLOSING)
+ return;
+
/* Error? */
if (flag != Comm::OK) {
- if (flag != Comm::ERR_CLOSING) {
- debugs(26, 4, HERE << "Closing client connection due to comm flags.");
- client.error(xerrno); // may call comm_close
- }
+ debugs(26, 4, "from-client read failed: " << xerrno);
+ client.error(xerrno); // may call comm_close
return;
}
}
/* Valid data */
- kb_incr(&(statCounter.client_http.kbytes_out), len);
+ statCounter.client_http.kbytes_out += len;
server.dataSent(len);
/* If the other end has closed, so should we */
CbcPointer<TunnelStateData> safetyLock(this); /* ??? should be locked by the caller... */
if (cbdataReferenceValid(this))
- copyRead(server, ReadServer);
+ copyServerBytes();
}
static void
copyRead(client, ReadClient);
}
+void
+TunnelStateData::copyServerBytes()
+{
+ if (preReadServerData.length()) {
+ size_t copyBytes = preReadServerData.length() > SQUID_TCP_SO_RCVBUF ? SQUID_TCP_SO_RCVBUF : preReadServerData.length();
+ memcpy(server.buf, preReadServerData.rawContent(), copyBytes);
+ preReadServerData.consume(copyBytes);
+ server.bytesIn(copyBytes);
+ if (keepGoingAfterRead(copyBytes, Comm::OK, 0, server, client))
+ copy(copyBytes, server, client, TunnelStateData::WriteClientDone);
+ } else
+ copyRead(server, ReadServer);
+}
+
/**
* Set the HTTP status for this request and sets the read handlers for client
* and server side connections.
// Shovel any payload already pushed into reply buffer by the server response
if (!tunnelState->server.len)
- tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer);
+ tunnelState->copyServerBytes();
else {
debugs(26, DBG_DATA, "Tunnel server PUSH Payload: \n" << Raw("", tunnelState->server.buf, tunnelState->server.len) << "\n----------");
tunnelState->copy(tunnelState->server.len, tunnelState->server, tunnelState->client, TunnelStateData::WriteClientDone);
}
- if (tunnelState->http.valid() && tunnelState->http->getConn() && !tunnelState->http->getConn()->in.buf.isEmpty()) {
- struct ConnStateData::In *in = &tunnelState->http->getConn()->in;
- debugs(26, DBG_DATA, "Tunnel client PUSH Payload: \n" << in->buf << "\n----------");
- tunnelState->preReadClientData.append(in->buf);
- in->buf.consume(); // ConnStateData buffer accounting after the shuffle.
+ if (tunnelState->http.valid() && tunnelState->http->getConn() && !tunnelState->http->getConn()->inBuf.isEmpty()) {
+ SBuf * const in = &tunnelState->http->getConn()->inBuf;
+ debugs(26, DBG_DATA, "Tunnel client PUSH Payload: \n" << *in << "\n----------");
+ tunnelState->preReadClientData.append(*in);
+ in->consume(); // ConnStateData buffer accounting after the shuffle.
}
tunnelState->copyClientBytes();
}
* Call the tunnelStartShoveling to start the blind pump.
*/
static void
-tunnelConnectedWriteDone(const Comm::ConnectionPointer &conn, char *, size_t, Comm::Flag flag, int, void *data)
+tunnelConnectedWriteDone(const Comm::ConnectionPointer &conn, char *, size_t len, Comm::Flag flag, int, void *data)
{
TunnelStateData *tunnelState = (TunnelStateData *)data;
debugs(26, 3, HERE << conn << ", flag=" << flag);
+ tunnelState->client.writer = NULL;
if (flag != Comm::OK) {
*tunnelState->status_ptr = Http::scInternalServerError;
return;
}
+ if (auto http = tunnelState->http.get()) {
+ http->out.headers_sz += len;
+ http->out.size += len;
+ }
+
tunnelStartShoveling(tunnelState);
}
/// Called when we are done writing CONNECT request to a peer.
static void
-tunnelConnectReqWriteDone(const Comm::ConnectionPointer &conn, char *, size_t, Comm::Flag flag, int, void *data)
+tunnelConnectReqWriteDone(const Comm::ConnectionPointer &conn, char *, size_t ioSize, Comm::Flag flag, int, void *data)
{
TunnelStateData *tunnelState = (TunnelStateData *)data;
debugs(26, 3, conn << ", flag=" << flag);
+ tunnelState->server.writer = NULL;
assert(tunnelState->waitingForConnectRequest());
+ tunnelState->request->hier.notePeerWrite();
+
if (flag != Comm::OK) {
*tunnelState->status_ptr = Http::scInternalServerError;
tunnelErrorComplete(conn->fd, data, 0);
return;
}
+ statCounter.server.all.kbytes_out += ioSize;
+ statCounter.server.other.kbytes_out += ioSize;
+
tunnelState->connectReqWriting = false;
tunnelState->connectExchangeCheckpoint();
}
if (!tunnelState->clientExpectsConnectResponse())
tunnelStartShoveling(tunnelState); // ssl-bumped connection, be quiet
else {
+ *tunnelState->status_ptr = Http::scOkay;
AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
- Comm::Write(tunnelState->client.conn, conn_established, strlen(conn_established), call, NULL);
+ tunnelState->client.write(conn_established, strlen(conn_established), call, NULL);
}
}
tunnelState->server.conn->close();
}
+/// reacts to a failure to establish the given TCP connection
+void
+TunnelStateData::noteConnectFailure(const Comm::ConnectionPointer &conn)
+{
+ debugs(26, 4, "removing the failed one from " << serverDestinations.size() <<
+ " destinations: " << conn);
+
+ if (CachePeer *peer = conn->getPeer())
+ peerConnectFailed(peer);
+
+ assert(!serverDestinations.empty());
+ serverDestinations.erase(serverDestinations.begin());
+
+ // Since no TCP payload has been passed to client or server, we may
+ // TCP-connect to other destinations (including alternate IPs).
+
+ if (!FwdState::EnoughTimeToReForward(startTime))
+ return sendError(savedError, "forwarding timeout");
+
+ if (!serverDestinations.empty())
+ return startConnecting();
+
+ if (!PeerSelectionInitiator::subscribed)
+ return sendError(savedError, "tried all destinations");
+
+ debugs(26, 4, "wait for more destinations to try");
+ // expect a noteDestination*() call
+}
+
static void
tunnelConnectDone(const Comm::ConnectionPointer &conn, Comm::Flag status, int xerrno, void *data)
{
TunnelStateData *tunnelState = (TunnelStateData *)data;
if (status != Comm::OK) {
- debugs(26, 4, HERE << conn << ", comm failure recovery.");
- /* At this point only the TCP handshake has failed. no data has been passed.
- * we are allowed to re-try the TCP-level connection to alternate IPs for CONNECT.
- */
- tunnelState->serverDestinations.erase(tunnelState->serverDestinations.begin());
- if (status != Comm::TIMEOUT && tunnelState->serverDestinations.size() > 0) {
- /* Try another IP of this destination host */
- GetMarkingsToServer(tunnelState->request.getRaw(), *tunnelState->serverDestinations[0]);
- debugs(26, 4, HERE << "retry with : " << tunnelState->serverDestinations[0]);
- AsyncCall::Pointer call = commCbCall(26,3, "tunnelConnectDone", CommConnectCbPtrFun(tunnelConnectDone, tunnelState));
- Comm::ConnOpener *cs = new Comm::ConnOpener(tunnelState->serverDestinations[0], call, Config.Timeout.connect);
- cs->setHost(tunnelState->url);
- AsyncJob::Start(cs);
- } else {
- debugs(26, 4, HERE << "terminate with error.");
- ErrorState *err = new ErrorState(ERR_CONNECT_FAIL, Http::scServiceUnavailable, tunnelState->request.getRaw());
- *tunnelState->status_ptr = Http::scServiceUnavailable;
- err->xerrno = xerrno;
- // on timeout is this still: err->xerrno = ETIMEDOUT;
- err->port = conn->remote.port();
- err->callback = tunnelErrorComplete;
- err->callback_data = tunnelState;
- errorSend(tunnelState->client.conn, err);
- if (tunnelState->request != NULL)
- tunnelState->request->hier.stopPeerClock(false);
- }
+ ErrorState *err = new ErrorState(ERR_CONNECT_FAIL, Http::scServiceUnavailable, tunnelState->request.getRaw());
+ err->xerrno = xerrno;
+ // on timeout is this still: err->xerrno = ETIMEDOUT;
+ err->port = conn->remote.port();
+ tunnelState->saveError(err);
+ tunnelState->noteConnectFailure(conn);
return;
}
tunnelState->server.setDelayId(DelayId());
#endif
- tunnelState->request->hier.note(conn, tunnelState->getHost());
+ tunnelState->request->hier.resetPeerNotes(conn, tunnelState->getHost());
tunnelState->server.conn = conn;
tunnelState->request->peer_host = conn->getPeer() ? conn->getPeer()->host : NULL;
debugs(26, 4, HERE << "determine post-connect handling pathway.");
if (conn->getPeer()) {
tunnelState->request->peer_login = conn->getPeer()->login;
+ tunnelState->request->peer_domain = conn->getPeer()->domain;
+ tunnelState->request->flags.auth_no_keytab = conn->getPeer()->options.auth_no_keytab;
tunnelState->request->flags.proxying = !(conn->getPeer()->options.originserver);
} else {
tunnelState->request->peer_login = NULL;
+ tunnelState->request->peer_domain = NULL;
+ tunnelState->request->flags.auth_no_keytab = false;
tunnelState->request->flags.proxying = false;
}
}
void
-tunnelStart(ClientHttpRequest * http, int64_t * size_ptr, int *status_ptr, const AccessLogEntryPointer &al)
+tunnelStart(ClientHttpRequest * http)
{
debugs(26, 3, HERE);
/* Create state structure. */
* default is to allow.
*/
ACLFilledChecklist ch(Config.accessList.miss, request, NULL);
+ ch.al = http->al;
ch.src_addr = request->client_addr;
ch.my_addr = request->my_addr;
- if (ch.fastCheck() == ACCESS_DENIED) {
+ ch.syncAle(request, http->log_uri);
+ if (ch.fastCheck().denied()) {
debugs(26, 4, HERE << "MISS access forbidden.");
err = new ErrorState(ERR_FORWARDING_DENIED, Http::scForbidden, request);
- *status_ptr = Http::scForbidden;
+ http->al->http.code = Http::scForbidden;
errorSend(http->getConn()->clientConnection, err);
return;
}
++statCounter.server.all.requests;
++statCounter.server.other.requests;
- tunnelState = new TunnelStateData;
+ tunnelState = new TunnelStateData(http);
#if USE_DELAY_POOLS
- tunnelState->server.setDelayId(DelayId::DelayClient(http));
+ //server.setDelayId called from tunnelConnectDone after server side connection established
#endif
- tunnelState->url = xstrdup(url);
- tunnelState->request = request;
- tunnelState->server.size_ptr = size_ptr;
- tunnelState->status_ptr = status_ptr;
- tunnelState->logTag_ptr = &http->logType;
- tunnelState->client.conn = http->getConn()->clientConnection;
- tunnelState->http = http;
- tunnelState->al = al;
-
- comm_add_close_handler(tunnelState->client.conn->fd,
- tunnelClientClosed,
- tunnelState);
-
- AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
- CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
- commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall);
-
- peerSelect(&(tunnelState->serverDestinations), request, al,
- NULL,
- tunnelPeerSelectComplete,
- tunnelState);
+ tunnelState->startSelectingDestinations(request, http->al, nullptr);
}
void
TunnelStateData::connectToPeer()
{
-#if USE_OPENSSL
if (CachePeer *p = server.conn->getPeer()) {
if (p->secure.encryptTransport) {
AsyncCall::Pointer callback = asyncCall(5,4,
"TunnelStateData::ConnectedToPeer",
MyAnswerDialer(&TunnelStateData::connectedToPeer, this));
- Ssl::BlindPeerConnector *connector =
- new Ssl::BlindPeerConnector(request, server.conn, callback);
+ auto *connector = new Security::BlindPeerConnector(request, server.conn, callback, al);
AsyncJob::Start(connector); // will call our callback
return;
}
}
-#endif
Security::EncryptorAnswer nil;
connectedToPeer(nil);
TunnelStateData::connectedToPeer(Security::EncryptorAnswer &answer)
{
if (ErrorState *error = answer.error.get()) {
- *status_ptr = error->httpStatus;
- error->callback = tunnelErrorComplete;
- error->callback_data = this;
- errorSend(client.conn, error);
- answer.error.clear(); // preserve error for errorSendComplete()
+ answer.error.clear(); // sendError() will own the error
+ sendError(error, "TLS peer connection error");
return;
}
TunnelStateData *tunnelState = (TunnelStateData *)data;
assert(!tunnelState->waitingForConnectExchange());
HttpHeader hdr_out(hoRequest);
- HttpStateFlags flags;
+ Http::StateFlags flags;
debugs(26, 3, HERE << srv << ", tunnelState=" << tunnelState);
memset(&flags, '\0', sizeof(flags));
flags.proxying = tunnelState->request->flags.proxying;
hdr_out.clean();
mb.append("\r\n", 2);
- debugs(11, 2, "Tunnel Server REQUEST: " << tunnelState->server.conn << ":\n----------\n" <<
- Raw("tunnelRelayConnectRequest", mb.content(), mb.contentSize()) << "\n----------");
+ debugs(11, 2, "Tunnel Server REQUEST: " << tunnelState->server.conn <<
+ ":\n----------\n" << mb.buf << "\n----------");
- if (tunnelState->clientExpectsConnectResponse()) {
- // hack: blindly tunnel peer response (to our CONNECT request) to the client as ours.
- AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectedWriteDone",
- CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
- Comm::Write(srv, &mb, writeCall);
- } else {
- // we have to eat the connect response from the peer (so that the client
- // does not see it) and only then start shoveling data to the client
- AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectReqWriteDone",
- CommIoCbPtrFun(tunnelConnectReqWriteDone,
- tunnelState));
- Comm::Write(srv, &mb, writeCall);
- tunnelState->connectReqWriting = true;
-
- tunnelState->connectRespBuf = new MemBuf;
- // SQUID_TCP_SO_RCVBUF: we should not accumulate more than regular I/O buffer
- // can hold since any CONNECT response leftovers have to fit into server.buf.
- // 2*SQUID_TCP_SO_RCVBUF: HttpMsg::parse() zero-terminates, which uses space.
- tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
- tunnelState->readConnectResponse();
-
- assert(tunnelState->waitingForConnectExchange());
- }
+ AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectReqWriteDone",
+ CommIoCbPtrFun(tunnelConnectReqWriteDone,
+ tunnelState));
+
+ tunnelState->server.write(mb.buf, mb.size, writeCall, mb.freeFunc());
+ tunnelState->connectReqWriting = true;
+
+ tunnelState->connectRespBuf = new MemBuf;
+ // SQUID_TCP_SO_RCVBUF: we should not accumulate more than regular I/O buffer
+ // can hold since any CONNECT response leftovers have to fit into server.buf.
+ // 2*SQUID_TCP_SO_RCVBUF: Http::Message::parse() zero-terminates, which uses space.
+ tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
+ tunnelState->readConnectResponse();
+
+ assert(tunnelState->waitingForConnectExchange());
AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
commSetConnTimeout(srv, Config.Timeout.read, timeoutCall);
}
-static void
-tunnelPeerSelectComplete(Comm::ConnectionList *peer_paths, ErrorState *err, void *data)
+static Comm::ConnectionPointer
+borrowPinnedConnection(HttpRequest *request, Comm::ConnectionPointer &serverDestination)
{
- TunnelStateData *tunnelState = (TunnelStateData *)data;
+ // pinned_connection may become nil after a pconn race
+ if (ConnStateData *pinned_connection = request ? request->pinnedConnection() : nullptr) {
+ Comm::ConnectionPointer serverConn = pinned_connection->borrowPinnedConnection(request, serverDestination->getPeer());
+ return serverConn;
+ }
- if (peer_paths == NULL || peer_paths->size() < 1) {
- debugs(26, 3, HERE << "No paths found. Aborting CONNECT");
- if (!err) {
- err = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, tunnelState->request.getRaw());
- }
- *tunnelState->status_ptr = err->httpStatus;
- err->callback = tunnelErrorComplete;
- err->callback_data = tunnelState;
- errorSend(tunnelState->client.conn, err);
- return;
+ return nullptr;
+}
+
+void
+TunnelStateData::noteDestination(Comm::ConnectionPointer path)
+{
+ const bool wasBlocked = serverDestinations.empty();
+ serverDestinations.push_back(path);
+ if (wasBlocked)
+ startConnecting();
+ // else continue to use one of the previously noted destinations;
+ // if all of them fail, we may try this path
+}
+
+void
+TunnelStateData::noteDestinationsEnd(ErrorState *selectionError)
+{
+ PeerSelectionInitiator::subscribed = false;
+ if (serverDestinations.empty()) { // was blocked, waiting for more paths
+
+ if (selectionError)
+ return sendError(selectionError, "path selection has failed");
+
+ if (savedError)
+ return sendError(savedError, "all found paths have failed");
+
+ return sendError(new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request.getRaw()),
+ "path selection found no paths");
}
- delete err;
+ // else continue to use one of the previously noted destinations;
+ // if all of them fail, tunneling as whole will fail
+ Must(!selectionError); // finding at least one path means selection succeeded
+}
+
+/// remembers an error to be used if there will be no more connection attempts
+void
+TunnelStateData::saveError(ErrorState *error)
+{
+ debugs(26, 4, savedError << " ? " << error);
+ assert(error);
+ delete savedError; // may be nil
+ savedError = error;
+}
- GetMarkingsToServer(tunnelState->request.getRaw(), *tunnelState->serverDestinations[0]);
+/// Starts sending the given error message to the client, leading to the
+/// eventual transaction termination. Call with savedError to send savedError.
+void
+TunnelStateData::sendError(ErrorState *finalError, const char *reason)
+{
+ debugs(26, 3, "aborting transaction for " << reason);
- if (tunnelState->request != NULL)
- tunnelState->request->hier.startPeerClock();
+ if (request)
+ request->hier.stopPeerClock(false);
- debugs(26, 3, HERE << "paths=" << peer_paths->size() << ", p[0]={" << (*peer_paths)[0] << "}, serverDest[0]={" <<
- tunnelState->serverDestinations[0] << "}");
+ assert(finalError);
- AsyncCall::Pointer call = commCbCall(26,3, "tunnelConnectDone", CommConnectCbPtrFun(tunnelConnectDone, tunnelState));
- Comm::ConnOpener *cs = new Comm::ConnOpener(tunnelState->serverDestinations[0], call, Config.Timeout.connect);
- cs->setHost(tunnelState->url);
+ // get rid of any cached error unless that is what the caller is sending
+ if (savedError != finalError)
+ delete savedError; // may be nil
+ savedError = nullptr;
+
+ // we cannot try other destinations after responding with an error
+ PeerSelectionInitiator::subscribed = false; // may already be false
+
+ *status_ptr = finalError->httpStatus;
+ finalError->callback = tunnelErrorComplete;
+ finalError->callback_data = this;
+ errorSend(client.conn, finalError);
+}
+
+void
+TunnelStateData::startConnecting()
+{
+ if (request)
+ request->hier.startPeerClock();
+
+ assert(!serverDestinations.empty());
+ Comm::ConnectionPointer &dest = serverDestinations.front();
+ debugs(26, 3, "to " << dest);
+
+ if (dest->peerType == PINNED) {
+ Comm::ConnectionPointer serverConn = borrowPinnedConnection(request.getRaw(), dest);
+ debugs(26,7, "pinned peer connection: " << serverConn);
+ if (Comm::IsConnOpen(serverConn)) {
+ tunnelConnectDone(serverConn, Comm::OK, 0, (void *)this);
+ return;
+ }
+ // a PINNED path failure is fatal; do not wait for more paths
+ sendError(new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request.getRaw()),
+ "pinned path failure");
+ return;
+ }
+
+ GetMarkingsToServer(request.getRaw(), *dest);
+
+ const time_t connectTimeout = dest->connectTimeout(startTime);
+ AsyncCall::Pointer call = commCbCall(26,3, "tunnelConnectDone", CommConnectCbPtrFun(tunnelConnectDone, this));
+ Comm::ConnOpener *cs = new Comm::ConnOpener(dest, call, connectTimeout);
+ cs->setHost(url);
AsyncJob::Start(cs);
}
switchToTunnel(HttpRequest *request, Comm::ConnectionPointer &clientConn, Comm::ConnectionPointer &srvConn)
{
debugs(26,5, "Revert to tunnel FD " << clientConn->fd << " with FD " << srvConn->fd);
- /* Create state structure. */
- TunnelStateData *tunnelState = NULL;
- const char *url = urlCanonical(request);
- debugs(26, 3, request->method << " " << url << " " << request->http_ver);
+ /* Create state structure. */
++statCounter.server.all.requests;
++statCounter.server.other.requests;
- tunnelState = new TunnelStateData;
- tunnelState->url = xstrdup(url);
- tunnelState->request = request;
- tunnelState->server.size_ptr = NULL; //Set later if ClientSocketContext is available
-
- // Temporary static variable to store the unneeded for our case status code
- static int status_code = 0;
- tunnelState->status_ptr = &status_code;
- tunnelState->client.conn = clientConn;
-
- ConnStateData *conn;
- if ((conn = request->clientConnectionManager.get())) {
- ClientSocketContext::Pointer context = conn->getCurrentContext();
- if (context != NULL && context->http != NULL) {
- tunnelState->logTag_ptr = &context->http->logType;
- tunnelState->server.size_ptr = &context->http->out.size;
+ auto conn = request->clientConnectionManager.get();
+ Must(conn);
+ Http::StreamPointer context = conn->pipeline.front();
+ Must(context && context->http);
-#if USE_DELAY_POOLS
- /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */
- if (srvConn->getPeer() && srvConn->getPeer()->options.no_delay)
- tunnelState->server.setDelayId(DelayId::DelayClient(context->http));
-#endif
- }
- }
+ debugs(26, 3, request->method << " " << context->http->uri << " " << request->http_ver);
- comm_add_close_handler(tunnelState->client.conn->fd,
- tunnelClientClosed,
- tunnelState);
+ TunnelStateData *tunnelState = new TunnelStateData(context->http);
- AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
- CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
- commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall);
fd_table[clientConn->fd].read_method = &default_read_method;
fd_table[clientConn->fd].write_method = &default_write_method;
- tunnelState->request->hier.note(srvConn, tunnelState->getHost());
+ request->hier.resetPeerNotes(srvConn, tunnelState->getHost());
tunnelState->server.conn = srvConn;
- tunnelState->request->peer_host = srvConn->getPeer() ? srvConn->getPeer()->host : NULL;
+
+#if USE_DELAY_POOLS
+ /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */
+ if (srvConn->getPeer() && srvConn->getPeer()->options.no_delay)
+ tunnelState->server.setDelayId(DelayId::DelayClient(context->http));
+#endif
+
+ request->peer_host = srvConn->getPeer() ? srvConn->getPeer()->host : nullptr;
comm_add_close_handler(srvConn->fd, tunnelServerClosed, tunnelState);
debugs(26, 4, "determine post-connect handling pathway.");
if (srvConn->getPeer()) {
- tunnelState->request->peer_login = srvConn->getPeer()->login;
- tunnelState->request->flags.proxying = !(srvConn->getPeer()->options.originserver);
+ request->peer_login = srvConn->getPeer()->login;
+ request->peer_domain = srvConn->getPeer()->domain;
+ request->flags.auth_no_keytab = srvConn->getPeer()->options.auth_no_keytab;
+ request->flags.proxying = !(srvConn->getPeer()->options.originserver);
} else {
- tunnelState->request->peer_login = NULL;
- tunnelState->request->flags.proxying = false;
+ request->peer_login = nullptr;
+ request->peer_domain = nullptr;
+ request->flags.auth_no_keytab = false;
+ request->flags.proxying = false;
}
- timeoutCall = commCbCall(5, 4, "tunnelTimeout",
- CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
+ AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
+ CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
commSetConnTimeout(srvConn, Config.Timeout.read, timeoutCall);
fd_table[srvConn->fd].read_method = &default_read_method;
fd_table[srvConn->fd].write_method = &default_write_method;
- SSL *ssl = fd_table[srvConn->fd].ssl;
+ auto ssl = fd_table[srvConn->fd].ssl.get();
assert(ssl);
BIO *b = SSL_get_rbio(ssl);
- Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
- const MemBuf &buf = srvBio->rBufData();
-
- AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
- CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
- Comm::Write(tunnelState->client.conn, buf.content(), buf.contentSize(), call, NULL);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
+ tunnelState->preReadServerData = srvBio->rBufData();
+ tunnelStartShoveling(tunnelState);
}
#endif //USE_OPENSSL