Addressed discussion concerns and refreshed the patch.
2016-07-19 8:13 GMT+03:00 Amos Jeffries <squ...@treenet.co.nz>: > Is ftp_port traffic another one? IMO this is not a case because Ftp::Server (similarly to Http::One::Server) cares about inBuf consumtion and ClientHttpRequest objects creation. Eduard.
Some failed transactions are not logged. There are situations when Squid logs nothing to access.log after an [abnormal] transaction termination. Such "stealthy" transactions may be a security risk and an accounting problem. ClientHttpRequest is responsible for logging most transactions but that object is created only after the HTTP request headers are successfully parsed. Request header parsing errors may be detected and logged appropriately, but the job handling the incoming transaction may terminate for reasons outside the parsing code control (e.g., a job- killing exception thrown when there are no request headers to start parsing yet or when the job waits for more request headers to finishing parsing). This change adds access logging for three cases: 1. accept(2) system call errors (before ConnStateData job is created). 2. Unexpected ConnStateData job termination, when there is no ClientHttpRequest to log the failure. 3. Connections which send no bytes: these connections drain Squid resources and, hence, should be logged. TODO: make this behavior configurable because some browsers are known to routinely create such connections(and, hence, logging them by default may create too much noise). === modified file 'doc/release-notes/release-4.sgml' --- doc/release-notes/release-4.sgml 2016-07-01 11:30:00 +0000 +++ doc/release-notes/release-4.sgml 2016-07-19 10:29:44 +0000 @@ -199,60 +199,66 @@ <tag>request_start_timeout</tag> <p>New directive controlling how long Squid waits for the first request bytes to arrive after initial connection establishment by a client. <tag>server_pconn_for_nonretriable</tag> <p>New directive to provide fine-grained control over persistent connection reuse when forwarding HTTP requests that Squid cannot retry. It is useful in environments where opening new connections is very expensive and race conditions associated with persistent connections are very rare and/or only cause minor problems. <tag>shared_memory_locking</tag> <p>New directive to ensure shared memory is all available immediately on startup. Protects against SIGBUS errors, but delays startup. <tag>tls_outgoing_options</tag> <p>New directive to define TLS security context options for outgoing connections. For example to HTTPS servers. <tag>url_rewrite_timeout</tag> <p>Squid times active requests to redirector. This option sets the timeout value and the Squid reaction to a timed out request. </descrip> <sect1>Changes to existing tags<label id="modifiedtags"> <p> <descrip> + <tag>access_log</tag> + <p>TCP accept(2) errors logged with URI <em>error:accept-user-connection</em> + <p>Unused connections received in <em>http_port</em> or <em>https_port</em> + or transactions terminated before reading[parsing] request headers + logged with URI <em>error:transaction-end-before-headers</em> + <tag>acl</tag> <p>New <em>-m</em> flag for <em>note</em> ACL to match substrings. <tag>auth_param</tag> <p>New parameter <em>queue-size=</em> to set the maximum number of queued requests. <tag>cache_peer</tag> <p>New option <em>auth-no-keytab</em> to let GSSAPI implementation determine which Kerberos credentials to use, instead of specifying a keytab. <p>New option <em>tls-min-version=1.N</em> to set minimum TLS version allowed. <p>New option <em>tls-default-ca</em> replaces <em>sslflags=NO_DEFAULT_CA</em> <p>New option <em>tls-no-npn</em> to disable sending TLS NPN extension. <p>All <em>ssloptions=</em> values for SSLv2 configuration or disabling have been removed. <p>Removed <em>sslversion=</em> option. Use <em>tls-options=</em> instead. <p>Manual squid.conf update may be required on upgrade. <p>Replaced <em>sslcafile=</em> with <em>tls-cafile=</em> which takes multiple entries. <tag>external_acl_type</tag> <p>New parameter <em>queue-size=</em> to set the maximum number of queued requests. <p>Format field updated to accept any logformat %macro code. <tag>http_port</tag> <p>New option <em>tls-min-version=1.N</em> to set minimum TLS version allowed. <p>New option <em>tls-default-ca</em> replaces <em>sslflags=NO_DEFAULT_CA</em> <p>New option <em>tls-no-npn</em> to disable sending TLS NPN extension. <p>All <em>option=</em> values for SSLv2 configuration or disabling have been removed. === modified file 'src/Pipeline.cc' --- src/Pipeline.cc 2016-01-24 17:41:43 +0000 +++ src/Pipeline.cc 2016-07-19 07:51:03 +0000 @@ -9,55 +9,67 @@ /* * DEBUG: section 33 Client Request Pipeline */ #include "squid.h" #include "anyp/PortCfg.h" #include "client_side.h" #include "Debug.h" #include "http/Stream.h" #include "Pipeline.h" void Pipeline::add(const Http::StreamPointer &c) { requests.push_back(c); ++nrequests; debugs(33, 3, "Pipeline " << (void*)this << " add request " << nrequests << ' ' << c); } Http::StreamPointer Pipeline::front() const { if (requests.empty()) { debugs(33, 3, "Pipeline " << (void*)this << " empty"); return Http::StreamPointer(); } debugs(33, 3, "Pipeline " << (void*)this << " front " << requests.front()); return requests.front(); } +Http::StreamPointer +Pipeline::back() const +{ + if (requests.empty()) { + debugs(33, 3, "Pipeline " << (void*)this << " empty"); + return Http::StreamPointer(); + } + + debugs(33, 3, "Pipeline " << (void*)this << " back " << requests.back()); + return requests.back(); +} + void Pipeline::terminateAll(int xerrno) { while (!requests.empty()) { Http::StreamPointer context = requests.front(); debugs(33, 3, "Pipeline " << (void*)this << " notify(" << xerrno << ") " << context); context->noteIoError(xerrno); context->finished(); // cleanup and self-deregister assert(context != requests.front()); } } void Pipeline::popMe(const Http::StreamPointer &which) { if (requests.empty()) return; debugs(33, 3, "Pipeline " << (void*)this << " drop " << requests.front()); // in reality there may be multiple contexts doing processing in parallel. // XXX: pipeline still assumes HTTP/1 FIFO semantics are obeyed. assert(which == requests.front()); requests.pop_front(); } === modified file 'src/Pipeline.h' --- src/Pipeline.h 2016-01-24 17:41:43 +0000 +++ src/Pipeline.h 2016-07-19 07:51:03 +0000 @@ -19,53 +19,56 @@ * * Transactions in the queue may be fully processed, but not yet delivered, * or only partially processed. * * - HTTP/1 pipelined requests can be processed out of order but * responses MUST be written to the client in-order. * The front() context is for the response writing transaction. * The back context may still be reading a request payload/body. * Other contexts are in deferred I/O state, but may be accumulating * payload/body data to be written later. * * - HTTP/2 multiplexed streams can be processed and delivered in any order. * * For consistency we treat the pipeline as a FIFO queue in both cases. */ class Pipeline { Pipeline(const Pipeline &) = delete; Pipeline & operator =(const Pipeline &) = delete; public: Pipeline() : nrequests(0) {} ~Pipeline() = default; /// register a new request context to the pipeline void add(const Http::StreamPointer &); /// get the first request context in the pipeline Http::StreamPointer front() const; + /// get the last request context in the pipeline + Http::StreamPointer back() const; + /// how many requests are currently pipelined size_t count() const {return requests.size();} /// whether there are none or any requests currently pipelined bool empty() const {return requests.empty();} /// tell everybody about the err, and abort all waiting requests void terminateAll(const int xerrno); /// deregister the front request from the pipeline void popMe(const Http::StreamPointer &); /// Number of requests seen in this pipeline (so far). /// Includes incomplete transactions. uint32_t nrequests; private: /// requests parsed from the connection but not yet completed. std::list<Http::StreamPointer> requests; }; #endif /* SQUID_SRC_PIPELINE_H */ === modified file 'src/client_side.cc' --- src/client_side.cc 2016-07-18 12:36:38 +0000 +++ src/client_side.cc 2016-07-19 09:48:15 +0000 @@ -557,60 +557,62 @@ debugs(33, 2, "WARNING: Graceful closure on " << clientConnection << " 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); 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); return; } /* NOT REACHABLE */ } #endif // cleans up before destructor is called void ConnStateData::swanSong() { debugs(33, 2, HERE << clientConnection); + checkLogging(); + flags.readMore = false; DeregisterRunner(this); clientdbEstablished(clientConnection->remote, -1); /* decrement */ pipeline.terminateAll(0); unpinConnection(true); Server::swanSong(); // closes the client connection #if USE_AUTH // NP: do this bit after closing the connections to avoid side effects from unwanted TCP RST setAuth(NULL, "ConnStateData::SwanSong cleanup"); #endif flags.swanSang = true; } bool ConnStateData::isOpen() const { return cbdataReferenceValid(this) && // XXX: checking "this" in a method Comm::IsConnOpen(clientConnection) && !fd_table[clientConnection->fd].closing(); } ConnStateData::~ConnStateData() { debugs(33, 3, HERE << clientConnection); if (isOpen()) @@ -3999,30 +4001,61 @@ void ConnStateData::unpinConnection(const bool andClose) { debugs(33, 3, HERE << pinning.serverConnection); if (pinning.peer) cbdataReferenceDone(pinning.peer); if (Comm::IsConnOpen(pinning.serverConnection)) { if (pinning.closeHandler != NULL) { comm_remove_close_handler(pinning.serverConnection->fd, pinning.closeHandler); pinning.closeHandler = NULL; } stopPinnedConnectionMonitoring(); // close the server side socket if requested if (andClose) pinning.serverConnection->close(); pinning.serverConnection = NULL; } safe_free(pinning.host); pinning.zeroReply = false; /* NOTE: pinning.pinned should be kept. This combined with fd == -1 at the end of a request indicates that the host * connection has gone away */ } +void +ConnStateData::checkLogging() +{ + // if we are parsing request body, its request is responsible for logging + if (bodyPipe) + return; + + // a request currently using this connection is responsible for logging + if (!pipeline.empty() && pipeline.back()->mayUseConnection()) + return; + + /* Either we are waiting for the very first transaction, or + * we are done with the Nth transaction and are waiting for N+1st. + * XXX: We assume that if anything was added to inBuf, then it could + * only be consumed by actions already covered by the above checks. + */ + + // do not log connections that closed after a transaction (it is normal) + // TODO: configurable logging for connections that sent us no bytes + // XXX: TLS bytes are consumed without going through inBuf + // XXX: PROXY protocol bytes are consumed without going through inBuf + if (receivedFirstByte_ && inBuf.isEmpty()) + return; + + /* Create a temporary ClientHttpRequest object. Its destructor will log. */ + ClientHttpRequest http(this); + http.req_sz = inBuf.length(); + char const *uri = "error:transaction-end-before-headers"; + http.uri = xstrdup(uri); + setLogUri(&http, uri); +} === modified file 'src/client_side.h' --- src/client_side.h 2016-05-18 16:26:16 +0000 +++ src/client_side.h 2016-07-19 07:51:03 +0000 @@ -302,60 +302,61 @@ err_type handleChunkedRequestBody(); void startPinnedConnectionMonitoring(); void clientPinnedConnectionRead(const CommIoCbParams &io); #if USE_OPENSSL /// Handles a ready-for-reading TLS squid-to-server connection that /// we thought was idle. /// \return false if and only if the connection should be closed. bool handleIdleClientPinnedTlsRead(); #endif /// parse input buffer prefix into a single transfer protocol request /// return NULL to request more header bytes (after checking any limits) /// use abortRequestParsing() to handle parsing errors w/o creating request virtual Http::Stream *parseOneRequest() = 0; /// start processing a freshly parsed request virtual void processParsedRequest(Http::Stream *) = 0; /// returning N allows a pipeline of 1+N requests (see pipeline_prefetch) virtual int pipelinePrefetchMax() const; /// timeout to use when waiting for the next request virtual time_t idleTimeout() const = 0; BodyPipe::Pointer bodyPipe; ///< set when we are reading request body private: /* ::Server API */ virtual bool connFinishedWithConn(int size); + virtual void checkLogging(); void clientAfterReadingRequests(); bool concurrentRequestQueueFilled() const; void pinNewConnection(const Comm::ConnectionPointer &pinServer, HttpRequest *request, CachePeer *aPeer, bool auth); /* PROXY protocol functionality */ bool proxyProtocolValidateClient(); bool parseProxyProtocolHeader(); bool parseProxy1p0(); bool parseProxy2p0(); bool proxyProtocolError(const char *reason); /// whether PROXY protocol header is still expected bool needProxyProtocolHeader_; #if USE_AUTH /// some user details that can be used to perform authentication on this connection Auth::UserRequest::Pointer auth_; #endif /// the parser state for current HTTP/1.x input buffer processing Http1::RequestParserPointer parser_; #if USE_OPENSSL bool switchedToHttps_; bool parsingTlsHandshake; ///< whether we are getting/parsing TLS Hello bytes /// 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 === modified file 'src/comm/TcpAcceptor.cc' --- src/comm/TcpAcceptor.cc 2016-04-03 23:41:58 +0000 +++ src/comm/TcpAcceptor.cc 2016-07-19 09:38:03 +0000 @@ -1,56 +1,58 @@ /* * Copyright (C) 1996-2016 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /* DEBUG: section 05 Listener Socket Handler */ #include "squid.h" +#include "acl/FilledChecklist.h" #include "anyp/PortCfg.h" #include "base/TextException.h" #include "client_db.h" #include "comm/AcceptLimiter.h" #include "comm/comm_internal.h" #include "comm/Connection.h" #include "comm/Loops.h" #include "comm/TcpAcceptor.h" #include "CommCalls.h" #include "eui/Config.h" #include "fd.h" #include "fde.h" #include "globals.h" #include "ip/Intercept.h" #include "ip/QosConfig.h" +#include "log/access_log.h" #include "MasterXaction.h" #include "profiler/Profiler.h" #include "SquidConfig.h" #include "SquidTime.h" #include "StatCounters.h" #include <cerrno> #ifdef HAVE_NETINET_TCP_H // required for accept_filter to build. #include <netinet/tcp.h> #endif CBDATA_NAMESPACED_CLASS_INIT(Comm, TcpAcceptor); Comm::TcpAcceptor::TcpAcceptor(const Comm::ConnectionPointer &newConn, const char *, const Subscription::Pointer &aSub) : AsyncJob("Comm::TcpAcceptor"), errcode(0), isLimited(0), theCallSub(aSub), conn(newConn), listenPort_() {} Comm::TcpAcceptor::TcpAcceptor(const AnyP::PortCfgPointer &p, const char *, const Subscription::Pointer &aSub) : AsyncJob("Comm::TcpAcceptor"), errcode(0), isLimited(0), theCallSub(aSub), conn(p->listenConn), listenPort_(p) @@ -229,85 +231,99 @@ if (!okToAccept()) { AcceptLimiter::Instance().defer(afd); } else { afd->acceptNext(); } SetSelect(fd, COMM_SELECT_READ, Comm::TcpAcceptor::doAccept, afd, 0); } catch (const std::exception &e) { fatalf("FATAL: error while accepting new client connection: %s\n", e.what()); } catch (...) { fatal("FATAL: error while accepting new client connection: [unknown]\n"); } } bool Comm::TcpAcceptor::okToAccept() { static time_t last_warn = 0; if (fdNFree() >= RESERVED_FD) return true; if (last_warn + 15 < squid_curtime) { debugs(5, DBG_CRITICAL, "WARNING! Your cache is running out of filedescriptors"); last_warn = squid_curtime; } return false; } +static void +logAcceptError(const Comm::ConnectionPointer &conn) +{ + AccessLogEntry::Pointer al = new AccessLogEntry; + al->tcpClient = conn; + al->url = "error:accept-user-connection"; + ACLFilledChecklist ch(NULL, NULL, NULL); + ch.src_addr = conn->remote; + ch.my_addr = conn->local; + accessLogLog(al, &ch); +} + void Comm::TcpAcceptor::acceptOne() { /* * We don't worry about running low on FDs here. Instead, * doAccept() will use AcceptLimiter if we reach the limit * there. */ /* Accept a new connection */ ConnectionPointer newConnDetails = new Connection(); const Comm::Flag flag = oldAccept(newConnDetails); /* Check for errors */ if (!newConnDetails->isOpen()) { if (flag == Comm::NOMESSAGE) { /* register interest again */ debugs(5, 5, HERE << "try later: " << conn << " handler Subscription: " << theCallSub); SetSelect(conn->fd, COMM_SELECT_READ, doAccept, this, 0); return; } // A non-recoverable error; notify the caller */ debugs(5, 5, HERE << "non-recoverable error:" << status() << " handler Subscription: " << theCallSub); + if (intendedForUserConnections()) + logAcceptError(newConnDetails); notify(flag, newConnDetails); mustStop("Listener socket closed"); return; } debugs(5, 5, HERE << "Listener: " << conn << " accepted new connection " << newConnDetails << " handler Subscription: " << theCallSub); notify(flag, newConnDetails); } void Comm::TcpAcceptor::acceptNext() { Must(IsConnOpen(conn)); debugs(5, 2, HERE << "connection on " << conn); acceptOne(); } void Comm::TcpAcceptor::notify(const Comm::Flag flag, const Comm::ConnectionPointer &newConnDetails) const { // listener socket handlers just abandon the port with Comm::ERR_CLOSING // it should only happen when this object is deleted... if (flag == Comm::ERR_CLOSING) { return; } if (theCallSub != NULL) { AsyncCall::Pointer call = theCallSub->callback(); === modified file 'src/comm/TcpAcceptor.h' --- src/comm/TcpAcceptor.h 2016-01-01 00:12:18 +0000 +++ src/comm/TcpAcceptor.h 2016-07-19 07:51:03 +0000 @@ -77,36 +77,38 @@ int errcode; protected: friend class AcceptLimiter; int32_t isLimited; ///< whether this socket is delayed and on the AcceptLimiter queue. private: Subscription::Pointer theCallSub; ///< used to generate AsyncCalls handling our events. /// conn being listened on for new connections /// Reserved for read-only use. ConnectionPointer conn; /// configuration details of the listening port (if provided) AnyP::PortCfgPointer listenPort_; /// listen socket closure handler AsyncCall::Pointer closer_; /// Method to test if there are enough file descriptors to open a new client connection /// if not the accept() will be postponed static bool okToAccept(); /// Method callback for whenever an FD is ready to accept a client connection. static void doAccept(int fd, void *data); void acceptOne(); Comm::Flag oldAccept(Comm::ConnectionPointer &details); void setListen(); void handleClosure(const CommCloseCbParams &io); + /// whether we are listening on one of the squid.conf *ports + bool intendedForUserConnections() const { return bool(listenPort_); } }; } // namespace Comm #endif /* SQUID_COMM_TCPACCEPTOR_H */ === modified file 'src/servers/Server.cc' --- src/servers/Server.cc 2016-06-14 16:54:23 +0000 +++ src/servers/Server.cc 2016-07-19 07:51:03 +0000 @@ -140,60 +140,61 @@ if (!handleReadData()) return; /* Continue to process previously read data */ break; case Comm::ENDFILE: // close detected by 0-byte read debugs(33, 5, io.conn << " closed?"); if (connFinishedWithConn(rd.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 */ break; // case Comm::COMM_ERROR: default: // no other flags should ever occur debugs(33, 2, io.conn << ": got flag " << rd.flag << "; " << xstrerr(rd.xerrno)); + checkLogging(); pipeline.terminateAll(rd.xerrno); io.conn->close(); return; } afterClientRead(); } /** callback handling the Comm::Write completion * * Will call afterClientWrite(size_t) to sync the I/O state. * Then writeSomeData() to initiate any followup writes that * could be immediately done. */ void Server::clientWriteDone(const CommIoCbParams &io) { debugs(33,5, io.conn); Must(writer != nullptr); writer = nullptr; /* Bail out quickly on Comm::ERR_CLOSING - close handlers will tidy up */ if (io.flag == Comm::ERR_CLOSING || !Comm::IsConnOpen(clientConnection)) { debugs(33,5, io.conn << " closing Bailout."); return; } Must(io.conn->fd == clientConnection->fd); if (io.flag && pipeline.front()) === modified file 'src/servers/Server.h' --- src/servers/Server.h 2016-06-14 16:54:23 +0000 +++ src/servers/Server.h 2016-07-19 07:51:03 +0000 @@ -90,36 +90,39 @@ public: /// grows the available read buffer space (if possible) void maybeMakeSpaceAvailable(); // Client TCP connection details from comm layer. Comm::ConnectionPointer clientConnection; /** * The transfer protocol currently being spoken on this connection. * HTTP/1.x CONNECT, HTTP/1.1 Upgrade and HTTP/2 SETTINGS offer the * ability to change protocols on the fly. */ AnyP::ProtocolVersion transferProtocol; /// Squid listening port details where this connection arrived. AnyP::PortCfgPointer port; /// read I/O buffer for the client connection SBuf inBuf; bool receivedFirstByte_; ///< true if at least one byte received on this connection /// set of requests waiting to be serviced Pipeline pipeline; protected: void doClientRead(const CommIoCbParams &io); void clientWriteDone(const CommIoCbParams &io); + /// Log the current [attempt at] transaction if nobody else will. + virtual void checkLogging() = 0; + AsyncCall::Pointer reader; ///< set when we are reading AsyncCall::Pointer writer; ///< set when we are writing }; #endif /* SQUID_SERVERS_SERVER_H */
_______________________________________________ squid-dev mailing list squid-dev@lists.squid-cache.org http://lists.squid-cache.org/listinfo/squid-dev