]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Log PROXY protocol v2 TLVs; fix PROXY protocol parsing bugs (#342) M-staged-PR342
authorEduard Bagdasaryan <eduard.bagdasaryan@measurement-factory.com>
Fri, 15 Feb 2019 00:17:41 +0000 (00:17 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Fri, 15 Feb 2019 11:45:34 +0000 (11:45 +0000)
Added %proxy_protocol::>h logformat code for logging received PROXY
protocol TLV values and passing them to adaptation services and helpers.
For simplicity, this implementation treats TLV values as text and reuses
the existing HTTP header field logging interfaces. Support for binary
TLV value logging can be added later if needed.

Also support logging of metadata extracted from the fixed portion of a
PROXY protocol header and referenced as "pseudo headers": :command,
:version, :src_addr, :dst_addr, :src_port, and :dst_port.

Also fixed several bugs in the old PROXY protocol v1/v2 parsing code:

* Buffer overrun in ConnStateData::parseProxy2p0(). The available
  local SBuf could be less than sizeof(pax), resulting in copying
  excessive bytes with SBuf::rawContent().

* Incorrect processing of malformed v1 headers lacking CRLF in
  ConnStateData::parseProxy1p0(), which waited for more data
  even if the buffer size already exceeded the maximum v1 header
  size.

* Incorrect processing of partial-buffered v1 headers, when only
  the initial (magic) part of the header has been received. The old
  code resulted with an error instead of waiting for more data in this
  case.

* Incorrect v1 header framing for proto=UNKNOWN headers.
  The code used only LF while the protocol requires CRLF for it.

* Do not use address information from v2 header if the header
  proto is UNSPEC.

* Incorrect v1 magic expectations (a 6-character `PROXY ` instead of the
  proper 5-character `PROXY` sequence) leading to mishandling of
  non-PROXY input. For example, receiving `PROXY\r\n` would result in
  "need more data" outcome (and long timeout) instead of an immediate
  error.

Also eliminated code duplication in HttpHeader::getByNameListMember()
and HttpHeader::getListMember(), moving the common part into a
separate getListMember() method.

Also eliminated code duplication and probably fixed a bug with applying
client_netmask parameter in ConnStateData constructor. The mask should
not be applied to localhost and IPv6 addresses but was.

Also parse PROXY protocol v2 LOCAL addresses (for logging purposes). In
compliance with the PROXY protocol specs, LOCAL addresses are still
unused for connection routing.

32 files changed:
configure.ac
src/AccessLogEntry.cc
src/AccessLogEntry.h
src/HttpHeader.cc
src/HttpHeader.h
src/Makefile.am
src/StrList.cc
src/StrList.h
src/adaptation/icap/ModXact.cc
src/adaptation/icap/OptXact.cc
src/cf.data.pre
src/client_side.cc
src/client_side.h
src/client_side_request.cc
src/esi/VarState.cc
src/format/ByteCode.h
src/format/Format.cc
src/format/Token.cc
src/format/Token.h
src/ip/Address.cc
src/ip/Address.h
src/parser/BinaryTokenizer.cc
src/parser/BinaryTokenizer.h
src/proxyp/Elements.cc [new file with mode: 0644]
src/proxyp/Elements.h [new file with mode: 0644]
src/proxyp/Header.cc [new file with mode: 0644]
src/proxyp/Header.h [new file with mode: 0644]
src/proxyp/Makefile.am [new file with mode: 0644]
src/proxyp/Parser.cc [new file with mode: 0644]
src/proxyp/Parser.h [new file with mode: 0644]
src/proxyp/forward.h [new file with mode: 0644]
src/tests/stub_HttpHeader.cc

index c3854c27473fc4ddd26b52d74ff6f88bc0926c12..f46110d86cec44f06b45fe02c702faf720d223c5 100644 (file)
@@ -3860,6 +3860,7 @@ AC_CONFIG_FILES([
        src/mem/Makefile
        src/mgr/Makefile
        src/parser/Makefile
+       src/proxyp/Makefile
        src/repl/Makefile
        src/sbuf/Makefile
        src/security/Makefile
index d54b00eee71563577180b2067b326432ff776676..09de0a22c6c9f23acfdc0a9b057aa47bb25e07f8 100644 (file)
@@ -10,6 +10,7 @@
 #include "AccessLogEntry.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
+#include "proxyp/Header.h"
 #include "SquidConfig.h"
 #include "ssl/support.h"
 
@@ -39,8 +40,7 @@ AccessLogEntry::getLogClientIp(char *buf, size_t bufsz) const
     // - IPv4 clients masked with client_netmask
     // - IPv6 clients use 'privacy addressing' instead.
 
-    if (!log_ip.isLocalhost() && log_ip.isIPv4())
-        log_ip.applyMask(Config.Addrs.client_netmask);
+    log_ip.applyClientMask(Config.Addrs.client_netmask);
 
     log_ip.toStr(buf, bufsz);
 }
@@ -94,6 +94,8 @@ AccessLogEntry::getExtUser() const
     return nullptr;
 }
 
+AccessLogEntry::AccessLogEntry() {}
+
 AccessLogEntry::~AccessLogEntry()
 {
     safe_free(headers.request);
index 2079b31f548721b63e233f0bd514c04fb1e6baa8..a33450bf5e05c9af5e62628418a41e15e2e42121 100644 (file)
@@ -21,6 +21,7 @@
 #include "LogTags.h"
 #include "MessageSizes.h"
 #include "Notes.h"
+#include "proxyp/forward.h"
 #include "sbuf/SBuf.h"
 #if ICAP_CLIENT
 #include "adaptation/icap/Elements.h"
@@ -41,7 +42,7 @@ class AccessLogEntry: public RefCountable
 public:
     typedef RefCount<AccessLogEntry> Pointer;
 
-    AccessLogEntry() {}
+    AccessLogEntry();
     ~AccessLogEntry();
 
     /// Fetch the client IP log string into the given buffer.
@@ -184,6 +185,9 @@ public:
     /// key=value pairs returned from URL rewrite/redirect helper
     NotePairs::Pointer notes;
 
+    /// see ConnStateData::proxyProtocolHeader_
+    ProxyProtocol::HeaderPointer proxyProtocolHeader;
+
 #if ICAP_CLIENT
     /** \brief This subclass holds log info for ICAP part of request
      *  \todo Inner class declarations should be moved outside
index 8850f52d5e7049a558f03f31e050f2f8f6121974..930b4a5263ed418f188a0758abf9948e3c571d29 100644 (file)
@@ -953,57 +953,23 @@ HttpHeader::hasNamed(const char *name, unsigned int namelen, String *result) con
 /*
  * Returns a the value of the specified list member, if any.
  */
-String
+SBuf
 HttpHeader::getByNameListMember(const char *name, const char *member, const char separator) const
 {
-    String header;
-    const char *pos = NULL;
-    const char *item;
-    int ilen;
-    int mlen = strlen(member);
-
     assert(name);
-
-    header = getByName(name);
-
-    String result;
-
-    while (strListGetItem(&header, separator, &item, &ilen, &pos)) {
-        if (strncmp(item, member, mlen) == 0 && item[mlen] == '=') {
-            result.append(item + mlen + 1, ilen - mlen - 1);
-            break;
-        }
-    }
-
-    return result;
+    const auto header = getByName(name);
+    return ::getListMember(header, member, separator);
 }
 
 /*
  * returns a the value of the specified list member, if any.
  */
-String
+SBuf
 HttpHeader::getListMember(Http::HdrType id, const char *member, const char separator) const
 {
-    String header;
-    const char *pos = NULL;
-    const char *item;
-    int ilen;
-    int mlen = strlen(member);
-
     assert(any_registered_header(id));
-
-    header = getStrOrList(id);
-    String result;
-
-    while (strListGetItem(&header, separator, &item, &ilen, &pos)) {
-        if (strncmp(item, member, mlen) == 0 && item[mlen] == '=') {
-            result.append(item + mlen + 1, ilen - mlen - 1);
-            break;
-        }
-    }
-
-    header.clean();
-    return result;
+    const auto header = getStrOrList(id);
+    return ::getListMember(header, member, separator);
 }
 
 /* test if a field is present */
index fa94fe2fd51cf0055845a815ed9c14fa397dda07..24e98c5f3647b75b792a6934f11a5595989c5cee 100644 (file)
@@ -118,8 +118,12 @@ public:
     bool hasNamed(const SBuf &s, String *value = 0) const;
     /// \deprecated use SBuf method instead.
     bool hasNamed(const char *name, unsigned int namelen, String *value = 0) const;
-    String getByNameListMember(const char *name, const char *member, const char separator) const;
-    String getListMember(Http::HdrType id, const char *member, const char separator) const;
+    /// searches for the first matching key=value pair within the name-identified field
+    /// \returns the value of the found pair or an empty string
+    SBuf getByNameListMember(const char *name, const char *member, const char separator) const;
+    /// searches for the first matching key=value pair within the field
+    /// \returns the value of the found pair or an empty string
+    SBuf getListMember(Http::HdrType id, const char *member, const char separator) const;
     int has(Http::HdrType id) const;
     /// Appends "this cache" information to VIA header field.
     /// Takes the initial VIA value from "from" parameter, if provided.
index 0113a3ba1be6217cefeeacf623d6e08319808354..3fefaf9706fea6a30afecc19e6de3fe3975eb349 100644 (file)
@@ -31,8 +31,8 @@ LOADABLE_MODULES_SOURCES = \
        LoadableModules.h \
        LoadableModules.cc
 
-SUBDIRS        = mem base anyp helper dns ftp parser comm eui acl format clients sbuf servers fs repl store DiskIO
-DIST_SUBDIRS = mem base anyp helper dns ftp parser comm eui acl format clients sbuf servers fs repl store DiskIO
+SUBDIRS        = mem base anyp helper dns ftp parser comm eui acl format clients sbuf servers fs repl store DiskIO proxyp
+DIST_SUBDIRS = mem base anyp helper dns ftp parser comm eui acl format clients sbuf servers fs repl store DiskIO proxyp
 
 if ENABLE_AUTH
 SUBDIRS += auth
@@ -551,6 +551,7 @@ squid_LDADD = \
        $(SSL_LIBS) \
        ipc/libipc.la \
        mgr/libmgr.la \
+       proxyp/libproxyp.la \
        parser/libparser.la \
        eui/libeui.la \
        icmp/libicmp.la \
@@ -1005,6 +1006,7 @@ nodist_tests_testURL_SOURCES = \
        tests/stub_StatHist.cc
 tests_testURL_LDADD = \
        libsquid.la \
+       proxyp/libproxyp.la \
        anyp/libanyp.la \
        base/libbase.la \
        ip/libip.la \
@@ -2040,6 +2042,7 @@ tests_test_http_range_LDADD = \
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
+       proxyp/libproxyp.la \
        parser/libparser.la \
        ip/libip.la \
        fs/libfs.la \
@@ -2426,6 +2429,7 @@ tests_testHttpRequest_LDADD = \
        fs/libfs.la \
        $(SSL_LIBS) \
        ipc/libipc.la \
+       proxyp/libproxyp.la \
        parser/libparser.la \
        dns/libdns.la \
        base/libbase.la \
@@ -2714,6 +2718,7 @@ tests_testCacheManager_LDADD = \
        ftp/libftp.la \
        helper/libhelper.la \
        http/libhttp.la \
+       proxyp/libproxyp.la \
        parser/libparser.la \
        ident/libident.la \
        acl/libacls.la \
@@ -3030,6 +3035,7 @@ tests_testEvent_LDADD = \
        ftp/libftp.la \
        helper/libhelper.la \
        http/libhttp.la \
+       proxyp/libproxyp.la \
        parser/libparser.la \
        ident/libident.la \
        acl/libacls.la \
@@ -3262,6 +3268,7 @@ tests_testEventLoop_LDADD = \
        ftp/libftp.la \
        helper/libhelper.la \
        http/libhttp.la \
+       proxyp/libproxyp.la \
        parser/libparser.la \
        ident/libident.la \
        acl/libacls.la \
index bc7c86bd46c6890c716161458f1dbe3d52fe25a4..980ac5bcb12751ba696c3109f2db220b5630a046 100644 (file)
@@ -131,3 +131,17 @@ strListGetItem(const String * str, char del, const char **item, int *ilen, const
     return len > 0;
 }
 
+SBuf
+getListMember(const String &list, const char *key, const char delimiter)
+{
+    const char *pos = nullptr;
+    const char *item = nullptr;
+    int ilen = 0;
+    const auto keyLen = strlen(key);
+    while (strListGetItem(&list, delimiter, &item, &ilen, &pos)) {
+        if (static_cast<size_t>(ilen) > keyLen && strncmp(item, key, keyLen) == 0 && item[keyLen] == '=')
+            return SBuf(item + keyLen + 1, ilen - keyLen - 1);
+    }
+    return SBuf();
+}
+
index 9212080a84638e23eed1fd7fa37827f01ea3d54f..475fac888734a9e5394d7cc6c7c53194beb37fb7 100644 (file)
@@ -19,6 +19,10 @@ void strListAdd(String * str, const char *item, char del);
 int strListIsMember(const String * str, const SBuf &item, char del);
 int strListIsSubstr(const String * list, const char *s, char del);
 int strListGetItem(const String * str, char del, const char **item, int *ilen, const char **pos);
+/// Searches for the first matching key=value pair
+/// within a delimiter-separated list of items.
+/// \returns the value of the found pair or an empty string.
+SBuf getListMember(const String &list, const char *key, const char delimiter);
 
 #endif /* SQUID_STRLIST_H_ */
 
index d541da4c96a8c7f0e009803188c023526b55b2d1..3a095a0bb295b042565f84fcad755b4a40601466 100644 (file)
@@ -1810,8 +1810,8 @@ void Adaptation::Icap::ModXact::fillDoneStatus(MemBuf &buf) const
 
 bool Adaptation::Icap::ModXact::gotEncapsulated(const char *section) const
 {
-    return icapReply->header.getByNameListMember("Encapsulated",
-            section, ',').size() > 0;
+    return !icapReply->header.getByNameListMember("Encapsulated",
+            section, ',').isEmpty();
 }
 
 // calculate whether there is a virgin HTTP body and
index 1dfea873241567744cee7ab8e0a22a9c953b086b..a2233e15d1e8c58bf7b5f6ad59ef9ee2be1d73ff 100644 (file)
@@ -87,8 +87,8 @@ void Adaptation::Icap::OptXact::handleCommRead(size_t)
         // We read everything if there is no response body. If there is a body,
         // we cannot parse it because we do not support any opt-body-types, so
         // we leave readAll false which forces connection closure.
-        readAll = !icapReply->header.getByNameListMember("Encapsulated",
-                  "opt-body", ',').size();
+        readAll = icapReply->header.getByNameListMember("Encapsulated",
+                  "opt-body", ',').isEmpty();
         debugs(93, 7, HERE << "readAll=" << readAll);
         icap_tio_finish = current_time;
         setOutcome(xoOpt);
index ed1ced4bab85b116086871aa1335255adc95cb95..908e824f78200c99ec5178c049355925ca418ddc 100644 (file)
@@ -4884,6 +4884,34 @@ DOC_START
        service name in curly braces to record response time(s) specific
        to that service. For example: %{my_service}adapt::sum_trs
 
+       Format codes related to the PROXY protocol:
+
+               proxy_protocol::>h PROXY protocol header, including optional TLVs.
+
+                               Supports the same field and element reporting/extraction logic
+                               as %http::>h. For configuration and reporting purposes, Squid
+                               maps each PROXY TLV to an HTTP header field: the TLV type
+                               (configured as a decimal integer) is the field name, and the
+                               TLV value is the field value. All TLVs of "LOCAL" connections
+                               (in PROXY protocol terminology) are currently skipped/ignored.
+
+                               Squid also maps the following standard PROXY protocol header
+                               blocks to pseudo HTTP headers (their names use PROXY
+                               terminology and start with a colon, following HTTP tradition
+                               for pseudo headers): :command, :version, :src_addr, :dst_addr,
+                               :src_port, and :dst_port.
+
+                               Without optional parameters, this logformat code logs
+                               pseudo headers and TLVs.
+
+                               This format code uses pass-through URL encoding by default.
+
+                               Example:
+                                       # relay custom PROXY TLV #224 to adaptation services
+                                       adaptation_meta Client-Foo "%proxy_protocol::>h{224}"
+
+                               See also: %http::>h
+
        The default formats available (which do not need re-defining) are:
 
 logformat squid      %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
index 57df3499838d9d9eaa01a9c7a7b1b998607a9187..e4285eedd60b3937003097488b7536a9c3240235 100644 (file)
 #include "mime_header.h"
 #include "parser/Tokenizer.h"
 #include "profiler/Profiler.h"
+#include "proxyp/Header.h"
+#include "proxyp/Parser.h"
 #include "security/NegotiationHistory.h"
 #include "servers/forward.h"
 #include "SquidConfig.h"
@@ -1838,217 +1840,32 @@ ConnStateData::proxyProtocolError(const char *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.
- */
+/// Attempts to extract a PROXY protocol header from the input buffer and,
+/// upon success, stores the parsed header in proxyProtocolHeader_.
+/// \returns true if the header was successfully parsed
+/// \returns false if more data is needed to parse the header or on error
 bool
 ConnStateData::parseProxyProtocolHeader()
 {
-    // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
-
-    // detect and parse PROXY/2.0 protocol header
-    if (inBuf.startsWith(Proxy2p0magic))
-        return parseProxy2p0();
-
-    // detect and parse PROXY/1.0 protocol header
-    if (inBuf.startsWith(Proxy1p0magic))
-        return parseProxy1p0();
-
-    // detect and terminate other protocols
-    if (inBuf.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(inBuf);
-    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
-            inBuf = 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(inBuf.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;
-        if ((clientConnection->flags & COMM_TRANSPARENT))
-            clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP.
-        debugs(33, 5, "PROXY/1.0 upgrade: " << clientConnection);
-        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 (inBuf.length() < prefixLen + 4)
-        return false; // need more bytes
-
-    if ((inBuf[prefixLen] & 0xF0) != 0x20) // version == 2 is mandatory
-        return proxyProtocolError("PROXY/2.0 error: invalid version");
-
-    const char command = (inBuf[prefixLen] & 0x0F);
-    if ((command & 0xFE) != 0x00) // values other than 0x0-0x1 are invalid
-        return proxyProtocolError("PROXY/2.0 error: invalid command");
-
-    const char family = (inBuf[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 = (inBuf[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 = inBuf.rawContent() + prefixLen + 2;
-    uint16_t len;
-    memcpy(&len, clen, sizeof(len));
-    len = ntohs(len);
-
-    if (inBuf.length() < prefixLen + 4 + len)
-        return false; // need more bytes
-
-    inBuf.consume(prefixLen + 4); // 4 being the extra bytes
-    const SBuf extra = inBuf.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));
-        if ((clientConnection->flags & COMM_TRANSPARENT))
-            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));
-        if ((clientConnection->flags & COMM_TRANSPARENT))
-            clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP.
-        break;
-    default: // do nothing
-        break;
+    try {
+        const auto parsed = ProxyProtocol::Parse(inBuf);
+        proxyProtocolHeader_ = parsed.header;
+        assert(bool(proxyProtocolHeader_));
+        inBuf.consume(parsed.size);
+        needProxyProtocolHeader_ = false;
+        if (proxyProtocolHeader_->hasForwardedAddresses()) {
+            clientConnection->local = proxyProtocolHeader_->destinationAddress;
+            clientConnection->remote = proxyProtocolHeader_->sourceAddress;
+            if ((clientConnection->flags & COMM_TRANSPARENT))
+                clientConnection->flags ^= COMM_TRANSPARENT; // prevent TPROXY spoofing of this new IP.
+            debugs(33, 5, "PROXY/" << proxyProtocolHeader_->version() << " upgrade: " << clientConnection);
+        }
+    } catch (const Parser::BinaryTokenizer::InsufficientInput &) {
+        debugs(33, 3, "PROXY protocol: waiting for more than " << inBuf.length() << " bytes");
+        return false;
+    } catch (const std::exception &e) {
+        return proxyProtocolError(e.what());
     }
-    debugs(33, 5, "PROXY/2.0 upgrade: " << clientConnection);
     return true;
 }
 
@@ -2383,7 +2200,7 @@ ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) :
 
     // store the details required for creating more MasterXaction objects as new requests come in
     log_addr = xact->tcpClient->remote;
-    log_addr.applyMask(Config.Addrs.client_netmask);
+    log_addr.applyClientMask(Config.Addrs.client_netmask);
 
     // register to receive notice of Squid signal events
     // which may affect long persisting client connections
@@ -2812,6 +2629,7 @@ ConnStateData::postHttpsAccept()
         acl_checklist->al->tcpClient = clientConnection;
         acl_checklist->al->cache.port = port;
         acl_checklist->al->cache.caddr = log_addr;
+        acl_checklist->al->proxyProtocolHeader = proxyProtocolHeader_;
         HTTPMSGUNLOCK(acl_checklist->al->request);
         acl_checklist->al->request = request;
         HTTPMSGLOCK(acl_checklist->al->request);
index 07d2b8d504cf1f18a353419ba69698b18020da6f..19530e72c8b126e9faa36b6fa080285cb0ddcf92 100644 (file)
@@ -18,6 +18,7 @@
 #include "http/forward.h"
 #include "HttpControlMsg.h"
 #include "ipc/FdNotes.h"
+#include "proxyp/forward.h"
 #include "sbuf/SBuf.h"
 #include "servers/Server.h"
 #if USE_AUTH
@@ -320,6 +321,8 @@ public:
     NotePairs::Pointer notes();
     bool hasNotes() const { return bool(theNotes) && !theNotes->empty(); }
 
+    const ProxyProtocol::HeaderPointer &proxyProtocolHeader() const { return proxyProtocolHeader_; }
+
 protected:
     void startDechunkingRequest();
     void finishDechunkingRequest(bool withSuccess);
@@ -368,8 +371,6 @@ private:
     /* PROXY protocol functionality */
     bool proxyProtocolValidateClient();
     bool parseProxyProtocolHeader();
-    bool parseProxy1p0();
-    bool parseProxy2p0();
     bool proxyProtocolError(const char *reason);
 
 #if USE_OPENSSL
@@ -384,6 +385,9 @@ private:
     /// whether PROXY protocol header is still expected
     bool needProxyProtocolHeader_;
 
+    /// the parsed PROXY protocol header
+    ProxyProtocol::HeaderPointer proxyProtocolHeader_;
+
 #if USE_AUTH
     /// some user details that can be used to perform authentication on this connection
     Auth::UserRequest::Pointer auth_;
index b509b29419386e978e1a3dea35ab63f1fa2c659f..07f0db61d83b5ce8029b7ae1915c7a10141a4ae7 100644 (file)
@@ -48,6 +48,7 @@
 #include "MemObject.h"
 #include "Parsing.h"
 #include "profiler/Profiler.h"
+#include "proxyp/Header.h"
 #include "redirect.h"
 #include "rfc1738.h"
 #include "SquidConfig.h"
@@ -172,6 +173,7 @@ ClientHttpRequest::ClientHttpRequest(ConnStateData * aConn) :
         al->tcpClient = clientConnection = aConn->clientConnection;
         al->cache.port = aConn->port;
         al->cache.caddr = aConn->log_addr;
+        al->proxyProtocolHeader = aConn->proxyProtocolHeader();
 
 #if USE_OPENSSL
         if (aConn->clientConnection != NULL && aConn->clientConnection->isOpen()) {
index c35742cd12f5d7247df0c3ec3e5768463dd5bc8a..b5daf6cde3704b0f5ca32beb3a2d56cab4b9702f 100644 (file)
@@ -380,10 +380,10 @@ ESIVariableCookie::eval (ESIVarState &state, char const *subref, char const *fou
         if (!subref)
             s = state.header().getStr (Http::HdrType::COOKIE);
         else {
-            String S = state.header().getListMember (Http::HdrType::COOKIE, subref, ';');
+            const auto subCookie = state.header().getListMember(Http::HdrType::COOKIE, subref, ';');
 
-            if (S.size())
-                ESISegment::ListAppend (state.getOutput(), S.rawBuf(), S.size());
+            if (subCookie.length())
+                ESISegment::ListAppend(state.getOutput(), subCookie.rawContent(), subCookie.length());
             else if (found_default)
                 ESISegment::ListAppend (state.getOutput(), found_default, strlen (found_default));
         }
index afceb7b5375f35ead4808df34acc3e694ee08b23..f12c9fd02010b3357f7b1415eec9f6b1fba67bb9 100644 (file)
@@ -244,8 +244,12 @@ typedef enum {
     LFT_EXT_ACL_CLIENT_EUI48,
     LFT_EXT_ACL_CLIENT_EUI64,
     LFT_EXT_ACL_NAME,
-    LFT_EXT_ACL_DATA
+    LFT_EXT_ACL_DATA,
 
+    /* PROXY protocol details */
+    LFT_PROXY_PROTOCOL_RECEIVED_HEADER,
+    LFT_PROXY_PROTOCOL_RECEIVED_HEADER_ELEM,
+    LFT_PROXY_PROTOCOL_RECEIVED_ALL_HEADERS
 } ByteCode_t;
 
 /// Quoting style for a format output.
index b4596ee43b93ef10ece8ee94adc3e099e333b2d1..b0e79639c7f336fc50625bd81901ceef2167258e 100644 (file)
@@ -21,6 +21,7 @@
 #include "http/Stream.h"
 #include "HttpRequest.h"
 #include "MemBuf.h"
+#include "proxyp/Header.h"
 #include "rfc1738.h"
 #include "sbuf/StringConvert.h"
 #include "security/CertError.h"
@@ -683,7 +684,7 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS
             if (al->request) {
                 const Adaptation::History::Pointer ah = al->request->adaptHistory();
                 if (ah) { // XXX: add adapt::<all_h but use lastMeta here
-                    sb = StringToSBuf(ah->allMeta.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator));
+                    sb = ah->allMeta.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
                     out = sb.c_str();
                     quote = 1;
                 }
@@ -742,7 +743,7 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS
 
         case LFT_ICAP_REQ_HEADER_ELEM:
             if (al->icap.request) {
-                sb = StringToSBuf(al->icap.request->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator));
+                sb = al->icap.request->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
                 out = sb.c_str();
                 quote = 1;
             }
@@ -772,7 +773,7 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS
 
         case LFT_ICAP_REP_HEADER_ELEM:
             if (al->icap.reply) {
-                sb = StringToSBuf(al->icap.reply->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator));
+                sb = al->icap.reply->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
                 out = sb.c_str();
                 quote = 1;
             }
@@ -818,7 +819,31 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS
 #endif
         case LFT_REQUEST_HEADER_ELEM:
             if (const Http::Message *msg = actualRequestHeader(al)) {
-                sb = StringToSBuf(msg->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator));
+                sb = msg->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
+                out = sb.c_str();
+                quote = 1;
+            }
+            break;
+
+        case LFT_PROXY_PROTOCOL_RECEIVED_HEADER:
+            if (al->proxyProtocolHeader) {
+                sb = al->proxyProtocolHeader->getValues(fmt->data.headerId, fmt->data.header.separator);
+                out = sb.c_str();
+                quote = 1;
+            }
+            break;
+
+        case LFT_PROXY_PROTOCOL_RECEIVED_ALL_HEADERS:
+            if (al->proxyProtocolHeader) {
+                sb = al->proxyProtocolHeader->toMime();
+                out = sb.c_str();
+                quote = 1;
+            }
+            break;
+
+        case LFT_PROXY_PROTOCOL_RECEIVED_HEADER_ELEM:
+            if (al->proxyProtocolHeader) {
+                sb = al->proxyProtocolHeader->getElem(fmt->data.headerId, fmt->data.header.element, fmt->data.header.separator);
                 out = sb.c_str();
                 quote = 1;
             }
@@ -826,7 +851,7 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS
 
         case LFT_ADAPTED_REQUEST_HEADER_ELEM:
             if (al->adapted_request) {
-                sb = StringToSBuf(al->adapted_request->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator));
+                sb = al->adapted_request->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
                 out = sb.c_str();
                 quote = 1;
             }
@@ -834,7 +859,7 @@ Format::Format::assemble(MemBuf &mb, const AccessLogEntry::Pointer &al, int logS
 
         case LFT_REPLY_HEADER_ELEM:
             if (const Http::Message *msg = actualReplyHeader(al)) {
-                sb = StringToSBuf(msg->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator));
+                sb = msg->header.getByNameListMember(fmt->data.header.header, fmt->data.header.element, fmt->data.header.separator);
                 out = sb.c_str();
                 quote = 1;
             }
index 658c89892369430bcc42a81a0212fed6a60d1850..f973d4a456a9286ef1641aa240c3f52fbf820c1c 100644 (file)
@@ -11,6 +11,8 @@
 #include "format/Token.h"
 #include "format/TokenTableEntry.h"
 #include "globals.h"
+#include "proxyp/Elements.h"
+#include "sbuf/Stream.h"
 #include "SquidConfig.h"
 #include "Store.h"
 
@@ -176,6 +178,10 @@ static TokenTableEntry TokenTableMisc[] = {
     TokenTableEntry(NULL, LFT_NONE)        /* this must be last */
 };
 
+static TokenTableEntry TokenTableProxyProtocol[] = {
+    TokenTableEntry(">h", LFT_PROXY_PROTOCOL_RECEIVED_HEADER),
+};
+
 #if USE_ADAPTATION
 static TokenTableEntry TokenTableAdapt[] = {
     TokenTableEntry("all_trs", LFT_ADAPTATION_ALL_XACT_TIMES),
@@ -251,6 +257,7 @@ Format::Token::Init()
     TheConfig.registerTokens(SBuf("tls"),::Format::TokenTableSsl);
     TheConfig.registerTokens(SBuf("ssl"),::Format::TokenTableSsl);
 #endif
+    TheConfig.registerTokens(SBuf("proxy_protocol"), ::Format::TokenTableProxyProtocol);
 }
 
 /// Scans a token table to see if the next token exists there
@@ -480,9 +487,14 @@ Format::Token::parse(const char *def, Quoting *quoting)
 
     case LFT_NOTE:
 
+    case LFT_PROXY_PROTOCOL_RECEIVED_HEADER:
+
         if (data.string) {
             char *header = data.string;
-            char *cp = strchr(header, ':');
+            const auto initialType = type;
+
+            const auto pseudoHeader = header[0] == ':';
+            char *cp = strchr(pseudoHeader ? header+1 : header, ':');
 
             if (cp) {
                 *cp = '\0';
@@ -522,11 +534,22 @@ Format::Token::parse(const char *def, Quoting *quoting)
                     type = LFT_ICAP_REP_HEADER_ELEM;
                     break;
 #endif
+                case LFT_PROXY_PROTOCOL_RECEIVED_HEADER:
+                    type = LFT_PROXY_PROTOCOL_RECEIVED_HEADER_ELEM;
+                    break;
                 default:
                     break;
                 }
             }
 
+            if (!*header)
+                throw TexcHere(ToSBuf("Can't parse configuration token: '", def, "': missing header name"));
+
+            if (initialType == LFT_PROXY_PROTOCOL_RECEIVED_HEADER)
+                data.headerId = ProxyProtocol::FieldNameToFieldType(SBuf(header));
+            else if (pseudoHeader)
+                throw TexcHere(ToSBuf("Pseudo headers are not supported in this context; got: '", def, "'"));
+
             data.header.header = header;
         } else {
             switch (type) {
@@ -554,6 +577,9 @@ Format::Token::parse(const char *def, Quoting *quoting)
                 type = LFT_ICAP_REP_ALL_HEADERS;
                 break;
 #endif
+            case LFT_PROXY_PROTOCOL_RECEIVED_HEADER:
+                type = LFT_PROXY_PROTOCOL_RECEIVED_ALL_HEADERS;
+                break;
             default:
                 break;
             }
index 858ffb9096d42f27321b52e548bbc33fe72efb18..9d9cc674be88e82ff136908467be01bcd19e22c7 100644 (file)
@@ -10,6 +10,7 @@
 #define _SQUID_FORMAT_TOKEN_H
 
 #include "format/ByteCode.h"
+#include "proxyp/Elements.h"
 
 /*
  * Squid configuration allows users to define custom formats in
@@ -50,6 +51,9 @@ public:
     const char *label;
     struct {
         char *string;
+        // TODO: Add ID caching for protocols other than PROXY protocol.
+        /// the cached ID of the parsed header or zero
+        ProxyProtocol::Two::FieldType headerId;
 
         struct {
             char *header;
index 0c34696f6a574e93477a557ee6615b870a6cf7a0..6761e94624b1aa848eb8dcf461a39c27e2dd039a 100644 (file)
@@ -101,6 +101,13 @@ Ip::Address::applyMask(Ip::Address const &mask_addr)
     return changes;
 }
 
+void
+Ip::Address::applyClientMask(const Address &mask)
+{
+    if (!isLocalhost() && isIPv4())
+        (void)applyMask(mask);
+}
+
 bool
 Ip::Address::applyMask(const unsigned int cidrMask, int mtype)
 {
index 2698dbca608ee97025bb917c33dd215b03fb9a86..138fe3c65cb4ff6ca9e0822780f5f889c72caa8b 100644 (file)
@@ -190,6 +190,11 @@ public:
      */
     bool applyMask(const unsigned int cidr, int mtype);
 
+    /// Apply so-called 'privacy masking' to IPv4 addresses,
+    /// except localhost IP.
+    /// IPv6 clients use 'privacy addressing' instead.
+    void applyClientMask(const Address &mask);
+
     /** Return the ASCII equivalent of the address
      *  Semantically equivalent to the IPv4 inet_ntoa()
      *  eg. 127.0.0.1 (IPv4) or ::1 (IPv6)
index fd196fae5df1ff4563446523460f0cfd89508353..e2f937ab0a464e1a2dcdc3aa8809bd2991168248 100644 (file)
@@ -9,6 +9,7 @@
 /* DEBUG: section 24    SBuf */
 
 #include "squid.h"
+#include "ip/Address.h"
 #include "parser/BinaryTokenizer.h"
 
 Parser::BinaryTokenizer::BinaryTokenizer(): BinaryTokenizer(SBuf())
@@ -75,6 +76,14 @@ Parser::BinaryTokenizer::got(const SBuf &value, uint64_t size, const char *descr
 
 }
 
+/// debugging helper for parsed addresses
+void
+Parser::BinaryTokenizer::got(const Ip::Address &value, uint64_t size, const char *description) const
+{
+    debugs(24, 7, context << description << '=' << value <<
+           BinaryTokenizer_tail(size, parsed_ - size));
+}
+
 /// debugging helper for skipped fields
 void
 Parser::BinaryTokenizer::skipped(uint64_t size, const char *description) const
@@ -164,6 +173,32 @@ Parser::BinaryTokenizer::area(uint64_t size, const char *description)
     return result;
 }
 
+template <class InAddr>
+Ip::Address
+Parser::BinaryTokenizer::inetAny(const char *description)
+{
+    InAddr addr;
+    const auto size = sizeof(addr);
+    want(size, description);
+    memcpy(&addr, data_.rawContent() + parsed_, size);
+    parsed_ += size;
+    const Ip::Address result(addr);
+    got(result, size, description);
+    return result;
+}
+
+Ip::Address
+Parser::BinaryTokenizer::inet4(const char *description)
+{
+    return inetAny<struct in_addr>(description);
+}
+
+Ip::Address
+Parser::BinaryTokenizer::inet6(const char *description)
+{
+    return inetAny<struct in6_addr>(description);
+}
+
 void
 Parser::BinaryTokenizer::skip(uint64_t size, const char *description)
 {
index 29ac815a1dd64910034fab553437ddfae30be990..de8369e2ae18ea2c3c8a978dfaa9dd7cfce69149 100644 (file)
@@ -9,6 +9,7 @@
 #ifndef SQUID_SRC_PARSER_BINARYTOKENIZER_H
 #define SQUID_SRC_PARSER_BINARYTOKENIZER_H
 
+#include "ip/forward.h"
 #include "sbuf/SBuf.h"
 
 namespace Parser
@@ -82,6 +83,12 @@ public:
     /// parse size consecutive bytes as an opaque blob
     SBuf area(uint64_t size, const char *description);
 
+    /// interpret the next 4 bytes as a raw in_addr structure
+    Ip::Address inet4(const char *description);
+
+    /// interpret the next 16 bytes as a raw in6_addr structure
+    Ip::Address inet6(const char *description);
+
     /*
      * Variable-length arrays (a.k.a. Pascal or prefix strings).
      * pstringN() extracts and returns N-bit length followed by length bytes
@@ -109,9 +116,13 @@ protected:
     void want(uint64_t size, const char *description) const;
     void got(uint32_t value, uint64_t size, const char *description) const;
     void got(const SBuf &value, uint64_t size, const char *description) const;
+    void got(const Ip::Address &value, uint64_t size, const char *description) const;
     void skipped(uint64_t size, const char *description) const;
 
 private:
+    template <class InAddr>
+    Ip::Address inetAny(const char *description);
+
     SBuf data_;
     uint64_t parsed_; ///< number of data bytes parsed or skipped
     uint64_t syncPoint_; ///< where to re-start the next parsing attempt
diff --git a/src/proxyp/Elements.cc b/src/proxyp/Elements.cc
new file mode 100644 (file)
index 0000000..60daea5
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 1996-2018 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.
+ */
+
+#include "squid.h"
+#include "parser/Tokenizer.h"
+#include "proxyp/Elements.h"
+#include "sbuf/Stream.h"
+
+#include <limits>
+
+const ProxyProtocol::FieldMap ProxyProtocol::PseudoHeaderFields = {
+    { SBuf(":version"), ProxyProtocol::Two::htPseudoVersion },
+    { SBuf(":command"), ProxyProtocol::Two::htPseudoCommand },
+    { SBuf(":src_addr"), ProxyProtocol::Two::htPseudoSrcAddr },
+    { SBuf(":dst_addr"), ProxyProtocol::Two::htPseudoDstAddr },
+    { SBuf(":src_port"), ProxyProtocol::Two::htPseudoSrcPort },
+    { SBuf(":dst_port"), ProxyProtocol::Two::htPseudoDstPort }
+};
+
+namespace ProxyProtocol {
+static Two::FieldType NameToFieldType(const SBuf &);
+static Two::FieldType IntegerToFieldType(const SBuf &);
+} // namespace ProxyProtocol
+
+/// FieldNameToFieldType() helper that handles pseudo headers
+ProxyProtocol::Two::FieldType
+ProxyProtocol::NameToFieldType(const SBuf &name)
+{
+    const auto it = PseudoHeaderFields.find(name);
+    if (it != PseudoHeaderFields.end())
+        return it->second;
+
+    static const SBuf pseudoMark(":");
+    if (name.startsWith(pseudoMark))
+        throw TexcHere(ToSBuf("Unsupported PROXY protocol pseudo header: ", name));
+
+    throw TexcHere(ToSBuf("Invalid PROXY protocol pseudo header or TLV type name. ",
+                          "Expected a pseudo header like :src_addr but got '", name, "'"));
+}
+
+/// FieldNameToFieldType() helper that handles integer TLV types
+ProxyProtocol::Two::FieldType
+ProxyProtocol::IntegerToFieldType(const SBuf &rawInteger)
+{
+    int64_t tlvType = 0;
+
+    Parser::Tokenizer ptok(rawInteger);
+    if (ptok.skip('0')) {
+        if (!ptok.atEnd())
+            throw TexcHere(ToSBuf("Invalid PROXY protocol TLV type value. ",
+                                  "Expected a decimal integer without leading zeros but got '",
+                                  rawInteger, "'")); // e.g., 077, 0xFF, or 0b101
+        // tlvType stays zero
+    } else {
+        Must(ptok.int64(tlvType, 10, false)); // the first character is a DIGIT
+        if (!ptok.atEnd())
+            throw TexcHere(ToSBuf("Invalid PROXY protocol TLV type value. ",
+                                  "Trailing garbage after ", tlvType, " in '",
+                                  rawInteger, "'")); // e.g., 1.0 or 5e0
+    }
+
+    const auto limit = std::numeric_limits<uint8_t>::max();
+    if (tlvType > limit)
+        throw TexcHere(ToSBuf("Invalid PROXY protocol TLV type value. ",
+                              "Expected an integer less than ", limit,
+                              " but got '", tlvType, "'"));
+
+    return Two::FieldType(tlvType);
+}
+
+ProxyProtocol::Two::FieldType
+ProxyProtocol::FieldNameToFieldType(const SBuf &tlvTypeRaw)
+{
+    // we could branch on ":" instead of DIGIT but then field names that lack a
+    // leading ":" (like "version") would get a less accurate error message
+    return Parser::Tokenizer(tlvTypeRaw).skipOne(CharacterSet::DIGIT) ?
+           IntegerToFieldType(tlvTypeRaw):
+           NameToFieldType(tlvTypeRaw);
+}
+
diff --git a/src/proxyp/Elements.h b/src/proxyp/Elements.h
new file mode 100644 (file)
index 0000000..ffd7b47
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 1996-2018 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.
+ */
+
+#ifndef SQUID_PROXYP_ELEMENTS_H
+#define SQUID_PROXYP_ELEMENTS_H
+
+#include "sbuf/SBuf.h"
+
+// https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
+namespace ProxyProtocol {
+namespace Two {
+
+/// numeric IDs of registered PROXY protocol TLV types and pseudo headers
+typedef enum {
+    htUnknown = 0x00,
+
+    // The PROXY protocol specs list these TLV types as already registered.
+    htAlpn = 0x01, // PP2_TYPE_ALPN
+    htAuthority = 0x02, // PP2_TYPE_AUTHORITY
+    htCrc32c = 0x03, // PP2_TYPE_CRC32C
+    htNoop = 0x04, // PP2_TYPE_NOOP
+    htSsl = 0x20, // PP2_TYPE_SSL
+    htSslVersion = 0x21, // PP2_SUBTYPE_SSL_VERSION
+    htSslCn = 0x22, // PP2_SUBTYPE_SSL_CN
+    htSslCipher = 0x23, // PP2_SUBTYPE_SSL_CIPHER
+    htSslSigAlg = 0x24, // PP2_SUBTYPE_SSL_SIG_ALG
+    htSslKeyAlg = 0x25, // PP2_SUBTYPE_SSL_KEY_ALG
+    htNetns = 0x30, // PP2_TYPE_NETNS
+
+    // IDs for PROXY protocol header pseudo-headers.
+    // Larger than 255 to avoid clashes with possible TLV type IDs.
+    htPseudoVersion = 0x101,
+    htPseudoCommand = 0x102,
+    htPseudoSrcAddr = 0x103,
+    htPseudoDstAddr = 0x104,
+    htPseudoSrcPort = 0x105,
+    htPseudoDstPort = 0x106
+} FieldType;
+
+/// PROXY protocol 'command' field value
+typedef enum {
+    cmdLocal = 0x00,
+    cmdProxy = 0x01
+} Command;
+
+typedef enum {
+    /// corresponds to a local connection or an unsupported protocol family
+    afUnspecified = 0x00,
+    afInet = 0x1,
+    afInet6 = 0x2,
+    afUnix = 0x3
+} AddressFamily;
+
+typedef enum {
+    tpUnspecified = 0x00,
+    tpStream = 0x1,
+    tpDgram = 0x2
+} TransportProtocol;
+
+/// a single Type-Length-Value (TLV) block from PROXY protocol specs
+class Tlv
+{
+public:
+    typedef uint8_t value_type;
+
+    Tlv(const value_type t, const SBuf &val): value(val), type(t) {}
+
+    SBuf value;
+    value_type type;
+};
+
+} // namespace Two
+
+typedef std::map<SBuf, Two::FieldType> FieldMap;
+/// a mapping between pseudo header names and ids
+extern const FieldMap PseudoHeaderFields;
+
+/// Parses human-friendly PROXY protocol field type representation.
+/// Only pseudo headers can (and should) be represented by their names.
+Two::FieldType FieldNameToFieldType(const SBuf &nameOrId);
+
+} // namespace ProxyProtocol
+
+#endif
+
diff --git a/src/proxyp/Header.cc b/src/proxyp/Header.cc
new file mode 100644 (file)
index 0000000..468e0ce
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 1996-2018 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.
+ */
+
+#include "squid.h"
+#include "proxyp/Elements.h"
+#include "proxyp/Header.h"
+#include "sbuf/Stream.h"
+#include "sbuf/StringConvert.h"
+#include "SquidConfig.h"
+#include "StrList.h"
+
+ProxyProtocol::Header::Header(const SBuf &ver, const Two::Command cmd):
+    version_(ver),
+    command_(cmd),
+    ignoreAddresses_(false)
+{}
+
+SBuf
+ProxyProtocol::Header::toMime() const
+{
+    SBufStream result;
+    for (const auto &p: PseudoHeaderFields) {
+        const auto value = getValues(p.second);
+        if (!value.isEmpty())
+            result << p.first << ": " << value << "\r\n";
+    }
+    // cannot reuse Header::getValues(): need the original TLVs layout
+    for (const auto &tlv: tlvs)
+        result << tlv.type << ": " << tlv.value << "\r\n";
+    return result.buf();
+}
+
+SBuf
+ProxyProtocol::Header::getValues(const uint32_t headerType, const char sep) const
+{
+    switch (headerType) {
+
+    case Two::htPseudoVersion:
+        return version_;
+
+    case Two::htPseudoCommand:
+        return ToSBuf(command_);
+
+    case Two::htPseudoSrcAddr: {
+        if (!hasAddresses())
+            return SBuf();
+        auto logAddr = sourceAddress;
+        logAddr.applyClientMask(Config.Addrs.client_netmask);
+        char ipBuf[MAX_IPSTRLEN];
+        return SBuf(logAddr.toStr(ipBuf, sizeof(ipBuf)));
+    }
+
+    case Two::htPseudoDstAddr: {
+        if (!hasAddresses())
+            return SBuf();
+        char ipBuf[MAX_IPSTRLEN];
+        return SBuf(destinationAddress.toStr(ipBuf, sizeof(ipBuf)));
+    }
+
+    case Two::htPseudoSrcPort: {
+        return hasAddresses() ? ToSBuf(sourceAddress.port()) : SBuf();
+    }
+
+    case Two::htPseudoDstPort: {
+        return hasAddresses() ? ToSBuf(destinationAddress.port()) : SBuf();
+    }
+
+    default: {
+        SBufStream result;
+        for (const auto &m: tlvs) {
+            if (m.type == headerType) {
+                // XXX: result.tellp() always returns -1
+                if (!result.buf().isEmpty())
+                    result << sep;
+                result << m.value;
+            }
+        }
+        return result.buf();
+    }
+    }
+}
+
+SBuf
+ProxyProtocol::Header::getElem(const uint32_t headerType, const char *member, const char sep) const
+{
+    const auto whole = SBufToString(getValues(headerType, sep));
+    return getListMember(whole, member, sep);
+}
+
+const SBuf &
+ProxyProtocol::Header::addressFamily() const
+{
+    static const SBuf v4("4");
+    static const SBuf v6("6");
+    static const SBuf vMix("mix");
+    return
+        (sourceAddress.isIPv6() && destinationAddress.isIPv6()) ? v6 :
+        (sourceAddress.isIPv4() && destinationAddress.isIPv4()) ? v4 :
+        vMix;
+}
+
diff --git a/src/proxyp/Header.h b/src/proxyp/Header.h
new file mode 100644 (file)
index 0000000..8b01fd0
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 1996-2018 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.
+ */
+
+#ifndef SQUID_PROXYP_HEADER_H
+#define SQUID_PROXYP_HEADER_H
+
+#include "base/RefCount.h"
+#include "ip/Address.h"
+#include "proxyp/Elements.h"
+#include "sbuf/SBuf.h"
+
+namespace ProxyProtocol {
+
+/// PROXY protocol v1 or v2 header
+class Header: public RefCountable
+{
+public:
+    typedef RefCount<Header> Pointer;
+    typedef std::vector<Two::Tlv> Tlvs;
+
+    Header(const SBuf &ver, const Two::Command cmd);
+
+    /// HTTP header-like string representation of the header.
+    /// The returned string has one line per pseudo header and
+    /// one line per TLV (if any).
+    SBuf toMime() const;
+
+    /// \returns a delimiter-separated list of values of TLVs of the given type
+    SBuf getValues(const uint32_t headerType, const char delimiter = ',') const;
+
+    /// Searches for the first key=value pair occurrence within the
+    /// value for the provided TLV type. Assumes that the TLV value
+    /// is a delimiter-separated list.
+    /// \returns the value of the found pair or the empty string.
+    SBuf getElem(const uint32_t headerType, const char *member, const char delimiter) const;
+
+    /// PROXY protocol version
+    const SBuf &version() const { return version_; }
+
+    /// whether source and destination addresses are valid addresses of the original "client" connection
+    bool hasForwardedAddresses() const { return !localConnection() && hasAddresses(); }
+
+    /// marks the header as lacking address information
+    void ignoreAddresses() { ignoreAddresses_ = true; }
+
+    /// whether the header relays address information (including LOCAL connections)
+    bool hasAddresses() const { return !ignoreAddresses_; }
+
+    /// \returns "4" or "6" if both source and destination addresses are IPv4 or IPv6
+    /// \returns "mix" otherwise
+    const SBuf &addressFamily() const;
+
+    /// source address of the client connection
+    Ip::Address sourceAddress;
+    /// intended destination address of the client connection
+    Ip::Address destinationAddress;
+    /// empty in v1 headers and when ignored in v2 headers
+    Tlvs tlvs;
+
+private:
+    /// Whether the connection over PROXY protocol is 'cmdLocal'.
+    /// Such connections are established without being relayed.
+    /// Received addresses and TLVs are discarded in this mode.
+    bool localConnection() const { return command_ == Two::cmdLocal; }
+
+    /// PROXY protocol version
+    SBuf version_;
+
+    /// for v2 headers: the command field
+    /// for v1 headers: Two::cmdProxy
+    Two::Command command_;
+
+    /// true if the header relays no address information
+    bool ignoreAddresses_;
+};
+
+} // namespace ProxyProtocol
+
+#endif
+
diff --git a/src/proxyp/Makefile.am b/src/proxyp/Makefile.am
new file mode 100644 (file)
index 0000000..0632aab
--- /dev/null
@@ -0,0 +1,21 @@
+## Copyright (C) 1996-2018 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.
+##
+
+include $(top_srcdir)/src/Common.am
+include $(top_srcdir)/src/TestHeaders.am
+
+noinst_LTLIBRARIES = libproxyp.la
+
+libproxyp_la_SOURCES = \
+       forward.h \
+       Elements.h \
+       Elements.cc \
+       Header.cc \
+       Header.h \
+       Parser.cc \
+       Parser.h
+
diff --git a/src/proxyp/Parser.cc b/src/proxyp/Parser.cc
new file mode 100644 (file)
index 0000000..ef1f282
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 1996-2018 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.
+ */
+
+#include "squid.h"
+#include "parser/BinaryTokenizer.h"
+#include "parser/Tokenizer.h"
+#include "proxyp/Elements.h"
+#include "proxyp/Header.h"
+#include "proxyp/Parser.h"
+#include "sbuf/Stream.h"
+
+#include <algorithm>
+
+#if HAVE_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif
+#if HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP_H
+#include <netinet/ip.h>
+#endif
+
+namespace ProxyProtocol {
+namespace One {
+/// magic octet prefix for PROXY protocol version 1
+static const SBuf Magic("PROXY", 5);
+/// extracts PROXY protocol v1 header from the given buffer
+static Parsed Parse(const SBuf &buf);
+
+static void ExtractIp(Parser::Tokenizer &tok, Ip::Address &addr);
+static void ExtractPort(Parser::Tokenizer &tok, Ip::Address &addr, const bool trailingSpace);
+static void ParseAddresses(Parser::Tokenizer &tok, Header::Pointer &header);
+}
+
+namespace Two {
+/// magic octet prefix for PROXY protocol version 2
+static const SBuf Magic("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12);
+/// extracts PROXY protocol v2 header from the given buffer
+static Parsed Parse(const SBuf &buf);
+
+static void ParseAddresses(const uint8_t family, Parser::BinaryTokenizer &tok, Header::Pointer &header);
+static void ParseTLVs(Parser::BinaryTokenizer &tok, Header::Pointer &header);
+}
+}
+
+void
+ProxyProtocol::One::ExtractIp(Parser::Tokenizer &tok, Ip::Address &addr)
+{
+    static const auto ipChars = CharacterSet("IP Address",".:") + CharacterSet::HEXDIG;
+
+    SBuf ip;
+
+    if (!tok.prefix(ip, ipChars))
+        throw TexcHere("PROXY/1.0 error: malformed IP address");
+
+    if (!tok.skip(' '))
+        throw TexcHere("PROXY/1.0 error: garbage after IP address");
+
+    if (!addr.GetHostByName(ip.c_str()))
+        throw TexcHere("PROXY/1.0 error: invalid IP address");
+
+}
+
+void
+ProxyProtocol::One::ExtractPort(Parser::Tokenizer &tok, Ip::Address &addr, const bool trailingSpace)
+{
+    int64_t port = -1;
+
+    if (!tok.int64(port, 10, false))
+        throw TexcHere("PROXY/1.0 error: malformed port");
+
+    if (trailingSpace && !tok.skip(' '))
+        throw TexcHere("PROXY/1.0 error: garbage after port");
+
+    if (port > std::numeric_limits<uint16_t>::max())
+        throw TexcHere("PROXY/1.0 error: invalid port");
+
+    addr.port(static_cast<uint16_t>(port));
+}
+
+void
+ProxyProtocol::One::ParseAddresses(Parser::Tokenizer &tok, Header::Pointer &header)
+{
+    static const CharacterSet addressFamilies("Address family", "46");
+    SBuf parsedAddressFamily;
+
+    if (!tok.prefix(parsedAddressFamily, addressFamilies, 1))
+        throw TexcHere("PROXY/1.0 error: missing or invalid IP address family");
+
+    if (!tok.skip(' '))
+        throw TexcHere("PROXY/1.0 error: missing SP after the IP address family");
+
+    // parse: src-IP SP dst-IP SP src-port SP dst-port
+    ExtractIp(tok, header->sourceAddress);
+    ExtractIp(tok, header->destinationAddress);
+
+    if (header->addressFamily() != parsedAddressFamily)
+        throw TexcHere("PROXY/1.0 error: declared and/or actual IP address families mismatch");
+
+    ExtractPort(tok, header->sourceAddress, true);
+    ExtractPort(tok, header->destinationAddress, false);
+}
+
+/// parses PROXY protocol v1 header from the buffer
+ProxyProtocol::Parsed
+ProxyProtocol::One::Parse(const SBuf &buf)
+{
+    Parser::Tokenizer tok(buf);
+
+    static const SBuf::size_type maxHeaderLength = 107; // including CRLF
+    static const auto maxInteriorLength = maxHeaderLength - Magic.length() - 2;
+    static const auto interiorChars = CharacterSet::CR.complement().rename("non-CR");
+    SBuf interior;
+
+    if (!(tok.prefix(interior, interiorChars, maxInteriorLength) &&
+            tok.skip('\r') &&
+            tok.skip('\n'))) {
+        if (tok.atEnd())
+            throw Parser::BinaryTokenizer::InsufficientInput();
+        // "empty interior", "too-long interior", or "missing LF after CR"
+        throw TexcHere("PROXY/1.0 error: malformed header");
+    }
+    // extracted all PROXY protocol bytes
+
+    static const SBuf v1("1.0");
+    Header::Pointer header = new Header(v1, Two::cmdProxy);
+
+    Parser::Tokenizer interiorTok(interior);
+
+    if (!interiorTok.skip(' '))
+        throw TexcHere("PROXY/1.0 error: missing SP after the magic sequence");
+
+    static const SBuf protoUnknown("UNKNOWN");
+    static const SBuf protoTcp("TCP");
+
+    if (interiorTok.skip(protoTcp))
+        ParseAddresses(interiorTok, header);
+    else if (interiorTok.skip(protoUnknown))
+        header->ignoreAddresses();
+    else
+        throw TexcHere("PROXY/1.0 error: invalid INET protocol or family");
+
+    return Parsed(header, tok.parsedSize());
+}
+
+void
+ProxyProtocol::Two::ParseAddresses(const uint8_t family, Parser::BinaryTokenizer &tok, Header::Pointer &header)
+{
+    switch (family) {
+
+    case afInet: {
+        header->sourceAddress = tok.inet4("src_addr IPv4");
+        header->destinationAddress = tok.inet4("dst_addr IPv4");
+        header->sourceAddress.port(tok.uint16("src_port"));
+        header->destinationAddress.port(tok.uint16("dst_port"));
+        break;
+    }
+
+    case afInet6: {
+        header->sourceAddress = tok.inet6("src_addr IPv6");
+        header->destinationAddress = tok.inet6("dst_addr IPv6");
+        header->sourceAddress.port(tok.uint16("src_port"));
+        header->destinationAddress.port(tok.uint16("dst_port"));
+        break;
+    }
+
+    case afUnix: { // TODO: add support
+        // the address block length is 216 bytes
+        tok.skip(216, "unix_addr");
+        break;
+    }
+
+    default: {
+        // unreachable code: we have checked family validity already
+        Must(false);
+        break;
+    }
+    }
+}
+
+void
+ProxyProtocol::Two::ParseTLVs(Parser::BinaryTokenizer &tok, Header::Pointer &header) {
+    while (!tok.atEnd()) {
+        const auto type = tok.uint8("pp2_tlv::type");
+        header->tlvs.emplace_back(type, tok.pstring16("pp2_tlv::value"));
+    }
+}
+
+ProxyProtocol::Parsed
+ProxyProtocol::Two::Parse(const SBuf &buf)
+{
+    Parser::BinaryTokenizer tokHeader(buf, true);
+
+    const auto versionAndCommand = tokHeader.uint8("version and command");
+
+    const auto version = (versionAndCommand & 0xF0) >> 4;
+    if (version != 2) // version == 2 is mandatory
+        throw TexcHere(ToSBuf("PROXY/2.0 error: invalid version ", version));
+
+    const auto command = (versionAndCommand & 0x0F);
+    if (command > cmdProxy)
+        throw TexcHere(ToSBuf("PROXY/2.0 error: invalid command ", command));
+
+    const auto familyAndProto = tokHeader.uint8("family and proto");
+
+    const auto family = (familyAndProto & 0xF0) >> 4;
+    if (family > afUnix)
+        throw TexcHere(ToSBuf("PROXY/2.0 error: invalid address family ", family));
+
+    const auto proto = (familyAndProto & 0x0F);
+    if (proto > tpDgram)
+        throw TexcHere(ToSBuf("PROXY/2.0 error: invalid transport protocol ", proto));
+
+    const auto rawHeader = tokHeader.pstring16("header");
+
+    static const SBuf v2("2.0");
+    Header::Pointer header = new Header(v2, Two::Command(command));
+
+    if (proto == tpUnspecified || family == afUnspecified) {
+        header->ignoreAddresses();
+        // discard address block and TLVs because we cannot tell
+        // how to parse such addresses and where the TLVs start.
+    } else {
+        Parser::BinaryTokenizer leftoverTok(rawHeader);
+        ParseAddresses(family, leftoverTok, header);
+        // TODO: parse TLVs for LOCAL connections
+        if (header->hasForwardedAddresses())
+            ParseTLVs(leftoverTok, header);
+    }
+
+    return Parsed(header, tokHeader.parsed());
+}
+
+ProxyProtocol::Parsed
+ProxyProtocol::Parse(const SBuf &buf)
+{
+    Parser::Tokenizer magicTok(buf);
+
+    const auto parser =
+        magicTok.skip(Two::Magic) ? &Two::Parse :
+        magicTok.skip(One::Magic) ? &One::Parse :
+        nullptr;
+
+    if (parser) {
+        const auto parsed = (parser)(magicTok.remaining());
+        return Parsed(parsed.header, magicTok.parsedSize() + parsed.size);
+    }
+
+    // detect and terminate other protocols
+    if (buf.length() >= Two::Magic.length()) {
+        // PROXY/1.0 magic is shorter, so we know that
+        // the input does not start with any PROXY magic
+        throw TexcHere("PROXY protocol error: invalid magic");
+    }
+
+    // TODO: detect short non-magic prefixes earlier to avoid
+    // waiting for more data which may never come
+
+    // not enough bytes to parse magic yet
+    throw Parser::BinaryTokenizer::InsufficientInput();
+}
+
+ProxyProtocol::Parsed::Parsed(const Header::Pointer &parsedHeader, const size_t parsedSize):
+    header(parsedHeader),
+    size(parsedSize)
+{
+    assert(bool(parsedHeader));
+}
+
diff --git a/src/proxyp/Parser.h b/src/proxyp/Parser.h
new file mode 100644 (file)
index 0000000..06d4c40
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 1996-2018 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.
+ */
+
+#ifndef SQUID_PROXYP_PARSER_H
+#define SQUID_PROXYP_PARSER_H
+
+#include "proxyp/forward.h"
+#include "sbuf/forward.h"
+
+namespace ProxyProtocol {
+
+/// successful parsing result
+class Parsed
+{
+public:
+    Parsed(const HeaderPointer &parsedHeader, const size_t parsedSize);
+
+    HeaderPointer header; ///< successfully parsed header; not nil
+    size_t size; ///< raw bytes parsed, including any magic/delimiters
+};
+
+/// Parses a PROXY protocol header from the buffer, determining
+/// the protocol version (v1 or v2) by the leading magic string.
+/// \throws Parser::BinaryTokenizer::InsufficientInput to ask for more data
+/// \returns the successfully parsed header
+Parsed Parse(const SBuf &);
+
+} // namespace ProxyProtocol
+
+#endif
+
diff --git a/src/proxyp/forward.h b/src/proxyp/forward.h
new file mode 100644 (file)
index 0000000..7c69999
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 1996-2018 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.
+ */
+
+#ifndef _SQUID_SRC_PROXYP_FORWARD_H
+#define _SQUID_SRC_PROXYP_FORWARD_H
+
+#include "base/RefCount.h"
+
+namespace ProxyProtocol
+{
+
+class Header;
+
+typedef RefCount<Header> HeaderPointer;
+
+}
+
+#endif /* _SQUID_SRC_PROXYP_FORWARD_H */
+
index f85c466da37b6c079eff804f193caca366e7178f..e898fa8e04334839a4ccca49be4cabfe7fa7027a 100644 (file)
@@ -50,8 +50,8 @@ String HttpHeader::getById(Http::HdrType) const STUB_RETVAL(String())
 bool HttpHeader::getByIdIfPresent(Http::HdrType, String *) const STUB_RETVAL(false)
 bool HttpHeader::hasNamed(const SBuf &, String *) const STUB_RETVAL(false)
 bool HttpHeader::hasNamed(const char *, unsigned int, String *) const STUB_RETVAL(false)
-String HttpHeader::getByNameListMember(const char *, const char *, const char) const STUB_RETVAL(String())
-String HttpHeader::getListMember(Http::HdrType, const char *, const char) const STUB_RETVAL(String())
+SBuf HttpHeader::getByNameListMember(const char *, const char *, const char) const STUB_RETVAL(SBuf())
+SBuf HttpHeader::getListMember(Http::HdrType, const char *, const char) const STUB_RETVAL(SBuf())
 int HttpHeader::has(Http::HdrType) const STUB_RETVAL(0)
 void HttpHeader::addVia(const AnyP::ProtocolVersion &, const HttpHeader *) STUB
 void HttpHeader::putInt(Http::HdrType, int) STUB