=== modified file 'src/MemBuf.cc' --- src/MemBuf.cc 2012-11-28 01:13:21 +0000 +++ src/MemBuf.cc 2013-10-16 11:56:06 +0000 @@ -242,41 +242,43 @@ assert(!stolen); /* not frozen */ size -= tailSize; } /** * calls memcpy, appends exactly size bytes, * extends buffer or creates buffer if needed. */ void MemBuf::append(const char *newContent, mb_size_t sz) { assert(sz >= 0); assert(buf || (0==capacity && 0==size)); assert(!stolen); /* not frozen */ PROF_start(MemBuf_append); if (sz > 0) { if (size + sz + 1 > capacity) grow(size + sz + 1); assert(size + sz <= capacity); /* paranoid */ - memcpy(space(), newContent, sz); + // memmove() allows memory blocks to overlap + // we do assume it handles space()==newContent efficiently + memmove(space(), newContent, sz); appended(sz); } PROF_stop(MemBuf_append); } /// updates content size after external append void MemBuf::appended(mb_size_t sz) { assert(size + sz <= capacity); size += sz; terminate(); } /** * Null-terminate in case we are used as a string. * Extra octet is not counted in the content size (or space size) * \note XXX: but the extra octet is counted when growth decisions are made! * This will cause the buffer to grow when spaceSize() == 1 on append, * which will assert() if the buffer cannot grow any more. === modified file 'src/acl/DestinationIp.cc' --- src/acl/DestinationIp.cc 2013-05-13 23:32:23 +0000 +++ src/acl/DestinationIp.cc 2013-10-13 08:28:30 +0000 @@ -40,42 +40,42 @@ #include "SquidConfig.h" ACLFlag ACLDestinationIP::SupportedFlags[] = {ACL_F_NO_LOOKUP, ACL_F_END}; char const * ACLDestinationIP::typeString() const { return "dst"; } int ACLDestinationIP::match(ACLChecklist *cl) { ACLFilledChecklist *checklist = Filled(cl); // Bug 3243: CVE 2009-0801 // Bypass of browser same-origin access control in intercepted communication // To resolve this we will force DIRECT and only to the original client destination. // In which case, we also need this ACL to accurately match the destination if (Config.onoff.client_dst_passthru && (checklist->request->flags.intercepted || checklist->request->flags.interceptTproxy)) { - assert(checklist->conn() && checklist->conn()->clientConnection != NULL); - return ACLIP::match(checklist->conn()->clientConnection->local); + assert(checklist->conn() && checklist->conn()->tcp != NULL); + return ACLIP::match(checklist->conn()->tcp->local); } if (flags.isSet(ACL_F_NO_LOOKUP)) { if (!checklist->request->GetHostIsNumeric()) { debugs(28, 3, "aclMatchAcl: No-lookup DNS ACL '" << AclMatchedName << "' for '" << checklist->request->GetHost() << "'"); return 0; } if (ACLIP::match(checklist->request->host_addr)) return 1; return 0; } const ipcache_addrs *ia = ipcache_gethostbyname(checklist->request->GetHost(), IP_LOOKUP_IF_MISS); if (ia) { /* Entry in cache found */ for (int k = 0; k < (int) ia->count; ++k) { if (ACLIP::match(ia->in_addrs[k])) === modified file 'src/acl/FilledChecklist.cc' --- src/acl/FilledChecklist.cc 2013-10-25 00:13:46 +0000 +++ src/acl/FilledChecklist.cc 2013-10-29 02:33:43 +0000 @@ -62,47 +62,47 @@ } ConnStateData * ACLFilledChecklist::conn() const { return conn_; } void ACLFilledChecklist::conn(ConnStateData *aConn) { if (conn() == aConn) return; assert (conn() == NULL); conn_ = cbdataReference(aConn); } int ACLFilledChecklist::fd() const { - return (conn_ != NULL && conn_->clientConnection != NULL) ? conn_->clientConnection->fd : fd_; + return (conn_ != NULL && conn_->tcp != NULL) ? conn_->tcp->fd : fd_; } void ACLFilledChecklist::fd(int aDescriptor) { - assert(!conn() || conn()->clientConnection == NULL || conn()->clientConnection->fd == aDescriptor); + assert(!conn() || conn()->tcp == NULL || conn()->tcp->fd == aDescriptor); fd_ = aDescriptor; } bool ACLFilledChecklist::destinationDomainChecked() const { return destinationDomainChecked_; } void ACLFilledChecklist::markDestinationDomainChecked() { assert (!finished() && !destinationDomainChecked()); destinationDomainChecked_ = true; } bool ACLFilledChecklist::sourceDomainChecked() const { return sourceDomainChecked_; === modified file 'src/auth/UserRequest.cc' --- src/auth/UserRequest.cc 2013-12-06 14:59:47 +0000 +++ src/auth/UserRequest.cc 2013-12-17 13:18:50 +0000 @@ -337,41 +337,41 @@ debugs(29, 2, "WARNING: DUPLICATE AUTH - authentication header on already authenticated connection!. AU " << conn->getAuth() << ", Current user '" << conn->getAuth()->username() << "' proxy_auth " << proxy_auth); /* remove this request struct - the link is already authed and it can't be to reauth. */ /* This should _only_ ever occur on the first pass through * authenticateAuthenticate */ assert(*auth_user_request == NULL); conn->setAuth(NULL, "changed credentials token"); } /* we have a proxy auth header and as far as we know this connection has * not had bungled connection oriented authentication happen on it. */ debugs(29, 9, HERE << "header " << (proxy_auth ? proxy_auth : "-") << "."); if (*auth_user_request == NULL) { if (conn != NULL) { - debugs(29, 9, HERE << "This is a new checklist test on:" << conn->clientConnection); + debugs(29, 9, "This is a new checklist test on:" << conn->tcp); } if (proxy_auth && request->auth_user_request == NULL && conn != NULL && conn->getAuth() != NULL) { Auth::Config * scheme = Auth::Config::Find(proxy_auth); if (conn->getAuth()->user() == NULL || conn->getAuth()->user()->config != scheme) { debugs(29, DBG_IMPORTANT, "WARNING: Unexpected change of authentication scheme from '" << (conn->getAuth()->user()!=NULL?conn->getAuth()->user()->config->type():"[no user]") << "' to '" << proxy_auth << "' (client " << src_addr << ")"); conn->setAuth(NULL, "changed auth scheme"); } } if (request->auth_user_request == NULL && (conn == NULL || conn->getAuth() == NULL)) { /* beginning of a new request check */ debugs(29, 4, HERE << "No connection authentication type"); *auth_user_request = Auth::Config::CreateAuthUser(proxy_auth, al); === modified file 'src/client_side.cc' --- src/client_side.cc 2014-01-05 19:49:23 +0000 +++ src/client_side.cc 2014-02-04 13:49:36 +0000 @@ -208,77 +208,56 @@ #endif static CSCB clientSocketRecipient; static CSD clientSocketDetach; static void clientSetKeepaliveFlag(ClientHttpRequest *); static int clientIsContentLengthValid(HttpRequest * r); static int clientIsRequestBodyTooLargeForPolicy(int64_t bodyLength); static void clientUpdateStatHistCounters(LogTags logType, int svc_time); static void clientUpdateStatCounters(LogTags logType); static void clientUpdateHierCounters(HierarchyLogEntry *); static bool clientPingHasFinished(ping_data const *aPing); void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry::Pointer &); #ifndef PURIFY static bool connIsUsable(ConnStateData * conn); #endif static int responseFinishedOrFailed(HttpReply * rep, StoreIOBuffer const &receivedData); static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn); static void clientUpdateSocketStats(LogTags logType, size_t size); char *skipLeadingSpace(char *aString); -static void connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount); clientStreamNode * ClientSocketContext::getTail() const { if (http->client_stream.tail) return (clientStreamNode *)http->client_stream.tail->data; return NULL; } clientStreamNode * ClientSocketContext::getClientReplyContext() const { return (clientStreamNode *)http->client_stream.tail->prev->data; } -/** - * This routine should be called to grow the inbuf and then - * call comm_read(). - */ -void -ConnStateData::readSomeData() -{ - if (reading()) - return; - - debugs(33, 4, HERE << clientConnection << ": reading request..."); - - if (!maybeMakeSpaceAvailable()) - return; - - typedef CommCbMemFunT Dialer; - reader = JobCallback(33, 5, Dialer, this, ConnStateData::clientReadRequest); - comm_read(clientConnection, in.addressToReadInto(), getAvailableBufferLength(), reader); -} - void ClientSocketContext::removeFromConnectionList(ConnStateData * conn) { ClientSocketContext::Pointer *tempContextPointer; assert(conn != NULL && cbdataReferenceValid(conn)); assert(conn->getCurrentContext() != NULL); /* Unlink us from the connection request list */ tempContextPointer = & conn->currentobject; while (tempContextPointer->getRaw()) { if (*tempContextPointer == this) break; tempContextPointer = &(*tempContextPointer)->next; } assert(tempContextPointer->getRaw() != NULL); *tempContextPointer = next; next = NULL; } @@ -356,80 +335,80 @@ { const HttpReply::Pointer rep(msg.reply); Must(rep != NULL); // apply selected clientReplyContext::buildReplyHeader() mods // it is not clear what headers are required for control messages rep->header.removeHopByHopEntries(); rep->header.putStr(HDR_CONNECTION, "keep-alive"); httpHdrMangleList(&rep->header, http->request, ROR_REPLY); // remember the callback cbControlMsgSent = msg.cbSuccess; MemBuf *mb = rep->pack(); debugs(11, 2, "HTTP Client " << clientConnection); debugs(11, 2, "HTTP Client CONTROL MSG:\n---------\n" << mb->buf << "\n----------"); AsyncCall::Pointer call = commCbCall(33, 5, "ClientSocketContext::wroteControlMsg", CommIoCbPtrFun(&WroteControlMsg, this)); - Comm::Write(clientConnection, mb, call); + http->getConn()->sendSomeData(*mb, call); delete mb; } /// called when we wrote the 1xx response void ClientSocketContext::wroteControlMsg(const Comm::ConnectionPointer &conn, char *, size_t, comm_err_t errflag, int xerrno) { if (errflag == COMM_ERR_CLOSING) return; if (errflag == COMM_OK) { ScheduleCallHere(cbControlMsgSent); return; } debugs(33, 3, HERE << "1xx writing failed: " << xstrerr(xerrno)); // no error notification: see HttpControlMsg.h for rationale and // note that some errors are detected elsewhere (e.g., close handler) // close on 1xx errors to be conservative and to simplify the code // (if we do not close, we must notify the source of a failure!) conn->close(); } /// wroteControlMsg() wrapper: ClientSocketContext is not an AsyncJob void ClientSocketContext::WroteControlMsg(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, comm_err_t errflag, int xerrno, void *data) { ClientSocketContext *context = static_cast(data); context->wroteControlMsg(conn, bufnotused, size, errflag, xerrno); } #if USE_IDENT static void clientIdentDone(const char *ident, void *data) { ConnStateData *conn = (ConnStateData *)data; - xstrncpy(conn->clientConnection->rfc931, ident ? ident : dash_str, USER_IDENT_SZ); + xstrncpy(conn->tcp->rfc931, ident ? ident : dash_str, USER_IDENT_SZ); } #endif void clientUpdateStatCounters(LogTags logType) { ++statCounter.client_http.requests; if (logTypeIsATcpHit(logType)) ++statCounter.client_http.hits; if (logType == LOG_TCP_HIT) ++statCounter.client_http.disk_hits; else if (logType == LOG_TCP_MEM_HIT) ++statCounter.client_http.mem_hits; } void clientUpdateStatHistCounters(LogTags logType, int svc_time) { @@ -621,42 +600,42 @@ debugs(33, 9, "clientLogRequest: http.code='" << al->http.code << "'"); if (loggingEntry() && loggingEntry()->mem_obj) al->cache.objectSize = loggingEntry()->contentLen(); // payload duplicate ?? with or without TE ? al->http.clientRequestSz.header = req_sz; al->http.clientReplySz.header = out.headers_sz; // XXX: calculate without payload encoding or headers !! al->http.clientReplySz.payloadData = out.size - out.headers_sz; // pretend its all un-encoded data for now. al->cache.highOffset = out.offset; al->cache.code = logType; al->cache.msec = tvSubMsec(al->cache.start_time, current_time); if (request) prepareLogWithRequestDetails(request, al); - if (getConn() != NULL && getConn()->clientConnection != NULL && getConn()->clientConnection->rfc931[0]) - al->cache.rfc931 = getConn()->clientConnection->rfc931; + if (getConn() != NULL && getConn()->tcp != NULL && getConn()->tcp->rfc931[0]) + al->cache.rfc931 = getConn()->tcp->rfc931; #if USE_SSL && 0 /* This is broken. Fails if the connection has been closed. Needs * to snarf the ssl details some place earlier.. */ if (getConn() != NULL) al->cache.ssluser = sslGetUserEmail(fd_table[getConn()->fd].ssl); #endif /*Add notes*/ // The al->notes and request->notes must point to the same object. (void)SyncNotes(*al, *request); typedef Notes::iterator ACAMLI; for (ACAMLI i = Config.notes.begin(); i != Config.notes.end(); ++i) { if (const char *value = (*i)->match(request, al->reply, NULL)) { NotePairs ¬es = SyncNotes(*al, *request); notes.add((*i)->key.termedBuf(), value); debugs(33, 3, HERE << (*i)->key.termedBuf() << " " << value); @@ -672,42 +651,42 @@ if (request) { al->adapted_request = request; HTTPMSGLOCK(al->adapted_request); } accessLogLog(al, &checklist); bool updatePerformanceCounters = true; if (Config.accessList.stats_collection) { ACLFilledChecklist statsCheck(Config.accessList.stats_collection, request, NULL); if (al->reply) { statsCheck.reply = al->reply; HTTPMSGLOCK(statsCheck.reply); } updatePerformanceCounters = (statsCheck.fastCheck() == ACCESS_ALLOWED); } if (updatePerformanceCounters) { if (request) updateCounters(); - if (getConn() != NULL && getConn()->clientConnection != NULL) - clientdbUpdate(getConn()->clientConnection->remote, logType, AnyP::PROTO_HTTP, out.size); + if (getConn() != NULL && getConn()->tcp != NULL) + clientdbUpdate(getConn()->tcp->remote, logType, AnyP::PROTO_HTTP, out.size); } } void ClientHttpRequest::freeResources() { safe_free(uri); safe_free(log_uri); safe_free(redirect.location); range_iter.boundary.clean(); HTTPMSGUNLOCK(request); if (client_stream.tail) clientStreamAbort((clientStreamNode *)client_stream.tail->data, this); } void httpRequestFree(void *data) { ClientHttpRequest *http = (ClientHttpRequest *)data; @@ -729,176 +708,174 @@ } return true; } void ConnStateData::freeAllContexts() { ClientSocketContext::Pointer context; while ((context = getCurrentContext()).getRaw() != NULL) { assert(getCurrentContext() != getCurrentContext()->next); context->connIsFinished(); assert (context != currentobject); } } /// propagates abort event to all contexts void -ConnStateData::notifyAllContexts(int xerrno) +ConnStateData::noteTcpReadError(int xerrno) { typedef ClientSocketContext::Pointer CSCP; for (CSCP c = getCurrentContext(); c.getRaw(); c = c->next) c->noteIoError(xerrno); } -/* This is a handler normally called by comm_close() */ -void ConnStateData::connStateClosed(const CommCloseCbParams &io) +void +ConnStateData::updateByteCountersOnRead(size_t sz) { - deleteThis("ConnStateData::connStateClosed"); + kb_incr(&(statCounter.client_http.kbytes_in), sz); } #if USE_AUTH void ConnStateData::setAuth(const Auth::UserRequest::Pointer &aur, const char *by) { if (auth_ == NULL) { if (aur != NULL) { - debugs(33, 2, "Adding connection-auth to " << clientConnection << " from " << by); + debugs(33, 2, "Adding connection-auth to " << tcp << " from " << by); auth_ = aur; } return; } // clobered with self-pointer // NP: something nasty is going on in Squid, but harmless. if (aur == auth_) { - debugs(33, 2, "WARNING: Ignoring duplicate connection-auth for " << clientConnection << " from " << by); + debugs(33, 2, "WARNING: Ignoring duplicate connection-auth for " << tcp << " from " << by); return; } /* * Connection-auth relies on a single set of credentials being preserved * for all requests on a connection once they have been setup. * There are several things which need to happen to preserve security * when connection-auth credentials change unexpectedly or are unset. * * 1) auth helper released from any active state * * They can only be reserved by a handshake process which this * connection can now never complete. * This prevents helpers hanging when their connections close. * * 2) pinning is expected to be removed and server conn closed * * The upstream link is authenticated with the same credentials. * Expecting the same level of consistency we should have received. * This prevents upstream being faced with multiple or missing * credentials after authentication. * NP: un-pin is left to the cleanup in ConnStateData::swanSong() * we just trigger that cleanup here via comm_reset_close() or * ConnStateData::stopReceiving() * * 3) the connection needs to close. * * This prevents attackers injecting requests into a connection, * or gateways wrongly multiplexing users into a single connection. * * When credentials are missing closure needs to follow an auth * challenge for best recovery by the client. * * When credentials change there is nothing we can do but abort as * fast as possible. Sending TCP RST instead of an HTTP response * is the best-case action. */ // clobbered with nul-pointer if (aur == NULL) { - debugs(33, 2, "WARNING: Graceful closure on " << clientConnection << " due to connection-auth erase from " << by); + debugs(33, 2, "WARNING: Graceful closure on " << tcp << " due to connection-auth erase from " << by); auth_->releaseAuthServer(); auth_ = NULL; // XXX: need to test whether the connection re-auth challenge is sent. If not, how to trigger it from here. // NP: the current situation seems to fix challenge loops in Safari without visible issues in others. // we stop receiving more traffic but can leave the Job running to terminate after the error or challenge is delivered. stopReceiving("connection-auth removed"); return; } // clobbered with alternative credentials if (aur != auth_) { - debugs(33, 2, "ERROR: Closing " << clientConnection << " due to change of connection-auth from " << by); + debugs(33, 2, "ERROR: Closing " << tcp << " due to change of connection-auth from " << by); auth_->releaseAuthServer(); auth_ = NULL; // this is a fatal type of problem. // Close the connection immediately with TCP RST to abort all traffic flow - comm_reset_close(clientConnection); + comm_reset_close(tcp); return; } /* NOT REACHABLE */ } #endif // cleans up before destructor is called void ConnStateData::swanSong() { - debugs(33, 2, HERE << clientConnection); + debugs(33, 2, tcp); flags.readMore = false; - clientdbEstablished(clientConnection->remote, -1); /* decrement */ + clientdbEstablished(tcp->remote, -1); /* decrement */ assert(areAllContextsForThisConnection()); freeAllContexts(); unpinConnection(); - - if (Comm::IsConnOpen(clientConnection)) - clientConnection->close(); + Comm::TcpReceiver::swanSong(); #if USE_AUTH - // NP: do this bit after closing the connections to avoid side effects from unwanted TCP RST + // NP: do this bit after TcpReceiver::swanSong (TCP connection cleanup) to avoid side effects from unwanted TCP RST setAuth(NULL, "ConnStateData::SwanSong cleanup"); #endif BodyProducer::swanSong(); flags.swanSang = true; } bool ConnStateData::isOpen() const { return cbdataReferenceValid(this) && // XXX: checking "this" in a method - Comm::IsConnOpen(clientConnection) && - !fd_table[clientConnection->fd].closing(); + Comm::IsConnOpen(tcp) && + !fd_table[tcp->fd].closing(); } ConnStateData::~ConnStateData() { assert(this != NULL); - debugs(33, 3, HERE << clientConnection); + debugs(33, 3, tcp); if (isOpen()) - debugs(33, DBG_IMPORTANT, "BUG: ConnStateData did not close " << clientConnection); + debugs(33, DBG_IMPORTANT, "BUG: ConnStateData did not close " << tcp); if (!flags.swanSang) - debugs(33, DBG_IMPORTANT, "BUG: ConnStateData was not destroyed properly; " << clientConnection); + debugs(33, DBG_IMPORTANT, "BUG: ConnStateData was not destroyed properly; " << tcp); cbdataReferenceDone(port); if (bodyPipe != NULL) stopProducingFor(bodyPipe, false); #if USE_SSL delete sslServerBump; #endif } /** * clientSetKeepaliveFlag() sets request->flags.proxyKeepalive. * This is the client-side persistent connection flag. We need * to set this relatively early in the request processing * to handle hacks for broken servers and clients. */ static void clientSetKeepaliveFlag(ClientHttpRequest * http) { @@ -929,41 +906,41 @@ return 1; } /* NOT REACHED */ } int clientIsRequestBodyTooLargeForPolicy(int64_t bodyLength) { if (Config.maxRequestBodySize && bodyLength > Config.maxRequestBodySize) return 1; /* too large */ return 0; } #ifndef PURIFY bool connIsUsable(ConnStateData * conn) { - if (conn == NULL || !cbdataReferenceValid(conn) || !Comm::IsConnOpen(conn->clientConnection)) + if (conn == NULL || !cbdataReferenceValid(conn) || !Comm::IsConnOpen(conn->tcp)) return false; return true; } #endif // careful: the "current" context may be gone if we wrote an early response ClientSocketContext::Pointer ConnStateData::getCurrentContext() const { assert(this); return currentobject; } void ClientSocketContext::deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData) { debugs(33, 2, "clientSocketRecipient: Deferring request " << http->uri); assert(flags.deferred == 0); @@ -1049,41 +1026,41 @@ if (!multipartRangeRequest() && !http->request->flags.chunkedReply) { size_t length = lengthToSend(bodyData.range()); noteSentBodyBytes (length); AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteBodyComplete", CommIoCbPtrFun(clientWriteBodyComplete, this)); Comm::Write(clientConnection, bodyData.data, length, call, NULL); return; } MemBuf mb; mb.init(); if (multipartRangeRequest()) packRange(bodyData, &mb); else packChunk(bodyData, mb); if (mb.contentSize()) { /* write */ AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteComplete", CommIoCbPtrFun(clientWriteComplete, this)); - Comm::Write(clientConnection, &mb, call); + http->getConn()->sendSomeData(mb, call); } else writeComplete(clientConnection, NULL, 0, COMM_OK); } /** * Packs bodyData into mb using chunked encoding. Packs the last-chunk * if bodyData is empty. */ void ClientSocketContext::packChunk(const StoreIOBuffer &bodyData, MemBuf &mb) { const uint64_t length = static_cast(lengthToSend(bodyData.range())); noteSentBodyBytes(length); mb.Printf("%" PRIX64 "\r\n", length); mb.append(bodyData.data, length); mb.Printf("\r\n"); } @@ -1451,41 +1428,41 @@ headersLog(0, 0, http->request->method, rep); #endif if (bodyData.data && bodyData.length) { if (multipartRangeRequest()) packRange(bodyData, mb); else if (http->request->flags.chunkedReply) { packChunk(bodyData, *mb); } else { size_t length = lengthToSend(bodyData.range()); noteSentBodyBytes (length); mb->append(bodyData.data, length); } } /* write */ debugs(33,7, HERE << "sendStartOfMessage schedules clientWriteComplete"); AsyncCall::Pointer call = commCbCall(33, 5, "clientWriteComplete", CommIoCbPtrFun(clientWriteComplete, this)); - Comm::Write(clientConnection, mb, call); + http->getConn()->sendSomeData(*mb, call); delete mb; } /** * Write a chunk of data to a client socket. If the reply is present, * send the reply headers down the wire too, and clean them up when * finished. * Pre-condition: * The request is one backed by a connection, not an internal request. * data context is not NULL * There are no more entries in the stream chain. */ static void clientSocketRecipient(clientStreamNode * node, ClientHttpRequest * http, HttpReply * rep, StoreIOBuffer receivedData) { /* Test preconditions */ assert(node != NULL); PROF_start(clientSocketRecipient); /* TODO: handle this rather than asserting @@ -1548,158 +1525,166 @@ /* Set null by ContextFree */ assert(node->node.next == NULL); /* this is the assert discussed above */ assert(NULL == dynamic_cast(node->data.getRaw())); /* We are only called when the client socket shutsdown. * Tell the prev pipeline member we're finished */ clientStreamDetach(node, http); } static void clientWriteBodyComplete(const Comm::ConnectionPointer &conn, char *buf, size_t size, comm_err_t errflag, int xerrno, void *data) { debugs(33,7, HERE << "clientWriteBodyComplete schedules clientWriteComplete"); clientWriteComplete(conn, NULL, size, errflag, xerrno, data); } void ConnStateData::readNextRequest() { - debugs(33, 5, HERE << clientConnection << " reading next req"); + debugs(33, 5, tcp << " reading next req"); - fd_note(clientConnection->fd, "Idle client: Waiting for next request"); + fd_note(tcp->fd, "Idle client: Waiting for next request"); /** - * Set the timeout BEFORE calling clientReadRequest(). + * Set the timeout BEFORE calling readSomeData(). */ typedef CommCbMemFunT TimeoutDialer; AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer, this, ConnStateData::requestTimeout); - commSetConnTimeout(clientConnection, Config.Timeout.clientIdlePconn, timeoutCall); + commSetConnTimeout(tcp, Config.Timeout.clientIdlePconn, timeoutCall); readSomeData(); /** Please don't do anything with the FD past here! */ } static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn) { - debugs(33, 2, HERE << conn->clientConnection << " Sending next"); + debugs(33, 2, conn->tcp << " Sending next"); /** If the client stream is waiting on a socket write to occur, then */ if (deferredRequest->flags.deferred) { /** NO data is allowed to have been sent. */ assert(deferredRequest->http->out.size == 0); /** defer now. */ clientSocketRecipient(deferredRequest->deferredparams.node, deferredRequest->http, deferredRequest->deferredparams.rep, deferredRequest->deferredparams.queuedBuffer); } /** otherwise, the request is still active in a callbacksomewhere, * and we are done */ } /// called when we have successfully finished writing the response void ClientSocketContext::keepaliveNextRequest() { ConnStateData * conn = http->getConn(); - debugs(33, 3, HERE << "ConnnStateData(" << conn->clientConnection << "), Context(" << clientConnection << ")"); + debugs(33, 3, "ConnnStateData(" << conn->tcp << "), Context(" << clientConnection << ")"); connIsFinished(); if (conn->pinning.pinned && !Comm::IsConnOpen(conn->pinning.serverConnection)) { - debugs(33, 2, HERE << conn->clientConnection << " Connection was pinned but server side gone. Terminating client connection"); - conn->clientConnection->close(); + debugs(33, 2, conn->tcp << " Connection was pinned but server disconnected. Terminating client connection traffic"); + conn->stopReceiving("Connection was pinned but server disconnected"); + conn->stopSending("Connection was pinned but server disconnected"); return; } +#if 0 // keep sending responses until existing pipeline finished. + /** \par * We are done with the response, and we are either still receiving request * body (early response!) or have already stopped receiving anything. * * If we are still receiving, then clientParseRequest() below will fail. * (XXX: but then we will call readNextRequest() which may succeed and * execute a smuggled request as we are not done with the current request). * * If we stopped because we got everything, then try the next request. * * If we stopped receiving because of an error, then close now to avoid * getting stuck and to prevent accidental request smuggling. */ + // XXX: what if we stopped receiving after pipelined 10 requests and have 6 reply still to send ?? + // XXX: if the 10th request has Connection:close indicating no more to read(2) if (const char *reason = conn->stoppedReceiving()) { - debugs(33, 3, HERE << "closing for earlier request error: " << reason); - conn->clientConnection->close(); + debugs(33, 3, "closing for earlier request error: " << reason); + conn->tcp->close(); return; } +#endif /** \par * Attempt to parse a request from the request buffer. * If we've been fed a pipelined request it may already * be in our read buffer. * \par * This needs to fall through - if we're unlucky and parse the _last_ request * from our read buffer we may never re-register for another client read. */ - if (conn->clientParseRequests()) { - debugs(33, 3, HERE << conn->clientConnection << ": parsed next request from buffer"); + debugs(33, 3, conn->tcp << ": parsed next request from buffer"); } /** \par * Either we need to kick-start another read or, if we have * a half-closed connection, kill it after the last request. * This saves waiting for half-closed connections to finished being * half-closed _AND_ then, sometimes, spending "Timeout" time in * the keepalive "Waiting for next request" state. */ - if (commIsHalfClosed(conn->clientConnection->fd) && (conn->getConcurrentRequestCount() == 0)) { - debugs(33, 3, "ClientSocketContext::keepaliveNextRequest: half-closed client with no pending requests, closing"); - conn->clientConnection->close(); + if (commIsHalfClosed(conn->tcp->fd) && (conn->getConcurrentRequestCount() == 0)) { + debugs(33, 3, "half-closed client with no pending requests, closing"); + conn->stopReceiving("half-closed client with no pending requests"); + conn->stopSending("half-closed client with no pending requests"); return; } ClientSocketContext::Pointer deferredRequest; /** \par * At this point we either have a parsed request (which we've * kicked off the processing for) or not. If we have a deferred * request (parsed but deferred for pipeling processing reasons) * then look at processing it. If not, simply kickstart * another read. */ if ((deferredRequest = conn->getCurrentContext()).getRaw()) { - debugs(33, 3, HERE << conn->clientConnection << ": calling PushDeferredIfNeeded"); + debugs(33, 3, conn->tcp << ": calling PushDeferredIfNeeded"); ClientSocketContextPushDeferredIfNeeded(deferredRequest, conn); } else if (conn->flags.readMore) { - debugs(33, 3, HERE << conn->clientConnection << ": calling conn->readNextRequest()"); + debugs(33, 3, conn->tcp << ": calling conn->readNextRequest()"); conn->readNextRequest(); } else { // XXX: Can this happen? CONNECT tunnels have deferredRequest set. - debugs(33, DBG_IMPORTANT, HERE << "abandoning " << conn->clientConnection); + // The answer is yes. Probably because getRaw()==NULL when the Pointer is invalidated, + // for example when the tunnel context is completed and in the process of closing. + debugs(33, DBG_IMPORTANT, HERE << "abandoning " << conn->tcp); } } void clientUpdateSocketStats(LogTags logType, size_t size) { if (size == 0) return; kb_incr(&statCounter.client_http.kbytes_out, size); if (logTypeIsATcpHit(logType)) kb_incr(&statCounter.client_http.hit_kbytes_out, size); } /** * increments iterator "i" * used by clientPackMoreRanges * \retval true there is still data available to pack more ranges @@ -1871,64 +1856,40 @@ http->al->http.timedout = true; else // even if xerrno is zero (which means read abort/eof) http->al->http.aborted = true; } } void ClientSocketContext::doClose() { clientConnection->close(); } /// called when we encounter a response-related error void ClientSocketContext::initiateClose(const char *reason) { http->getConn()->stopSending(reason); // closes ASAP } void -ConnStateData::stopSending(const char *error) -{ - debugs(33, 4, HERE << "sending error (" << clientConnection << "): " << error << - "; old receiving error: " << - (stoppedReceiving() ? stoppedReceiving_ : "none")); - - if (const char *oldError = stoppedSending()) { - debugs(33, 3, HERE << "already stopped sending: " << oldError); - return; // nothing has changed as far as this connection is concerned - } - stoppedSending_ = error; - - if (!stoppedReceiving()) { - if (const int64_t expecting = mayNeedToReadMoreBody()) { - debugs(33, 5, HERE << "must still read " << expecting << - " request body bytes with " << in.notYetUsed << " unused"); - return; // wait for the request receiver to finish reading - } - } - - clientConnection->close(); -} - -void ClientSocketContext::writeComplete(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, comm_err_t errflag) { const StoreEntry *entry = http->storeEntry(); http->out.size += size; debugs(33, 5, HERE << conn << ", sz " << size << ", err " << errflag << ", off " << http->out.size << ", len " << (entry ? entry->objectLen() : 0)); clientUpdateSocketStats(http->logType, size); /* Bail out quickly on COMM_ERR_CLOSING - close handlers will tidy up */ if (errflag == COMM_ERR_CLOSING || !Comm::IsConnOpen(conn)) return; if (errflag || clientHttpRequestStatus(conn->fd, http)) { initiateClose("failure or true request status"); /* Do we leak here ? */ return; } @@ -1950,44 +1911,44 @@ case STREAM_FAILED: initiateClose("STREAM_FAILED"); return; default: fatal("Hit unreachable code in clientWriteComplete\n"); } } SQUIDCEXTERN CSR clientGetMoreData; SQUIDCEXTERN CSS clientReplyStatus; SQUIDCEXTERN CSD clientReplyDetach; static ClientSocketContext * parseHttpRequestAbort(ConnStateData * csd, const char *uri) { ClientHttpRequest *http; ClientSocketContext *context; StoreIOBuffer tempBuffer; http = new ClientHttpRequest(csd); - http->req_sz = csd->in.notYetUsed; + http->req_sz = csd->inBuf.contentSize(); http->uri = xstrdup(uri); setLogUri (http, uri); - context = new ClientSocketContext(csd->clientConnection, http); + context = new ClientSocketContext(csd->tcp, http); tempBuffer.data = context->reqbuf; tempBuffer.length = HTTP_REQBUF_SZ; clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach, clientReplyStatus, new clientReplyContext(http), clientSocketRecipient, clientSocketDetach, context, tempBuffer); return context; } char * skipLeadingSpace(char *aString) { char *result = aString; while (xisspace(*aString)) ++aString; return result; } /** @@ -2090,41 +2051,41 @@ /* else we need to ignore the host name */ url = strstr(url, "//"); #if SHOULD_REJECT_UNKNOWN_URLS if (!url) { hp->request_parse_status = Http::scBadRequest; return parseHttpRequestAbort(conn, "error:invalid-request"); } #endif if (url) url = strchr(url + 2, '/'); if (!url) url = (char *) "/"; } if (vport < 0) - vport = http->getConn()->clientConnection->local.port(); + vport = http->getConn()->tcp->local.port(); const bool switchedToHttps = conn->switchedToHttps(); const bool tryHostHeader = vhost || switchedToHttps; if (tryHostHeader && (host = mime_get_header(req_hdr, "Host")) != NULL) { debugs(33, 5, "ACCEL VHOST REWRITE: vhost=" << host << " + vport=" << vport); char thost[256]; if (vport > 0) { thost[0] = '\0'; char *t = NULL; if (host[strlen(host)] != ']' && (t = strrchr(host,':')) != NULL) { strncpy(thost, host, (t-host)); snprintf(thost+(t-host), sizeof(thost)-(t-host), ":%d", vport); host = thost; } else if (!t) { snprintf(thost, sizeof(thost), "%s:%d",host, vport); host = thost; } } // else nothing to alter port-wise. int url_sz = strlen(url) + 32 + Config.appendDomainLen + strlen(host); @@ -2134,73 +2095,73 @@ snprintf(http->uri, url_sz, "%s://%s%s", protocol, host, url); debugs(33, 5, "ACCEL VHOST REWRITE: '" << http->uri << "'"); } else if (conn->port->defaultsite /* && !vhost */) { debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: defaultsite=" << conn->port->defaultsite << " + vport=" << vport); int url_sz = strlen(url) + 32 + Config.appendDomainLen + strlen(conn->port->defaultsite); http->uri = (char *)xcalloc(url_sz, 1); char vportStr[32]; vportStr[0] = '\0'; if (vport > 0) { snprintf(vportStr, sizeof(vportStr),":%d",vport); } snprintf(http->uri, url_sz, "%s://%s%s%s", URLScheme(conn->port->transport.protocol).const_str(), conn->port->defaultsite, vportStr, url); debugs(33, 5, "ACCEL DEFAULTSITE REWRITE: '" << http->uri <<"'"); } else if (vport > 0 /* && (!vhost || no Host:) */) { debugs(33, 5, "ACCEL VPORT REWRITE: http_port IP + vport=" << vport); /* Put the local socket IP address as the hostname, with whatever vport we found */ int url_sz = strlen(url) + 32 + Config.appendDomainLen; http->uri = (char *)xcalloc(url_sz, 1); - http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN); + http->getConn()->tcp->local.toHostStr(ipbuf,MAX_IPSTRLEN); snprintf(http->uri, url_sz, "%s://%s:%d%s", URLScheme(conn->port->transport.protocol).const_str(), ipbuf, vport, url); debugs(33, 5, "ACCEL VPORT REWRITE: '" << http->uri << "'"); } } static void prepareTransparentURL(ConnStateData * conn, ClientHttpRequest *http, char *url, const char *req_hdr) { char *host; char ipbuf[MAX_IPSTRLEN]; if (*url != '/') return; /* already in good shape */ /* BUG: Squid cannot deal with '*' URLs (RFC2616 5.1.2) */ if ((host = mime_get_header(req_hdr, "Host")) != NULL) { int url_sz = strlen(url) + 32 + Config.appendDomainLen + strlen(host); http->uri = (char *)xcalloc(url_sz, 1); snprintf(http->uri, url_sz, "%s://%s%s", URLScheme(conn->port->transport.protocol).const_str(), host, url); debugs(33, 5, "TRANSPARENT HOST REWRITE: '" << http->uri <<"'"); } else { /* Put the local socket IP address as the hostname. */ int url_sz = strlen(url) + 32 + Config.appendDomainLen; http->uri = (char *)xcalloc(url_sz, 1); - http->getConn()->clientConnection->local.toHostStr(ipbuf,MAX_IPSTRLEN); + http->getConn()->tcp->local.toHostStr(ipbuf,MAX_IPSTRLEN); snprintf(http->uri, url_sz, "%s://%s:%d%s", URLScheme(http->getConn()->port->transport.protocol).const_str(), - ipbuf, http->getConn()->clientConnection->local.port(), url); + ipbuf, http->getConn()->tcp->local.port(), url); debugs(33, 5, "TRANSPARENT REWRITE: '" << http->uri << "'"); } } /** Parse an HTTP request * * \note Sets result->flags.parsed_ok to 0 if failed to parse the request, * to 1 if the request was correctly parsed. * \param[in] csd a ConnStateData. The caller must make sure it is not null * \param[in] hp an HttpParser * \param[out] mehtod_p will be set as a side-effect of the parsing. * Pointed-to value will be set to Http::METHOD_NONE in case of * parsing failure * \param[out] http_ver will be set as a side-effect of the parsing * \return NULL on incomplete requests, * a ClientSocketContext structure on success or failure. */ static ClientSocketContext * parseHttpRequest(ConnStateData *csd, HttpParser *hp, HttpRequestMethod * method_p, Http::ProtocolVersion *http_ver) { @@ -2294,41 +2255,41 @@ * Process headers after request line * TODO: Use httpRequestParse here. */ /* XXX this code should be modified to take a const char * later! */ req_hdr = (char *) hp->buf + hp->req.end + 1; debugs(33, 3, "parseHttpRequest: req_hdr = {" << req_hdr << "}"); end = (char *) hp->buf + hp->hdr_end; debugs(33, 3, "parseHttpRequest: end = {" << end << "}"); debugs(33, 3, "parseHttpRequest: prefix_sz = " << (int) HttpParserRequestLen(hp) << ", req_line_sz = " << HttpParserReqSz(hp)); /* Ok, all headers are received */ http = new ClientHttpRequest(csd); http->req_sz = HttpParserRequestLen(hp); - result = new ClientSocketContext(csd->clientConnection, http); + result = new ClientSocketContext(csd->tcp, http); tempBuffer.data = result->reqbuf; tempBuffer.length = HTTP_REQBUF_SZ; ClientStreamData newServer = new clientReplyContext(http); ClientStreamData newClient = result; clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach, clientReplyStatus, newServer, clientSocketRecipient, clientSocketDetach, newClient, tempBuffer); debugs(33, 5, "parseHttpRequest: Request Header is\n" <<(hp->buf) + hp->hdr_start); /* set url */ /* * XXX this should eventually not use a malloc'ed buffer; the transformation code * below needs to be modified to not expect a mutable nul-terminated string. */ char *url = (char *)xmalloc(hp->req.u_end - hp->req.u_start + 16); memcpy(url, hp->buf + hp->req.u_start, hp->req.u_end - hp->req.u_start + 1); @@ -2365,198 +2326,119 @@ // We just re-wrote the URL. Must replace the Host: header. // But have not parsed there yet!! flag for local-only handling. http->flags.internal = true; } else if (csd->port->flags.accelSurrogate || csd->switchedToHttps()) { /* accelerator mode */ prepareAcceleratedURL(csd, http, url, req_hdr); } if (!http->uri) { /* No special rewrites have been applied above, use the * requested url. may be rewritten later, so make extra room */ int url_sz = strlen(url) + Config.appendDomainLen + 5; http->uri = (char *)xcalloc(url_sz, 1); strcpy(http->uri, url); } debugs(33, 5, "parseHttpRequest: Complete request received"); // XXX: crop this dump at the end of headers. No need for extras - debugs(11, 2, "HTTP Client " << csd->clientConnection); + debugs(11, 2, "HTTP Client " << csd->tcp); debugs(11, 2, "HTTP Client REQUEST:\n---------\n" << (hp->buf) + hp->req.m_start << "\n----------"); result->flags.parsed_ok = 1; xfree(url); return result; } -int -ConnStateData::getAvailableBufferLength() const -{ - assert (in.allocatedSize > in.notYetUsed); // allocated more than used - const size_t result = in.allocatedSize - in.notYetUsed - 1; - // huge request_header_max_size may lead to more than INT_MAX unused space - assert (static_cast(result) <= INT_MAX); - return result; -} - -bool -ConnStateData::maybeMakeSpaceAvailable() -{ - if (getAvailableBufferLength() < 2) { - size_t newSize; - if (in.allocatedSize >= Config.maxRequestBufferSize) { - debugs(33, 4, "request buffer full: client_request_buffer_max_size=" << Config.maxRequestBufferSize); - return false; - } - if ((newSize=in.allocatedSize * 2) > Config.maxRequestBufferSize) { - newSize=Config.maxRequestBufferSize; - } - in.buf = (char *)memReallocBuf(in.buf, newSize, &in.allocatedSize); - debugs(33, 2, "growing request buffer: notYetUsed=" << in.notYetUsed << " size=" << in.allocatedSize); - } - return true; -} - void ConnStateData::addContextToQueue(ClientSocketContext * context) { ClientSocketContext::Pointer *S; for (S = (ClientSocketContext::Pointer *) & currentobject; S->getRaw(); S = &(*S)->next); *S = context; ++nrequests; } int ConnStateData::getConcurrentRequestCount() const { int result = 0; ClientSocketContext::Pointer *T; for (T = (ClientSocketContext::Pointer *) ¤tobject; T->getRaw(); T = &(*T)->next, ++result); return result; } -int -ConnStateData::connReadWasError(comm_err_t flag, int size, int xerrno) -{ - if (flag != COMM_OK) { - debugs(33, 2, "connReadWasError: FD " << clientConnection << ": got flag " << flag); - return 1; - } - - if (size < 0) { - if (!ignoreErrno(xerrno)) { - debugs(33, 2, "connReadWasError: FD " << clientConnection << ": " << xstrerr(xerrno)); - return 1; - } else if (in.notYetUsed == 0) { - debugs(33, 2, "connReadWasError: FD " << clientConnection << ": no data to process (" << xstrerr(xerrno) << ")"); - } - } - - return 0; -} - -int -ConnStateData::connFinishedWithConn(int size) +const char * +ConnStateData::maybeFinishedWithTcp() { - if (size == 0) { - if (getConcurrentRequestCount() == 0 && in.notYetUsed == 0) { - /* no current or pending requests */ - debugs(33, 4, HERE << clientConnection << " closed"); - return 1; - } else if (!Config.onoff.half_closed_clients) { - /* admin doesn't want to support half-closed client sockets */ - debugs(33, 3, HERE << clientConnection << " aborted (half_closed_clients disabled)"); - notifyAllContexts(0); // no specific error implies abort - return 1; - } + if (getConcurrentRequestCount() == 0 && !inBuf.hasContent()) { + /* no current or pending requests */ + debugs(33, 4, tcp << " closed"); + return "done"; + } else if (!Config.onoff.half_closed_clients) { + /* admin doesn't want to support half-closed client sockets */ + debugs(33, 3, tcp << " aborted (half_closed_clients disabled)"); + noteTcpReadError(0); // no specific error implies abort + return "half_closed_clients disabled"; } - return 0; -} - -void -connNoteUseOfBuffer(ConnStateData* conn, size_t byteCount) -{ - assert(byteCount > 0 && byteCount <= conn->in.notYetUsed); - conn->in.notYetUsed -= byteCount; - debugs(33, 5, HERE << "conn->in.notYetUsed = " << conn->in.notYetUsed); - /* - * If there is still data that will be used, - * move it to the beginning. - */ - - if (conn->in.notYetUsed > 0) - memmove(conn->in.buf, conn->in.buf + byteCount, conn->in.notYetUsed); + return NULL; } /// respond with ERR_TOO_BIG if request header exceeds request_header_max_size void ConnStateData::checkHeaderLimits() { - if (in.notYetUsed < Config.maxRequestHeaderSize) + if (inBuf.contentSize() < static_cast(Config.maxRequestHeaderSize)) return; // can accumulte more header data - debugs(33, 3, "Request header is too large (" << in.notYetUsed << " > " << + debugs(33, 3, "Request header is too large (" << inBuf.contentSize() << " > " << Config.maxRequestHeaderSize << " bytes)"); ClientSocketContext *context = parseHttpRequestAbort(this, "error:request-too-large"); clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); repContext->setReplyToError(ERR_TOO_BIG, Http::scBadRequest, Http::METHOD_NONE, NULL, - clientConnection->remote, NULL, NULL, NULL); + tcp->remote, NULL, NULL, NULL); context->registerWithConn(); context->pullData(); } void -ConnStateData::clientAfterReadingRequests() -{ - // Were we expecting to read more request body from half-closed connection? - if (mayNeedToReadMoreBody() && commIsHalfClosed(clientConnection->fd)) { - debugs(33, 3, HERE << "truncated body: closing half-closed " << clientConnection); - clientConnection->close(); - return; - } - - if (flags.readMore) - readSomeData(); -} - -void ConnStateData::quitAfterError(HttpRequest *request) { // From HTTP p.o.v., we do not have to close after every error detected // at the client-side, but many such errors do require closure and the // client-side code is bad at handling errors so we play it safe. if (request) request->flags.proxyKeepalive = false; flags.readMore = false; - debugs(33,4, HERE << "Will close after error: " << clientConnection); + debugs(33,4, "Will close after error: " << tcp); } #if USE_SSL bool ConnStateData::serveDelayedError(ClientSocketContext *context) { ClientHttpRequest *http = context->http; if (!sslServerBump) return false; assert(sslServerBump->entry); // Did we create an error entry while processing CONNECT? if (!sslServerBump->entry->isEmpty()) { quitAfterError(http->request); // Get the saved error entry and send it to the client by replacing the // ClientHttpRequest store entry with it. clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert(repContext); @@ -2589,147 +2471,147 @@ ACLFilledChecklist check(Config.ssl_client.cert_error, request, dash_str); check.sslErrors = new Ssl::CertErrors(Ssl::CertError(SQUID_X509_V_ERR_DOMAIN_MISMATCH, srvCert)); allowDomainMismatch = (check.fastCheck() == ACCESS_ALLOWED); delete check.sslErrors; check.sslErrors = NULL; } if (!allowDomainMismatch) { quitAfterError(request); clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); // Fill the server IP and hostname for error page generation. HttpRequest::Pointer const & peekerRequest = sslServerBump->request; request->hier.note(peekerRequest->hier.tcpServer, request->GetHost()); // Create an error object and fill it ErrorState *err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request); - err->src_addr = clientConnection->remote; + err->src_addr = tcp->remote; Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail( SQUID_X509_V_ERR_DOMAIN_MISMATCH, srvCert, NULL); err->detail = errDetail; // Save the original request for logging purposes. if (!context->http->al->request) { context->http->al->request = request; HTTPMSGLOCK(context->http->al->request); } repContext->setReplyToError(request->method, err); assert(context->http->out.offset == 0); context->pullData(); return true; } } } return false; } #endif // USE_SSL static void clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, Http::ProtocolVersion http_ver) { ClientHttpRequest *http = context->http; HttpRequest::Pointer request; - bool notedUseOfBuffer = false; + bool reqConsumedFromBuffer = false; bool chunked = false; bool mustReplyToOptions = false; bool unsupportedTe = false; bool expectBody = false; /* We have an initial client stream in place should it be needed */ /* setup our private context */ context->registerWithConn(); if (context->flags.parsed_ok == 0) { clientStreamNode *node = context->getClientReplyContext(); debugs(33, 2, "clientProcessRequest: Invalid Request"); conn->quitAfterError(NULL); // setLogUri should called before repContext->setReplyToError setLogUri(http, http->uri, true); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); switch (hp->request_parse_status) { case Http::scHeaderTooLarge: - repContext->setReplyToError(ERR_TOO_BIG, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, conn->in.buf, NULL); + repContext->setReplyToError(ERR_TOO_BIG, Http::scBadRequest, method, http->uri, conn->tcp->remote, NULL, conn->inBuf.content(), NULL); break; case Http::scMethodNotAllowed: repContext->setReplyToError(ERR_UNSUP_REQ, Http::scMethodNotAllowed, method, http->uri, - conn->clientConnection->remote, NULL, conn->in.buf, NULL); + conn->tcp->remote, NULL, conn->inBuf.content(), NULL); break; default: repContext->setReplyToError(ERR_INVALID_REQ, hp->request_parse_status, method, http->uri, - conn->clientConnection->remote, NULL, conn->in.buf, NULL); + conn->tcp->remote, NULL, conn->inBuf.content(), NULL); } assert(context->http->out.offset == 0); context->pullData(); goto finish; } if ((request = HttpRequest::CreateFromUrlAndMethod(http->uri, method)) == NULL) { clientStreamNode *node = context->getClientReplyContext(); debugs(33, 5, "Invalid URL: " << http->uri); conn->quitAfterError(request.getRaw()); // setLogUri should called before repContext->setReplyToError setLogUri(http, http->uri, true); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); - repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); + repContext->setReplyToError(ERR_INVALID_URL, Http::scBadRequest, method, http->uri, conn->tcp->remote, NULL, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */ /* We currently only support 0.9, 1.0, 1.1 properly */ if ( (http_ver.major == 0 && http_ver.minor != 9) || (http_ver.major > 1) ) { clientStreamNode *node = context->getClientReplyContext(); debugs(33, 5, "Unsupported HTTP version discovered. :\n" << HttpParserHdrBuf(hp)); conn->quitAfterError(request.getRaw()); // setLogUri should called before repContext->setReplyToError setLogUri(http, http->uri, true); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); repContext->setReplyToError(ERR_UNSUP_HTTPVERSION, Http::scHttpVersionNotSupported, method, http->uri, - conn->clientConnection->remote, NULL, HttpParserHdrBuf(hp), NULL); + conn->tcp->remote, NULL, HttpParserHdrBuf(hp), NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } /* compile headers */ /* we should skip request line! */ /* XXX should actually know the damned buffer size here */ if (http_ver.major >= 1 && !request->parseHeader(HttpParserHdrBuf(hp), HttpParserHdrSz(hp))) { clientStreamNode *node = context->getClientReplyContext(); debugs(33, 5, "Failed to parse request headers:\n" << HttpParserHdrBuf(hp)); conn->quitAfterError(request.getRaw()); // setLogUri should called before repContext->setReplyToError setLogUri(http, http->uri, true); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); - repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, method, http->uri, conn->clientConnection->remote, NULL, NULL, NULL); + repContext->setReplyToError(ERR_INVALID_REQ, Http::scBadRequest, method, http->uri, conn->tcp->remote, NULL, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } request->clientConnectionManager = conn; request->flags.accelerated = http->flags.accel; request->flags.sslBumped=conn->switchedToHttps(); request->flags.ignoreCc = conn->port->ignore_cc; // TODO: decouple http->flags.accel from request->flags.sslBumped request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ? !conn->port->allow_direct : 0; #if USE_AUTH if (request->flags.sslBumped) { if (conn->getAuth() != NULL) request->auth_user_request = conn->getAuth(); } #endif @@ -2752,533 +2634,429 @@ } if (internalCheck(request->urlpath.termedBuf())) { if (internalHostnameIs(request->GetHost()) && request->port == getMyPort()) { http->flags.internal = true; } else if (Config.onoff.global_internal_static && internalStaticCheck(request->urlpath.termedBuf())) { request->SetHost(internalHostname()); request->port = getMyPort(); http->flags.internal = true; } } if (http->flags.internal) { request->protocol = AnyP::PROTO_HTTP; request->login[0] = '\0'; } request->flags.internal = http->flags.internal; setLogUri (http, urlCanonicalClean(request.getRaw())); - request->client_addr = conn->clientConnection->remote; // XXX: remove reuest->client_addr member. + request->client_addr = conn->tcp->remote; // XXX: remove reuest->client_addr member. #if FOLLOW_X_FORWARDED_FOR // indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:) // not a details about teh TCP connection itself - request->indirect_client_addr = conn->clientConnection->remote; + request->indirect_client_addr = conn->tcp->remote; #endif /* FOLLOW_X_FORWARDED_FOR */ - request->my_addr = conn->clientConnection->local; + request->my_addr = conn->tcp->local; request->myportname = conn->port->name; request->http_ver = http_ver; // Link this HttpRequest to ConnStateData relatively early so the following complex handling can use it // TODO: this effectively obsoletes a lot of conn->FOO copying. That needs cleaning up later. request->clientConnectionManager = conn; if (request->header.chunked()) { chunked = true; } else if (request->header.has(HDR_TRANSFER_ENCODING)) { const String te = request->header.getList(HDR_TRANSFER_ENCODING); // HTTP/1.1 requires chunking to be the last encoding if there is one unsupportedTe = te.size() && te != "identity"; } // else implied identity coding mustReplyToOptions = (method == Http::METHOD_OPTIONS) && (request->header.getInt64(HDR_MAX_FORWARDS) == 0); if (!urlCheckRequest(request.getRaw()) || mustReplyToOptions || unsupportedTe) { clientStreamNode *node = context->getClientReplyContext(); conn->quitAfterError(request.getRaw()); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); repContext->setReplyToError(ERR_UNSUP_REQ, Http::scNotImplemented, request->method, NULL, - conn->clientConnection->remote, request.getRaw(), NULL, NULL); + conn->tcp->remote, request.getRaw(), NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } if (!chunked && !clientIsContentLengthValid(request.getRaw())) { clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); conn->quitAfterError(request.getRaw()); repContext->setReplyToError(ERR_INVALID_REQ, Http::scLengthRequired, request->method, NULL, - conn->clientConnection->remote, request.getRaw(), NULL, NULL); + conn->tcp->remote, request.getRaw(), NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } if (request->header.has(HDR_EXPECT)) { const String expect = request->header.getList(HDR_EXPECT); const bool supportedExpect = (expect.caseCmp("100-continue") == 0); if (!supportedExpect) { clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); conn->quitAfterError(request.getRaw()); repContext->setReplyToError(ERR_INVALID_REQ, Http::scExpectationFailed, request->method, http->uri, - conn->clientConnection->remote, request.getRaw(), NULL, NULL); + conn->tcp->remote, request.getRaw(), NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } } http->request = request.getRaw(); HTTPMSGLOCK(http->request); clientSetKeepaliveFlag(http); // Let tunneling code be fully responsible for CONNECT requests if (http->request->method == Http::METHOD_CONNECT) { context->mayUseConnection(true); conn->flags.readMore = false; } #if USE_SSL if (conn->switchedToHttps() && conn->serveDelayedError(context)) goto finish; #endif /* Do we expect a request-body? */ expectBody = chunked || request->content_length > 0; if (!context->mayUseConnection() && expectBody) { request->body_pipe = conn->expectRequestBody( chunked ? -1 : request->content_length); // consume header early so that body pipe gets just the body - connNoteUseOfBuffer(conn, http->req_sz); - notedUseOfBuffer = true; + conn->inBuf.consume(http->req_sz); + reqConsumedFromBuffer = true; /* Is it too large? */ if (!chunked && // if chunked, we will check as we accumulate clientIsRequestBodyTooLargeForPolicy(request->content_length)) { clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); conn->quitAfterError(request.getRaw()); repContext->setReplyToError(ERR_TOO_BIG, Http::scRequestEntityTooLarge, Http::METHOD_NONE, NULL, - conn->clientConnection->remote, http->request, NULL, NULL); + conn->tcp->remote, http->request, NULL, NULL); assert(context->http->out.offset == 0); context->pullData(); goto finish; } // We may stop producing, comm_close, and/or call setReplyToError() // below, so quit on errors to avoid http->doCallouts() - if (!conn->handleRequestBodyData()) + if (!conn->processRequestBodyData(conn->inBuf)) goto finish; if (!request->body_pipe->productionEnded()) { debugs(33, 5, HERE << "need more request body"); context->mayUseConnection(true); assert(conn->flags.readMore); } } http->calloutContext = new ClientRequestContext(http); http->doCallouts(); finish: - if (!notedUseOfBuffer) - connNoteUseOfBuffer(conn, http->req_sz); + if (!reqConsumedFromBuffer) + conn->inBuf.consume(http->req_sz); - /* - * DPW 2007-05-18 - * Moved the TCP_RESET feature from clientReplyContext::sendMoreData - * to here because calling comm_reset_close() causes http to - * be freed and the above connNoteUseOfBuffer() would hit an - * assertion, not to mention that we were accessing freed memory. - */ - if (request != NULL && request->flags.resetTcp && Comm::IsConnOpen(conn->clientConnection)) { - debugs(33, 3, HERE << "Sending TCP RST on " << conn->clientConnection); + if (request != NULL && request->flags.resetTcp && Comm::IsConnOpen(conn->tcp)) { + debugs(33, 3, "Sending TCP RST on " << conn->tcp); conn->flags.readMore = false; - comm_reset_close(conn->clientConnection); - } -} - -static void -connStripBufferWhitespace (ConnStateData * conn) -{ - while (conn->in.notYetUsed > 0 && xisspace(conn->in.buf[0])) { - memmove(conn->in.buf, conn->in.buf + 1, conn->in.notYetUsed - 1); - -- conn->in.notYetUsed; + comm_reset_close(conn->tcp); // may free 'http' } } /** * Limit the number of concurrent requests. * \return true when there are available position(s) in the pipeline queue for another request. * \return false when the pipeline queue is full or disabled. */ bool ConnStateData::concurrentRequestQueueFilled() const { const int existingRequestCount = getConcurrentRequestCount(); // default to the configured pipeline size. // add 1 because the head of pipeline is counted in concurrent requests and not prefetch queue const int concurrentRequestLimit = Config.pipeline_max_prefetch + 1; // when queue filled already we cant add more. if (existingRequestCount >= concurrentRequestLimit) { - debugs(33, 3, clientConnection << " max concurrent requests reached (" << concurrentRequestLimit << ")"); - debugs(33, 5, clientConnection << " deferring new request until one is done"); + debugs(33, 3, tcp << " max concurrent requests reached (" << concurrentRequestLimit << ")"); + debugs(33, 5, tcp << " deferring new request until one is done"); return true; } return false; } /** * Attempt to parse one or more requests from the input buffer. * If a request is successfully parsed, even if the next request * is only partially parsed, it will return TRUE. */ bool ConnStateData::clientParseRequests() { HttpRequestMethod method; bool parsed_req = false; - debugs(33, 5, HERE << clientConnection << ": attempting to parse"); + debugs(33, 5, tcp << ": attempting to parse ..."); // Loop while we have read bytes that are not needed for producing the body // On errors, bodyPipe may become nil, but readMore will be cleared - while (in.notYetUsed > 0 && !bodyPipe && flags.readMore) { - connStripBufferWhitespace(this); + while (inBuf.hasContent() && !bodyPipe && flags.readMore) { + inBuf.consumeWhitespacePrefix(); /* Don't try to parse if the buffer is empty */ - if (in.notYetUsed == 0) + if (!inBuf.hasContent()) break; /* Limit the number of concurrent requests */ if (concurrentRequestQueueFilled()) break; - /* Should not be needed anymore */ - /* Terminate the string */ - in.buf[in.notYetUsed] = '\0'; - /* Begin the parsing */ PROF_start(parseHttpRequest); - HttpParserInit(&parser_, in.buf, in.notYetUsed); + HttpParserInit(&parser_, inBuf.content(), inBuf.contentSize()); // XXX: pass MemBuf & /* Process request */ Http::ProtocolVersion http_ver; ClientSocketContext *context = parseHttpRequest(this, &parser_, &method, &http_ver); PROF_stop(parseHttpRequest); /* partial or incomplete request */ if (!context) { // TODO: why parseHttpRequest can just return parseHttpRequestAbort // (which becomes context) but checkHeaderLimits cannot? checkHeaderLimits(); break; } /* status -1 or 1 */ if (context) { - debugs(33, 5, HERE << clientConnection << ": parsed a request"); + debugs(33, 5, tcp << ": parsed a request"); AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout", CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http)); - commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall); + commSetConnTimeout(tcp, Config.Timeout.lifetime, timeoutCall); clientProcessRequest(this, &parser_, context, method, http_ver); parsed_req = true; // XXX: do we really need to parse everything right NOW ? if (context->mayUseConnection()) { debugs(33, 3, HERE << "Not parsing new requests, as this request may need the connection"); break; } } } /* XXX where to 'finish' the parsing pass? */ return parsed_req; } -void -ConnStateData::clientReadRequest(const CommIoCbParams &io) +/** + * called when new request message data has been buffered in inBuf + * may close the connection if we were closing and piped everything out + * + * \return false when read() needs to be abandoned + */ +bool +ConnStateData::processRequestMessageData(MemBuf &aBuf) { - debugs(33,5,HERE << io.conn << " size " << io.size); - Must(reading()); - reader = NULL; - - /* Bail out quickly on COMM_ERR_CLOSING - close handlers will tidy up */ - - if (io.flag == COMM_ERR_CLOSING) { - debugs(33,5, HERE << io.conn << " closing Bailout."); - return; - } - - assert(Comm::IsConnOpen(clientConnection)); - assert(io.conn->fd == clientConnection->fd); - - /* - * Don't reset the timeout value here. The timeout value will be - * set to Config.Timeout.request by httpAccept() and - * clientWriteComplete(), and should apply to the request as a - * whole, not individual read() calls. Plus, it breaks our - * lame half-close detection - */ - if (connReadWasError(io.flag, io.size, io.xerrno)) { - notifyAllContexts(io.xerrno); - io.conn->close(); - return; - } - - if (io.flag == COMM_OK) { - if (io.size > 0) { - kb_incr(&(statCounter.client_http.kbytes_in), io.size); - - // may comm_close or setReplyToError - if (!handleReadData(io.buf, io.size)) - return; - - } else if (io.size == 0) { - debugs(33, 5, HERE << io.conn << " closed?"); - - if (connFinishedWithConn(io.size)) { - clientConnection->close(); - return; - } - - /* It might be half-closed, we can't tell */ - fd_table[io.conn->fd].flags.socket_eof = true; - - commMarkHalfClosed(io.conn->fd); - - fd_note(io.conn->fd, "half-closed"); - - /* There is one more close check at the end, to detect aborted - * (partial) requests. At this point we can't tell if the request - * is partial. - */ - /* Continue to process previously read data */ - } - } - /* Process next request */ - if (getConcurrentRequestCount() == 0) - fd_note(io.fd, "Reading next request"); + fd_note(tcp->fd, "Reading next request"); if (!clientParseRequests()) { - if (!isOpen()) - return; - /* - * If the client here is half closed and we failed - * to parse a request, close the connection. - * The above check with connFinishedWithConn() only - * succeeds _if_ the buffer is empty which it won't - * be if we have an incomplete request. - * XXX: This duplicates ClientSocketContext::keepaliveNextRequest - */ - if (getConcurrentRequestCount() == 0 && commIsHalfClosed(io.fd)) { - debugs(33, 5, HERE << io.conn << ": half-closed connection, no completed request parsed, connection closing."); - clientConnection->close(); - return; + if (stoppedReceiving() && getConcurrentRequestCount() == 0) { + // all outstanding requests done. no more requests able to come in. + debugs(33, 5, tcp << ": half-closed connection, no completed request parsed, connection closing."); + stopSending("no more requests to happen. Abandon incomplete request."); + return false; } } + // XXX: should not be needed anymore if (!isOpen()) - return; + return false; - clientAfterReadingRequests(); + // when readMore is false we abort reading, even if the socket is good + return flags.readMore; } -/** - * called when new request data has been read from the socket - * - * \retval false called comm_close or setReplyToError (the caller should bail) - * \retval true we did not call comm_close or setReplyToError - */ bool -ConnStateData::handleReadData(char *buf, size_t size) +ConnStateData::processReadBuffer(MemBuf &aBuf) { - char *current_buf = in.addressToReadInto(); - - if (buf != current_buf) - memmove(current_buf, buf, size); - - in.notYetUsed += size; - - in.buf[in.notYetUsed] = '\0'; /* Terminate the string */ - // if we are reading a body, stuff data into the body pipe if (bodyPipe != NULL) - return handleRequestBodyData(); - return true; + return processRequestBodyData(aBuf); + + // if we are expecting a message frame, try to parse + return processRequestMessageData(aBuf); } /** - * called when new request body data has been buffered in in.buf + * called when new request body data has been buffered in inBuf * may close the connection if we were closing and piped everything out * * \retval false called comm_close or setReplyToError (the caller should bail) * \retval true we did not call comm_close or setReplyToError */ bool -ConnStateData::handleRequestBodyData() +ConnStateData::processRequestBodyData(MemBuf &aBuf) { assert(bodyPipe != NULL); size_t putSize = 0; - if (in.bodyParser) { // chunked encoding + if (bodyParser_) { // chunked encoding if (const err_type error = handleChunkedRequestBody(putSize)) { abortChunkedRequestBody(error); return false; } } else { // identity encoding - debugs(33,5, HERE << "handling plain request body for " << clientConnection); - putSize = bodyPipe->putMoreData(in.buf, in.notYetUsed); + debugs(33,5, "handling plain request body for " << tcp); + putSize = bodyPipe->putMoreData(aBuf.content(), aBuf.contentSize()); if (!bodyPipe->mayNeedMoreData()) { // BodyPipe will clear us automagically when we produced everything bodyPipe = NULL; } } if (putSize > 0) - connNoteUseOfBuffer(this, putSize); + aBuf.consume(putSize); if (!bodyPipe) { - debugs(33,5, HERE << "produced entire request body for " << clientConnection); + debugs(33,5, "produced entire request body for " << tcp); if (const char *reason = stoppedSending()) { /* we've finished reading like good clients, * now do the close that initiateClose initiated. */ debugs(33, 3, HERE << "closing for earlier sending error: " << reason); - clientConnection->close(); + tcp->close(); return false; } } return true; } /// parses available chunked encoded body bytes, checks size, returns errors err_type ConnStateData::handleChunkedRequestBody(size_t &putSize) { - debugs(33,7, HERE << "chunked from " << clientConnection << ": " << in.notYetUsed); + debugs(33,7, "chunked from " << tcp << ": " << inBuf.contentSize()); try { // the parser will throw on errors - if (!in.notYetUsed) // nothing to do (MemBuf::init requires this check) + if (!inBuf.hasContent()) return ERR_NONE; - MemBuf raw; // ChunkedCodingParser only works with MemBufs - // add one because MemBuf will assert if it cannot 0-terminate - raw.init(in.notYetUsed, in.notYetUsed+1); - raw.append(in.buf, in.notYetUsed); - - const mb_size_t wasContentSize = raw.contentSize(); + const mb_size_t wasContentSize = inBuf.contentSize(); BodyPipeCheckout bpc(*bodyPipe); - const bool parsed = in.bodyParser->parse(&raw, &bpc.buf); + const bool parsed = bodyParser_->parse(&inBuf, &bpc.buf); bpc.checkIn(); - putSize = wasContentSize - raw.contentSize(); + putSize = wasContentSize - inBuf.contentSize(); // dechunk then check: the size limit applies to _dechunked_ content if (clientIsRequestBodyTooLargeForPolicy(bodyPipe->producedSize())) return ERR_TOO_BIG; if (parsed) { finishDechunkingRequest(true); Must(!bodyPipe); return ERR_NONE; // nil bodyPipe implies body end for the caller } // if chunk parser needs data, then the body pipe must need it too - Must(!in.bodyParser->needsMoreData() || bodyPipe->mayNeedMoreData()); + Must(!bodyParser_->needsMoreData() || bodyPipe->mayNeedMoreData()); // if parser needs more space and we can consume nothing, we will stall - Must(!in.bodyParser->needsMoreSpace() || bodyPipe->buf().hasContent()); + Must(!bodyParser_->needsMoreSpace() || bodyPipe->buf().hasContent()); } catch (...) { // TODO: be more specific debugs(33, 3, HERE << "malformed chunks" << bodyPipe->status()); return ERR_INVALID_REQ; } debugs(33, 7, HERE << "need more chunked data" << *bodyPipe->status()); return ERR_NONE; } /// quit on errors related to chunked request body handling void ConnStateData::abortChunkedRequestBody(const err_type error) { finishDechunkingRequest(false); // XXX: The code below works if we fail during initial request parsing, // but if we fail when the server-side works already, the server may send // us its response too, causing various assertions. How to prevent that? #if WE_KNOW_HOW_TO_SEND_ERRORS ClientSocketContext::Pointer context = getCurrentContext(); if (context != NULL && !context->http->out.offset) { // output nothing yet clientStreamNode *node = context->getClientReplyContext(); clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert(repContext); const Http::StatusCode scode = (error == ERR_TOO_BIG) ? Http::scRequestEntityTooLarge : HTTP_BAD_REQUEST; repContext->setReplyToError(error, scode, repContext->http->request->method, repContext->http->uri, CachePeer, repContext->http->request, - in.buf, NULL); + inBuf.content(), NULL); context->pullData(); } else { // close or otherwise we may get stuck as nobody will notice the error? - comm_reset_close(clientConnection); + comm_reset_close(tcp); } #else debugs(33, 3, HERE << "aborting chunked request without error " << error); - comm_reset_close(clientConnection); + comm_reset_close(tcp); #endif flags.readMore = false; } void ConnStateData::noteMoreBodySpaceAvailable(BodyPipe::Pointer ) { - if (!handleRequestBodyData()) - return; - - // too late to read more body - if (!isOpen() || stoppedReceiving()) - return; - - readSomeData(); + if (processReadBuffer(inBuf)) + readSomeData(); } void ConnStateData::noteBodyConsumerAborted(BodyPipe::Pointer ) { // request reader may get stuck waiting for space if nobody consumes body if (bodyPipe != NULL) bodyPipe->enableAutoConsumption(); stopReceiving("virgin request body consumer aborted"); // closes ASAP } /** general lifetime handler for HTTP requests */ void ConnStateData::requestTimeout(const CommTimeoutCbParams &io) { /* * Just close the connection to not confuse browsers * using persistent connections. Some browsers open * a connection and then do not use it until much @@ -3286,97 +3064,94 @@ * the open has already been completed on another * connection) */ debugs(33, 3, "requestTimeout: FD " << io.fd << ": lifetime is expired."); io.conn->close(); } static void clientLifetimeTimeout(const CommTimeoutCbParams &io) { ClientHttpRequest *http = static_cast(io.data); debugs(33, DBG_IMPORTANT, "WARNING: Closing client connection due to lifetime timeout"); debugs(33, DBG_IMPORTANT, "\t" << http->uri); http->al->http.timedout = true; if (Comm::IsConnOpen(io.conn)) io.conn->close(); } ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) : AsyncJob("ConnStateData"), + Comm::TcpReceiver(), #if USE_SSL sslBumpMode(Ssl::bumpEnd), switchedToHttps_(false), sslServerBump(NULL), #endif - stoppedSending_(NULL), - stoppedReceiving_(NULL) + bodyParser_(NULL) { pinning.host = NULL; pinning.port = -1; pinning.pinned = false; pinning.auth = false; pinning.zeroReply = false; pinning.peer = NULL; // store the details required for creating more MasterXaction objects as new requests come in - clientConnection = xact->tcpClient; port = cbdataReference(xact->squidPort.get()); log_addr = xact->tcpClient->remote; log_addr.applyMask(Config.Addrs.client_netmask); - in.buf = (char *)memAllocBuf(CLIENT_REQ_BUF_SZ, &in.allocatedSize); + connectionInit(xact->tcpClient); + inBuf.init(CLIENT_REQ_BUF_SZ, Config.maxRequestBufferSize); + debugs(33, 8, "inBuf space=" << inBuf.spaceSize()); if (port->disable_pmtu_discovery != DISABLE_PMTU_OFF && (transparent() || port->disable_pmtu_discovery == DISABLE_PMTU_ALWAYS)) { #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) int i = IP_PMTUDISC_DONT; - if (setsockopt(clientConnection->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0) - debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " << clientConnection << " : " << xstrerror()); + if (setsockopt(tcp->fd, SOL_IP, IP_MTU_DISCOVER, &i, sizeof(i)) < 0) + debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " << tcp << " : " << xstrerror()); #else static bool reported = false; if (!reported) { debugs(33, DBG_IMPORTANT, "NOTICE: Path MTU discovery disabling is not supported on your platform."); reported = true; } #endif } - typedef CommCbMemFunT Dialer; - AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, ConnStateData::connStateClosed); - comm_add_close_handler(clientConnection->fd, call); - if (Config.onoff.log_fqdn) - fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); + fqdncache_gethostbyaddr(tcp->remote, FQDN_LOOKUP_IF_MISS); #if USE_IDENT if (Ident::TheConfig.identLookup) { ACLFilledChecklist identChecklist(Ident::TheConfig.identLookup, NULL, NULL); identChecklist.src_addr = xact->tcpClient->remote; identChecklist.my_addr = xact->tcpClient->local; if (identChecklist.fastCheck() == ACCESS_ALLOWED) Ident::Start(xact->tcpClient, clientIdentDone, this); } #endif - clientdbEstablished(clientConnection->remote, 1); + clientdbEstablished(tcp->remote, 1); flags.readMore = true; } /** Handle a new connection on HTTP socket. */ void httpAccept(const CommAcceptCbParams ¶ms) { MasterXaction::Pointer xact = params.xaction; AnyP::PortCfgPointer s = xact->squidPort; if (!s.valid()) { // it is possible the call or accept() was still queued when the port was reconfigured debugs(33, 2, "HTTP accept failure: port reconfigured."); return; } if (params.flag != COMM_OK) { // Its possible the call was still queued when the client disconnected debugs(33, 2, "httpAccept: " << s->listenConn << ": accept failure: " << xstrerr(params.xerrno)); @@ -3583,116 +3358,113 @@ X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0)); X509_free(client_cert); } else { debugs(83, 5, "clientNegotiateSSL: FD " << fd << " has no certificate."); } conn->readSomeData(); } /** * If SSL_CTX is given, starts reading the SSL handshake. * Otherwise, calls switchToHttps to generate a dynamic SSL_CTX. */ static void httpsEstablish(ConnStateData *connState, SSL_CTX *sslContext, Ssl::BumpMode bumpMode) { SSL *ssl = NULL; assert(connState); - const Comm::ConnectionPointer &details = connState->clientConnection; + const Comm::ConnectionPointer &details = connState->tcp; if (sslContext && !(ssl = httpsCreate(details, sslContext))) return; typedef CommCbMemFunT TimeoutDialer; AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer, connState, ConnStateData::requestTimeout); commSetConnTimeout(details, Config.Timeout.request, timeoutCall); if (ssl) Comm::SetSelect(details->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0); else { char buf[MAX_IPSTRLEN]; assert(bumpMode != Ssl::bumpNone && bumpMode != Ssl::bumpEnd); HttpRequest::Pointer fakeRequest(new HttpRequest); fakeRequest->SetHost(details->local.toStr(buf, sizeof(buf))); fakeRequest->port = details->local.port(); fakeRequest->clientConnectionManager = connState; - fakeRequest->client_addr = connState->clientConnection->remote; + fakeRequest->client_addr = connState->tcp->remote; #if FOLLOW_X_FORWARDED_FOR - fakeRequest->indirect_client_addr = connState->clientConnection->remote; + fakeRequest->indirect_client_addr = connState->tcp->remote; #endif - fakeRequest->my_addr = connState->clientConnection->local; - fakeRequest->flags.interceptTproxy = ((connState->clientConnection->flags & COMM_TRANSPARENT) != 0 ) ; - fakeRequest->flags.intercepted = ((connState->clientConnection->flags & COMM_INTERCEPTION) != 0); + fakeRequest->my_addr = connState->tcp->local; + fakeRequest->flags.interceptTproxy = ((connState->tcp->flags & COMM_TRANSPARENT) != 0 ) ; + fakeRequest->flags.intercepted = ((connState->tcp->flags & COMM_INTERCEPTION) != 0); fakeRequest->myportname = connState->port->name; if (fakeRequest->flags.interceptTproxy) { if (Config.accessList.spoof_client_ip) { ACLFilledChecklist checklist(Config.accessList.spoof_client_ip, fakeRequest.getRaw(), NULL); fakeRequest->flags.spoofClientIp = (checklist.fastCheck() == ACCESS_ALLOWED); } else fakeRequest->flags.spoofClientIp = true; } else fakeRequest->flags.spoofClientIp = false; debugs(33, 4, HERE << details << " try to generate a Dynamic SSL CTX"); connState->switchToHttps(fakeRequest.getRaw(), bumpMode); } } /** * A callback function to use with the ACLFilledChecklist callback. * In the case of ACCESS_ALLOWED answer initializes a bumped SSL connection, * else reverts the connection to tunnel mode. */ static void httpsSslBumpAccessCheckDone(allow_t answer, void *data) { ConnStateData *connState = (ConnStateData *) data; // if the connection is closed or closing, just return. if (!connState->isOpen()) return; // Require both a match and a positive bump mode to work around exceptional // cases where ACL code may return ACCESS_ALLOWED with zero answer.kind. if (answer == ACCESS_ALLOWED && answer.kind != Ssl::bumpNone) { - debugs(33, 2, HERE << "sslBump needed for " << connState->clientConnection); + debugs(33, 2, "sslBump needed for " << connState->tcp); connState->sslBumpMode = static_cast(answer.kind); httpsEstablish(connState, NULL, (Ssl::BumpMode)answer.kind); } else { - debugs(33, 2, HERE << "sslBump not needed for " << connState->clientConnection); + debugs(33, 2, "sslBump not needed for " << connState->tcp); connState->sslBumpMode = Ssl::bumpNone; // fake a CONNECT request to force connState to tunnel static char ip[MAX_IPSTRLEN]; - static char reqStr[MAX_IPSTRLEN + 80]; - connState->clientConnection->local.toUrl(ip, sizeof(ip)); - snprintf(reqStr, sizeof(reqStr), "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", ip, ip); - bool ret = connState->handleReadData(reqStr, strlen(reqStr)); - if (ret) - ret = connState->clientParseRequests(); - - if (!ret) { - debugs(33, 2, HERE << "Failed to start fake CONNECT request for ssl bumped connection: " << connState->clientConnection); - connState->clientConnection->close(); + connState->tcp->local.toUrl(ip, sizeof(ip)); + MemBuf fake; + fake.Printf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", ip, ip); + if (!connState->injectFakeRequestXXX(fake)) { + debugs(33, 2, "Failed to start fake CONNECT request for ssl bumped connection: " << connState->tcp); + connState->stopReceiving("fake CONNECT request for ssl bump failed"); + connState->stopSending("fake CONNECT request for ssl bump failed"); } } } /** handle a new HTTPS connection */ static void httpsAccept(const CommAcceptCbParams ¶ms) { MasterXaction::Pointer xact = params.xaction; const AnyP::PortCfgPointer s = xact->squidPort; if (!s.valid()) { // it is possible the call or accept() was still queued when the port was reconfigured debugs(33, 2, "HTTPS accept failure: port reconfigured."); return; } if (params.flag != COMM_OK) { // Its possible the call was still queued when the client disconnected debugs(33, 2, "httpsAccept: " << s->listenConn << ": accept failure: " << xstrerr(params.xerrno)); @@ -3778,41 +3550,41 @@ // fake certificate adaptation requires bump-server-first mode if (!sslServerBump) { assert(port->signingCert.get()); certProperties.signWithX509.resetAndLock(port->signingCert.get()); if (port->signPkey.get()) certProperties.signWithPkey.resetAndLock(port->signPkey.get()); certProperties.signAlgorithm = Ssl::algSignTrusted; return; } // In case of an error while connecting to the secure server, use a fake // trusted certificate, with no mimicked fields and no adaptation // algorithms. There is nothing we can mimic so we want to minimize the // number of warnings the user will have to see to get to the error page. assert(sslServerBump->entry); if (sslServerBump->entry->isEmpty()) { if (X509 *mimicCert = sslServerBump->serverCert.get()) certProperties.mimicCert.resetAndLock(mimicCert); ACLFilledChecklist checklist(NULL, sslServerBump->request.getRaw(), - clientConnection != NULL ? clientConnection->rfc931 : dash_str); + tcp != NULL ? tcp->rfc931 : dash_str); checklist.sslErrors = cbdataReference(sslServerBump->sslErrors); for (sslproxy_cert_adapt *ca = Config.ssl_client.cert_adapt; ca != NULL; ca = ca->next) { // If the algorithm already set, then ignore it. if ((ca->alg == Ssl::algSetCommonName && certProperties.setCommonName) || (ca->alg == Ssl::algSetValidAfter && certProperties.setValidAfter) || (ca->alg == Ssl::algSetValidBefore && certProperties.setValidBefore) ) continue; if (ca->aclList && checklist.fastCheck(ca->aclList) == ACCESS_ALLOWED) { const char *alg = Ssl::CertAdaptAlgorithmStr[ca->alg]; const char *param = ca->param; // For parameterless CN adaptation, use hostname from the // CONNECT request. if (ca->alg == Ssl::algSetCommonName) { if (!param) param = sslConnectHostOrIp.termedBuf(); certProperties.commonName = param; certProperties.setCommonName = true; @@ -3922,91 +3694,91 @@ if (signAlgorithm == Ssl::algSignTrusted) { // Add signing certificate to the certificates chain X509 *cert = port->signingCert.get(); if (SSL_CTX_add_extra_chain_cert(sslContext, cert)) { // increase the certificate lock CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509); } else { const int ssl_error = ERR_get_error(); debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL)); } Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); } //else it is self-signed or untrusted do not attrach any certificate Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0'); if (sslContext) { if (!ssl_ctx_cache.add(sslBumpCertKey.termedBuf(), new Ssl::SSL_CTX_Pointer(sslContext))) { // If it is not in storage delete after using. Else storage deleted it. - fd_table[clientConnection->fd].dynamicSslContext = sslContext; + fd_table[tcp->fd].dynamicSslContext = sslContext; } } else { debugs(33, 2, HERE << "Failed to generate SSL cert for " << sslConnectHostOrIp); } } // If generated ssl context = NULL, try to use static ssl context. if (!sslContext) { if (!port->staticSslContext) { - debugs(83, DBG_IMPORTANT, "Closing SSL " << clientConnection->remote << " as lacking SSL context"); - clientConnection->close(); + debugs(83, DBG_IMPORTANT, "Closing SSL " << tcp->remote << " as lacking SSL context"); + tcp->close(); return; } else { debugs(33, 5, HERE << "Using static ssl context."); sslContext = port->staticSslContext.get(); } } - if (!httpsCreate(clientConnection, sslContext)) + if (!httpsCreate(tcp, sslContext)) return; // commSetConnTimeout() was called for this request before we switched. // Disable the client read handler until CachePeer selection is complete - Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0); - Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0); + Comm::SetSelect(tcp->fd, COMM_SELECT_READ, NULL, NULL, 0); + Comm::SetSelect(tcp->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0); switchedToHttps_ = true; } void ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode) { assert(!switchedToHttps_); sslConnectHostOrIp = request->GetHost(); sslCommonName = request->GetHost(); // We are going to read new request flags.readMore = true; - debugs(33, 5, HERE << "converting " << clientConnection << " to SSL"); + debugs(33, 5, "converting " << tcp << " to SSL"); // If sslServerBump is set, then we have decided to deny CONNECT // and now want to switch to SSL to send the error to the client // without even peeking at the origin server certificate. if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) { request->flags.sslPeek = true; sslServerBump = new Ssl::ServerBump(request); // will call httpsPeeked() with certificate and connection, eventually - FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw()); + FwdState::fwdStart(tcp, sslServerBump->entry, sslServerBump->request.getRaw()); return; } // otherwise, use sslConnectHostOrIp getSslContextStart(); } void ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) { Must(sslServerBump != NULL); if (Comm::IsConnOpen(serverConnection)) { SSL *ssl = fd_table[serverConnection->fd].ssl; assert(ssl); Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl)); assert(serverCert.get() != NULL); sslCommonName = Ssl::CommonHostName(serverCert.get()); debugs(33, 5, HERE << "HTTPS server CN: " << sslCommonName << " bumped: " << *serverConnection); @@ -4289,251 +4061,199 @@ /* XXX This cannot really happen, but just to be complete */ return VARY_CANCEL; } else if (strcmp(vary, entry->mem_obj->vary_headers) == 0) { return VARY_MATCH; } else { /* Oops.. we have already been here and still haven't * found the requested variant. Bail out */ debugs(33, DBG_IMPORTANT, "varyEvaluateMatch: Oops. Not a Vary match on second attempt, '" << entry->mem_obj->urlXXX() << "' '" << vary << "'"); return VARY_CANCEL; } } } ACLFilledChecklist * clientAclChecklistCreate(const acl_access * acl, ClientHttpRequest * http) { ConnStateData * conn = http->getConn(); ACLFilledChecklist *ch = new ACLFilledChecklist(acl, http->request, - cbdataReferenceValid(conn) && conn != NULL && conn->clientConnection != NULL ? conn->clientConnection->rfc931 : dash_str); + cbdataReferenceValid(conn) && conn != NULL && conn->tcp != NULL ? conn->tcp->rfc931 : dash_str); ch->al = http->al; /* * hack for ident ACL. It needs to get full addresses, and a place to store * the ident result on persistent connections... */ /* connection oriented auth also needs these two lines for it's operation. */ return ch; } CBDATA_CLASS_INIT(ConnStateData); bool ConnStateData::transparent() const { - return clientConnection != NULL && (clientConnection->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION)); -} - -bool -ConnStateData::reading() const -{ - return reader != NULL; -} - -void -ConnStateData::stopReading() -{ - if (reading()) { - comm_read_cancel(clientConnection->fd, reader); - reader = NULL; - } + return tcp != NULL && (tcp->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION)); } BodyPipe::Pointer ConnStateData::expectRequestBody(int64_t size) { bodyPipe = new BodyPipe(this); if (size >= 0) bodyPipe->setBodySize(size); else startDechunkingRequest(); return bodyPipe; } int64_t -ConnStateData::mayNeedToReadMoreBody() const +ConnStateData::mayNeedToReadMore() const { if (!bodyPipe) return 0; // request without a body or read/produced all body bytes if (!bodyPipe->bodySizeKnown()) return -1; // probably need to read more, but we cannot be sure const int64_t needToProduce = bodyPipe->unproducedSize(); - const int64_t haveAvailable = static_cast(in.notYetUsed); + const int64_t haveAvailable = static_cast(inBuf.contentSize()); if (needToProduce <= haveAvailable) return 0; // we have read what we need (but are waiting for pipe space) return needToProduce - haveAvailable; } void -ConnStateData::stopReceiving(const char *error) -{ - debugs(33, 4, HERE << "receiving error (" << clientConnection << "): " << error << - "; old sending error: " << - (stoppedSending() ? stoppedSending_ : "none")); - - if (const char *oldError = stoppedReceiving()) { - debugs(33, 3, HERE << "already stopped receiving: " << oldError); - return; // nothing has changed as far as this connection is concerned - } - - stoppedReceiving_ = error; - - if (const char *sendError = stoppedSending()) { - debugs(33, 3, HERE << "closing because also stopped sending: " << sendError); - clientConnection->close(); - } -} - -void ConnStateData::expectNoForwarding() { if (bodyPipe != NULL) { debugs(33, 4, HERE << "no consumer for virgin body " << bodyPipe->status()); bodyPipe->expectNoConsumption(); } } /// initialize dechunking state void ConnStateData::startDechunkingRequest() { Must(bodyPipe != NULL); debugs(33, 5, HERE << "start dechunking" << bodyPipe->status()); - assert(!in.bodyParser); - in.bodyParser = new ChunkedCodingParser; + assert(!bodyParser_); + bodyParser_ = new ChunkedCodingParser; } /// put parsed content into input buffer and clean up void ConnStateData::finishDechunkingRequest(bool withSuccess) { debugs(33, 5, HERE << "finish dechunking: " << withSuccess); if (bodyPipe != NULL) { debugs(33, 7, HERE << "dechunked tail: " << bodyPipe->status()); BodyPipe::Pointer myPipe = bodyPipe; stopProducingFor(bodyPipe, withSuccess); // sets bodyPipe->bodySize() Must(!bodyPipe); // we rely on it being nil after we are done with body if (withSuccess) { Must(myPipe->bodySizeKnown()); ClientSocketContext::Pointer context = getCurrentContext(); if (context != NULL && context->http && context->http->request) context->http->request->setContentLength(myPipe->bodySize()); } } - delete in.bodyParser; - in.bodyParser = NULL; -} - -char * -ConnStateData::In::addressToReadInto() const -{ - return buf + notYetUsed; -} - -ConnStateData::In::In() : bodyParser(NULL), - buf (NULL), notYetUsed (0), allocatedSize (0) -{} - -ConnStateData::In::~In() -{ - if (allocatedSize) - memFreeBuf(allocatedSize, buf); - delete bodyParser; // TODO: pool + delete bodyParser_; + bodyParser_ = NULL; } void ConnStateData::sendControlMsg(HttpControlMsg msg) { if (!isOpen()) { debugs(33, 3, HERE << "ignoring 1xx due to earlier closure"); return; } ClientSocketContext::Pointer context = getCurrentContext(); if (context != NULL) { context->writeControlMsg(msg); // will call msg.cbSuccess return; } debugs(33, 3, HERE << " closing due to missing context for 1xx"); - clientConnection->close(); + tcp->close(); } /// Our close handler called by Comm when the pinned connection is closed void ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &io) { // FwdState might repin a failed connection sooner than this close // callback is called for the failed connection. assert(pinning.serverConnection == io.conn); pinning.closeHandler = NULL; // Comm unregisters handlers before calling const bool sawZeroReply = pinning.zeroReply; // reset when unpinning unpinConnection(); - if (sawZeroReply && clientConnection != NULL) { + if (sawZeroReply && tcp != NULL) { debugs(33, 3, "Closing client connection on pinned zero reply."); - clientConnection->close(); + tcp->close(); } } void ConnStateData::pinConnection(const Comm::ConnectionPointer &pinServer, HttpRequest *request, CachePeer *aPeer, bool auth) { char desc[FD_DESC_SZ]; if (Comm::IsConnOpen(pinning.serverConnection)) { if (pinning.serverConnection->fd == pinServer->fd) { startPinnedConnectionMonitoring(); return; } } unpinConnection(); // closes pinned connection, if any, and resets fields pinning.serverConnection = pinServer; debugs(33, 3, HERE << pinning.serverConnection); // when pinning an SSL bumped connection, the request may be NULL const char *pinnedHost = "[unknown]"; if (request) { pinning.host = xstrdup(request->GetHost()); pinning.port = request->port; pinnedHost = pinning.host; } else { pinning.port = pinServer->remote.port(); } pinning.pinned = true; if (aPeer) pinning.peer = cbdataReference(aPeer); pinning.auth = auth; char stmp[MAX_IPSTRLEN]; snprintf(desc, FD_DESC_SZ, "%s pinned connection for %s (%d)", (auth || !aPeer) ? pinnedHost : aPeer->name, - clientConnection->remote.toUrl(stmp,MAX_IPSTRLEN), - clientConnection->fd); + tcp->remote.toUrl(stmp,MAX_IPSTRLEN), + tcp->fd); fd_note(pinning.serverConnection->fd, desc); typedef CommCbMemFunT Dialer; pinning.closeHandler = JobCallback(33, 5, Dialer, this, ConnStateData::clientPinnedConnectionClosed); // remember the pinned connection so that cb does not unpin a fresher one typedef CommCloseCbParams Params; Params ¶ms = GetCommParams(pinning.closeHandler); params.conn = pinning.serverConnection; comm_add_close_handler(pinning.serverConnection->fd, pinning.closeHandler); startPinnedConnectionMonitoring(); } /// Assign a read handler to an idle pinned connection so that we can detect connection closures. void ConnStateData::startPinnedConnectionMonitoring() { if (pinning.readHandler != NULL) return; // already monitoring @@ -4559,42 +4279,42 @@ void ConnStateData::clientPinnedConnectionRead(const CommIoCbParams &io) { pinning.readHandler = NULL; // Comm unregisters handlers before calling if (io.flag == COMM_ERR_CLOSING) return; // close handler will clean up // We could use getConcurrentRequestCount(), but this may be faster. const bool clientIsIdle = !getCurrentContext(); debugs(33, 3, "idle pinned " << pinning.serverConnection << " read " << io.size << (clientIsIdle ? " with idle client" : "")); assert(pinning.serverConnection == io.conn); pinning.serverConnection->close(); // If we are still sending data to the client, do not close now. When we are done sending, // ClientSocketContext::keepaliveNextRequest() checks pinning.serverConnection and will close. // However, if we are idle, then we must close to inform the idle client and minimize races. - if (clientIsIdle && clientConnection != NULL) - clientConnection->close(); + if (clientIsIdle && tcp != NULL) + tcp->close(); } const Comm::ConnectionPointer ConnStateData::validatePinnedConnection(HttpRequest *request, const CachePeer *aPeer) { debugs(33, 7, HERE << pinning.serverConnection); bool valid = true; if (!Comm::IsConnOpen(pinning.serverConnection)) valid = false; else if (pinning.auth && pinning.host && request && strcasecmp(pinning.host, request->GetHost()) != 0) valid = false; else if (request && pinning.port != request->port) valid = false; else if (pinning.peer && !cbdataReferenceValid(pinning.peer)) valid = false; else if (aPeer != pinning.peer) valid = false; if (!valid) { === modified file 'src/client_side.h' --- src/client_side.h 2014-01-05 19:49:23 +0000 +++ src/client_side.h 2014-01-19 05:41:22 +0000 @@ -17,40 +17,41 @@ * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #ifndef SQUID_CLIENTSIDE_H #define SQUID_CLIENTSIDE_H #include "comm.h" +#include "comm/TcpReceiver.h" #include "HttpControlMsg.h" #include "HttpParser.h" #if USE_AUTH #include "auth/UserRequest.h" #endif #if USE_SSL #include "ssl/support.h" #endif class ConnStateData; class ClientHttpRequest; class clientStreamNode; class ChunkedCodingParser; class HelperReply; namespace AnyP { class PortCfg; } // namespace Anyp /** @@ -164,86 +165,69 @@ #if USE_SSL namespace Ssl { class ServerBump; } #endif /** * Manages a connection to a client. * * Multiple requests (up to pipeline_prefetch) can be pipelined. This object is responsible for managing * which one is currently being fulfilled and what happens to the queue if the current one * causes the client connection to be closed early. * * Act as a manager for the connection and passes data in buffer to the current parser. * the parser has ambiguous scope at present due to being made from global functions * I believe this object uses the parser to identify boundaries and kick off the * actual HTTP request handling objects (ClientSocketContext, ClientHttpRequest, HttpRequest) * * If the above can be confirmed accurate we can call this object PipelineManager or similar */ -class ConnStateData : public BodyProducer, public HttpControlMsgSink +class ConnStateData : public Comm::TcpReceiver, public BodyProducer, public HttpControlMsgSink { public: explicit ConnStateData(const MasterXaction::Pointer &xact); ~ConnStateData(); - void readSomeData(); - int getAvailableBufferLength() const; bool areAllContextsForThisConnection() const; void freeAllContexts(); - void notifyAllContexts(const int xerrno); ///< tell everybody about the err /// Traffic parsing bool clientParseRequests(); void readNextRequest(); - bool maybeMakeSpaceAvailable(); ClientSocketContext::Pointer getCurrentContext() const; void addContextToQueue(ClientSocketContext * context); int getConcurrentRequestCount() const; bool isOpen() const; void checkHeaderLimits(); // HttpControlMsgSink API virtual void sendControlMsg(HttpControlMsg msg); - // Client TCP connection details from comm layer. - Comm::ConnectionPointer clientConnection; - - struct In { - In(); - ~In(); - char *addressToReadInto() const; - - ChunkedCodingParser *bodyParser; ///< parses chunked request body - char *buf; - size_t notYetUsed; - size_t allocatedSize; - } in; - - /** number of body bytes we need to comm_read for the "current" request + /** Number of body bytes we need to comm_read for the "current" request. + * Request messages can be aborted. Only incomplete body matter here. * * \retval 0 We do not need to read any [more] body bytes * \retval negative May need more but do not know how many; could be zero! * \retval positive Need to read exactly that many more body bytes */ - int64_t mayNeedToReadMoreBody() const; + virtual int64_t mayNeedToReadMore() const; #if USE_AUTH /** * Fetch the user details for connection based authentication * NOTE: this is ONLY connection based because NTLM and Negotiate is against HTTP spec. */ const Auth::UserRequest::Pointer &getAuth() const { return auth_; } /** * Set the user details for connection-based authentication to use from now until connection closure. * * Any change to existing credentials shows that something invalid has happened. Such as: * - NTLM/Negotiate auth was violated by the per-request headers missing a revalidation token * - NTLM/Negotiate auth was violated by the per-request headers being for another user * - SSL-Bump CONNECT tunnel with persistent credentials has ended */ void setAuth(const Auth::UserRequest::Pointer &aur, const char *cause); #endif /** @@ -258,93 +242,79 @@ struct { bool readMore; ///< needs comm_read (for this request or new requests) bool swanSang; // XXX: temporary flag to check proper cleanup } flags; struct { Comm::ConnectionPointer serverConnection; /* pinned server side connection */ char *host; /* host name of pinned connection */ int port; /* port of pinned connection */ bool pinned; /* this connection was pinned */ bool auth; /* pinned for www authentication */ bool zeroReply; ///< server closed w/o response (ERR_ZERO_SIZE_OBJECT) CachePeer *peer; /* CachePeer the connection goes via */ AsyncCall::Pointer readHandler; ///< detects serverConnection closure AsyncCall::Pointer closeHandler; /*The close handler for pinned server side connection*/ } pinning; /// Squid listening port details where this connection arrived. AnyP::PortCfg *port; bool transparent() const; - bool reading() const; - void stopReading(); ///< cancels comm_read if it is scheduled - - /// true if we stopped receiving the request - const char *stoppedReceiving() const { return stoppedReceiving_; } - /// true if we stopped sending the response - const char *stoppedSending() const { return stoppedSending_; } - /// note request receiving error and close as soon as we write the response - void stopReceiving(const char *error); - /// note response sending error and close as soon as we read the request - void stopSending(const char *error); void expectNoForwarding(); ///< cleans up virgin request [body] forwarding state BodyPipe::Pointer expectRequestBody(int64_t size); virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer); virtual void noteBodyConsumerAborted(BodyPipe::Pointer); - bool handleReadData(char *buf, size_t size); - bool handleRequestBodyData(); + bool processRequestBodyData(MemBuf &aBuf); /** * Correlate the current ConnStateData object with the pinning_fd socket descriptor. */ void pinConnection(const Comm::ConnectionPointer &pinServerConn, HttpRequest *request, CachePeer *peer, bool auth); /** * Decorrelate the ConnStateData object from its pinned CachePeer */ void unpinConnection(); /** * Checks if there is pinning info if it is valid. It can close the server side connection * if pinned info is not valid. \param request if it is not NULL also checks if the pinning info refers to the request client side HttpRequest \param CachePeer if it is not NULL also check if the CachePeer is the pinning CachePeer \return The details of the server side connection (may be closed if failures were present). */ const Comm::ConnectionPointer validatePinnedConnection(HttpRequest *request, const CachePeer *peer); /** * returts the pinned CachePeer if exists, NULL otherwise */ CachePeer *pinnedPeer() const {return pinning.peer;} bool pinnedAuth() const {return pinning.auth;} // pining related comm callbacks void clientPinnedConnectionClosed(const CommCloseCbParams &io); // comm callbacks - void clientReadRequest(const CommIoCbParams &io); - void connStateClosed(const CommCloseCbParams &io); void requestTimeout(const CommTimeoutCbParams ¶ms); // AsyncJob API - virtual bool doneAll() const { return BodyProducer::doneAll() && false;} + virtual bool doneAll() const {return BodyProducer::doneAll() && Comm::TcpReceiver::doneAll();} virtual void swanSong(); /// Changes state so that we close the connection and quit after serving /// the client-side-detected error response instead of getting stuck. void quitAfterError(HttpRequest *request); // meant to be private /// The caller assumes responsibility for connection closure detection. void stopPinnedConnectionMonitoring(); #if USE_SSL /// called by FwdState when it is done bumping the server void httpsPeeked(Comm::ConnectionPointer serverConnection); /// Start to create dynamic SSL_CTX for host or uses static port SSL context. void getSslContextStart(); /** * Done create dynamic ssl certificate. * * \param[in] isNew if generated certificate is new, so we need to add this certificate to storage. */ @@ -371,68 +341,68 @@ /// Otherwise, writes the error to the client and returns true. Also checks /// for SQUID_X509_V_ERR_DOMAIN_MISMATCH on bumped requests. bool serveDelayedError(ClientSocketContext *context); Ssl::BumpMode sslBumpMode; ///< ssl_bump decision (Ssl::bumpEnd if n/a). #else bool switchedToHttps() const { return false; } #endif protected: void startDechunkingRequest(); void finishDechunkingRequest(bool withSuccess); void abortChunkedRequestBody(const err_type error); err_type handleChunkedRequestBody(size_t &putSize); void startPinnedConnectionMonitoring(); void clientPinnedConnectionRead(const CommIoCbParams &io); private: - int connReadWasError(comm_err_t flag, int size, int xerrno); - int connFinishedWithConn(int size); - void clientAfterReadingRequests(); + // Comm::TcpReceiver API + virtual void updateByteCountersOnRead(size_t sz); + virtual bool processReadBuffer(MemBuf &aBuf); + virtual void noteTcpReadError(int xerrno); + virtual const char * maybeFinishedWithTcp(); + + bool processRequestMessageData(MemBuf &aBuf); bool concurrentRequestQueueFilled() const; #if USE_AUTH /// some user details that can be used to perform authentication on this connection Auth::UserRequest::Pointer auth_; #endif HttpParser parser_; // XXX: CBDATA plays with public/private and leaves the following 'private' fields all public... :( #if USE_SSL bool switchedToHttps_; /// The SSL server host name appears in CONNECT request or the server ip address for the intercepted requests String sslConnectHostOrIp; ///< The SSL server host name as passed in the CONNECT request String sslCommonName; ///< CN name for SSL certificate generation String sslBumpCertKey; ///< Key to use to store/retrieve generated certificate /// HTTPS server cert. fetching state for bump-ssl-server-first Ssl::ServerBump *sslServerBump; Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use #endif - /// the reason why we no longer write the response or nil - const char *stoppedSending_; - /// the reason why we no longer read the request or nil - const char *stoppedReceiving_; - - AsyncCall::Pointer reader; ///< set when we are reading BodyPipe::Pointer bodyPipe; // set when we are reading request body + ChunkedCodingParser *bodyParser_; ///< parses chunked request body + CBDATA_CLASS2(ConnStateData); }; void setLogUri(ClientHttpRequest * http, char const *uri, bool cleanUrl = false); const char *findTrailingHTTPVersion(const char *uriAndHTTPVersion, const char *end = NULL); int varyEvaluateMatch(StoreEntry * entry, HttpRequest * req); void clientOpenListenSockets(void); void clientHttpConnectionsClose(void); void httpRequestFree(void *); #endif /* SQUID_CLIENTSIDE_H */ === modified file 'src/client_side_reply.cc' --- src/client_side_reply.cc 2014-01-01 19:20:49 +0000 +++ src/client_side_reply.cc 2014-01-19 05:41:22 +0000 @@ -273,41 +273,41 @@ sc->setDelayId(DelayId::DelayClient(http)); #endif http->request->lastmod = old_entry->lastmod; if (!http->request->header.has(HDR_IF_NONE_MATCH)) { ETag etag = {NULL, -1}; // TODO: make that a default ETag constructor if (old_entry->hasEtag(etag) && !etag.weak) http->request->etag = etag.str; } debugs(88, 5, "clientReplyContext::processExpired : lastmod " << entry->lastmod ); http->storeEntry(entry); assert(http->out.offset == 0); assert(http->request->clientConnectionManager == http->getConn()); /* * A refcounted pointer so that FwdState stays around as long as * this clientReplyContext does */ - Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConnection : NULL; + Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->tcp : NULL; FwdState::Start(conn, http->storeEntry(), http->request, http->al); /* Register with storage manager to receive updates when data comes in. */ if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) debugs(88, DBG_CRITICAL, "clientReplyContext::processExpired: Found ENTRY_ABORTED object"); { /* start counting the length from 0 */ StoreIOBuffer localTempBuffer(HTTP_REQBUF_SZ, 0, tempbuf); storeClientCopy(sc, entry, localTempBuffer, HandleIMSReply, this); } } void clientReplyContext::sendClientUpstreamResponse() { StoreIOBuffer tempresult; removeStoreReference(&old_sc, &old_entry); /* here the data to send is the data we just received */ @@ -638,86 +638,86 @@ /** Check if its a PURGE request to be actioned. */ if (r->method == Http::METHOD_PURGE) { purgeRequest(); return; } /** Check if its an 'OTHER' request. Purge all cached entries if so and continue. */ if (r->method == Http::METHOD_OTHER) { purgeAllCached(); } /** Check if 'only-if-cached' flag is set. Action if so. */ if (http->onlyIfCached()) { processOnlyIfCachedMiss(); return; } /// Deny loops if (r->flags.loopDetected) { http->al->http.code = Http::scForbidden; - err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, http->getConn()->clientConnection->remote, http->request); + err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, http->getConn()->tcp->remote, http->request); createStoreEntry(r->method, RequestFlags()); errorAppendEntry(http->storeEntry(), err); triggerInitialStoreRead(); return; } else { assert(http->out.offset == 0); createStoreEntry(r->method, r->flags); triggerInitialStoreRead(); if (http->redirect.status) { HttpReply *rep = new HttpReply; http->logType = LOG_TCP_REDIRECT; http->storeEntry()->releaseRequest(); rep->redirect(http->redirect.status, http->redirect.location); http->storeEntry()->replaceHttpReply(rep); http->storeEntry()->complete(); return; } /** Check for internal requests. Update Protocol info if so. */ if (http->flags.internal) r->protocol = AnyP::PROTO_INTERNAL; assert(r->clientConnectionManager == http->getConn()); /** Start forwarding to get the new object from network */ - Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->clientConnection : NULL; + Comm::ConnectionPointer conn = http->getConn() != NULL ? http->getConn()->tcp : NULL; FwdState::Start(conn, http->storeEntry(), r, http->al); } } /** * client issued a request with an only-if-cached cache-control directive; * we did not find a cached object that can be returned without * contacting other servers; * respond with a 504 (Gateway Timeout) as suggested in [RFC 2068] */ void clientReplyContext::processOnlyIfCachedMiss() { debugs(88, 4, "clientProcessOnlyIfCachedMiss: '" << RequestMethodStr(http->request->method) << " " << http->uri << "'"); http->al->http.code = Http::scGateway_Timeout; ErrorState *err = clientBuildError(ERR_ONLY_IF_CACHED_MISS, Http::scGateway_Timeout, NULL, - http->getConn()->clientConnection->remote, http->request); + http->getConn()->tcp->remote, http->request); removeClientStoreReference(&sc, http); startError(err); } /// process conditional request from client void clientReplyContext::processConditional(StoreIOBuffer &result) { StoreEntry *const e = http->storeEntry(); if (e->getReply()->sline.status() != Http::scOkay) { debugs(88, 4, "clientReplyContext::processConditional: Reply code " << e->getReply()->sline.status() << " != 200"); http->logType = LOG_TCP_MISS; processMiss(); return; } HttpRequest &r = *http->request; @@ -874,78 +874,78 @@ purgeFoundObject (newEntry); } void clientReplyContext::purgeFoundHead(StoreEntry *newEntry) { if (newEntry->isNull()) purgeDoMissPurge(); else purgeFoundObject (newEntry); } void clientReplyContext::purgeFoundObject(StoreEntry *entry) { assert (entry && !entry->isNull()); if (EBIT_TEST(entry->flags, ENTRY_SPECIAL)) { http->logType = LOG_TCP_DENIED; ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, - http->getConn()->clientConnection->remote, http->request); + http->getConn()->tcp->remote, http->request); startError(err); return; // XXX: leaking unused entry if some store does not keep it } StoreIOBuffer localTempBuffer; /* Swap in the metadata */ http->storeEntry(entry); http->storeEntry()->lock("clientReplyContext::purgeFoundObject"); http->storeEntry()->createMemObject(storeId(), http->log_uri, http->request->method); sc = storeClientListAdd(http->storeEntry(), this); http->logType = LOG_TCP_HIT; reqofs = 0; localTempBuffer.offset = http->out.offset; localTempBuffer.length = next()->readBuffer.length; localTempBuffer.data = next()->readBuffer.data; storeClientCopy(sc, http->storeEntry(), localTempBuffer, CacheHit, this); } void clientReplyContext::purgeRequest() { debugs(88, 3, "Config2.onoff.enable_purge = " << Config2.onoff.enable_purge); if (!Config2.onoff.enable_purge) { http->logType = LOG_TCP_DENIED; - ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, http->getConn()->clientConnection->remote, http->request); + ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL, http->getConn()->tcp->remote, http->request); startError(err); return; } /* Release both IP cache */ ipcacheInvalidate(http->request->GetHost()); if (!http->flags.purging) purgeRequestFindObjectToPurge(); else purgeDoMissPurge(); } void clientReplyContext::purgeDoMissPurge() { http->logType = LOG_TCP_MISS; lookingforstore = 3; StoreEntry::getPublicByRequestMethod(this,http->request, Http::METHOD_GET); } @@ -1756,45 +1756,45 @@ mem_obj->setUris(storeId(), http->log_uri, http->request->method); /** * Here we can see if the object was * created using URL or alternative StoreID from helper. */ debugs(88, 3, "storeId: " << http->storeEntry()->mem_obj->storeId()); } sc = storeClientListAdd(http->storeEntry(), this); #if USE_DELAY_POOLS sc->setDelayId(DelayId::DelayClient(http)); #endif assert(http->logType == LOG_TCP_HIT); reqofs = 0; /* guarantee nothing has been sent yet! */ assert(http->out.size == 0); assert(http->out.offset == 0); if (Ip::Qos::TheConfig.isHitTosActive()) { - Ip::Qos::doTosLocalHit(http->getConn()->clientConnection); + Ip::Qos::doTosLocalHit(http->getConn()->tcp); } if (Ip::Qos::TheConfig.isHitNfmarkActive()) { - Ip::Qos::doNfmarkLocalHit(http->getConn()->clientConnection); + Ip::Qos::doNfmarkLocalHit(http->getConn()->tcp); } localTempBuffer.offset = reqofs; localTempBuffer.length = getNextNode()->readBuffer.length; localTempBuffer.data = getNextNode()->readBuffer.data; storeClientCopy(sc, http->storeEntry(), localTempBuffer, CacheHit, this); } else { /* MISS CASE, http->logType is already set! */ processMiss(); } } /** The next node has removed itself from the stream. */ void clientReplyDetach(clientStreamNode * node, ClientHttpRequest * http) { /** detach from the stream */ clientStreamDetach(node, http); } @@ -1861,56 +1861,56 @@ localTempBuffer.data = source; clientStreamCallback((clientStreamNode*)http->client_stream.head->data, http, NULL, localTempBuffer); } clientStreamNode * clientReplyContext::next() const { assert ( (clientStreamNode*)http->client_stream.head->next->data == getNextNode()); return getNextNode(); } void clientReplyContext::sendBodyTooLargeError() { Ip::Address tmp_noaddr; tmp_noaddr.setNoAddr(); // TODO: make a global const http->logType = LOG_TCP_DENIED_REPLY; ErrorState *err = clientBuildError(ERR_TOO_BIG, Http::scForbidden, NULL, - http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr, + http->getConn() != NULL ? http->getConn()->tcp->remote : tmp_noaddr, http->request); removeClientStoreReference(&(sc), http); HTTPMSGUNLOCK(reply); startError(err); } /// send 412 (Precondition Failed) to client void clientReplyContext::sendPreconditionFailedError() { http->logType = LOG_TCP_HIT; ErrorState *const err = clientBuildError(ERR_PRECONDITION_FAILED, Http::scPreconditionFailed, - NULL, http->getConn()->clientConnection->remote, http->request); + NULL, http->getConn()->tcp->remote, http->request); removeClientStoreReference(&sc, http); HTTPMSGUNLOCK(reply); startError(err); } /// send 304 (Not Modified) to client void clientReplyContext::sendNotModified() { StoreEntry *e = http->storeEntry(); const time_t timestamp = e->timestamp; HttpReply *const temprep = e->getReply()->make304(); http->logType = LOG_TCP_IMS_HIT; removeClientStoreReference(&sc, http); createStoreEntry(http->request->method, RequestFlags()); e = http->storeEntry(); // Copy timestamp from the original entry so the 304 // reply has a meaningful Age: header. e->timestampsSet(); e->timestamp = timestamp; @@ -1983,41 +1983,41 @@ void clientReplyContext::processReplyAccessResult(const allow_t &accessAllowed) { debugs(88, 2, "The reply for " << RequestMethodStr(http->request->method) << " " << http->uri << " is " << accessAllowed << ", because it matched '" << (AclMatchedName ? AclMatchedName : "NO ACL's") << "'" ); if (accessAllowed != ACCESS_ALLOWED) { ErrorState *err; err_type page_id; page_id = aclGetDenyInfoPage(&Config.denyInfoList, AclMatchedName, 1); http->logType = LOG_TCP_DENIED_REPLY; if (page_id == ERR_NONE) page_id = ERR_ACCESS_DENIED; Ip::Address tmp_noaddr; tmp_noaddr.setNoAddr(); err = clientBuildError(page_id, Http::scForbidden, NULL, - http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr, + http->getConn() != NULL ? http->getConn()->tcp->remote : tmp_noaddr, http->request); removeClientStoreReference(&sc, http); HTTPMSGUNLOCK(reply); startError(err); return; } /* Ok, the reply is allowed, */ http->loggingEntry(http->storeEntry()); ssize_t body_size = reqofs - reply->hdr_sz; if (body_size < 0) { reqofs = reply->hdr_sz; body_size = 0; } @@ -2079,85 +2079,85 @@ return; } void clientReplyContext::sendMoreData (StoreIOBuffer result) { if (deleting) return; StoreEntry *entry = http->storeEntry(); ConnStateData * conn = http->getConn(); // too late, our conn is closing // TODO: should we also quit? if (conn == NULL) { debugs(33,3, "not sending more data to a closed connection" ); return; } if (!conn->isOpen()) { - debugs(33,3, "not sending more data to closing connection " << conn->clientConnection); + debugs(33,3, "not sending more data to closing connection " << conn->tcp); return; } if (conn->pinning.zeroReply) { - debugs(33,3, "not sending more data after a pinned zero reply " << conn->clientConnection); + debugs(33,3, "not sending more data after a pinned zero reply " << conn->tcp); return; } char *buf = next()->readBuffer.data; if (buf != result.data) { /* we've got to copy some data */ assert(result.length <= next()->readBuffer.length); memcpy(buf, result.data, result.length); } - if (reqofs==0 && !logTypeIsATcpHit(http->logType) && Comm::IsConnOpen(conn->clientConnection)) { + if (reqofs==0 && !logTypeIsATcpHit(http->logType) && Comm::IsConnOpen(conn->tcp)) { if (Ip::Qos::TheConfig.isHitTosActive()) { - Ip::Qos::doTosLocalMiss(conn->clientConnection, http->request->hier.code); + Ip::Qos::doTosLocalMiss(conn->tcp, http->request->hier.code); } if (Ip::Qos::TheConfig.isHitNfmarkActive()) { - Ip::Qos::doNfmarkLocalMiss(conn->clientConnection, http->request->hier.code); + Ip::Qos::doNfmarkLocalMiss(conn->tcp, http->request->hier.code); } } /* We've got the final data to start pushing... */ flags.storelogiccomplete = 1; reqofs += result.length; assert(reqofs <= HTTP_REQBUF_SZ || flags.headersSent); assert(http->request != NULL); /* ESI TODO: remove this assert once everything is stable */ assert(http->client_stream.head->data && cbdataReferenceValid(http->client_stream.head->data)); makeThisHead(); debugs(88, 5, "clientReplyContext::sendMoreData: " << http->uri << ", " << reqofs << " bytes (" << result.length << " new bytes)"); debugs(88, 5, "clientReplyContext::sendMoreData:" - << conn->clientConnection << + << conn->tcp << " '" << entry->url() << "'" << " out.offset=" << http->out.offset); /* update size of the request */ reqsize = reqofs; if (errorInStream(result, reqofs)) { sendStreamError(result); return; } if (flags.headersSent) { pushStreamData (result, buf); return; } cloneReply(); /* handle headers */ === modified file 'src/client_side_request.cc' --- src/client_side_request.cc 2013-12-06 23:52:26 +0000 +++ src/client_side_request.cc 2014-02-04 17:51:02 +0000 @@ -145,48 +145,49 @@ store_id_done = false; store_id_fail_count = 0; no_cache_done = false; interpreted_req_hdrs = false; #if USE_SSL sslBumpCheckDone = false; #endif debugs(85,3, HERE << this << " ClientRequestContext constructed"); } CBDATA_CLASS_INIT(ClientHttpRequest); ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) : #if USE_ADAPTATION AsyncJob("ClientHttpRequest"), #endif loggingEntry_(NULL) { setConn(aConn); al = new AccessLogEntry; + al->tcpClient = clientConnection = aConn->tcp; + al->cache.start_time = current_time; - al->tcpClient = clientConnection = aConn->clientConnection; al->cache.port = cbdataReference(aConn->port); al->cache.caddr = aConn->log_addr; #if USE_SSL - if (aConn->clientConnection != NULL && aConn->clientConnection->isOpen()) { - if (SSL *ssl = fd_table[aConn->clientConnection->fd].ssl) + if (aConn->tcp != NULL && aConn->tcp->isOpen()) { + if (SSL *ssl = fd_table[aConn->tcp->fd].ssl) al->cache.sslClientCert.reset(SSL_get_peer_certificate(ssl)); } #endif dlinkAdd(this, &active, &ClientActiveRequests); #if USE_ADAPTATION request_satisfaction_mode = false; #endif #if USE_SSL sslBumpNeed_ = Ssl::bumpEnd; #endif } /* * returns true if client specified that the object must come from the cache * without contacting origin server */ bool ClientHttpRequest::onlyIfCached()const { assert(request); @@ -517,92 +518,92 @@ if (answer != ACCESS_ALLOWED && answer != ACCESS_DENIED) { debugs(28, DBG_CRITICAL, "ERROR: Processing X-Forwarded-For. Stopping at IP address: " << request->indirect_client_addr ); } /* process actual access ACL as normal. */ calloutContext->clientAccessCheck(); } #endif /* FOLLOW_X_FORWARDED_FOR */ static void hostHeaderIpVerifyWrapper(const ipcache_addrs* ia, const DnsLookupDetails &dns, void *data) { ClientRequestContext *c = static_cast(data); c->hostHeaderIpVerify(ia, dns); } void ClientRequestContext::hostHeaderIpVerify(const ipcache_addrs* ia, const DnsLookupDetails &dns) { - Comm::ConnectionPointer clientConn = http->getConn()->clientConnection; + Comm::ConnectionPointer clientConn = http->getConn()->tcp; // note the DNS details for the transaction stats. http->request->recordLookup(dns); if (ia != NULL && ia->count > 0) { // Is the NAT destination IP in DNS? for (int i = 0; i < ia->count; ++i) { if (clientConn->local.matchIPAddr(ia->in_addrs[i]) == 0) { debugs(85, 3, HERE << "validate IP " << clientConn->local << " possible from Host:"); http->request->flags.hostVerified = true; http->doCallouts(); return; } debugs(85, 3, HERE << "validate IP " << clientConn->local << " non-match from Host: IP " << ia->in_addrs[i]); } } debugs(85, 3, HERE << "FAIL: validate IP " << clientConn->local << " possible from Host:"); hostHeaderVerifyFailed("local IP", "any domain IP"); } void ClientRequestContext::hostHeaderVerifyFailed(const char *A, const char *B) { // IP address validation for Host: failed. Admin wants to ignore them. // NP: we do not yet handle CONNECT tunnels well, so ignore for them if (!Config.onoff.hostStrictVerify && http->request->method != Http::METHOD_CONNECT) { - debugs(85, 3, "SECURITY ALERT: Host header forgery detected on " << http->getConn()->clientConnection << + debugs(85, 3, "SECURITY ALERT: Host header forgery detected on " << http->getConn()->tcp << " (" << A << " does not match " << B << ") on URL: " << urlCanonical(http->request)); // NP: it is tempting to use 'flags.noCache' but that is all about READing cache data. // The problems here are about WRITE for new cache content, which means flags.cachable http->request->flags.cachable = false; // MUST NOT cache (for now) // XXX: when we have updated the cache key to base on raw-IP + URI this cacheable limit can go. http->request->flags.hierarchical = false; // MUST NOT pass to peers (for now) // XXX: when we have sorted out the best way to relay requests properly to peers this hierarchical limit can go. http->doCallouts(); return; } debugs(85, DBG_IMPORTANT, "SECURITY ALERT: Host header forgery detected on " << - http->getConn()->clientConnection << " (" << A << " does not match " << B << ")"); + http->getConn()->tcp << " (" << A << " does not match " << B << ")"); debugs(85, DBG_IMPORTANT, "SECURITY ALERT: By user agent: " << http->request->header.getStr(HDR_USER_AGENT)); debugs(85, DBG_IMPORTANT, "SECURITY ALERT: on URL: " << urlCanonical(http->request)); // IP address validation for Host: failed. reject the connection. clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data; clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); repContext->setReplyToError(ERR_CONFLICT_HOST, Http::scConflict, http->request->method, NULL, - http->getConn()->clientConnection->remote, + http->getConn()->tcp->remote, http->request, NULL, #if USE_AUTH http->getConn() != NULL && http->getConn()->getAuth() != NULL ? http->getConn()->getAuth() : http->request->auth_user_request); #else NULL); #endif node = (clientStreamNode *)http->client_stream.tail->data; clientStreamRead(node, http, node->readBuffer); } void ClientRequestContext::hostHeaderVerify() { // Require a Host: header. const char *host = http->request->header.getStr(HDR_HOST); if (!host) { // TODO: dump out the HTTP/1.1 error about missing host header. @@ -636,42 +637,42 @@ } uint16_t port = 0; if (portStr) { *portStr = '\0'; // strip the ':' if (*(++portStr) != '\0') { char *end = NULL; int64_t ret = strtoll(portStr, &end, 10); if (end == portStr || *end != '\0' || ret < 1 || ret > 0xFFFF) { // invalid port details. Replace the ':' *(--portStr) = ':'; portStr = NULL; } else port = (ret & 0xFFFF); } } debugs(85, 3, HERE << "validate host=" << host << ", port=" << port << ", portStr=" << (portStr?portStr:"NULL")); if (http->request->flags.intercepted || http->request->flags.interceptTproxy) { // verify the Host: port (if any) matches the apparent destination - if (portStr && port != http->getConn()->clientConnection->local.port()) { - debugs(85, 3, HERE << "FAIL on validate port " << http->getConn()->clientConnection->local.port() << + if (portStr && port != http->getConn()->tcp->local.port()) { + debugs(85, 3, "FAIL on validate port " << http->getConn()->tcp->local.port() << " matches Host: port " << port << " (" << portStr << ")"); hostHeaderVerifyFailed("intercepted port", portStr); } else { // XXX: match the scheme default port against the apparent destination // verify the destination DNS is one of the Host: headers IPs ipcache_nbgethostbyname(host, hostHeaderIpVerifyWrapper, this); } } else if (!Config.onoff.hostStrictVerify) { debugs(85, 3, HERE << "validate skipped."); http->doCallouts(); } else if (strlen(host) != strlen(http->request->GetHost())) { // Verify forward-proxy requested URL domain matches the Host: header debugs(85, 3, HERE << "FAIL on validate URL domain length " << http->request->GetHost() << " matches Host: " << host); hostHeaderVerifyFailed(host, http->request->GetHost()); } else if (matchDomainName(host, http->request->GetHost()) != 0) { // Verify forward-proxy requested URL domain matches the Host: header debugs(85, 3, HERE << "FAIL on validate URL domain " << http->request->GetHost() << " matches Host: " << host); hostHeaderVerifyFailed(host, http->request->GetHost()); } else if (portStr && port != http->request->port) { @@ -806,75 +807,75 @@ /* WWW authorisation needed */ status = Http::scUnauthorized; } #else // need auth, but not possible to do. status = Http::scForbidden; #endif if (page_id == ERR_NONE) page_id = ERR_CACHE_ACCESS_DENIED; } else { status = Http::scForbidden; if (page_id == ERR_NONE) page_id = ERR_ACCESS_DENIED; } Ip::Address tmpnoaddr; tmpnoaddr.setNoAddr(); error = clientBuildError(page_id, status, NULL, - http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmpnoaddr, + http->getConn() != NULL ? http->getConn()->tcp->remote : tmpnoaddr, http->request ); #if USE_AUTH error->auth_user_request = http->getConn() != NULL && http->getConn()->getAuth() != NULL ? http->getConn()->getAuth() : http->request->auth_user_request; #endif readNextRequest = true; } /* ACCESS_ALLOWED continues here ... */ safe_free(http->uri); http->uri = xstrdup(urlCanonical(http->request)); http->doCallouts(); } #if USE_ADAPTATION void ClientHttpRequest::noteAdaptationAclCheckDone(Adaptation::ServiceGroupPointer g) { debugs(93,3,HERE << this << " adaptationAclCheckDone called"); #if ICAP_CLIENT Adaptation::Icap::History::Pointer ih = request->icapHistory(); if (ih != NULL) { if (getConn() != NULL) { - ih->rfc931 = getConn()->clientConnection->rfc931; + ih->rfc931 = getConn()->tcp->rfc931; #if USE_SSL - assert(getConn()->clientConnection != NULL); - ih->ssluser = sslGetUserEmail(fd_table[getConn()->clientConnection->fd].ssl); + assert(getConn()->tcp != NULL); + ih->ssluser = sslGetUserEmail(fd_table[getConn()->tcp->fd].ssl); #endif } ih->log_uri = log_uri; ih->req_sz = req_sz; } #endif if (!g) { debugs(85,3, HERE << "no adaptation needed"); doCallouts(); return; } startAdaptation(g); } #endif static void clientRedirectAccessCheckDone(allow_t answer, void *data) @@ -1322,42 +1323,42 @@ // update the current working ClientHttpRequest fields safe_free(http->uri); http->uri = xstrdup(urlCanonical(new_request)); HTTPMSGUNLOCK(old_request); http->request = new_request; HTTPMSGLOCK(http->request); } else { debugs(85, DBG_CRITICAL, "ERROR: URL-rewrite produces invalid request: " << old_request->method << " " << urlNote << " " << old_request->http_ver); delete new_request; } } } } break; } /* FIXME PIPELINE: This is innacurate during pipelining */ - if (http->getConn() != NULL && Comm::IsConnOpen(http->getConn()->clientConnection)) - fd_note(http->getConn()->clientConnection->fd, http->uri); + if (http->getConn() != NULL && Comm::IsConnOpen(http->getConn()->tcp)) + fd_note(http->getConn()->tcp->fd, http->uri); assert(http->uri); http->doCallouts(); } /** * This method handles the different replies from StoreID helper. */ void ClientRequestContext::clientStoreIdDone(const HelperReply &reply) { HttpRequest *old_request = http->request; debugs(85, 5, "'" << http->uri << "' result=" << reply); assert(store_id_state == REDIRECT_PENDING); store_id_state = REDIRECT_DONE; // Put helper response Notes into the transaction state record (ALE) eventually // do it early to ensure that no matter what the outcome the notes are present. if (http->al != NULL) { @@ -1508,41 +1509,41 @@ #endif /* * Identify requests that do not go through the store and client side stream * and forward them to the appropriate location. All other requests, request * them. */ void ClientHttpRequest::processRequest() { debugs(85, 4, "clientProcessRequest: " << RequestMethodStr(request->method) << " '" << uri << "'"); if (request->method == Http::METHOD_CONNECT && !redirect.status) { #if USE_SSL if (sslBumpNeeded()) { sslBumpStart(); return; } #endif logType = LOG_TCP_MISS; - getConn()->stopReading(); // tunnels read for themselves + getConn()->stopReadingXXX(); // tunnels read for themselves tunnelStart(this, &out.size, &al->http.code, al); return; } httpStart(); } void ClientHttpRequest::httpStart() { PROF_start(httpStart); logType = LOG_TAG_NONE; debugs(85, 4, LogTags_str[logType] << " for '" << uri << "'"); /* no one should have touched this */ assert(out.offset == 0); /* Use the Stream Luke */ clientStreamNode *node = (clientStreamNode *)client_stream.tail->data; clientStreamRead(node, this, node->readBuffer); PROF_stop(httpStart); @@ -1560,71 +1561,76 @@ // called when comm_write has completed static void SslBumpEstablish(const Comm::ConnectionPointer &, char *, size_t, comm_err_t errflag, int, void *data) { ClientHttpRequest *r = static_cast(data); debugs(85, 5, HERE << "responded to CONNECT: " << r << " ? " << errflag); assert(r && cbdataReferenceValid(r)); r->sslBumpEstablish(errflag); } void ClientHttpRequest::sslBumpEstablish(comm_err_t errflag) { // Bail out quickly on COMM_ERR_CLOSING - close handlers will tidy up if (errflag == COMM_ERR_CLOSING) return; if (errflag) { debugs(85, 3, HERE << "CONNECT response failure in SslBump: " << errflag); - getConn()->clientConnection->close(); + getConn()->tcp->close(); return; } // We lack HttpReply which logRequest() uses to log the status code. // TODO: Use HttpReply instead of the "200 Connection established" string. al->http.code = 200; #if USE_AUTH // Preserve authentication info for the ssl-bumped request if (request->auth_user_request != NULL) getConn()->setAuth(request->auth_user_request, "SSL-bumped CONNECT"); #endif assert(sslBumpNeeded()); getConn()->switchToHttps(request, sslBumpNeed_); } void ClientHttpRequest::sslBumpStart() { debugs(85, 5, HERE << "Confirming " << Ssl::bumpMode(sslBumpNeed_) << - "-bumped CONNECT tunnel on FD " << getConn()->clientConnection); + "-bumped CONNECT tunnel on FD " << getConn()->tcp); getConn()->sslBumpMode = sslBumpNeed_; // send an HTTP 200 response to kick client SSL negotiation // TODO: Unify with tunnel.cc and add a Server(?) header - static const char *const conn_established = "HTTP/1.1 200 Connection established\r\n\r\n"; + static const char *fakeRequest = "HTTP/1.1 200 Connection established\r\n\r\n"; + static MemBuf conn_established; + if (!conn_established.hasContent()) { + conn_established.init(); + conn_established.append(fakeRequest, strlen(fakeRequest)); + } AsyncCall::Pointer call = commCbCall(85, 5, "ClientSocketContext::sslBumpEstablish", CommIoCbPtrFun(&SslBumpEstablish, this)); - Comm::Write(getConn()->clientConnection, conn_established, strlen(conn_established), call, NULL); + getConn()->sendSomeData(conn_established, call); } #endif bool ClientHttpRequest::gotEnough() const { /** TODO: should be querying the stream. */ int64_t contentLength = memObject()->getReply()->bodySize(request->method); assert(contentLength >= 0); if (out.offset < contentLength) return false; return true; } void ClientHttpRequest::storeEntry(StoreEntry *newEntry) @@ -1749,59 +1755,59 @@ if (!calloutContext->interpreted_req_hdrs) { debugs(83, 3, HERE << "Doing clientInterpretRequestHeaders()"); calloutContext->interpreted_req_hdrs = 1; clientInterpretRequestHeaders(this); } if (!calloutContext->no_cache_done) { calloutContext->no_cache_done = true; if (Config.accessList.noCache && request->flags.cachable) { debugs(83, 3, HERE << "Doing calloutContext->checkNoCache()"); calloutContext->checkNoCache(); return; } } } // if !calloutContext->error if (!calloutContext->tosToClientDone) { calloutContext->tosToClientDone = true; - if (getConn() != NULL && Comm::IsConnOpen(getConn()->clientConnection)) { + if (getConn() != NULL && Comm::IsConnOpen(getConn()->tcp)) { ACLFilledChecklist ch(NULL, request, NULL); ch.src_addr = request->client_addr; ch.my_addr = request->my_addr; tos_t tos = aclMapTOS(Ip::Qos::TheConfig.tosToClient, &ch); if (tos) - Ip::Qos::setSockTos(getConn()->clientConnection, tos); + Ip::Qos::setSockTos(getConn()->tcp, tos); } } if (!calloutContext->nfmarkToClientDone) { calloutContext->nfmarkToClientDone = true; - if (getConn() != NULL && Comm::IsConnOpen(getConn()->clientConnection)) { + if (getConn() != NULL && Comm::IsConnOpen(getConn()->tcp)) { ACLFilledChecklist ch(NULL, request, NULL); ch.src_addr = request->client_addr; ch.my_addr = request->my_addr; nfmark_t mark = aclMapNfmark(Ip::Qos::TheConfig.nfmarkToClient, &ch); if (mark) - Ip::Qos::setSockNfmark(getConn()->clientConnection, mark); + Ip::Qos::setSockNfmark(getConn()->tcp, mark); } } #if USE_SSL // We need to check for SslBump even if the calloutContext->error is set // because bumping may require delaying the error until after CONNECT. if (!calloutContext->sslBumpCheckDone) { calloutContext->sslBumpCheckDone = true; if (calloutContext->sslBumpAccessCheck()) return; /* else no ssl bump required*/ } #endif if (calloutContext->error) { const char *storeUri = request->storeId(); StoreEntry *e= storeCreateEntry(storeUri, storeUri, request->flags, request->method); #if USE_SSL if (sslBumpNeeded()) { // set final error but delay sending until we bump @@ -2014,76 +2020,76 @@ void ClientHttpRequest::endRequestSatisfaction() { debugs(85,4, HERE << this << " ends request satisfaction"); assert(request_satisfaction_mode); stopConsumingFrom(adaptedBodySource); // TODO: anything else needed to end store entry formation correctly? storeEntry()->complete(); } void ClientHttpRequest::noteBodyProducerAborted(BodyPipe::Pointer) { assert(!virginHeadSource); stopConsumingFrom(adaptedBodySource); debugs(85,3, HERE << "REQMOD body production failed"); if (request_satisfaction_mode) { // too late to recover or serve an error request->detailError(ERR_ICAP_FAILURE, ERR_DETAIL_CLT_REQMOD_RESP_BODY); - const Comm::ConnectionPointer c = getConn()->clientConnection; + const Comm::ConnectionPointer c = getConn()->tcp; Must(Comm::IsConnOpen(c)); c->close(); // drastic, but we may be writing a response already } else { handleAdaptationFailure(ERR_DETAIL_CLT_REQMOD_REQ_BODY); } } void ClientHttpRequest::handleAdaptationFailure(int errDetail, bool bypassable) { debugs(85,3, HERE << "handleAdaptationFailure(" << bypassable << ")"); const bool usedStore = storeEntry() && !storeEntry()->isEmpty(); const bool usedPipe = request->body_pipe != NULL && request->body_pipe->consumedSize() > 0; if (bypassable && !usedStore && !usedPipe) { debugs(85,3, HERE << "ICAP REQMOD callout failed, bypassing: " << calloutContext); if (calloutContext) doCallouts(); return; } debugs(85,3, HERE << "ICAP REQMOD callout failed, responding with error"); clientStreamNode *node = (clientStreamNode *)client_stream.tail->prev->data; clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert(repContext); // The original author of the code also wanted to pass an errno to // setReplyToError, but it seems unlikely that the errno reflects the // true cause of the error at this point, so I did not pass it. if (calloutContext) { Ip::Address noAddr; noAddr.setNoAddr(); ConnStateData * c = getConn(); calloutContext->error = clientBuildError(ERR_ICAP_FAILURE, Http::scInternalServerError, NULL, - c != NULL ? c->clientConnection->remote : noAddr, + c != NULL ? c->tcp->remote : noAddr, request ); #if USE_AUTH calloutContext->error->auth_user_request = c != NULL && c->getAuth() != NULL ? c->getAuth() : request->auth_user_request; #endif calloutContext->error->detailError(errDetail); calloutContext->readNextRequest = true; if (c != NULL) c->expectNoForwarding(); doCallouts(); } //else if(calloutContext == NULL) is it possible? } #endif === modified file 'src/comm/Makefile.am' --- src/comm/Makefile.am 2012-03-29 09:22:41 +0000 +++ src/comm/Makefile.am 2013-10-16 12:14:31 +0000 @@ -6,25 +6,27 @@ ## Library holding comm socket handlers libcomm_la_SOURCES= \ AcceptLimiter.cc \ AcceptLimiter.h \ ConnOpener.cc \ ConnOpener.h \ Connection.cc \ Connection.h \ forward.h \ IoCallback.cc \ IoCallback.h \ Loops.h \ ModDevPoll.cc \ ModEpoll.cc \ ModKqueue.cc \ ModPoll.cc \ ModSelect.cc \ ModSelectWin32.cc \ TcpAcceptor.cc \ TcpAcceptor.h \ + TcpReceiver.cc \ + TcpReceiver.h \ UdpOpenDialer.h \ Write.cc \ Write.h \ \ comm_internal.h === added file 'src/comm/TcpReceiver.cc' --- src/comm/TcpReceiver.cc 1970-01-01 00:00:00 +0000 +++ src/comm/TcpReceiver.cc 2014-02-04 13:44:39 +0000 @@ -0,0 +1,297 @@ +/* + * DEBUG: section 05 TCP Read + * + * - level 2 minor TCP errors + * - level 3 duplicate reasons for halting I/O (bugs? only need to halt once) + * - level 4 reasons for errors and halting I/O + * - level 5 common I/O and buffer activity + */ +#include "squid.h" +#include "comm.h" +#include "comm/TcpReceiver.h" +#include "comm/Write.h" +#include "Debug.h" +#include "fd.h" +#include "fde.h" +#include "StatCounters.h" +#include "tools.h" + +Comm::TcpReceiver::TcpReceiver() : + AsyncJob("Comm::TcpReceiver"), + tcp(), + stoppedReceiving_(NULL), + stoppedSending_(NULL), + closed_(), + reader_() +{} + +void +Comm::TcpReceiver::connectionInit(const Comm::ConnectionPointer &c) +{ + tcp = c; + + typedef CommCbMemFunT Dialer; + closed_ = JobCallback(33, 5, Dialer, this, Comm::TcpReceiver::handleConnectionClosed); + comm_add_close_handler(tcp->fd, closed_); +} + +bool +Comm::TcpReceiver::doneAll() const +{ + return stoppedSending() && stoppedReceiving() && !inBuf.hasContent() && AsyncJob::doneAll(); +} + +void +Comm::TcpReceiver::swanSong() +{ + if (closed_ != NULL) + closed_->cancel("Comm::TcpReceiver::swanSong"); + + if (Comm::IsConnOpen(tcp)) + tcp->close(); +} + +void +Comm::TcpReceiver::releaseConnection(const char *reason) +{ + // Used by clients to release the connection before + // storing it in a Pconn pool for reuse. + comm_remove_close_handler(tcp->fd, closed_); + closed_->cancel(reason); + if (reading()) { + comm_read_cancel(tcp->fd, reader_); + reader_ = NULL; + } + // XXX: remove half-closed handler ?? +} + +void +Comm::TcpReceiver::stopReadingXXX() +{ + /* NP: This is a hack needed to allow TunnelStateData + * to take control of a socket despite any scheduled read. + */ + if (reading()) { + comm_read_cancel(tcp->fd, reader_); + reader_ = NULL; + } +} + +bool +Comm::TcpReceiver::injectFakeRequestXXX(MemBuf &fake) +{ + if (inBuf.hasContent()) + fake.append(inBuf.content(), inBuf.contentSize()); + + inBuf.reset(); + inBuf.append(fake.content(), fake.contentSize()); + + return processReadBuffer(inBuf); +} + +void +Comm::TcpReceiver::stopReceiving(const char *error) +{ + debugs(5, 4, "receiving error (" << tcp << "): " << error << + "; old sending error: " << (stoppedSending() ? stoppedSending_ : "none")); + + if (const char *oldError = stoppedReceiving()) { + debugs(5, 3, "already stopped receiving: " << oldError); + return; // nothing has changed as far as this connection is concerned + } + + stoppedReceiving_ = error; + + if (const char *sendError = stoppedSending()) { + debugs(5, 3, "closing because also stopped sending: " << sendError); + closed_->cancel("graceful close"); + tcp->close(); + } +} + +void +Comm::TcpReceiver::stopSending(const char *error) +{ + debugs(5, 4, "sending error (" << tcp << "): " << error << + "; old receiving error: " << + (stoppedReceiving() ? stoppedReceiving_ : "none")); + + if (const char *oldError = stoppedSending()) { + debugs(5, 3, "already stopped sending: " << oldError); + return; // nothing has changed as far as this connection is concerned + } + stoppedSending_ = error; + + if (!stoppedReceiving()) { + if (const int64_t expecting = mayNeedToReadMore()) { + debugs(5, 5, "must still read " << expecting << + " bytes with " << inBuf.contentSize() << " unused"); + return; // wait for the receiver to finish reading + } + } + closed_->cancel("Comm::TcpReceiver::stopSending"); + tcp->close(); +} + +bool +Comm::TcpReceiver::maybeMakeSpaceAvailable() +{ + /* Grow the bufer whenever there is <2 bytes of space available. + * + * why <2? Because delayAwareRead() won't actually read if + * you ask it to read 1 byte. The delayed read(2) request + * just gets re-queued until the client side drains, then + * the I/O thread hangs. + * Better to return false and cause the caller not to register + * any read handler now until we get a notification from someone + * that its okay to read again if the buffer cannot grow. + */ + + if (inBuf.spaceSize() < 2) { + if (!inBuf.hasPotentialSpace()) { + debugs(5, 5, "buffer full: " << inBuf.contentSize() << " of " << (inBuf.max_capacity-1) << " bytes"); + return false; + } + (void)inBuf.space(inBuf.contentSize()*2); + debugs(5, 5, "growing buffer: content-size=" << inBuf.contentSize() << " capacity=" << inBuf.capacity); + } + + // in case the grow operation above failed for any reason. + return (inBuf.spaceSize() > 1); +} + +void +Comm::TcpReceiver::readSomeData() +{ + // one read() at a time + if (reading()) + return; + + // useless to read() after aborting read() + if (stoppedReceiving()) + return; + + // useless to try when there is no buffer space available + if (!maybeMakeSpaceAvailable()) + return; + + debugs(5, 5, tcp << ": reading... buffer space " << inBuf.spaceSize() << " bytes."); + + typedef CommCbMemFunT Dialer; + reader_ = JobCallback(33, 5, Dialer, this, Comm::TcpReceiver::readHandler); + if (!maybeDelayRead(reader_)) + comm_read(tcp, inBuf.space(), inBuf.spaceSize(), reader_); +} + +void +Comm::TcpReceiver::readHandler(const CommIoCbParams &io) +{ + debugs(5, 5, io.conn << " size " << io.size); + Must(reading()); + reader_ = NULL; + + /* Bail out quickly on COMM_ERR_CLOSING - close handlers will tidy up */ + if (io.flag == COMM_ERR_CLOSING) { + debugs(5, 5, io.conn << " closing Bailout."); + return; + } + + Must(Comm::IsConnOpen(tcp)); + Must(io.conn->fd == tcp->fd); + + /* NOTE: + * Don't reset the read timeout value here. + * The timeout value will be set to a specific config + * value which applies to each message as a whole, not + * individual read(2) calls. + * + * Plus, it breaks our lame half-close monitor detection + */ + + if (io.flag != COMM_OK) { + debugs(5, 2, tcp << ": got flag " << io.flag); + noteTcpReadError(io.xerrno); + io.conn->close(); + return; + } + + if (io.size < 0) { + if (!ignoreErrno(io.xerrno)) { + debugs(5, 2, tcp << " read failure: " << xstrerr(io.xerrno)); + noteTcpReadError(io.xerrno); + io.conn->close(); + return; + } else if (!inBuf.hasContent()) { + debugs(5, 2, tcp << ": no data to process (" << xstrerr(io.xerrno) << ")"); + } + + // grow the buffer if necessary, then read if there is space. + if (!maybeMakeSpaceAvailable()) { + stopReceiving("full read buffer - but processing does not free any space"); + // fall through to setup the half-closed monitoring + } else { + // schedule another read() - unless aborted by maybeMakeSpaceAvailable() failure + readSomeData(); + return; // wait for the results of this attempt. + } + + } else if (io.size > 0) { + updateByteCountersOnRead(io.size); + inBuf.append(io.buf, io.size); + + bool mayReadMore = true; + // pass handling on to child instance code + if (inBuf.hasContent()) + mayReadMore = processReadBuffer(inBuf); + // gro the buffer is necessary, after processing as much as possible out already + if (mayReadMore && !maybeMakeSpaceAvailable()) { + stopReceiving("full read buffer - but processing does not free any space"); + mayReadMore = false; + } + // schedule another read() - unless aborted by processing actions + if (mayReadMore) + readSomeData(); + + return; // everything is fine. stop. + + } else if (io.size == 0) { + debugs(5, 5, io.conn << " closed?"); + stopReceiving("zero sized read(2) result"); + // fall through to setup the half-closed monitoring + } + + // Ask the child class if it can stop immediately. + // It may still need to send via the connection, or + // to process any remainders in the buffer. + if (const char *reason = maybeFinishedWithTcp()) { + stopSending(reason); // will close connection + return; + } + + // if already stopped sending, the above will close the connection + // avoid setting up monitoring on an already closed FD. + if (stoppedSending()) + return; + + /* It might be half-closed, we can't tell */ + fd_table[io.conn->fd].flags.socket_eof = true; + commMarkHalfClosed(io.conn->fd); + fd_note(io.conn->fd, "half-closed"); +} + +void +Comm::TcpReceiver::sendSomeData(MemBuf &mb, AsyncCall::Pointer &callback) +{ + assert(!stoppedSending()); + + // TODO: allow writing multiple segments by queueing + Comm::Write(tcp, &mb, callback); +} + +/* This is a handler normally called by comm_close() */ +void +Comm::TcpReceiver::handleConnectionClosed(const CommCloseCbParams &io) +{ + stopReceiving("TCP connection closed"); + stopSending("TCP connection closed"); +} === added file 'src/comm/TcpReceiver.h' --- src/comm/TcpReceiver.h 1970-01-01 00:00:00 +0000 +++ src/comm/TcpReceiver.h 2014-02-04 12:59:13 +0000 @@ -0,0 +1,143 @@ +#ifndef SQUID_SRC_COMM_TCPRECEIVER_H +#define SQUID_SRC_COMM_TCPRECEIVER_H + +#include "base/AsyncCall.h" +#include "base/AsyncJob.h" +#include "comm/Connection.h" +#include "comm_err_t.h" +#include "MemBuf.h" + +class CommIoCbParams; + +namespace Comm { + +// XXX: finalize and describe scope +class TcpReceiver : virtual public AsyncJob +{ +public: + /// Attempt to read some data. + /// Will call processReadBuffer() when there is data to process. + void readSomeData(); + + /// note receiving error and close as soon as we have done with writing as well + void stopReceiving(const char *error); + + /// \see Comm::Write(const Comm::ConnectionPointer &conn, MemBuf *mb, AsyncCall::Pointer &callback) + void sendSomeData(MemBuf &mb, AsyncCall::Pointer &callback); + + /// note response sending error and close as soon as we read the request + void stopSending(const char *error); + + /** Hack to cancel a read if one is scheduled, without blocking future socket use. + * \note Avoid using this method when possible. If the read(2) is done but + * AsyncCall is still queued the read(2) bytes will be lost permanently. + */ + void stopReadingXXX(); + + /// Inject a fake request as if the client had sent it. + // httpsSslBumpAccessCheckDone() should be doing explicit state setup instead of parsing. + bool injectFakeRequestXXX(MemBuf &fake); + + /* public instead of protected due to wide-ranging layer violations in client_side*.cc (at least) */ + Comm::ConnectionPointer tcp; + MemBuf inBuf; + +protected: + TcpReceiver(); + virtual ~TcpReceiver() {} + + // AsyncJob API + virtual bool doneAll() const; + virtual void swanSong(); + + /// initialize the connection event handlers + /// close(2) callback etc. + void connectionInit(const Comm::ConnectionPointer &c); + + /// releases connection event handlers without closing it + void releaseConnection(const char *reason); + + /// whether a read(2) operation is currently underway + bool reading() const {return reader_!=NULL;} + + /// true if we stopped receiving data + const char *stoppedReceiving() const { return stoppedReceiving_; } + + /// true if we stopped sending the response + const char *stoppedSending() const { return stoppedSending_; } + + /** Called when sending has stopped to check if more read(2)s may be required. + * + * \retval >0 Number of bytes expected still to arrive. + * \retval -1 More data still expected to arrive, unknown number of bytes at this time. + * \retval 0 No more bytes expected right now. + */ + virtual int64_t mayNeedToReadMore() const = 0; + + /// called when buffer may be used to receive new network data + bool maybeMakeSpaceAvailable(); + + /** + * Called before scheduling a read(2) operation in case the + * child class uses delay_pools to slow read(2) I/O down. + * \return true if this read has been deferred. + */ + // TODO: make the delaying part of TcpReceivers task + virtual bool maybeDelayRead(const AsyncCall::Pointer &call) {return false;} + + /** called when there is new buffered data to process. + * + * If the processing requires further read(2) to be halted temporarily it + * may return false. The processor is then responsible for ensuring that + * readSomeData() is called when read(2) calls are to be resumed. + * + * \retval true if additional read(2) should be scheduled by the caller. + * \retval false if read(2) is to be suspended. + */ + virtual bool processReadBuffer(MemBuf &) = 0; + + /** Called when there is an error performing read(2) + * so the child class can perform any cleanup or error handling. + * The TCP connection will be closed immediately after this method + * completes. + */ + virtual void noteTcpReadError(int) {} + + /// Called when there has been a successful read(2). + /// The child class is responsible for data counting. + virtual void updateByteCountersOnRead(size_t) = 0; + + /// callback to handle read(2) input + void readHandler(const CommIoCbParams &io); + + /** + * called when TCP 0-size read(2) occurs to ask the child class + * whether it is able to stop sending yet. + * + * There may also be unhandled data in the buffer to clean up + * or make use of. No futher read(2) will be attempted. + * + * \return a reason for stopping I/O, + * or NULL to continue I/O with client half-closed. + */ + virtual const char * maybeFinishedWithTcp() = 0; + +private: + void handleConnectionClosed(const CommCloseCbParams &io); + + /// the reason why we no longer read(2) or nil + const char *stoppedReceiving_; + + /// the reason why we no longer write(2) or nil + const char *stoppedSending_; + + /// callback to stop traffic processing when FD closes + AsyncCall::Pointer closed_; + + ///< set when we are reading + AsyncCall::Pointer reader_; +}; + +} // namespace Comm + +#endif /* SQUID_SRC_COMM_TCPRECEIVER_H */ === modified file 'src/esi/Esi.cc' --- src/esi/Esi.cc 2013-10-25 00:13:46 +0000 +++ src/esi/Esi.cc 2013-10-29 02:33:43 +0000 @@ -1435,42 +1435,42 @@ /* don't touch incoming, it's a pointer into buffered anyway */ } ErrorState *clientBuildError (err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *); /* This can ONLY be used before we have sent *any* data to the client */ void ESIContext::fail () { debugs(86, 5, "ESIContext::fail: this=" << this); /* check preconditions */ assert (pos == 0); /* cleanup current state */ freeResources (); /* Stop altering thisNode request */ flags.oktosend = 1; flags.finished = 1; /* don't honour range requests - for errors we send it all */ flags.error = 1; /* create an error object */ - // XXX: with the in-direction on remote IP. does the http->getConn()->clientConnection exist? - ErrorState * err = clientBuildError(errorpage, errorstatus, NULL, http->getConn()->clientConnection->remote, http->request); + // XXX: with the in-direction on remote IP. does the http->getConn()->tcp exist? + ErrorState * err = clientBuildError(errorpage, errorstatus, NULL, http->getConn()->tcp->remote, http->request); err->err_msg = errormessage; errormessage = NULL; rep = err->BuildHttpReply(); assert (rep->body.hasContent()); size_t errorprogress = rep->body.contentSize(); /* Tell esiSend where to start sending from */ outbound_offset = 0; /* copy the membuf from the reply to outbound */ while (errorprogress < (size_t)rep->body.contentSize()) { appendOutboundData(new ESISegment); errorprogress += outboundtail->append(rep->body.content() + errorprogress, rep->body.contentSize() - errorprogress); } /* the esiCode now thinks that the error is the outbound, * and all processing has finished. */ /* Send as much as we can */ send (); /* don't cancel anything. The stream nodes will clean up after === modified file 'src/external_acl.cc' --- src/external_acl.cc 2013-11-29 04:41:07 +0000 +++ src/external_acl.cc 2013-12-01 21:31:45 +0000 @@ -1002,48 +1002,48 @@ // if we fail to go async, we still return NULL and the caller // will detect the failure in ACLExternal::match(). (void)ch->goAsync(IdentLookup::Instance()); return NULL; } break; #endif case _external_acl_format::EXT_ACL_SRC: str = ch->src_addr.toStr(buf,sizeof(buf)); break; case _external_acl_format::EXT_ACL_SRCPORT: snprintf(buf, sizeof(buf), "%d", request->client_addr.port()); str = buf; break; #if USE_SQUID_EUI case _external_acl_format::EXT_ACL_SRCEUI48: - if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL && - request->clientConnectionManager->clientConnection->remoteEui48.encode(buf, sizeof(buf))) + if (request->clientConnectionManager.valid() && request->clientConnectionManager->tcp != NULL && + request->clientConnectionManager->tcp->remoteEui48.encode(buf, sizeof(buf))) str = buf; break; case _external_acl_format::EXT_ACL_SRCEUI64: - if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL && - request->clientConnectionManager->clientConnection->remoteEui64.encode(buf, sizeof(buf))) + if (request->clientConnectionManager.valid() && request->clientConnectionManager->tcp != NULL && + request->clientConnectionManager->tcp->remoteEui64.encode(buf, sizeof(buf))) str = buf; break; #endif case _external_acl_format::EXT_ACL_MYADDR: str = request->my_addr.toStr(buf, sizeof(buf)); break; case _external_acl_format::EXT_ACL_MYPORT: snprintf(buf, sizeof(buf), "%d", request->my_addr.port()); str = buf; break; case _external_acl_format::EXT_ACL_URI: str = urlCanonical(request); break; case _external_acl_format::EXT_ACL_DST: str = request->GetHost(); break; @@ -1099,75 +1099,75 @@ } break; case _external_acl_format::EXT_ACL_HEADER_REPLY_MEMBER: if (reply) { sb = reply->header.getByNameListMember(format->header, format->member, format->separator); str = sb.termedBuf(); } break; case _external_acl_format::EXT_ACL_HEADER_REPLY_ID_MEMBER: if (reply) { sb = reply->header.getListMember(format->header_id, format->member, format->separator); str = sb.termedBuf(); } break; #if USE_SSL case _external_acl_format::EXT_ACL_USER_CERT_RAW: - if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) { - SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl; + if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->tcp)) { + SSL *ssl = fd_table[ch->conn()->tcp->fd].ssl; if (ssl) str = sslGetUserCertificatePEM(ssl); } break; case _external_acl_format::EXT_ACL_USER_CERTCHAIN_RAW: - if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) { - SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl; + if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->tcp)) { + SSL *ssl = fd_table[ch->conn()->tcp->fd].ssl; if (ssl) str = sslGetUserCertificateChainPEM(ssl); } break; case _external_acl_format::EXT_ACL_USER_CERT: - if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) { - SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl; + if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->tcp)) { + SSL *ssl = fd_table[ch->conn()->tcp->fd].ssl; if (ssl) str = sslGetUserAttribute(ssl, format->header); } break; case _external_acl_format::EXT_ACL_USER_CA_CERT: - if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) { - SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl; + if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->tcp)) { + SSL *ssl = fd_table[ch->conn()->tcp->fd].ssl; if (ssl) str = sslGetCAAttribute(ssl, format->header); } break; #endif #if USE_AUTH case _external_acl_format::EXT_ACL_EXT_USER: str = request->extacl_user.termedBuf(); break; #endif case _external_acl_format::EXT_ACL_EXT_LOG: str = request->extacl_log.termedBuf(); break; case _external_acl_format::EXT_ACL_TAG: str = request->tag.termedBuf(); break; case _external_acl_format::EXT_ACL_ACLNAME: str = acl_data->name; === modified file 'src/format/Format.cc' --- src/format/Format.cc 2014-01-05 02:56:31 +0000 +++ src/format/Format.cc 2014-01-19 05:41:22 +0000 @@ -334,45 +334,45 @@ if (al->cache.caddr.isAnyAddr()) // e.g., ICAP OPTIONS lack client out = "-"; else out = fqdncache_gethostbyaddr(al->cache.caddr, FQDN_LOOKUP_IF_MISS); if (!out) { out = al->cache.caddr.toStr(tmp,1024); } break; case LFT_CLIENT_PORT: if (al->request) { outint = al->request->client_addr.port(); doint = 1; } break; case LFT_CLIENT_EUI: #if USE_SQUID_EUI // TODO make the ACL checklist have a direct link to any TCP details. - if (al->request && al->request->clientConnectionManager.valid() && al->request->clientConnectionManager->clientConnection != NULL) { - if (al->request->clientConnectionManager->clientConnection->remote.isIPv4()) - al->request->clientConnectionManager->clientConnection->remoteEui48.encode(tmp, 1024); + if (al->request && al->request->clientConnectionManager.valid() && al->request->clientConnectionManager->tcp != NULL) { + if (al->request->clientConnectionManager->tcp->remote.isIPv4()) + al->request->clientConnectionManager->tcp->remoteEui48.encode(tmp, 1024); else - al->request->clientConnectionManager->clientConnection->remoteEui64.encode(tmp, 1024); + al->request->clientConnectionManager->tcp->remoteEui64.encode(tmp, 1024); out = tmp; } #else out = "-"; #endif break; case LFT_SERVER_IP_ADDRESS: if (al->hier.tcpServer != NULL) { out = al->hier.tcpServer->remote.toStr(tmp,sizeof(tmp)); } break; case LFT_SERVER_FQDN_OR_PEER_NAME: out = al->hier.host; break; case LFT_SERVER_PORT: if (al->hier.tcpServer != NULL) { outint = al->hier.tcpServer->remote.port(); === modified file 'src/http.cc' --- src/http.cc 2013-12-06 23:52:26 +0000 +++ src/http.cc 2014-01-19 12:46:23 +0000 @@ -86,123 +86,117 @@ #define SQUID_ENTER_THROWING_CODE() try { #define SQUID_EXIT_THROWING_CODE(status) \ status = true; \ } \ catch (const std::exception &e) { \ debugs (11, 1, "Exception error:" << e.what()); \ status = false; \ } CBDATA_CLASS_INIT(HttpStateData); static const char *const crlf = "\r\n"; static void httpMaybeRemovePublic(StoreEntry *, Http::StatusCode); static void copyOneHeaderFromClientsideRequestToUpstreamRequest(const HttpHeaderEntry *e, const String strConnection, const HttpRequest * request, HttpHeader * hdr_out, const int we_do_ranges, const HttpStateFlags &); //Declared in HttpHeaderTools.cc void httpHdrAdd(HttpHeader *heads, HttpRequest *request, const AccessLogEntryPointer &al, HeaderWithAclList &headers_add); -HttpStateData::HttpStateData(FwdState *theFwdState) : AsyncJob("HttpStateData"), ServerStateData(theFwdState), +HttpStateData::HttpStateData(FwdState *theFwdState) : + AsyncJob("HttpStateData"), + Comm::TcpReceiver(), + ServerStateData(theFwdState), lastChunk(0), header_bytes_read(0), reply_bytes_read(0), body_bytes_truncated(0), httpChunkDecoder(NULL) { - debugs(11,5,HERE << "HttpStateData " << this << " created"); + debugs(11, 5, "HttpStateData " << this << " created"); ignoreCacheControl = false; surrogateNoStore = false; - serverConnection = fwd->serverConnection(); - readBuf = new MemBuf; - readBuf->init(16*1024, 256*1024); + // XXX: there is no config option to set the HTTP server-side buffer size + // So for now use 16KB but allow growth up to 2x the larger of reply_header_max_size and read_ahead_gap + // which defaults to 16-128 KB + inBuf.init(16*1024, 2*max(static_cast(Config.maxReplyHeaderSize), Config.readAheadGap)); // reset peer response time stats for %hier.peer_http_request_sent.tv_sec = 0; request->hier.peer_http_request_sent.tv_usec = 0; if (fwd->serverConnection() != NULL) _peer = cbdataReference(fwd->serverConnection()->getPeer()); /* might be NULL */ if (_peer) { request->flags.proxying = true; /* * This NEIGHBOR_PROXY_ONLY check probably shouldn't be here. * We might end up getting the object from somewhere else if, * for example, the request to this neighbor fails. */ if (_peer->options.proxy_only) entry->releaseRequest(); #if USE_DELAY_POOLS entry->setNoDelay(_peer->options.no_delay); #endif } - /* - * register the handler to free HTTP state data when the FD closes - */ - typedef CommCbMemFunT Dialer; - closeHandler = JobCallback(9, 5, Dialer, this, HttpStateData::httpStateConnClosed); - comm_add_close_handler(serverConnection->fd, closeHandler); + connectionInit(theFwdState->serverConnection()); } HttpStateData::~HttpStateData() { /* * don't forget that ~ServerStateData() gets called automatically */ - if (!readBuf->isNull()) - readBuf->clean(); - - delete readBuf; - if (httpChunkDecoder) delete httpChunkDecoder; cbdataReferenceDone(_peer); - debugs(11,5, HERE << "HttpStateData " << this << " destroyed; " << serverConnection); + debugs(11, 5, "HttpStateData " << this << " destroyed; " << tcp); } -const Comm::ConnectionPointer & -HttpStateData::dataConnection() const +void +HttpStateData::swanSong() { - return serverConnection; + Comm::TcpReceiver::swanSong(); } -void -HttpStateData::httpStateConnClosed(const CommCloseCbParams ¶ms) +const Comm::ConnectionPointer & +HttpStateData::dataConnection() const { - debugs(11, 5, "httpStateFree: FD " << params.fd << ", httpState=" << params.data); - mustStop("HttpStateData::httpStateConnClosed"); + return tcp; } void HttpStateData::httpTimeout(const CommTimeoutCbParams ¶ms) { - debugs(11, 4, HERE << serverConnection << ": '" << entry->url() << "'" ); + debugs(11, 4, tcp << ": '" << entry->url() << "'"); if (entry->store_status == STORE_PENDING) { fwd->fail(new ErrorState(ERR_READ_TIMEOUT, Http::scGateway_Timeout, fwd->request)); } - serverConnection->close(); + stopSending("HttpStateData timeout"); + stopReceiving("HttpStateData timeout"); } /// Remove an existing public store entry if the incoming response (to be /// stored in a currently private entry) is going to invalidate it. static void httpMaybeRemovePublic(StoreEntry * e, Http::StatusCode status) { int remove = 0; int forbidden = 0; StoreEntry *pe; // If the incoming response already goes into a public entry, then there is // nothing to remove. This protects ready-for-collapsing entries as well. if (!EBIT_TEST(e->flags, KEY_PRIVATE)) return; switch (status) { case Http::scOkay: @@ -663,120 +657,120 @@ #endif debugs(11, 3, "httpMakeVaryMark: " << vstr); return vstr.termedBuf(); } void HttpStateData::keepaliveAccounting(HttpReply *reply) { if (flags.keepalive) if (_peer) ++ _peer->stats.n_keepalives_sent; if (reply->keep_alive) { if (_peer) ++ _peer->stats.n_keepalives_recv; if (Config.onoff.detect_broken_server_pconns && reply->bodySize(request->method) == -1 && !flags.chunked) { debugs(11, DBG_IMPORTANT, "keepaliveAccounting: Impossible keep-alive header from '" << entry->url() << "'" ); - // debugs(11, 2, "GOT HTTP REPLY HDR:\n---------\n" << readBuf->content() << "\n----------" ); + // debugs(11, 2, "GOT HTTP REPLY HDR:\n---------\n" << inBuf.content() << "\n----------" ); flags.keepalive_broken = true; } } } void HttpStateData::checkDateSkew(HttpReply *reply) { if (reply->date > -1 && !_peer) { int skew = abs((int)(reply->date - squid_curtime)); if (skew > 86400) debugs(11, 3, "" << request->GetHost() << "'s clock is skewed by " << skew << " seconds!"); } } /** * This creates the error page itself.. its likely * that the forward ported reply header max size patch * generates non http conformant error pages - in which * case the errors where should be 'BAD_GATEWAY' etc */ void HttpStateData::processReplyHeader() { /** Creates a blank header. If this routine is made incremental, this will not do */ /* NP: all exit points to this function MUST call ctx_exit(ctx) */ Ctx ctx = ctx_enter(entry->mem_obj->urlXXX()); debugs(11, 3, "processReplyHeader: key '" << entry->getMD5Text() << "'"); assert(!flags.headers_parsed); - if (!readBuf->hasContent()) { + if (!inBuf.hasContent()) { ctx_exit(ctx); return; } Http::StatusCode error = Http::scNone; HttpReply *newrep = new HttpReply; - const bool parsed = newrep->parse(readBuf, eof, &error); + const bool parsed = newrep->parse(&inBuf, eof, &error); - if (!parsed && readBuf->contentSize() > 5 && strncmp(readBuf->content(), "HTTP/", 5) != 0 && strncmp(readBuf->content(), "ICY", 3) != 0) { + if (!parsed && inBuf.contentSize() > 5 && strncmp(inBuf.content(), "HTTP/", 5) != 0 && strncmp(inBuf.content(), "ICY", 3) != 0) { MemBuf *mb; HttpReply *tmprep = new HttpReply; tmprep->setHeaders(Http::scOkay, "Gatewaying", NULL, -1, -1, -1); tmprep->header.putExt("X-Transformed-From", "HTTP/0.9"); mb = tmprep->pack(); newrep->parse(mb, eof, &error); delete mb; delete tmprep; } else { if (!parsed && error > 0) { // unrecoverable parsing error - debugs(11, 3, "processReplyHeader: Non-HTTP-compliant header: '" << readBuf->content() << "'"); + debugs(11, 3, "processReplyHeader: Non-HTTP-compliant header: '" << inBuf.content() << "'"); flags.headers_parsed = true; // XXX: when sanityCheck is gone and Http::StatusLine is used to parse, // the sline should be already set the appropriate values during that parser stage newrep->sline.set(Http::ProtocolVersion(1,1), error); HttpReply *vrep = setVirginReply(newrep); entry->replaceHttpReply(vrep); ctx_exit(ctx); return; } if (!parsed) { // need more data assert(!error); assert(!eof); delete newrep; ctx_exit(ctx); return; } - debugs(11, 2, "HTTP Server " << serverConnection); - debugs(11, 2, "HTTP Server REPLY:\n---------\n" << readBuf->content() << "\n----------"); + debugs(11, 2, "HTTP Server " << tcp); + debugs(11, 2, "HTTP Server REPLY:\n---------\n" << inBuf.content() << "\n----------"); - header_bytes_read = headersEnd(readBuf->content(), readBuf->contentSize()); - readBuf->consume(header_bytes_read); + header_bytes_read = headersEnd(inBuf.content(), inBuf.contentSize()); + inBuf.consume(header_bytes_read); } newrep->removeStaleWarnings(); if (newrep->sline.protocol == AnyP::PROTO_HTTP && newrep->sline.status() >= 100 && newrep->sline.status() < 200) { handle1xx(newrep); ctx_exit(ctx); return; } flags.chunked = false; if (newrep->sline.protocol == AnyP::PROTO_HTTP && newrep->header.chunked()) { flags.chunked = true; httpChunkDecoder = new ChunkedCodingParser; } if (!peerSupportsConnectionPinning()) request->flags.connectionAuthDisabled = true; HttpReply *vrep = setVirginReply(newrep); @@ -831,41 +825,46 @@ HttpStateData::proceedAfter1xx); CallJobHere1(11, 4, request->clientConnectionManager, ConnStateData, ConnStateData::sendControlMsg, HttpControlMsg(msg, cb)); // If the call is not fired, then the Sink is gone, and HttpStateData // will terminate due to an aborted store entry or another similar error. // If we get stuck, it is not handle1xx fault if we could get stuck // for similar reasons without a 1xx response. } /// restores state and resumes processing after 1xx is ignored or forwarded void HttpStateData::proceedAfter1xx() { Must(flags.handling1xx); debugs(11, 2, HERE << "consuming " << header_bytes_read << " header and " << reply_bytes_read << " body bytes read after 1xx"); header_bytes_read = 0; reply_bytes_read = 0; - CallJobHere(11, 3, this, HttpStateData, HttpStateData::processReply); + if (inBuf.hasContent()) { + if(!processReadBuffer(inBuf)) + return; + } + + readSomeData(); } /** * returns true if the peer can support connection pinning */ bool HttpStateData::peerSupportsConnectionPinning() const { const HttpReply *rep = entry->mem_obj->getReply(); const HttpHeader *hdr = &rep->header; bool rc; String header; if (!_peer) return true; /*If this peer does not support connection pinning (authenticated connections) return false */ if (!_peer->connection_auth) return false; @@ -1054,49 +1053,49 @@ * What does the reply have to say about keep-alive? */ /** \bug XXX BUG? * If the origin server (HTTP/1.0) does not send a keep-alive * header, but keeps the connection open anyway, what happens? * We'll return here and http.c waits for an EOF before changing * store_status to STORE_OK. Combine this with ENTRY_FWD_HDR_WAIT * and an error status code, and we might have to wait until * the server times out the socket. */ if (!rep->keep_alive) return COMPLETE_NONPERSISTENT_MSG; return COMPLETE_PERSISTENT_MSG; } HttpStateData::ConnectionStatus HttpStateData::persistentConnStatus() const { - debugs(11, 3, HERE << serverConnection << " eof=" << eof); + debugs(11, 3, tcp << " eof=" << eof); if (eof) // already reached EOF return COMPLETE_NONPERSISTENT_MSG; /* If server fd is closing (but we have not been notified yet), stop Comm I/O to avoid assertions. TODO: Change Comm API to handle callers that want more I/O after async closing (usually initiated by others). */ // XXX: add canReceive or s/canSend/canTalkToServer/ - if (!Comm::IsConnOpen(serverConnection)) + if (!Comm::IsConnOpen(tcp)) return COMPLETE_NONPERSISTENT_MSG; /** \par * In chunked response we do not know the content length but we are absolutely * sure about the end of response, so we are calling the statusIfComplete to * decide if we can be persistant */ if (lastChunk && flags.chunked) return statusIfComplete(); const HttpReply *vrep = virginReply(); debugs(11, 5, "persistentConnStatus: content_length=" << vrep->content_length); const int64_t clen = vrep->bodySize(request->method); debugs(11, 5, "persistentConnStatus: clen=" << clen); /* If the body size is unknown we must wait for EOF */ if (clen < 0) return INCOMPLETE_MSG; @@ -1105,514 +1104,499 @@ * If the body size is known, we must wait until we've gotten all of it. */ if (clen > 0) { // old technique: // if (entry->mem_obj->endOffset() < vrep->content_length + vrep->hdr_sz) const int64_t body_bytes_read = reply_bytes_read - header_bytes_read; debugs(11,5, "persistentConnStatus: body_bytes_read=" << body_bytes_read << " content_length=" << vrep->content_length); if (body_bytes_read < vrep->content_length) return INCOMPLETE_MSG; if (body_bytes_truncated > 0) // already read more than needed return COMPLETE_NONPERSISTENT_MSG; // disable pconns } /** \par * If there is no message body or we got it all, we can be persistent */ return statusIfComplete(); } -/* - * This is the callback after some data has been read from the network - */ -/* +/// handle I/O errors when reading void -HttpStateData::ReadReplyWrapper(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, void *data) +HttpStateData::noteTcpReadError(int xerrno) { - HttpStateData *httpState = static_cast(data); - assert (fd == httpState->serverConnection->fd); - // assert(buf == readBuf->content()); - PROF_start(HttpStateData_readReply); - httpState->readReply(len, flag, xerrno); - PROF_stop(HttpStateData_readReply); + ErrorState *err = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, fwd->request); + err->xerrno = xerrno; + fwd->fail(err); + flags.do_next_read = false; // XXX: should not be needed now. TCP conection is closed. } -*/ -/* XXX this function is too long! */ +// update I/O stats void -HttpStateData::readReply(const CommIoCbParams &io) +HttpStateData::updateByteCountersOnRead(size_t sz) { - int bin; - int clen; - int len = io.size; - - flags.do_next_read = false; - - debugs(11, 5, HERE << io.conn << ": len " << len << "."); + reply_bytes_read += sz; - // Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us - if (io.flag == COMM_ERR_CLOSING) { - debugs(11, 3, "http socket closing"); - return; - } - - if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { - abortTransaction("store entry aborted while reading reply"); - return; - } - - // handle I/O errors - if (io.flag != COMM_OK || len < 0) { - debugs(11, 2, HERE << io.conn << ": read failure: " << xstrerror() << "."); - - if (ignoreErrno(io.xerrno)) { - flags.do_next_read = true; - } else { - ErrorState *err = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, fwd->request); - err->xerrno = io.xerrno; - fwd->fail(err); - flags.do_next_read = false; - serverConnection->close(); - } - - return; - } - - // update I/O stats - if (len > 0) { - readBuf->appended(len); - reply_bytes_read += len; #if USE_DELAY_POOLS DelayId delayId = entry->mem_obj->mostBytesAllowed(); - delayId.bytesIn(len); + delayId.bytesIn(sz); #endif - kb_incr(&(statCounter.server.all.kbytes_in), len); - kb_incr(&(statCounter.server.http.kbytes_in), len); + kb_incr(&(statCounter.server.all.kbytes_in), sz); + kb_incr(&(statCounter.server.http.kbytes_in), sz); ++ IOStats.Http.reads; - for (clen = len - 1, bin = 0; clen; ++bin) + int bin = 0; + for (size_t clen = sz - 1; clen; ++bin) clen >>= 1; - ++ IOStats.Http.read_hist[bin]; + // XXX: this seems sort of wrong. maybe should be done + // once in the response parse code instead of every read. + // update peer response time stats (%hier.peer_http_request_sent; request->hier.peer_response_time = sent.tv_sec ? tvSubMsec(sent, current_time) : -1; - } +} - /** \par +// checks before stop sending after 0-sized read +const char * +HttpStateData::maybeFinishedWithTcp() +{ + /* * Here the RFC says we should ignore whitespace between replies, but we can't as - * doing so breaks HTTP/0.9 replies beginning with witespace, and in addition + * doing so breaks HTTP/0.9 replies beginning with whitespace, and in addition * the response splitting countermeasures is extremely likely to trigger on this, * not allowing connection reuse in the first place. * * 2012-02-10: which RFC? not 2068 or 2616, * tolerance there is all about whitespace between requests and header tokens. */ - if (len == 0) { // reached EOF? - eof = 1; - flags.do_next_read = false; + // reached EOF? + eof = 1; + flags.do_next_read = false; - /* Bug 2879: Replies may terminate with \r\n then EOF instead of \r\n\r\n - * Ensure here that we have at minimum two \r\n when EOF is seen. - * TODO: Add eof parameter to headersEnd() and move this hack there. + /* Bug 2879: Replies may terminate with \r\n then EOF instead of \r\n\r\n + * Ensure here that we have at minimum two \r\n when EOF is seen. + * TODO: Add eof parameter to headersEnd() and move this hack there. + */ + if (inBuf.contentSize() && !flags.headers_parsed) { + /* + * Yes Henrik, there is a point to doing this. When we + * called httpProcessReplyHeader() before, we didn't find + * the end of headers, but now we are definately at EOF, so + * we want to process the reply headers. */ - if (readBuf->contentSize() && !flags.headers_parsed) { - /* - * Yes Henrik, there is a point to doing this. When we - * called httpProcessReplyHeader() before, we didn't find - * the end of headers, but now we are definately at EOF, so - * we want to process the reply headers. - */ - /* Fake an "end-of-headers" to work around such broken servers */ - readBuf->append("\r\n", 2); - } - } + /* Fake an "end-of-headers" to work around such broken servers */ + inBuf.append("\r\n", 2); + processReadBuffer(inBuf); + } + + // Now that the above hack has consumed any pending headers that + // can be consumed clear the remaining buffer. doneAll() depends + // on an empty buffer as well as doneWithServer(). + inBuf.clean(); + + // TcpReceiver guarantees that no more is going to be read. + // whether anything may still be sent depends on us returning NULL below... + // XXX: doneWithServer() is the wrong thing to depend on long-term, so + // how do we identify pending writes? - processReply(); + return doneWithServer() ? "doneWithServer" : NULL; } /// processes the already read and buffered response data, possibly after /// waiting for asynchronous 1xx control message processing -void -HttpStateData::processReply() +bool +HttpStateData::processReadBuffer(MemBuf &data) { + // XXX: this is equivalent to readMore on client-side + // and needs to be replaced with return results from the parse/process functions + flags.do_next_read = false; + + if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { + abortTransaction("store entry aborted while reading reply"); + return false; + } if (flags.handling1xx) { // we came back after handling a 1xx response debugs(11, 5, HERE << "done with 1xx handling"); flags.handling1xx = false; Must(!flags.headers_parsed); } if (!flags.headers_parsed) { // have not parsed headers yet? PROF_start(HttpStateData_processReplyHeader); processReplyHeader(); PROF_stop(HttpStateData_processReplyHeader); if (!continueAfterParsingHeader()) // parsing error or need more data - return; // TODO: send errors to ICAP + return flags.do_next_read; // TODO: send errors to ICAP adaptOrFinalizeReply(); // may write to, abort, or "close" the entry } // kick more reads if needed and/or process the response body, if any PROF_start(HttpStateData_processReplyBody); processReplyBody(); // may call serverComplete() PROF_stop(HttpStateData_processReplyBody); + + return flags.do_next_read; } /** \retval true if we can continue with processing the body or doing ICAP. */ bool HttpStateData::continueAfterParsingHeader() { if (flags.handling1xx) { - debugs(11, 5, HERE << "wait for 1xx handling"); + debugs(11, 5, "wait for 1xx handling"); Must(!flags.headers_parsed); return false; } if (!flags.headers_parsed && !eof) { - debugs(11, 9, HERE << "needs more at " << readBuf->contentSize()); + debugs(11, 9, "needs more at " << inBuf.contentSize()); flags.do_next_read = true; /** \retval false If we have not finished parsing the headers and may get more data. * Schedules more reads to retrieve the missing data. */ maybeReadVirginBody(); // schedules all kinds of reads; TODO: rename return false; } /** If we are done with parsing, check for errors */ err_type error = ERR_NONE; if (flags.headers_parsed) { // parsed headers, possibly with errors // check for header parsing errors if (HttpReply *vrep = virginReply()) { const Http::StatusCode s = vrep->sline.status(); const Http::ProtocolVersion &v = vrep->sline.version; if (s == Http::scInvalidHeader && v != Http::ProtocolVersion(0,9)) { debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: Bad header encountered from " << entry->url() << " AKA " << request->GetHost() << request->urlpath.termedBuf() ); error = ERR_INVALID_RESP; + stopReceiving("invalid response"); } else if (s == Http::scHeaderTooLarge) { fwd->dontRetry(true); error = ERR_TOO_BIG; + stopReceiving("response too big"); } else { return true; // done parsing, got reply, and no error } } else { // parsed headers but got no reply debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: No reply at all for " << entry->url() << " AKA " << request->GetHost() << request->urlpath.termedBuf() ); error = ERR_INVALID_RESP; + stopReceiving("response missing"); } } else { assert(eof); - if (readBuf->hasContent()) { + if (inBuf.hasContent()) { error = ERR_INVALID_RESP; debugs(11, DBG_IMPORTANT, "WARNING: HTTP: Invalid Response: Headers did not parse at all for " << entry->url() << " AKA " << request->GetHost() << request->urlpath.termedBuf() ); + stopReceiving("response Headers did not parse"); } else { error = ERR_ZERO_SIZE_OBJECT; debugs(11, (request->flags.accelerated?DBG_IMPORTANT:2), "WARNING: HTTP: Invalid Response: No object data received for " << entry->url() << " AKA " << request->GetHost() << request->urlpath.termedBuf() ); + stopReceiving("response missing object data"); } } assert(error != ERR_NONE); entry->reset(); fwd->fail(new ErrorState(error, Http::scBadGateway, fwd->request)); flags.do_next_read = false; - serverConnection->close(); + stopSending("response error"); return false; // quit on error } /** truncate what we read if we read too much so that writeReplyBody() writes no more than what we should have read */ void HttpStateData::truncateVirginBody() { assert(flags.headers_parsed); HttpReply *vrep = virginReply(); int64_t clen = -1; if (!vrep->expectingBody(request->method, clen) || clen < 0) return; // no body or a body of unknown size, including chunked const int64_t body_bytes_read = reply_bytes_read - header_bytes_read; if (body_bytes_read - body_bytes_truncated <= clen) return; // we did not read too much or already took care of the extras if (const int64_t extras = body_bytes_read - body_bytes_truncated - clen) { // server sent more that the advertised content length debugs(11,5, HERE << "body_bytes_read=" << body_bytes_read << " clen=" << clen << '/' << vrep->content_length << " body_bytes_truncated=" << body_bytes_truncated << '+' << extras); - readBuf->truncate(extras); + inBuf.truncate(extras); body_bytes_truncated += extras; + stopReceiving("server sent too many bytes for the response"); + // XXX: should we abortTransaction() instead? + // this will prevent future response reads, but allow request to complete. } } /** * Call this when there is data from the origin server * which should be sent to either StoreEntry, or to ICAP... */ void HttpStateData::writeReplyBody() { truncateVirginBody(); // if needed - const char *data = readBuf->content(); - int len = readBuf->contentSize(); + const char *data = inBuf.content(); + int len = inBuf.contentSize(); addVirginReplyBody(data, len); - readBuf->consume(len); + inBuf.consume(len); } bool HttpStateData::decodeAndWriteReplyBody() { const char *data = NULL; int len; bool wasThereAnException = false; assert(flags.chunked); assert(httpChunkDecoder); SQUID_ENTER_THROWING_CODE(); MemBuf decodedData; decodedData.init(); - const bool doneParsing = httpChunkDecoder->parse(readBuf,&decodedData); + const bool doneParsing = httpChunkDecoder->parse(&inBuf, &decodedData); len = decodedData.contentSize(); data=decodedData.content(); addVirginReplyBody(data, len); if (doneParsing) { lastChunk = 1; flags.do_next_read = false; } SQUID_EXIT_THROWING_CODE(wasThereAnException); return wasThereAnException; } /** * processReplyBody has two purposes: * 1 - take the reply body data, if any, and put it into either * the StoreEntry, or give it over to ICAP. * 2 - see if we made it to the end of the response (persistent * connections and such) */ void HttpStateData::processReplyBody() { Ip::Address client_addr; bool ispinned = false; if (!flags.headers_parsed) { flags.do_next_read = true; maybeReadVirginBody(); return; } #if USE_ADAPTATION debugs(11,5, HERE << "adaptationAccessCheckPending=" << adaptationAccessCheckPending); if (adaptationAccessCheckPending) return; #endif /* * At this point the reply headers have been parsed and consumed. - * That means header content has been removed from readBuf and + * That means header content has been removed from inBuf and * it contains only body data. */ if (entry->isAccepting()) { if (flags.chunked) { if (!decodeAndWriteReplyBody()) { flags.do_next_read = false; serverComplete(); return; } } else writeReplyBody(); } if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) { // The above writeReplyBody() call may have aborted the store entry. abortTransaction("store entry aborted while storing reply"); return; } else switch (persistentConnStatus()) { case INCOMPLETE_MSG: { - debugs(11, 5, "processReplyBody: INCOMPLETE_MSG from " << serverConnection); + debugs(11, 5, "INCOMPLETE_MSG from " << tcp); /* Wait for more data or EOF condition */ AsyncCall::Pointer nil; if (flags.keepalive_broken) { - commSetConnTimeout(serverConnection, 10, nil); + commSetConnTimeout(tcp, 10, nil); } else { - commSetConnTimeout(serverConnection, Config.Timeout.read, nil); + commSetConnTimeout(tcp, Config.Timeout.read, nil); } flags.do_next_read = true; } break; case COMPLETE_PERSISTENT_MSG: - debugs(11, 5, "processReplyBody: COMPLETE_PERSISTENT_MSG from " << serverConnection); + debugs(11, 5, "COMPLETE_PERSISTENT_MSG from " << tcp); /* yes we have to clear all these! */ - commUnsetConnTimeout(serverConnection); + commUnsetConnTimeout(tcp); flags.do_next_read = false; - - comm_remove_close_handler(serverConnection->fd, closeHandler); - closeHandler = NULL; - fwd->unregister(serverConnection); + releaseConnection("done with persistent connection"); + fwd->unregister(tcp); if (request->flags.spoofClientIp) client_addr = request->client_addr; if (request->flags.pinned) { ispinned = true; } else if (request->flags.connectionAuth && request->flags.authSent) { ispinned = true; } if (ispinned && request->clientConnectionManager.valid()) { - request->clientConnectionManager->pinConnection(serverConnection, request, _peer, + request->clientConnectionManager->pinConnection(tcp, request, _peer, (request->flags.connectionAuth)); } else { - fwd->pconnPush(serverConnection, request->GetHost()); + fwd->pconnPush(tcp, request->GetHost()); } - serverConnection = NULL; + tcp = NULL; // prevent termination of HttpStateData closing the connection. serverComplete(); return; case COMPLETE_NONPERSISTENT_MSG: - debugs(11, 5, "processReplyBody: COMPLETE_NONPERSISTENT_MSG from " << serverConnection); + debugs(11, 5, "COMPLETE_NONPERSISTENT_MSG from " << tcp); serverComplete(); return; } maybeReadVirginBody(); } void HttpStateData::maybeReadVirginBody() { +#if 0 // XXX: no longer needed? + // too late to read - if (!Comm::IsConnOpen(serverConnection) || fd_table[serverConnection->fd].closing()) + if (!Comm::IsConnOpen(tcp) || fd_table[tcp->fd].closing()) return; // we may need to grow the buffer if headers do not fit const int minRead = flags.headers_parsed ? 0 :1024; - const int read_size = replyBodySpace(*readBuf, minRead); - - debugs(11,9, HERE << (flags.do_next_read ? "may" : "wont") << - " read up to " << read_size << " bytes from " << serverConnection); + const int read_size = replyBodySpace(inBuf, minRead); +#endif - /* - * why <2? Because delayAwareRead() won't actually read if - * you ask it to read 1 byte. The delayed read request - * just gets re-queued until the client side drains, then - * the I/O thread hangs. Better to not register any read - * handler until we get a notification from someone that - * its okay to read again. - */ - if (read_size < 2) - return; + if (flags.do_next_read) + readSomeData(); +} - if (flags.do_next_read) { - flags.do_next_read = false; - typedef CommCbMemFunT Dialer; - entry->delayAwareRead(serverConnection, readBuf->space(read_size), read_size, - JobCallback(11, 5, Dialer, this, HttpStateData::readReply)); - } +bool +HttpStateData::maybeDelayRead(const AsyncCall::Pointer &call) +{ + flags.do_next_read = false; + entry->delayAwareRead(tcp, inBuf.space(), inBuf.spaceSize(), call); + return true; // always true for HTTP server connections. } /// called after writing the very last request byte (body, last-chunk, etc) void HttpStateData::wroteLast(const CommIoCbParams &io) { - debugs(11, 5, HERE << serverConnection << ": size " << io.size << ": errflag " << io.flag << "."); + debugs(11, 5, tcp << ": size " << io.size << ": errflag " << io.flag << "."); #if URL_CHECKSUM_DEBUG entry->mem_obj->checkUrlChecksum(); #endif if (io.size > 0) { fd_bytes(io.fd, io.size, FD_WRITE); kb_incr(&(statCounter.server.all.kbytes_out), io.size); kb_incr(&(statCounter.server.http.kbytes_out), io.size); } if (io.flag == COMM_ERR_CLOSING) return; if (io.flag) { ErrorState *err = new ErrorState(ERR_WRITE_ERROR, Http::scBadGateway, fwd->request); err->xerrno = io.xerrno; fwd->fail(err); - serverConnection->close(); + // XXX: use abortTransaction() instead? + stopSending("write error"); + stopReceiving("write error"); return; } sendComplete(); } /// successfully wrote the entire request (including body, last-chunk, etc.) void HttpStateData::sendComplete() { /* * Set the read timeout here because it hasn't been set yet. * We only set the read timeout after the request has been * fully written to the server-side. If we start the timeout * after connection establishment, then we are likely to hit * the timeout for POST/PUT requests that have very large * request bodies. */ typedef CommCbMemFunT TimeoutDialer; AsyncCall::Pointer timeoutCall = JobCallback(11, 5, TimeoutDialer, this, HttpStateData::httpTimeout); - commSetConnTimeout(serverConnection, Config.Timeout.read, timeoutCall); + commSetConnTimeout(tcp, Config.Timeout.read, timeoutCall); flags.request_sent = true; request->hier.peer_http_request_sent = current_time; } // Close the HTTP server connection. Used by serverComplete(). void HttpStateData::closeServer() { - debugs(11,5, HERE << "closing HTTP server " << serverConnection << " this " << this); + debugs(11,5, "closing HTTP server " << tcp << " this " << this); - if (Comm::IsConnOpen(serverConnection)) { - fwd->unregister(serverConnection); - comm_remove_close_handler(serverConnection->fd, closeHandler); - closeHandler = NULL; - serverConnection->close(); + if (Comm::IsConnOpen(tcp)) { + fwd->unregister(tcp); + releaseConnection("closeServer"); + stopSending("done"); + stopReceiving("done"); } } bool HttpStateData::doneWithServer() const { - return !Comm::IsConnOpen(serverConnection); + // XXX: use stoppedSending() && stoppedReading() instead? + return !Comm::IsConnOpen(tcp); } /* * Fixup authentication request headers for special cases */ static void httpFixupAuthentication(HttpRequest * request, const HttpHeader * hdr_in, HttpHeader * hdr_out, const HttpStateFlags &flags) { http_hdr_type header = flags.originpeer ? HDR_AUTHORIZATION : HDR_PROXY_AUTHORIZATION; /* Nothing to do unless we are forwarding to a peer */ if (!request->flags.proxying) return; /* Needs to be explicitly enabled */ if (!request->peer_login) return; /* Maybe already dealt with? */ if (hdr_out->has(header)) @@ -2133,52 +2117,52 @@ request->flags.authSent = true; else if (hdr.has(HDR_AUTHORIZATION)) request->flags.authSent = true; packerToMemInit(&p, mb); hdr.packInto(&p); hdr.clean(); packerClean(&p); } /* append header terminator */ mb->append(crlf, 2); return mb->size - offset; } /* This will be called when connect completes. Write request. */ bool HttpStateData::sendRequest() { MemBuf mb; - debugs(11, 5, HERE << serverConnection << ", request " << request << ", this " << this << "."); + debugs(11, 5, tcp << ", request " << request << ", this " << this << "."); - if (!Comm::IsConnOpen(serverConnection)) { - debugs(11,3, HERE << "cannot send request to closing " << serverConnection); + if (!Comm::IsConnOpen(tcp)) { + debugs(11,3, "cannot send request to closing " << tcp); assert(closeHandler != NULL); return false; } typedef CommCbMemFunT TimeoutDialer; AsyncCall::Pointer timeoutCall = JobCallback(11, 5, TimeoutDialer, this, HttpStateData::httpTimeout); - commSetConnTimeout(serverConnection, Config.Timeout.lifetime, timeoutCall); + commSetConnTimeout(tcp, Config.Timeout.lifetime, timeoutCall); flags.do_next_read = true; maybeReadVirginBody(); if (request->body_pipe != NULL) { if (!startRequestBodyFlow()) // register to receive body data return false; typedef CommCbMemFunT Dialer; requestSender = JobCallback(11,5, Dialer, this, HttpStateData::sentRequestBody); Must(!flags.chunked_request); // use chunked encoding if we do not know the length if (request->content_length < 0) flags.chunked_request = true; } else { assert(!requestBodySource); typedef CommCbMemFunT Dialer; requestSender = JobCallback(11,5, Dialer, this, HttpStateData::wroteLast); } @@ -2209,44 +2193,43 @@ which is equivalent to: if (neighborType(_peer, NULL) == PEER_SIBLING && ... or better: if (((_peer->type == PEER_MULTICAST && p->options.mcast_siblings) || _peer->type == PEER_SIBLINGS ) && _peer->options.allow_miss) flags.only_if_cached = 1; But I suppose it was a bug */ if (neighborType(_peer, request) == PEER_SIBLING && !_peer->options.allow_miss) flags.only_if_cached = true; flags.front_end_https = _peer->front_end_https; } mb.init(); request->peer_host=_peer?_peer->host:NULL; buildRequestPrefix(&mb); - debugs(11, 2, "HTTP Server " << serverConnection); + debugs(11, 2, "HTTP Server " << tcp); debugs(11, 2, "HTTP Server REQUEST:\n---------\n" << mb.buf << "\n----------"); - - Comm::Write(serverConnection, &mb, requestSender); + sendSomeData(mb, requestSender); return true; } bool HttpStateData::getMoreRequestBody(MemBuf &buf) { // parent's implementation can handle the no-encoding case if (!flags.chunked_request) return ServerStateData::getMoreRequestBody(buf); MemBuf raw; Must(requestBodySource != NULL); if (!requestBodySource->getMoreData(raw)) return false; // no request body bytes to chunk yet // optimization: pre-allocate buffer size that should be enough const mb_size_t rawDataSize = raw.contentSize(); // we may need to send: hex-chunk-size CRLF raw-data CRLF last-chunk buf.init(16 + 2 + rawDataSize + 2 + 5, raw.max_capacity); @@ -2292,147 +2275,148 @@ * including request body, has been written to the server. */ } /// if broken posts are enabled for the request, try to fix and return true bool HttpStateData::finishingBrokenPost() { #if USE_HTTP_VIOLATIONS if (!Config.accessList.brokenPosts) { debugs(11, 5, HERE << "No brokenPosts list"); return false; } ACLFilledChecklist ch(Config.accessList.brokenPosts, originalRequest(), NULL); if (ch.fastCheck() != ACCESS_ALLOWED) { debugs(11, 5, HERE << "didn't match brokenPosts"); return false; } - if (!Comm::IsConnOpen(serverConnection)) { - debugs(11, 3, HERE << "ignoring broken POST for closed " << serverConnection); + if (!Comm::IsConnOpen(tcp)) { + debugs(11, 3, "ignoring broken POST for closed " << tcp); assert(closeHandler != NULL); return true; // prevent caller from proceeding as if nothing happened } debugs(11, 3, "finishingBrokenPost: fixing broken POST"); typedef CommCbMemFunT Dialer; requestSender = JobCallback(11,5, Dialer, this, HttpStateData::wroteLast); - Comm::Write(serverConnection, "\r\n", 2, requestSender, NULL); + Comm::Write(tcp, "\r\n", 2, requestSender, NULL); return true; #else return false; #endif /* USE_HTTP_VIOLATIONS */ } /// if needed, write last-chunk to end the request body and return true bool HttpStateData::finishingChunkedRequest() { if (flags.sentLastChunk) { debugs(11, 5, HERE << "already sent last-chunk"); return false; } Must(receivedWholeRequestBody); // or we should not be sending last-chunk flags.sentLastChunk = true; typedef CommCbMemFunT Dialer; requestSender = JobCallback(11,5, Dialer, this, HttpStateData::wroteLast); - Comm::Write(serverConnection, "0\r\n\r\n", 5, requestSender, NULL); + Comm::Write(tcp, "0\r\n\r\n", 5, requestSender, NULL); return true; } void HttpStateData::doneSendingRequestBody() { ServerStateData::doneSendingRequestBody(); - debugs(11,5, HERE << serverConnection); + debugs(11, 5, tcp); // do we need to write something after the last body byte? if (flags.chunked_request && finishingChunkedRequest()) return; if (!flags.chunked_request && finishingBrokenPost()) return; sendComplete(); } // more origin request body data is available void HttpStateData::handleMoreRequestBodyAvailable() { - if (eof || !Comm::IsConnOpen(serverConnection)) { + if (eof || !Comm::IsConnOpen(tcp)) { // XXX: we should check this condition in other callbacks then! // TODO: Check whether this can actually happen: We should unsubscribe // as a body consumer when the above condition(s) are detected. debugs(11, DBG_IMPORTANT, HERE << "Transaction aborted while reading HTTP body"); return; } assert(requestBodySource != NULL); if (requestBodySource->buf().hasContent()) { // XXX: why does not this trigger a debug message on every request? if (flags.headers_parsed && !flags.abuse_detected) { flags.abuse_detected = true; debugs(11, DBG_IMPORTANT, "http handleMoreRequestBodyAvailable: Likely proxy abuse detected '" << request->client_addr << "' -> '" << entry->url() << "'" ); if (virginReply()->sline.status() == Http::scInvalidHeader) { - serverConnection->close(); + stopSending("proxy abuse detected"); + stopReceiving("proxy abuse detected"); return; } } } HttpStateData::handleMoreRequestBodyAvailable(); } // premature end of the request body void HttpStateData::handleRequestBodyProducerAborted() { ServerStateData::handleRequestBodyProducerAborted(); if (entry->isEmpty()) { - debugs(11, 3, "request body aborted: " << serverConnection); + debugs(11, 3, "request body aborted: " << tcp); // We usually get here when ICAP REQMOD aborts during body processing. // We might also get here if client-side aborts, but then our response // should not matter because either client-side will provide its own or // there will be no response at all (e.g., if the the client has left). ErrorState *err = new ErrorState(ERR_ICAP_FAILURE, Http::scInternalServerError, fwd->request); err->detailError(ERR_DETAIL_SRV_REQMOD_REQ_BODY); fwd->fail(err); } abortTransaction("request body producer aborted"); } // called when we wrote request headers(!) or a part of the body void HttpStateData::sentRequestBody(const CommIoCbParams &io) { if (io.size > 0) kb_incr(&statCounter.server.http.kbytes_out, io.size); ServerStateData::sentRequestBody(io); } // Quickly abort the transaction // TODO: destruction should be sufficient as the destructor should cleanup, // including canceling close handlers void HttpStateData::abortTransaction(const char *reason) { - debugs(11,5, HERE << "aborting transaction for " << reason << - "; " << serverConnection << ", this " << this); + debugs(11,5, "aborting transaction for " << reason << "; " << tcp << ", this " << this); - if (Comm::IsConnOpen(serverConnection)) { - serverConnection->close(); - return; + if (Comm::IsConnOpen(tcp)) { + // when both called the TCP connection will be closed. + stopReceiving(reason); + stopSending(reason); } fwd->handleUnregisteredServerEnd(); mustStop("HttpStateData::abortTransaction"); } === modified file 'src/http.h' --- src/http.h 2012-10-19 09:17:33 +0000 +++ src/http.h 2014-01-02 10:43:29 +0000 @@ -16,116 +16,125 @@ * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #ifndef SQUID_HTTP_H #define SQUID_HTTP_H #include "comm.h" +#include "comm/TcpReceiver.h" #include "HttpStateFlags.h" #include "Server.h" class ChunkedCodingParser; class FwdState; class HttpHeader; -class HttpStateData : public ServerStateData +class HttpStateData : public Comm::TcpReceiver, public ServerStateData { public: HttpStateData(FwdState *); ~HttpStateData(); static void httpBuildRequestHeader(HttpRequest * request, StoreEntry * entry, const AccessLogEntryPointer &al, HttpHeader * hdr_out, const HttpStateFlags &flags); virtual const Comm::ConnectionPointer & dataConnection() const; + /* should be private */ bool sendRequest(); void processReplyHeader(); void processReplyBody(); - void readReply(const CommIoCbParams &io); virtual void maybeReadVirginBody(); // read response data from the network // Determine whether the response is a cacheable representation int cacheableReply(); CachePeer *_peer; /* CachePeer request made to */ int eof; /* reached end-of-object? */ int lastChunk; /* reached last chunk of a chunk-encoded reply */ HttpStateFlags flags; size_t read_sz; int header_bytes_read; // to find end of response, int64_t reply_bytes_read; // without relying on StoreEntry int body_bytes_truncated; // positive when we read more than we wanted - MemBuf *readBuf; bool ignoreCacheControl; bool surrogateNoStore; void processSurrogateControl(HttpReply *); + // AsyncJob API + virtual bool doneAll() const {return Comm::TcpReceiver::doneAll() && ServerStateData::doneAll() && doneWithServer();} + virtual void swanSong(); + + // Comm::TcpReceiver API + virtual int64_t mayNeedToReadMore() const {return -1;} // XXX: whether more body or headers expected ?? how much? + virtual bool maybeDelayRead(const AsyncCall::Pointer &call); + virtual void updateByteCountersOnRead(size_t); + virtual void noteTcpReadError(int); + virtual bool processReadBuffer(MemBuf &); + virtual const char * maybeFinishedWithTcp(); + protected: - void processReply(); void proceedAfter1xx(); void handle1xx(HttpReply *msg); private: - /** - * The current server connection. - * Maybe open, closed, or NULL. - * Use doneWithServer() to check if the server is available for use. - */ - Comm::ConnectionPointer serverConnection; AsyncCall::Pointer closeHandler; enum ConnectionStatus { INCOMPLETE_MSG, COMPLETE_PERSISTENT_MSG, COMPLETE_NONPERSISTENT_MSG }; ConnectionStatus statusIfComplete() const; ConnectionStatus persistentConnStatus() const; void keepaliveAccounting(HttpReply *); void checkDateSkew(HttpReply *); bool continueAfterParsingHeader(); void truncateVirginBody(); virtual void start(); virtual void haveParsedReplyHeaders(); virtual bool getMoreRequestBody(MemBuf &buf); virtual void closeServer(); // end communication with the server - virtual bool doneWithServer() const; // did we end communication? + /** Did we end communication? + * The current server connection may be open, closed, or NULL. + * Use doneWithServer() to check if the server is available for use. + */ + virtual bool doneWithServer() const; virtual void abortTransaction(const char *reason); // abnormal termination // consuming request body virtual void handleMoreRequestBodyAvailable(); virtual void handleRequestBodyProducerAborted(); void writeReplyBody(); bool decodeAndWriteReplyBody(); bool finishingBrokenPost(); bool finishingChunkedRequest(); void doneSendingRequestBody(); void requestBodyHandler(MemBuf &); virtual void sentRequestBody(const CommIoCbParams &io); void wroteLast(const CommIoCbParams &io); void sendComplete(); void httpStateConnClosed(const CommCloseCbParams ¶ms); void httpTimeout(const CommTimeoutCbParams ¶ms); mb_size_t buildRequestPrefix(MemBuf * mb); static bool decideIfWeDoRanges (HttpRequest * orig_request); === modified file 'src/ident/AclIdent.cc' --- src/ident/AclIdent.cc 2013-05-13 22:48:23 +0000 +++ src/ident/AclIdent.cc 2013-10-13 09:12:24 +0000 @@ -69,43 +69,43 @@ return type_; } void ACLIdent::parse() { if (!data) { debugs(28, 3, HERE << "current is null. Creating"); data = new ACLUserData; } data->parse(); } int ACLIdent::match(ACLChecklist *cl) { ACLFilledChecklist *checklist = Filled(cl); if (checklist->rfc931[0]) { return data->match(checklist->rfc931); - } else if (checklist->conn() != NULL && checklist->conn()->clientConnection != NULL && checklist->conn()->clientConnection->rfc931[0]) { - return data->match(checklist->conn()->clientConnection->rfc931); - } else if (checklist->conn() != NULL && Comm::IsConnOpen(checklist->conn()->clientConnection)) { + } else if (checklist->conn() != NULL && checklist->conn()->tcp != NULL && checklist->conn()->tcp->rfc931[0]) { + return data->match(checklist->conn()->tcp->rfc931); + } else if (checklist->conn() != NULL && Comm::IsConnOpen(checklist->conn()->tcp)) { if (checklist->goAsync(IdentLookup::Instance())) { debugs(28, 3, "switching to ident lookup state"); return -1; } // else fall through to ACCESS_DUNNO failure below } else { debugs(28, DBG_IMPORTANT, HERE << "Can't start ident lookup. No client connection" ); // fall through to ACCESS_DUNNO failure below } checklist->markFinished(ACCESS_DUNNO, "cannot start ident lookup"); return -1; } wordlist * ACLIdent::dump() const { return data->dump(); } @@ -118,47 +118,47 @@ ACL * ACLIdent::clone() const { return new ACLIdent(*this); } IdentLookup IdentLookup::instance_; IdentLookup * IdentLookup::Instance() { return &instance_; } void IdentLookup::checkForAsync(ACLChecklist *cl)const { ACLFilledChecklist *checklist = Filled(cl); const ConnStateData *conn = checklist->conn(); // check that ACLIdent::match() tested this lookup precondition - assert(conn && Comm::IsConnOpen(conn->clientConnection)); + assert(conn && Comm::IsConnOpen(conn->tcp)); debugs(28, 3, HERE << "Doing ident lookup" ); - Ident::Start(checklist->conn()->clientConnection, LookupDone, checklist); + Ident::Start(checklist->conn()->tcp, LookupDone, checklist); } void IdentLookup::LookupDone(const char *ident, void *data) { ACLFilledChecklist *checklist = Filled(static_cast(data)); if (ident) { xstrncpy(checklist->rfc931, ident, USER_IDENT_SZ); } else { xstrncpy(checklist->rfc931, dash_str, USER_IDENT_SZ); } /* * Cache the ident result in the connection, to avoid redoing ident lookup * over and over on persistent connections */ - if (checklist->conn() != NULL && checklist->conn()->clientConnection != NULL && !checklist->conn()->clientConnection->rfc931[0]) - xstrncpy(checklist->conn()->clientConnection->rfc931, checklist->rfc931, USER_IDENT_SZ); + if (checklist->conn() != NULL && checklist->conn()->tcp != NULL && !checklist->conn()->tcp->rfc931[0]) + xstrncpy(checklist->conn()->tcp->rfc931, checklist->rfc931, USER_IDENT_SZ); checklist->resumeNonBlockingCheck(IdentLookup::Instance()); } #endif /* USE_IDENT */ === modified file 'src/peer_select.cc' --- src/peer_select.cc 2014-02-02 01:24:53 +0000 +++ src/peer_select.cc 2014-02-04 22:21:34 +0000 @@ -226,41 +226,41 @@ void peerSelectDnsPaths(ps_state *psstate) { FwdServer *fs = psstate->servers; // Bug 3243: CVE 2009-0801 // Bypass of browser same-origin access control in intercepted communication // To resolve this we must use only the original client destination when going DIRECT // on intercepted traffic which failed Host verification const HttpRequest *req = psstate->request; const bool isIntercepted = !req->flags.redirected && (req->flags.intercepted || req->flags.interceptTproxy); const bool useOriginalDst = Config.onoff.client_dst_passthru || !req->flags.hostVerified; const bool choseDirect = fs && fs->code == HIER_DIRECT; if (isIntercepted && useOriginalDst && choseDirect) { // check the client is still around before using any of its details if (req->clientConnectionManager.valid()) { // construct a "result" adding the ORIGINAL_DST to the set instead of DIRECT Comm::ConnectionPointer p = new Comm::Connection(); - p->remote = req->clientConnectionManager->clientConnection->local; + p->remote = req->clientConnectionManager->tcp->local; p->peerType = fs->code; p->setPeer(fs->_peer); // check for a configured outgoing address for this destination... getOutgoingAddress(psstate->request, p); psstate->paths->push_back(p); } // clear the used fs and continue psstate->servers = fs->next; cbdataReferenceDone(fs->_peer); memFree(fs, MEM_FWD_SERVER); peerSelectDnsPaths(psstate); return; } // convert the list of FwdServer destinations into destinations IP addresses if (fs && psstate->paths->size() < (unsigned int)Config.forward_max_tries) { // send the next one off for DNS lookup. const char *host = fs->_peer ? fs->_peer->host : psstate->request->GetHost(); === modified file 'src/redirect.cc' --- src/redirect.cc 2013-11-23 00:58:42 +0000 +++ src/redirect.cc 2013-11-25 23:03:45 +0000 @@ -247,49 +247,49 @@ * the RedirectStateData for all the helpers. */ RedirectStateData *r = new RedirectStateData(http->uri); if (conn != NULL) r->client_addr = conn->log_addr; else r->client_addr.setNoAddr(); r->client_ident = NULL; #if USE_AUTH if (http->request->auth_user_request != NULL) { r->client_ident = http->request->auth_user_request->username(); debugs(61, 5, HERE << "auth-user=" << (r->client_ident?r->client_ident:"NULL")); } #endif if (!r->client_ident && http->request->extacl_user.size() > 0) { r->client_ident = http->request->extacl_user.termedBuf(); debugs(61, 5, HERE << "acl-user=" << (r->client_ident?r->client_ident:"NULL")); } - if (!r->client_ident && conn != NULL && conn->clientConnection != NULL && conn->clientConnection->rfc931[0]) { - r->client_ident = conn->clientConnection->rfc931; + if (!r->client_ident && conn != NULL && conn->tcp != NULL && conn->tcp->rfc931[0]) { + r->client_ident = conn->tcp->rfc931; debugs(61, 5, HERE << "ident-user=" << (r->client_ident?r->client_ident:"NULL")); } #if USE_SSL - if (!r->client_ident && conn != NULL && Comm::IsConnOpen(conn->clientConnection)) { - r->client_ident = sslGetUserEmail(fd_table[conn->clientConnection->fd].ssl); + if (!r->client_ident && conn != NULL && Comm::IsConnOpen(conn->tcp)) { + r->client_ident = sslGetUserEmail(fd_table[conn->tcp->fd].ssl); debugs(61, 5, HERE << "ssl-user=" << (r->client_ident?r->client_ident:"NULL")); } #endif if (!r->client_ident) r->client_ident = dash_str; r->method_s = RequestMethodStr(http->request->method); r->handler = handler; r->data = cbdataReference(data); if ((fqdn = fqdncache_gethostbyaddr(r->client_addr, 0)) == NULL) fqdn = dash_str; sz = snprintf(buf, MAX_REDIRECTOR_REQUEST_STRLEN, "%s %s/%s %s %s myip=%s myport=%d\n", r->orig_url.c_str(), r->client_addr.toStr(claddr,MAX_IPSTRLEN), fqdn, @@ -297,42 +297,42 @@ r->method_s, http->request->my_addr.toStr(myaddr,MAX_IPSTRLEN), http->request->my_addr.port()); if ((sz<=0) || (sz>=MAX_REDIRECTOR_REQUEST_STRLEN)) { if (sz<=0) { status = Http::scInternalServerError; debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Can not build request to be passed to " << name << ". Request ABORTED."); } else { status = Http::scRequestUriTooLarge; debugs(61, DBG_CRITICAL, "ERROR: Gateway Failure. Request passed to " << name << " exceeds MAX_REDIRECTOR_REQUEST_STRLEN (" << MAX_REDIRECTOR_REQUEST_STRLEN << "). Request ABORTED."); } clientStreamNode *node = (clientStreamNode *)http->client_stream.tail->prev->data; clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); assert (repContext); Ip::Address tmpnoaddr; tmpnoaddr.setNoAddr(); repContext->setReplyToError(ERR_GATEWAY_FAILURE, status, http->request->method, NULL, - http->getConn() != NULL && http->getConn()->clientConnection != NULL ? - http->getConn()->clientConnection->remote : tmpnoaddr, + http->getConn() != NULL && http->getConn()->tcp != NULL ? + http->getConn()->tcp->remote : tmpnoaddr, http->request, NULL, #if USE_AUTH http->getConn() != NULL && http->getConn()->getAuth() != NULL ? http->getConn()->getAuth() : http->request->auth_user_request); #else NULL); #endif node = (clientStreamNode *)http->client_stream.tail->data; clientStreamRead(node, http, node->readBuffer); return; } debugs(61,6, HERE << "sending '" << buf << "' to the " << name << " helper"); helperSubmit(hlp, buf, replyHandler, r); } /**** PUBLIC FUNCTIONS ****/ === modified file 'src/stat.cc' --- src/stat.cc 2014-01-24 01:57:15 +0000 +++ src/stat.cc 2014-01-19 05:41:22 +0000 @@ -2002,81 +2002,81 @@ else return (-1.0 * Math::doublePercent(s - c, c)); } static void statClientRequests(StoreEntry * s) { dlink_node *i; ClientHttpRequest *http; StoreEntry *e; char buf[MAX_IPSTRLEN]; for (i = ClientActiveRequests.head; i; i = i->next) { const char *p = NULL; http = static_cast(i->data); assert(http); ConnStateData * conn = http->getConn(); storeAppendPrintf(s, "Connection: %p\n", conn); if (conn != NULL) { - const int fd = conn->clientConnection->fd; + const int fd = conn->tcp->fd; storeAppendPrintf(s, "\tFD %d, read %" PRId64 ", wrote %" PRId64 "\n", fd, fd_table[fd].bytes_read, fd_table[fd].bytes_written); storeAppendPrintf(s, "\tFD desc: %s\n", fd_table[fd].desc); storeAppendPrintf(s, "\tin: buf %p, offset %ld, size %ld\n", - conn->in.buf, (long int) conn->in.notYetUsed, (long int) conn->in.allocatedSize); + conn->inBuf.content(), (long int) conn->inBuf.contentSize(), (long int) conn->inBuf.capacity); storeAppendPrintf(s, "\tremote: %s\n", - conn->clientConnection->remote.toUrl(buf,MAX_IPSTRLEN)); + conn->tcp->remote.toUrl(buf,MAX_IPSTRLEN)); storeAppendPrintf(s, "\tlocal: %s\n", - conn->clientConnection->local.toUrl(buf,MAX_IPSTRLEN)); + conn->tcp->local.toUrl(buf,MAX_IPSTRLEN)); storeAppendPrintf(s, "\tnrequests: %d\n", conn->nrequests); } storeAppendPrintf(s, "uri %s\n", http->uri); storeAppendPrintf(s, "logType %s\n", LogTags_str[http->logType]); storeAppendPrintf(s, "out.offset %ld, out.size %lu\n", (long int) http->out.offset, (unsigned long int) http->out.size); storeAppendPrintf(s, "req_sz %ld\n", (long int) http->req_sz); e = http->storeEntry(); storeAppendPrintf(s, "entry %p/%s\n", e, e ? e->getMD5Text() : "N/A"); storeAppendPrintf(s, "start %ld.%06d (%f seconds ago)\n", (long int) http->al->cache.start_time.tv_sec, (int) http->al->cache.start_time.tv_usec, tvSubDsec(http->al->cache.start_time, current_time)); #if USE_AUTH if (http->request->auth_user_request != NULL) p = http->request->auth_user_request->username(); else #endif if (http->request->extacl_user.size() > 0) { p = http->request->extacl_user.termedBuf(); } - if (!p && conn != NULL && conn->clientConnection->rfc931[0]) - p = conn->clientConnection->rfc931; + if (!p && conn != NULL && conn->tcp->rfc931[0]) + p = conn->tcp->rfc931; #if USE_SSL - if (!p && conn != NULL && Comm::IsConnOpen(conn->clientConnection)) - p = sslGetUserEmail(fd_table[conn->clientConnection->fd].ssl); + if (!p && conn != NULL && Comm::IsConnOpen(conn->tcp)) + p = sslGetUserEmail(fd_table[conn->tcp->fd].ssl); #endif if (!p) p = dash_str; storeAppendPrintf(s, "username %s\n", p); #if USE_DELAY_POOLS storeAppendPrintf(s, "delay_pool %d\n", DelayId::DelayClient(http).pool()); #endif storeAppendPrintf(s, "\n"); } } #if STAT_GRAPHS /* * urgh, i don't like these, but they do cut the amount of code down immensely */ === modified file 'src/tests/stub_client_side.cc' --- src/tests/stub_client_side.cc 2014-01-05 19:49:23 +0000 +++ src/tests/stub_client_side.cc 2014-01-19 05:41:22 +0000 @@ -11,74 +11,62 @@ void ClientSocketContext::keepaliveNextRequest() STUB void ClientSocketContext::pullData() STUB int64_t ClientSocketContext::getNextRangeOffset() const STUB_RETVAL(0) bool ClientSocketContext::canPackMoreRanges() const STUB_RETVAL(false) clientStream_status_t ClientSocketContext::socketState() STUB_RETVAL(STREAM_NONE) void ClientSocketContext::sendBody(HttpReply * rep, StoreIOBuffer bodyData) STUB void ClientSocketContext::sendStartOfMessage(HttpReply * rep, StoreIOBuffer bodyData) STUB size_t ClientSocketContext::lengthToSend(Range const &available) STUB_RETVAL(0) void ClientSocketContext::noteSentBodyBytes(size_t) STUB void ClientSocketContext::buildRangeHeader(HttpReply * rep) STUB clientStreamNode * ClientSocketContext::getTail() const STUB_RETVAL(NULL) clientStreamNode * ClientSocketContext::getClientReplyContext() const STUB_RETVAL(NULL) void ClientSocketContext::connIsFinished() STUB void ClientSocketContext::removeFromConnectionList(ConnStateData * conn) STUB void ClientSocketContext::deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData) STUB bool ClientSocketContext::multipartRangeRequest() const STUB_RETVAL(false) void ClientSocketContext::registerWithConn() STUB void ClientSocketContext::noteIoError(const int xerrno) STUB void ClientSocketContext::writeControlMsg(HttpControlMsg &msg) STUB -void ConnStateData::readSomeData() STUB -int ConnStateData::getAvailableBufferLength() const STUB_RETVAL(0) bool ConnStateData::areAllContextsForThisConnection() const STUB_RETVAL(false) void ConnStateData::freeAllContexts() STUB -void ConnStateData::notifyAllContexts(const int xerrno) STUB +void ConnStateData::noteTcpReadError(const int xerrno) STUB bool ConnStateData::clientParseRequests() STUB_RETVAL(false) void ConnStateData::readNextRequest() STUB -bool ConnStateData::maybeMakeSpaceAvailable() STUB_RETVAL(false) void ConnStateData::addContextToQueue(ClientSocketContext * context) STUB int ConnStateData::getConcurrentRequestCount() const STUB_RETVAL(0) bool ConnStateData::isOpen() const STUB_RETVAL(false) void ConnStateData::checkHeaderLimits() STUB void ConnStateData::sendControlMsg(HttpControlMsg msg) STUB -char *ConnStateData::In::addressToReadInto() const STUB_RETVAL(NULL) -int64_t ConnStateData::mayNeedToReadMoreBody() const STUB_RETVAL(0) #if USE_AUTH void ConnStateData::setAuth(const Auth::UserRequest::Pointer &aur, const char *cause) STUB #endif bool ConnStateData::transparent() const STUB_RETVAL(false) -bool ConnStateData::reading() const STUB_RETVAL(false) -void ConnStateData::stopReading() STUB -void ConnStateData::stopReceiving(const char *error) STUB -void ConnStateData::stopSending(const char *error) STUB void ConnStateData::expectNoForwarding() STUB void ConnStateData::noteMoreBodySpaceAvailable(BodyPipe::Pointer) STUB void ConnStateData::noteBodyConsumerAborted(BodyPipe::Pointer) STUB -bool ConnStateData::handleReadData(char *buf, size_t size) STUB_RETVAL(false) -bool ConnStateData::handleRequestBodyData() STUB_RETVAL(false) +bool ConnStateData::processRequestBodyData(MemBuf &) STUB_RETVAL(false) void ConnStateData::pinConnection(const Comm::ConnectionPointer &pinServerConn, HttpRequest *request, CachePeer *peer, bool auth) STUB void ConnStateData::unpinConnection() STUB const Comm::ConnectionPointer ConnStateData::validatePinnedConnection(HttpRequest *request, const CachePeer *peer) STUB_RETVAL(NULL) void ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &io) STUB -void ConnStateData::clientReadRequest(const CommIoCbParams &io) STUB -void ConnStateData::connStateClosed(const CommCloseCbParams &io) STUB void ConnStateData::requestTimeout(const CommTimeoutCbParams ¶ms) STUB void ConnStateData::swanSong() STUB void ConnStateData::quitAfterError(HttpRequest *request) STUB #if USE_SSL void ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) STUB void ConnStateData::getSslContextStart() STUB void ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) STUB void ConnStateData::sslCrtdHandleReplyWrapper(void *data, const HelperReply &reply) STUB void ConnStateData::sslCrtdHandleReply(const HelperReply &reply) STUB void ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode) STUB void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties) STUB bool ConnStateData::serveDelayedError(ClientSocketContext *context) STUB_RETVAL(false) #endif void setLogUri(ClientHttpRequest * http, char const *uri, bool cleanUrl) STUB const char *findTrailingHTTPVersion(const char *uriAndHTTPVersion, const char *end) STUB_RETVAL(NULL) int varyEvaluateMatch(StoreEntry * entry, HttpRequest * req) STUB_RETVAL(0) void clientOpenListenSockets(void) STUB void clientHttpConnectionsClose(void) STUB void httpRequestFree(void *) STUB === modified file 'src/tests/stub_libcomm.cc' --- src/tests/stub_libcomm.cc 2013-03-26 10:38:20 +0000 +++ src/tests/stub_libcomm.cc 2014-01-18 13:40:24 +0000 @@ -43,20 +43,35 @@ #include "comm/Loops.h" void Comm::SelectLoopInit(void) STUB void Comm::SetSelect(int, unsigned int, PF *, void *, time_t) STUB void Comm::ResetSelect(int) STUB comm_err_t Comm::DoSelect(int) STUB_RETVAL(COMM_ERROR) void Comm::QuickPollRequired(void) STUB #include "comm/TcpAcceptor.h" //Comm::TcpAcceptor(const Comm::ConnectionPointer &conn, const char *note, const Subscription::Pointer &aSub) STUB void Comm::TcpAcceptor::subscribe(const Subscription::Pointer &aSub) STUB void Comm::TcpAcceptor::unsubscribe(const char *) STUB void Comm::TcpAcceptor::acceptNext() STUB void Comm::TcpAcceptor::notify(const comm_err_t flag, const Comm::ConnectionPointer &) const STUB #include "comm/Write.h" void Comm::Write(const Comm::ConnectionPointer &, const char *, int, AsyncCall::Pointer &, FREE *) STUB void Comm::Write(const Comm::ConnectionPointer &conn, MemBuf *mb, AsyncCall::Pointer &callback) STUB void Comm::WriteCancel(const Comm::ConnectionPointer &conn, const char *reason) STUB /*PF*/ void Comm::HandleWrite(int, void*) STUB + +#include "comm/TcpReceiver.h" +//Comm::TcpReceiver::TcpReceiver(); +bool Comm::TcpReceiver::doneAll() const STUB_RETVAL(false) +void Comm::TcpReceiver::swanSong() STUB +void Comm::TcpReceiver::stopReadingXXX() STUB +void Comm::TcpReceiver::stopReceiving(const char *error) STUB +void Comm::TcpReceiver::stopSending(const char *error) STUB +bool Comm::TcpReceiver::maybeMakeSpaceAvailable() STUB_RETVAL(false) +void Comm::TcpReceiver::readSomeData() STUB +void Comm::TcpReceiver::readHandler(const CommIoCbParams &io) STUB +void Comm::TcpReceiver::handleConnectionClosed(const CommCloseCbParams &io) STUB +//Comm::ConnectionPointer Comm::TcpReceiver::tcp; +//MemBuf Comm::TcpReceiver::inBuf; +//AsyncCall::Pointer Comm::TcpReceiver::reader; === modified file 'src/tunnel.cc' --- src/tunnel.cc 2013-12-06 14:59:47 +0000 +++ src/tunnel.cc 2013-12-17 13:18:50 +0000 @@ -856,58 +856,58 @@ char *url = http->uri; /* * client_addr.isNoAddr() indicates this is an "internal" request * from peer_digest.c, asn.c, netdb.c, etc and should always * be allowed. yuck, I know. */ if (Config.accessList.miss && !request->client_addr.isNoAddr()) { /* * Check if this host is allowed to fetch MISSES from us (miss_access) * default is to allow. */ ACLFilledChecklist ch(Config.accessList.miss, request, NULL); ch.src_addr = request->client_addr; ch.my_addr = request->my_addr; if (ch.fastCheck() == ACCESS_DENIED) { debugs(26, 4, HERE << "MISS access forbidden."); err = new ErrorState(ERR_FORWARDING_DENIED, Http::scForbidden, request); *status_ptr = Http::scForbidden; - errorSend(http->getConn()->clientConnection, err); + errorSend(http->getConn()->tcp, err); return; } } debugs(26, 3, HERE << "'" << RequestMethodStr(request->method) << " " << url << " " << request->http_ver << "'"); ++statCounter.server.all.requests; ++statCounter.server.other.requests; tunnelState = new TunnelStateData; #if USE_DELAY_POOLS tunnelState->server.setDelayId(DelayId::DelayClient(http)); #endif tunnelState->url = xstrdup(url); tunnelState->request = request; tunnelState->server.size_ptr = size_ptr; tunnelState->status_ptr = status_ptr; - tunnelState->client.conn = http->getConn()->clientConnection; + tunnelState->client.conn = http->getConn()->tcp; 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); } static void tunnelRelayConnectRequest(const Comm::ConnectionPointer &srv, void *data) { TunnelStateData *tunnelState = (TunnelStateData *)data;