]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Split PROXY protocol parse into v1 and v2 methods
authorAmos Jeffries <squid3@treenet.co.nz>
Sat, 12 Jul 2014 13:19:10 +0000 (06:19 -0700)
committerAmos Jeffries <squid3@treenet.co.nz>
Sat, 12 Jul 2014 13:19:10 +0000 (06:19 -0700)
src/client_side.cc
src/client_side.h

index 037294388e66f7f9882a05232e2e0500e5439787..ec52a5f6969c7a1b389b4b6c4b2f60d0d87a78fd 100644 (file)
@@ -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<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;
 }
 
 /**
@@ -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());
index 07040762ee40e6696b2439322f5e72f6b2de08d1..3ab2c5b3f18a0f8f02c650c9684798456df1946e 100644 (file)
@@ -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