From: Amos Jeffries Date: Sat, 12 Jul 2014 13:19:10 +0000 (-0700) Subject: Split PROXY protocol parse into v1 and v2 methods X-Git-Tag: SQUID_3_5_0_1~75^2~28 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3bd97e7e4fa0fe45ef229aa9abb552d51c0bd4f2;p=thirdparty%2Fsquid.git Split PROXY protocol parse into v1 and v2 methods --- diff --git a/src/client_side.cc b/src/client_side.cc index 037294388e..ec52a5f696 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -2905,8 +2905,9 @@ ConnStateData::concurrentRequestQueueFilled() const } /** - * Perform follow_x_forwarded_for ACL tests on the - * client which connected to PROXY protocol port. + * Perform forwarded_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() @@ -2916,10 +2917,9 @@ ConnStateData::proxyProtocolValidateClient() ch.my_addr = clientConnection->local; ch.conn(this); - if (ch.fastCheck() != ACCESS_ALLOWED) { - mustStop("PROXY client not permitted by ACLs"); - return false; - } + if (ch.fastCheck() != ACCESS_ALLOWED) + return proxyProtocolError("PROXY client not permitted by ACLs"); + return true; } @@ -2929,200 +2929,211 @@ ConnStateData::proxyProtocolValidateClient() * otherwise wait for more data. */ bool -ConnStateData::proxyProtocolError(bool fatalError) +ConnStateData::proxyProtocolError(const char *msg) { - if (fatalError) { - // terminate the connection. invalid input. - stopReceiving("PROXY protocol error"); - stopSending("PROXY protocol error"); - } + if (msg) + mustStop(msg); return false; } +/// magic octet prefix for PROXY protocol version 1 +static const SBuf Proxy10magic("PROXY ", 6); + +/// magic octet prefix for PROXY protocol version 2 +static const SBuf Proxy20magic("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); + /** - * Parse the connection read buffer for PROXY protocol header. - * Only version 1 header currently supported. + * Test the connection read buffer for PROXY protocol header. + * Version 1 and 2 header currently supported. */ bool -ConnStateData::parseProxyProtocolMagic() +ConnStateData::findProxyProtocolMagic() { // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt - // magic octet prefix for PROXY protocol version 1 - static const SBuf proxy1magic("PROXY "); - - // magic octet prefix for PROXY protocol version 2 - static const SBuf proxy2magic("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); - // detect and parse PROXY protocol version 1 header - if (in.buf.length() > proxy1magic.length() && in.buf.startsWith(proxy1magic)) { - ::Parser::Tokenizer tok(in.buf); - tok.skip(proxy1magic); - - SBuf tcpVersion; - if (!tok.prefix(tcpVersion, CharacterSet::ALPHA+CharacterSet::DIGIT)) - return proxyProtocolError(tok.atEnd()); - - if (!tcpVersion.cmp("UNKNOWN")) { - // skip to first LF (assumes it is part of CRLF) - const SBuf::size_type pos = in.buf.findFirstOf(CharacterSet::LF); - if (pos != SBuf::npos) { - if (in.buf[pos-1] != '\r') - return proxyProtocolError(false); // invalid terminator - // found valid but unusable header - in.buf.consume(pos); - needProxyProtocolHeader_ = false; - return true; - } - // else, no LF found - - // protocol error only if there are more than 107 bytes prefix header - return proxyProtocolError(in.buf.length() > 107); + if (in.buf.length() > Proxy10magic.length() && in.buf.startsWith(Proxy10magic)) { + return parseProxy10(); - } else if (!tcpVersion.cmp("TCP",3)) { + // detect and parse PROXY protocol version 2 header + } else if (in.buf.length() > Proxy20magic.length() && in.buf.startsWith(Proxy20magic)) { + return parseProxy20(); - // skip SP after protocol version - if (!tok.skip(' ')) - return proxyProtocolError(tok.atEnd()); + // detect and terminate other protocols + } else if (in.buf.length() >= Proxy20magic.length()) { + // input other than the PROXY header is a protocol error + return proxyProtocolError("PROXY protocol error: invalid header"); + } - SBuf ipa, ipb; - int64_t porta, portb; - const CharacterSet ipChars = CharacterSet("IP Address",".:") + CharacterSet::HEXDIG; + // not enough bytes to parse yet. + return false; +} - // parse src-IP SP dst-IP SP src-port SP dst-port CRLF - if (!tok.prefix(ipa, ipChars) || !tok.skip(' ') || - !tok.prefix(ipb, ipChars) || !tok.skip(' ') || - !tok.int64(porta) || !tok.skip(' ') || - !tok.int64(portb) || !tok.skip('\r') || !tok.skip('\n')) - return proxyProtocolError(!tok.atEnd()); +/// parse the PROXY/1.0 protocol header from the connection read buffer +bool +ConnStateData::parseProxy10() +{ + ::Parser::Tokenizer tok(in.buf); + tok.skip(Proxy10magic); + + SBuf tcpVersion; + if (!tok.prefix(tcpVersion, CharacterSet::ALPHA+CharacterSet::DIGIT)) + return proxyProtocolError(tok.atEnd()?"PROXY/1.0 error: invalid protocol family":NULL); + + if (!tcpVersion.cmp("UNKNOWN")) { + // skip to first LF (assumes it is part of CRLF) + const SBuf::size_type pos = in.buf.findFirstOf(CharacterSet::LF); + if (pos != SBuf::npos) { + if (in.buf[pos-1] != '\r') + return proxyProtocolError("PROXY/1.0 error: missing CR"); + // found valid but unusable header + in.buf.consume(pos); + needProxyProtocolHeader_ = false; + return true; + } + // else, no LF found - // XXX parse IP and port strings - Ip::Address originalClient, originalDest; + // protocol error only if there are more than 107 bytes prefix header + return proxyProtocolError(in.buf.length() > 107? "PROXY error: missing CRLF":NULL); - if (!originalClient.GetHostByName(ipa.c_str())) - return proxyProtocolError(true); + } else if (!tcpVersion.cmp("TCP",3)) { - if (!originalDest.GetHostByName(ipb.c_str())) - return false; + // skip SP after protocol version + if (!tok.skip(' ')) + return proxyProtocolError(tok.atEnd()?"PROXY/1.0 error: missing SP":NULL); - if (porta > 0 && porta <= 0xFFFF) // max uint16_t - originalClient.port(static_cast(porta)); - else - return proxyProtocolError(true); + SBuf ipa, ipb; + int64_t porta, portb; + const CharacterSet ipChars = CharacterSet("IP Address",".:") + CharacterSet::HEXDIG; - if (portb > 0 && portb <= 0xFFFF) // max uint16_t - originalDest.port(static_cast(portb)); - else - return proxyProtocolError(true); + // parse src-IP SP dst-IP SP src-port SP dst-port CRLF + if (!tok.prefix(ipa, ipChars) || !tok.skip(' ') || + !tok.prefix(ipb, ipChars) || !tok.skip(' ') || + !tok.int64(porta) || !tok.skip(' ') || + !tok.int64(portb) || !tok.skip('\r') || !tok.skip('\n')) + return proxyProtocolError(!tok.atEnd()?"PROXY/1.0 error: invalid syntax":NULL); - in.buf = tok.remaining(); // sync buffers - needProxyProtocolHeader_ = false; // found successfully + in.buf = tok.remaining(); // sync buffers + needProxyProtocolHeader_ = false; // found successfully - // we have original client and destination details now - // replace the client connection values - debugs(33, 5, "PROXY protocol on connection " << clientConnection); - clientConnection->local = originalDest; - clientConnection->remote = originalClient; - debugs(33, 5, "PROXY upgrade: " << clientConnection); + // parse IP and port strings + Ip::Address originalClient, originalDest; - // repeat fetch ensuring the new client FQDN can be logged - if (Config.onoff.log_fqdn) - fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); + if (!originalClient.GetHostByName(ipa.c_str())) + return proxyProtocolError("PROXY/1.0 error: invalid src-IP address"); - return true; - } + if (!originalDest.GetHostByName(ipb.c_str())) + return proxyProtocolError("PROXY/1.0 error: invalid dst-IP address"); - // detect and parse PROXY protocol version 2 header - } else if (in.buf.length() > proxy2magic.length() && in.buf.startsWith(proxy2magic)) { + if (porta > 0 && porta <= 0xFFFF) // max uint16_t + originalClient.port(static_cast(porta)); + else + return proxyProtocolError("PROXY/1.0 error: invalid src port"); - if ((in.buf[0] & 0xF0) != 0x20) // version == 2 is mandatory - proxyProtocolError(true); + if (portb > 0 && portb <= 0xFFFF) // max uint16_t + originalDest.port(static_cast(portb)); + else + return proxyProtocolError("PROXY/1.0 error: invalid dst port"); - const char command = (in.buf[0] & 0x0F); - if ((command & 0xFE) != 0x00) // values other than 0x0-0x1 are invalid - proxyProtocolError(true); + // 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; + debugs(33, 5, "PROXY/1.0 upgrade: " << clientConnection); - const char family = (in.buf[1] & 0xF0) >>4; - if (family > 0x3) // values other than 0x0-0x3 are invalid - proxyProtocolError(true); + // repeat fetch ensuring the new client FQDN can be logged + if (Config.onoff.log_fqdn) + fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); - const char proto = (in.buf[1] & 0x0F); - if (proto > 0x2) // values other than 0x0-0x2 are invalid - proxyProtocolError(true); + return true; + } - const char *clen = in.buf.rawContent() + proxy2magic.length() + 2; - const uint16_t len = ntohs(*(reinterpret_cast(clen))); + return false; +} - if (in.buf.length() < proxy2magic.length() + 4 + len) - return false; // need more bytes +/// parse the PROXY/2.0 protocol header from the connection read buffer +bool +ConnStateData::parseProxy20() +{ + if ((in.buf[0] & 0xF0) != 0x20) // version == 2 is mandatory + return proxyProtocolError("PROXY/2.0 error: invalid version"); - in.buf.consume(proxy2magic.length() + 4); // 4 being the extra bytes - const SBuf extra = in.buf.consume(len); - needProxyProtocolHeader_ = false; // found successfully + const char command = (in.buf[0] & 0x0F); + if ((command & 0xFE) != 0x00) // values other than 0x0-0x1 are invalid + return proxyProtocolError("PROXY/2.0 error: invalid command"); - // LOCAL connections do nothing with the extras - if (command == 0x00/* LOCAL*/) - return true; + const char family = (in.buf[1] & 0xF0) >>4; + if (family > 0x3) // values other than 0x0-0x3 are invalid + return proxyProtocolError("PROXY/2.0 error: invalid family"); - typedef union proxy_addr { - 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; + const char proto = (in.buf[1] & 0x0F); + if (proto > 0x2) // values other than 0x0-0x2 are invalid + return proxyProtocolError("PROXY/2.0 error: invalid protocol type"); - const pax *ipu = reinterpret_cast(extra.rawContent()); + const char *clen = in.buf.rawContent() + Proxy20magic.length() + 2; + const uint16_t len = ntohs(*(reinterpret_cast(clen))); - // replace the client connection values - debugs(33, 5, "PROXY 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)); - 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)); - break; - default: // do nothing - break; - } - debugs(33, 5, "PROXY upgrade: " << clientConnection); + if (in.buf.length() < Proxy20magic.length() + 4 + len) + return false; // need more bytes - // repeat fetch ensuring the new client FQDN can be logged - if (Config.onoff.log_fqdn) - fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS); + in.buf.consume(Proxy20magic.length() + 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; - // detect and terminate other protocols - } else if (in.buf.length() >= proxy2magic.length()) { - - // input other than the PROXY header is a protocol error - return proxyProtocolError(true); + typedef union proxy_addr { + 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; + + const pax *ipu = reinterpret_cast(extra.rawContent()); + + // 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)); + 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)); + break; + default: // do nothing + break; } + debugs(33, 5, "PROXY/2.0 upgrade: " << clientConnection); - // not enough bytes to parse yet. - return false; + // 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; } /** @@ -3155,7 +3166,7 @@ ConnStateData::clientParseRequests() PROF_start(parseHttpRequest); // try to parse the PROXY protocol header magic bytes - if (needProxyProtocolHeader_ && !parseProxyProtocolMagic()) + if (needProxyProtocolHeader_ && !findProxyProtocolMagic()) break; HttpParserInit(&parser_, in.buf.c_str(), in.buf.length()); diff --git a/src/client_side.h b/src/client_side.h index 07040762ee..3ab2c5b3f1 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -392,10 +392,12 @@ private: /* PROXY protocol functionality */ bool proxyProtocolValidateClient(); - bool parseProxyProtocolMagic(); - bool proxyProtocolError(bool isFatal); + bool findProxyProtocolMagic(); + bool parseProxy10(); + bool parseProxy20(); + bool proxyProtocolError(const char *reason = NULL); - /// whether PROXY protocol header is still expected on this port + /// whether PROXY protocol header is still expected bool needProxyProtocolHeader_; #if USE_AUTH