return false;
}
+/**
+ * Perform proxy_protocol_access ACL tests on the client which
+ * connected to PROXY protocol port to see if we trust the
+ * sender enough to accept their PROXY header claim.
+ */
+bool
+ConnStateData::proxyProtocolValidateClient()
+{
+ if (!Config.accessList.proxyProtocol)
+ return proxyProtocolError("PROXY client not permitted by default ACL");
+
+ ACLFilledChecklist ch(Config.accessList.proxyProtocol, NULL, clientConnection->rfc931);
+ ch.src_addr = clientConnection->remote;
+ ch.my_addr = clientConnection->local;
+ ch.conn(this);
+
+ if (ch.fastCheck() != ACCESS_ALLOWED)
+ return proxyProtocolError("PROXY client not permitted by ACLs");
+
+ return true;
+}
+
+/**
+ * Perform cleanup on PROXY protocol errors.
+ * If header parsing hits a fatal error terminate the connection,
+ * otherwise wait for more data.
+ */
+bool
+ConnStateData::proxyProtocolError(const char *msg)
+{
+ if (msg) {
+ // This is important to know, but maybe not so much that flooding the log is okay.
+#if QUIET_PROXY_PROTOCOL
+ // display the first of every 32 occurances at level 1, the others at level 2.
+ static uint8_t hide = 0;
+ debugs(33, (hide++ % 32 == 0 ? DBG_IMPORTANT : 2), msg << " from " << clientConnection);
+#else
+ debugs(33, DBG_IMPORTANT, msg << " from " << clientConnection);
+#endif
+ mustStop(msg);
+ }
+ return false;
+}
+
+/// magic octet prefix for PROXY protocol version 1
+static const SBuf Proxy1p0magic("PROXY ", 6);
+
+/// magic octet prefix for PROXY protocol version 2
+static const SBuf Proxy2p0magic("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12);
+
+/**
+ * Test the connection read buffer for PROXY protocol header.
+ * Version 1 and 2 header currently supported.
+ */
+bool
+ConnStateData::parseProxyProtocolHeader()
+{
+ // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
+
+ // detect and parse PROXY/2.0 protocol header
+ if (in.buf.startsWith(Proxy2p0magic))
+ return parseProxy2p0();
+
+ // detect and parse PROXY/1.0 protocol header
+ if (in.buf.startsWith(Proxy1p0magic))
+ return parseProxy1p0();
+
+ // detect and terminate other protocols
+ if (in.buf.length() >= Proxy2p0magic.length()) {
+ // PROXY/1.0 magic is shorter, so we know that
+ // the input does not start with any PROXY magic
+ return proxyProtocolError("PROXY protocol error: invalid header");
+ }
+
+ // TODO: detect short non-magic prefixes earlier to avoid
+ // waiting for more data which may never come
+
+ // not enough bytes to parse yet.
+ return false;
+}
+
+/// parse the PROXY/1.0 protocol header from the connection read buffer
+bool
+ConnStateData::parseProxy1p0()
+{
+ ::Parser::Tokenizer tok(in.buf);
+ tok.skip(Proxy1p0magic);
+
+ // skip to first LF (assumes it is part of CRLF)
+ static const CharacterSet lineContent = CharacterSet::LF.complement("non-LF");
+ SBuf line;
+ if (tok.prefix(line, lineContent, 107-Proxy1p0magic.length())) {
+ if (tok.skip('\n')) {
+ // found valid header
+ in.buf = tok.remaining();
+ needProxyProtocolHeader_ = false;
+ // reset the tokenizer to work on found line only.
+ tok.reset(line);
+ } else
+ return false; // no LF yet
+
+ } else // protocol error only if there are more than 107 bytes prefix header
+ return proxyProtocolError(in.buf.length() > 107? "PROXY/1.0 error: missing CRLF" : NULL);
+
+ static const SBuf unknown("UNKNOWN"), tcpName("TCP");
+ if (tok.skip(tcpName)) {
+
+ // skip TCP/IP version number
+ static const CharacterSet tcpVersions("TCP-version","46");
+ if(!tok.skipOne(tcpVersions))
+ return proxyProtocolError("PROXY/1.0 error: missing TCP version");
+
+ // skip SP after protocol version
+ if (!tok.skip(' '))
+ return proxyProtocolError("PROXY/1.0 error: missing SP");
+
+ SBuf ipa, ipb;
+ int64_t porta, portb;
+ static const CharacterSet ipChars = CharacterSet("IP Address",".:") + CharacterSet::HEXDIG;
+
+ // parse: src-IP SP dst-IP SP src-port SP dst-port CR
+ // leave the LF until later.
+ const bool correct = tok.prefix(ipa, ipChars) && tok.skip(' ') &&
+ tok.prefix(ipb, ipChars) && tok.skip(' ') &&
+ tok.int64(porta) && tok.skip(' ') &&
+ tok.int64(portb) &&
+ tok.skip('\r');
+ if (!correct)
+ return proxyProtocolError("PROXY/1.0 error: invalid syntax");
+
+ // parse IP and port strings
+ Ip::Address originalClient, originalDest;
+
+ if (!originalClient.GetHostByName(ipa.c_str()))
+ return proxyProtocolError("PROXY/1.0 error: invalid src-IP address");
+
+ if (!originalDest.GetHostByName(ipb.c_str()))
+ return proxyProtocolError("PROXY/1.0 error: invalid dst-IP address");
+
+ if (porta > 0 && porta <= 0xFFFF) // max uint16_t
+ originalClient.port(static_cast<uint16_t>(porta));
+ else
+ return proxyProtocolError("PROXY/1.0 error: invalid src port");
+
+ if (portb > 0 && portb <= 0xFFFF) // max uint16_t
+ originalDest.port(static_cast<uint16_t>(portb));
+ else
+ return proxyProtocolError("PROXY/1.0 error: invalid dst port");
+
+ // we have original client and destination details now
+ // replace the client connection values
+ debugs(33, 5, "PROXY/1.0 protocol on connection " << clientConnection);
+ clientConnection->local = originalDest;
+ clientConnection->remote = originalClient;
+ clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP.
+ debugs(33, 5, "PROXY/1.0 upgrade: " << clientConnection);
+
+ // repeat fetch ensuring the new client FQDN can be logged
+ if (Config.onoff.log_fqdn)
+ fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS);
+
+ return true;
+
+ } else if (tok.skip(unknown)) {
+ // found valid but unusable header
+ return true;
+
+ } else
+ return proxyProtocolError("PROXY/1.0 error: invalid protocol family");
+
+ return false;
+}
+
+/// parse the PROXY/2.0 protocol header from the connection read buffer
+bool
+ConnStateData::parseProxy2p0()
+{
+ static const SBuf::size_type prefixLen = Proxy2p0magic.length();
+ if (in.buf.length() < prefixLen + 4)
+ return false; // need more bytes
+
+ if ((in.buf[prefixLen] & 0xF0) != 0x20) // version == 2 is mandatory
+ return proxyProtocolError("PROXY/2.0 error: invalid version");
+
+ const char command = (in.buf[prefixLen] & 0x0F);
+ if ((command & 0xFE) != 0x00) // values other than 0x0-0x1 are invalid
+ return proxyProtocolError("PROXY/2.0 error: invalid command");
+
+ const char family = (in.buf[prefixLen+1] & 0xF0) >>4;
+ if (family > 0x3) // values other than 0x0-0x3 are invalid
+ return proxyProtocolError("PROXY/2.0 error: invalid family");
+
+ const char proto = (in.buf[prefixLen+1] & 0x0F);
+ if (proto > 0x2) // values other than 0x0-0x2 are invalid
+ return proxyProtocolError("PROXY/2.0 error: invalid protocol type");
+
+ const char *clen = in.buf.rawContent() + prefixLen + 2;
+ uint16_t len;
+ memcpy(&len, clen, sizeof(len));
+ len = ntohs(len);
+
+ if (in.buf.length() < prefixLen + 4 + len)
+ return false; // need more bytes
+
+ in.buf.consume(prefixLen + 4); // 4 being the extra bytes
+ const SBuf extra = in.buf.consume(len);
+ needProxyProtocolHeader_ = false; // found successfully
+
+ // LOCAL connections do nothing with the extras
+ if (command == 0x00/* LOCAL*/)
+ return true;
+
+ union pax {
+ struct { /* for TCP/UDP over IPv4, len = 12 */
+ struct in_addr src_addr;
+ struct in_addr dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ipv4_addr;
+ struct { /* for TCP/UDP over IPv6, len = 36 */
+ struct in6_addr src_addr;
+ struct in6_addr dst_addr;
+ uint16_t src_port;
+ uint16_t dst_port;
+ } ipv6_addr;
+#if NOT_SUPPORTED
+ struct { /* for AF_UNIX sockets, len = 216 */
+ uint8_t src_addr[108];
+ uint8_t dst_addr[108];
+ } unix_addr;
+#endif
+ };
+
+ pax ipu;
+ memcpy(&ipu, extra.rawContent(), sizeof(pax));
+
+ // replace the client connection values
+ debugs(33, 5, "PROXY/2.0 protocol on connection " << clientConnection);
+ switch (family)
+ {
+ case 0x1: // IPv4
+ clientConnection->local = ipu.ipv4_addr.dst_addr;
+ clientConnection->local.port(ntohs(ipu.ipv4_addr.dst_port));
+ clientConnection->remote = ipu.ipv4_addr.src_addr;
+ clientConnection->remote.port(ntohs(ipu.ipv4_addr.src_port));
+ clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP.
+ break;
+ case 0x2: // IPv6
+ clientConnection->local = ipu.ipv6_addr.dst_addr;
+ clientConnection->local.port(ntohs(ipu.ipv6_addr.dst_port));
+ clientConnection->remote = ipu.ipv6_addr.src_addr;
+ clientConnection->remote.port(ntohs(ipu.ipv6_addr.src_port));
+ clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP.
+ break;
+ default: // do nothing
+ break;
+ }
+ debugs(33, 5, "PROXY/2.0 upgrade: " << clientConnection);
+
+ // repeat fetch ensuring the new client FQDN can be logged
+ if (Config.onoff.log_fqdn)
+ fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS);
+
+ return true;
+}
+
/**
* 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.
+ * Returns true after completing parsing of at least one request [header]. That
+ * includes cases where parsing ended with an error (e.g., a huge request).
*/
bool
ConnStateData::clientParseRequests()
if (concurrentRequestQueueFilled())
break;
+ // try to parse the PROXY protocol header magic bytes
+ if (needProxyProtocolHeader_ && !parseProxyProtocolHeader())
+ break;
+
Http::ProtocolVersion http_ver;
- ClientSocketContext *context = parseOneRequest(http_ver);
-
- /* 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");
+ if (ClientSocketContext *context = parseOneRequest(http_ver)) {
+ debugs(33, 5, clientConnection << ": done parsing a request");
AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http));
commSetConnTimeout(clientConnection, Config.Timeout.lifetime, timeoutCall);