}
/**
- * 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()
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;
}
* 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<uint16_t>(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<uint16_t>(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<uint16_t>(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<uint16_t>(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<const uint16_t *>(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<const pax*>(extra.rawContent());
+ const char *clen = in.buf.rawContent() + Proxy20magic.length() + 2;
+ const uint16_t len = ntohs(*(reinterpret_cast<const uint16_t *>(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<const pax*>(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;
}
/**
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());