src/mem/Makefile
src/mgr/Makefile
src/parser/Makefile
+ src/proxyp/Makefile
src/repl/Makefile
src/sbuf/Makefile
src/security/Makefile
#include "AccessLogEntry.h"
#include "HttpReply.h"
#include "HttpRequest.h"
+#include "proxyp/Header.h"
#include "SquidConfig.h"
#include "ssl/support.h"
// - 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);
}
return nullptr;
}
+AccessLogEntry::AccessLogEntry() {}
+
AccessLogEntry::~AccessLogEntry()
{
safe_free(headers.request);
#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"
public:
typedef RefCount<AccessLogEntry> Pointer;
- AccessLogEntry() {}
+ AccessLogEntry();
~AccessLogEntry();
/// Fetch the client IP log string into the given buffer.
/// 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
/*
* 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 */
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.
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
$(SSL_LIBS) \
ipc/libipc.la \
mgr/libmgr.la \
+ proxyp/libproxyp.la \
parser/libparser.la \
eui/libeui.la \
icmp/libicmp.la \
tests/stub_StatHist.cc
tests_testURL_LDADD = \
libsquid.la \
+ proxyp/libproxyp.la \
anyp/libanyp.la \
base/libbase.la \
ip/libip.la \
acl/libacls.la \
acl/libstate.la \
acl/libapi.la \
+ proxyp/libproxyp.la \
parser/libparser.la \
ip/libip.la \
fs/libfs.la \
fs/libfs.la \
$(SSL_LIBS) \
ipc/libipc.la \
+ proxyp/libproxyp.la \
parser/libparser.la \
dns/libdns.la \
base/libbase.la \
ftp/libftp.la \
helper/libhelper.la \
http/libhttp.la \
+ proxyp/libproxyp.la \
parser/libparser.la \
ident/libident.la \
acl/libacls.la \
ftp/libftp.la \
helper/libhelper.la \
http/libhttp.la \
+ proxyp/libproxyp.la \
parser/libparser.la \
ident/libident.la \
acl/libacls.la \
ftp/libftp.la \
helper/libhelper.la \
http/libhttp.la \
+ proxyp/libproxyp.la \
parser/libparser.la \
ident/libident.la \
acl/libacls.la \
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();
+}
+
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_ */
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
// 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);
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
#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"
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;
}
// 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
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);
#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
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);
/* PROXY protocol functionality */
bool proxyProtocolValidateClient();
bool parseProxyProtocolHeader();
- bool parseProxy1p0();
- bool parseProxy2p0();
bool proxyProtocolError(const char *reason);
#if USE_OPENSSL
/// 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_;
#include "MemObject.h"
#include "Parsing.h"
#include "profiler/Profiler.h"
+#include "proxyp/Header.h"
#include "redirect.h"
#include "rfc1738.h"
#include "SquidConfig.h"
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()) {
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));
}
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.
#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"
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;
}
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;
}
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;
}
#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;
}
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;
}
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;
}
#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"
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),
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
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';
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) {
type = LFT_ICAP_REP_ALL_HEADERS;
break;
#endif
+ case LFT_PROXY_PROTOCOL_RECEIVED_HEADER:
+ type = LFT_PROXY_PROTOCOL_RECEIVED_ALL_HEADERS;
+ break;
default:
break;
}
#define _SQUID_FORMAT_TOKEN_H
#include "format/ByteCode.h"
+#include "proxyp/Elements.h"
/*
* Squid configuration allows users to define custom formats in
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;
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)
{
*/
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)
/* DEBUG: section 24 SBuf */
#include "squid.h"
+#include "ip/Address.h"
#include "parser/BinaryTokenizer.h"
Parser::BinaryTokenizer::BinaryTokenizer(): BinaryTokenizer(SBuf())
}
+/// 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
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)
{
#ifndef SQUID_SRC_PARSER_BINARYTOKENIZER_H
#define SQUID_SRC_PARSER_BINARYTOKENIZER_H
+#include "ip/forward.h"
#include "sbuf/SBuf.h"
namespace Parser
/// 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
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
--- /dev/null
+/*
+ * 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);
+}
+
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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;
+}
+
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+## 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
+
--- /dev/null
+/*
+ * 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));
+}
+
--- /dev/null
+/*
+ * 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
+
--- /dev/null
+/*
+ * 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 */
+
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