diff --git a/src/FwdState.cc b/src/FwdState.cc index 3e1a2ce264a..74002ee61ec 100644 --- a/src/FwdState.cc +++ b/src/FwdState.cc @@ -47,8 +47,12 @@ #include "MemObject.h" #include "mgr/Registration.h" #include "neighbors.h" +#include "parser/BinaryPacker.h" #include "pconn.h" #include "PeerPoolMgr.h" +#include "proxyp/Elements.h" +#include "proxyp/Header.h" +#include "proxyp/OutgoingHttpConfig.h" #include "ResolvedPeers.h" #include "security/BlindPeerConnector.h" #include "SquidConfig.h" @@ -881,8 +885,14 @@ FwdState::noteConnection(HappyConnOpener::Answer &answer) return dispatch(); } + sendProxyProtoHeaderIfNeeded(answer.conn); +} + +void +FwdState::tunnelIfNeeded(const Comm::ConnectionPointer &conn) +{ // Check if we need to TLS before use - if (const auto *peer = answer.conn->getPeer()) { + if (const auto *peer = conn->getPeer()) { // Assume that it is only possible for the client-first from the // bumping modes to try connect to a remote server. The bumped // requests with other modes are using pinned connections or fails. @@ -896,12 +906,95 @@ FwdState::noteConnection(HappyConnOpener::Answer &answer) if (originWantsEncryptedTraffic && // the "encrypted traffic" part !peer->options.originserver && // the "through a proxy" part !peer->secure.encryptTransport) // the "exclude HTTPS proxies" part - return advanceDestination("establish tunnel through proxy", answer.conn, [this,&answer] { - establishTunnelThruProxy(answer.conn); + return advanceDestination("establish tunnel through proxy", conn, [this,&conn] { + establishTunnelThruProxy(conn); }); } - secureConnectionToPeerIfNeeded(answer.conn); + secureConnectionToPeerIfNeeded(conn); +} + +/// whether it is required to send Proxy protocol header +bool +FwdState::needProxyProtoHeader() const +{ + if (!Config.outgoingProxyProtocolHttp) + return false; + + if (!Config.outgoingProxyProtocolHttp->aclList) + return true; + + ACLFilledChecklist ch(Config.outgoingProxyProtocolHttp->aclList, request); + ch.al = al; + ch.syncAle(request, nullptr); + return ch.fastCheck().allowed(); +} + +void +FwdState::createProxyProtoHeaderIfNeeded() +{ + proxyProtocolHeader.reset(); + + if (!needProxyProtoHeader()) + return; + + static const SBuf v2("2.0"); + ProxyProtocol::Header header(v2, ProxyProtocol::Two::cmdProxy); + Config.outgoingProxyProtocolHttp->fill(header, al); + + BinaryPacker packer; + header.pack(packer); + proxyProtocolHeader = packer.packed(); +} + +void +FwdState::sendProxyProtoHeaderIfNeeded(const Comm::ConnectionPointer &conn) +{ + if (proxyProtocolHeader) { + // XXX: Avoid this copying by adding an SBuf-friendly Comm::Write()! + MemBuf mb; + mb.init(); + mb.append(proxyProtocolHeader->rawContent(), proxyProtocolHeader->length()); + + AsyncCall::Pointer call = commCbCall(17, 5, "FwdState::proxyHeaderSent", + CommIoCbPtrFun(&FwdState::proxyHeaderSent, this)); + Comm::Write(conn, &mb, call); + return; + } + + tunnelIfNeeded(conn); +} + +/// resumes operations after the (possibly failed) Proxy protocol header sending +void +FwdState::proxyHeaderSent(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag, int, void *data) +{ + auto fwd = reinterpret_cast(data); + + ErrorState *error = nullptr; + + if (errflag == Comm::ERR_CLOSING) + return; + + if (!Comm::IsConnOpen(conn) || fd_table[conn->fd].closing()) { + // The socket could get closed while our callback was queued. Sync + // Connection. XXX: Connection::fd may already be stale/invalid here. + fwd->closePendingConnection(conn, "conn was closed while waiting for FwdState::proxyHeaderSent"); + error = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, fwd->request, fwd->al); + } + + if (errflag != Comm::OK || size <= 0) { + conn->close(); + return; + } + + if (error) { + fwd->fail(error); + fwd->retryOrBail(); + return; + } + + fwd->tunnelIfNeeded(conn); } void @@ -1127,6 +1220,8 @@ FwdState::connectStart() err = nullptr; request->clearError(); + createProxyProtoHeaderIfNeeded(); + const auto callback = asyncCallback(17, 5, FwdState::noteConnection, this); HttpRequest::Pointer cause = request; const auto cs = new HappyConnOpener(destinations, callback, cause, start_t, n_tries, al); @@ -1139,7 +1234,7 @@ FwdState::connectStart() retriable = ch.fastCheck().allowed(); } cs->setRetriable(retriable); - cs->allowPersistent(pconnRace != raceHappened); + cs->allowPersistent((pconnRace != raceHappened) && !proxyProtocolHeader); destinations->notificationPending = true; // start() is async transportWait.start(cs, callback); } diff --git a/src/FwdState.h b/src/FwdState.h index 316243c9030..27c9759f484 100644 --- a/src/FwdState.h +++ b/src/FwdState.h @@ -174,6 +174,10 @@ class FwdState: public RefCountable, public PeerSelectionInitiator void establishTunnelThruProxy(const Comm::ConnectionPointer &); void tunnelEstablishmentDone(Http::TunnelerAnswer &answer); + bool needProxyProtoHeader() const; + void createProxyProtoHeaderIfNeeded(); + void sendProxyProtoHeaderIfNeeded(const Comm::ConnectionPointer &); + void tunnelIfNeeded(const Comm::ConnectionPointer &); void secureConnectionToPeerIfNeeded(const Comm::ConnectionPointer &); void secureConnectionToPeer(const Comm::ConnectionPointer &); void successfullyConnectedToPeer(const Comm::ConnectionPointer &); @@ -202,10 +206,14 @@ class FwdState: public RefCountable, public PeerSelectionInitiator StoreEntry *entry; HttpRequest *request; AccessLogEntryPointer al; ///< info for the future access.log entry + std::optional proxyProtocolHeader; /// called by Store if the entry is no longer usable static void HandleStoreAbort(FwdState *); + /// callback for sendProxyProtoHeaderIfNeeded() + static void proxyHeaderSent(const Comm::ConnectionPointer &, char *, size_t, Comm::Flag, int, void *data); + private: Pointer self; ErrorState *err; diff --git a/src/SquidConfig.h b/src/SquidConfig.h index a79a6cdc4fc..2e0a065e19e 100644 --- a/src/SquidConfig.h +++ b/src/SquidConfig.h @@ -23,6 +23,7 @@ #include "MessageDelayPools.h" #endif #include "Notes.h" +#include "proxyp/forward.h" #include "security/Context.h" #include "security/forward.h" #if USE_OPENSSL @@ -547,6 +548,8 @@ class SquidConfig int connect_gap; int connect_timeout; } happyEyeballs; + + ProxyProtocol::OutgoingHttpConfig *outgoingProxyProtocolHttp; }; extern SquidConfig Config; diff --git a/src/cf.data.depend b/src/cf.data.depend index fa896248cf7..42a02c672f1 100644 --- a/src/cf.data.depend +++ b/src/cf.data.depend @@ -98,3 +98,4 @@ sslproxy_cert_sign acl sslproxy_cert_adapt acl ftp_epsv acl cache_log_message +ProxyProtocol::OutgoingHttpConfig* acl diff --git a/src/cf.data.pre b/src/cf.data.pre index 770ab0abfd8..9c8fa319d6b 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1769,6 +1769,173 @@ DOC_START See https://wiki.squid-cache.org/SquidFaq/SquidAcl for details. DOC_END +NAME: http_outgoing_proxy_protocol +TYPE: ProxyProtocol::OutgoingHttpConfig* +LOC: Config.outgoingProxyProtocolHttp +DEFAULT: none +DEFAULT_DOC: Squid does not send PROXY protocol headers +DOC_START + Determines which Squid-to-peer HTTP connections start with a PROXY + protocol header. The first decision is made after Squid selects the next + forwarding hop for the request but before Squid determines that hop IP + address. Thus, at the decision-making time, the HTTP or FTP peer (e.g., + cache_peer name or origin server hostname) is known, but the source and + destination IP addresses of a Squid-to-peer connection may be unknown. The + decision is remade every time Squid has to reforward the request to a + different hop (e.g., another cache_peer). + + http_outgoing_proxy_protocol [options]
... [if [!]...] + + For now, Squid only sends PROXY protocol v2 headers. + + For now, Squid disables HTTP connection reuse after sending + the PROXY protocol header on that connection. + + For now, no options are supported for this directive (XXX: Remove this + paragraph and [options] then, while still keeping future needs in mind). + + For now, Squid does not support multiple http_outgoing_proxy_protocol + directives. + + For now, PROXY protocol header is sent unencrypted, even for + connections to TLS origin servers and cache_peers with "tls" option. + This allows some network services to extract PROXY protocol header + information before switching to forwarding of encrypted bytes. Future + enhancements may support optional encryption of PROXY protocol bytes. + For now, no options are supported. TODO: Consider adding a _required_ + "encrypt=no" option _now_, so that we can later change the _default_ + logic from the current "never encrypt" to "encrypt=auto" (that would + depend on whether the next hop is using TLS). + + When opening a CONNECT tunnel through a cache_peer, Squid sends the + PROXY protocol header before sending CONNECT request. PROXY protocol + header bytes and CONNECT request header bytes may share the same TCP + packet. + + Normally, Squid sends a "PROXY" command, but when sending a request + generated internally by Squid (instead of forwarding a request received + from a client), Squid uses PROXY protocol "LOCAL" command and fills the + protocol block (including the four mandatory address fields itemized + above) with zeros. PROXY protocol requires LOCAL command recipients to + "discard the protocol block". LOCAL commands may carry TLVs (see below). + + Required header fields: + + The following four header fields are required. They must be configured in + the documented order. Their names and quoted brief descriptions are taken + from proxy_addr structure and the corresponding text in the PROXY protocol + specification. Squid enforces certain PROXY protocol header constraints, + but does not assign any specific meaning to header values. + + * src_addr=logformat: "source layer 3 address". A logformat format + specification evaluating to a constant IPv4 address (e.g., + "192.0.2.1"), a constant IPv6 address (e.g., "2001:db8::1"), or a + single dash character (i.e. "-") signifying an unknown IP address. + If the address is unknown, then Squid sends either INADDR_ANY or + IN6ADDR_ANY_INIT IP address value. See further below for details. + + * dst_addr=logformat: "destination layer 3 address". See src_addr + bullet above for value handling details. + + * src_port=logformat: "source layer 4 address". A logformat format + specification evaluating to a decimal number from [0,65535] range or + a single dash character (i.e. "-") signifying an unknown port. If + the port is unknown, Squid sends 0 port value. + + * dst_port=logformat: "destination layer 4 address". See src_port + bullet above for value handling details. + + Address and port values are interpreted as "format specifications" + documented in logformat directive (e.g., "%>a"). They are evaluated at + PROXY protocol header construction time (at least, see below). A format + specification has to be spelled out -- header values cannot use a + logformat name to refer to a format specification defined elsewhere. Squid + failure to "compile" a configured format specification is a fatal + configuration error (e.g., src_port="192.0.2.1"). + + If Squid believes that the result of compiled format evaluation does not + change from one transaction to another (e.g., "dst_port=80"), then Squid + will validate the result at configuration time; validation failures (if + any) are fatal configuration errors. + + When constructing a PROXY protocol header for a given transaction, if the + result of compiled format evaluation is not compatible with header field + type (e.g., src_port="%connection_id"). + + After logformat evaluation (i.e. %code evaluation, if any), Squid treats + TLV values as opaque strings. An empty logformat value (that must be + specified as an empty quoted string) is not treated specially. A logformat + value of "-" and an evaluated format value of "-" are not treated + specially either. + + Squid failure to "compile" a configured TLV format specification is a + fatal configuration error (e.g., 0xEF="%"). If a TLV value cannot be + computed at header construction time or the computed value exceeds 65535 + bytes in length, Squid does not send the affected field and records a + level-1 ERROR to cache.log. + + Squid does not yet support sending well-known TLVs like PP2_TYPE_CRC32C. + + Optional TLVs are sent in the order they are specified by the + http_outgoing_proxy_protocol directive. Using one TLV type multiple times + is allowed, but identical (at configuration interpretation time, before + logformat %code substitution) type=value entries lead to fatal + configuration errors. + + Details that apply to both required and optional header fields: + + Quoted (using "double quotes") header field values are supported. Squid + removes quotes before using the quoted value. + + Logformat evaluation is subject to log_uses_indirect_client directive. In + most cases, Squid-to-peer connection and response-related details are not + available during this evaluation because Squid has not opened the + corresponding connection to the peer yet. + + Example: + + http_outgoing_proxy_protocol \ + src_addr="%>a" \ + dst_addr="%(tokenSize); } +bool +Format::Format::isConstant() const +{ + for (auto t = format; t; t = t->next) { + if (t->type != LFT_NONE && + t->type != LFT_STRING && + t->type != LFT_PERCENT && + t->type != LFT_BYTE && + t->type != LFT_TIME_LOCALTIME && + t->type != LFT_TIME_GMT) + return true; + } + return false; +} + void Format::Format::dump(StoreEntry * entry, const char *directiveName, bool eol) const { @@ -124,163 +139,11 @@ Format::Format::dump(StoreEntry * entry, const char *directiveName, bool eol) co if (directiveName) storeAppendPrintf(entry, "%s %s ", directiveName, fmt->name); - for (Token *t = fmt->format; t; t = t->next) { - if (t->type == LFT_STRING) - storeAppendPrintf(entry, "%s", t->data.string); - else { - char argbuf[256]; - char *arg = nullptr; - ByteCode_t type = t->type; - - switch (type) { - /* special cases */ - - case LFT_STRING: - break; -#if USE_ADAPTATION - case LFT_ADAPTATION_LAST_HEADER_ELEM: -#endif -#if ICAP_CLIENT - case LFT_ICAP_REQ_HEADER_ELEM: - case LFT_ICAP_REP_HEADER_ELEM: -#endif - case LFT_REQUEST_HEADER_ELEM: - case LFT_ADAPTED_REQUEST_HEADER_ELEM: - case LFT_REPLY_HEADER_ELEM: - - if (t->data.header.separator != ',') - snprintf(argbuf, sizeof(argbuf), "%s:%c%s", t->data.header.header, t->data.header.separator, t->data.header.element); - else - snprintf(argbuf, sizeof(argbuf), "%s:%s", t->data.header.header, t->data.header.element); - - arg = argbuf; - - switch (type) { - case LFT_REQUEST_HEADER_ELEM: - type = LFT_REQUEST_HEADER_ELEM; // XXX: remove _ELEM? - break; - case LFT_ADAPTED_REQUEST_HEADER_ELEM: - type = LFT_ADAPTED_REQUEST_HEADER_ELEM; // XXX: remove _ELEM? - break; - case LFT_REPLY_HEADER_ELEM: - type = LFT_REPLY_HEADER_ELEM; // XXX: remove _ELEM? - break; -#if USE_ADAPTATION - case LFT_ADAPTATION_LAST_HEADER_ELEM: - type = LFT_ADAPTATION_LAST_HEADER; - break; -#endif -#if ICAP_CLIENT - case LFT_ICAP_REQ_HEADER_ELEM: - type = LFT_ICAP_REQ_HEADER; - break; - case LFT_ICAP_REP_HEADER_ELEM: - type = LFT_ICAP_REP_HEADER; - break; -#endif - default: - break; - } - - break; - - case LFT_REQUEST_ALL_HEADERS: - case LFT_ADAPTED_REQUEST_ALL_HEADERS: - case LFT_REPLY_ALL_HEADERS: - -#if USE_ADAPTATION - case LFT_ADAPTATION_LAST_ALL_HEADERS: -#endif -#if ICAP_CLIENT - case LFT_ICAP_REQ_ALL_HEADERS: - case LFT_ICAP_REP_ALL_HEADERS: -#endif - - switch (type) { - case LFT_REQUEST_ALL_HEADERS: - type = LFT_REQUEST_HEADER; - break; - case LFT_ADAPTED_REQUEST_ALL_HEADERS: - type = LFT_ADAPTED_REQUEST_HEADER; - break; - case LFT_REPLY_ALL_HEADERS: - type = LFT_REPLY_HEADER; - break; -#if USE_ADAPTATION - case LFT_ADAPTATION_LAST_ALL_HEADERS: - type = LFT_ADAPTATION_LAST_HEADER; - break; -#endif -#if ICAP_CLIENT - case LFT_ICAP_REQ_ALL_HEADERS: - type = LFT_ICAP_REQ_HEADER; - break; - case LFT_ICAP_REP_ALL_HEADERS: - type = LFT_ICAP_REP_HEADER; - break; -#endif - default: - break; - } - - break; - - default: - if (t->data.string) - arg = t->data.string; - - break; - } - - entry->append("%", 1); - - switch (t->quote) { - - case LOG_QUOTE_QUOTES: - entry->append("\"", 1); - break; - - case LOG_QUOTE_MIMEBLOB: - entry->append("[", 1); - break; - - case LOG_QUOTE_URL: - entry->append("#", 1); - break; - - case LOG_QUOTE_RAW: - entry->append("'", 1); - break; - - case LOG_QUOTE_SHELL: - entry->append("/", 1); - break; - - case LOG_QUOTE_NONE: - break; - } - - if (t->left) - entry->append("-", 1); - - if (t->zero) - entry->append("0", 1); - - if (t->widthMin >= 0) - storeAppendPrintf(entry, "%d", t->widthMin); - - if (t->widthMax >= 0) - storeAppendPrintf(entry, ".%d", t->widthMax); - - if (arg) - storeAppendPrintf(entry, "{%s}", arg); - - storeAppendPrintf(entry, "%s", t->label); - - if (t->space) - entry->append(" ", 1); - } - } + SBufStream os; + fmt->format->print(os); + const auto buf = os.buf(); + if (buf.length()) + entry->append(buf.rawContent(), buf.length()); if (eol) entry->append("\n", 1); diff --git a/src/format/Format.h b/src/format/Format.h index fd4eb2b67fa..20e789cbba8 100644 --- a/src/format/Format.h +++ b/src/format/Format.h @@ -56,6 +56,9 @@ class Format /// dump this whole list of formats into the provided StoreEntry void dump(StoreEntry * entry, const char *directiveName, bool eol = true) const; + /// whether assemble() needs ALE + bool isConstant() const; + char *name; Token *format; Format *next; diff --git a/src/format/Makefile.am b/src/format/Makefile.am index 17e7622feb1..1632848b49e 100644 --- a/src/format/Makefile.am +++ b/src/format/Makefile.am @@ -19,5 +19,6 @@ libformat_la_SOURCES = \ Quoting.h \ Token.cc \ Token.h \ - TokenTableEntry.h + TokenTableEntry.h \ + forward.h diff --git a/src/format/Token.cc b/src/format/Token.cc index 8fc2048ccfa..dc3f9d13da5 100644 --- a/src/format/Token.cc +++ b/src/format/Token.cc @@ -736,6 +736,168 @@ Format::Token::parse(const char *def, Quoting *quoting) return (cur - def); } +void +Format::Token::print(std::ostream &os) +{ + if (type == LFT_STRING) + os << data.string; + else { + char argbuf[256]; + char *arg = nullptr; + + switch (type) { + /* special cases */ + + case LFT_STRING: + break; +#if USE_TATION + case LFT_ADAPTATION_LAST_HEADER_ELEM: +#endif +#if ICAPENT + case LFT_ICAP_REQ_HEADER_ELEM: + case LFT_ICAP_REP_HEADER_ELEM: +#endif + case LFT_REQUEST_HEADER_ELEM: + case LFT_ADAPTED_REQUEST_HEADER_ELEM: + case LFT_REPLY_HEADER_ELEM: + + if (data.header.separator != ',') + snprintf(argbuf, sizeof(argbuf), "%s:%c%s", data.header.header, data.header.separator, data.header.element); + else + snprintf(argbuf, sizeof(argbuf), "%s:%s", data.header.header, data.header.element); + + arg = argbuf; + + switch (type) { + case LFT_REQUEST_HEADER_ELEM: + type = LFT_REQUEST_HEADER_ELEM; // XXX: remove _ELEM? + break; + case LFT_ADAPTED_REQUEST_HEADER_ELEM: + type = LFT_ADAPTED_REQUEST_HEADER_ELEM; // XXX: remove _ELEM? + break; + case LFT_REPLY_HEADER_ELEM: + type = LFT_REPLY_HEADER_ELEM; // XXX: remove _ELEM? + break; +#if USE_TATI + case LFT_ADAPTATION_LAST_HEADER_ELEM: + type = LFT_ADAPTATION_LAST_HEADER; + break; +#endif +#if ICAPENT + case LFT_ICAP_REQ_HEADER_ELEM: + type = LFT_ICAP_REQ_HEADER; + break; + case LFT_ICAP_REP_HEADER_ELEM: + type = LFT_ICAP_REP_HEADER; + break; +#endif + default: + break; + } + + break; + + case LFT_REQUEST_ALL_HEADERS: + case LFT_ADAPTED_REQUEST_ALL_HEADERS: + case LFT_REPLY_ALL_HEADERS: + +#if USE_TATION + case LFT_ADAPTATION_LAST_ALL_HEADERS: +#endif +#if ICAPENT + case LFT_ICAP_REQ_ALL_HEADERS: + case LFT_ICAP_REP_ALL_HEADERS: +#endif + + switch (type) { + case LFT_REQUEST_ALL_HEADERS: + type = LFT_REQUEST_HEADER; + break; + case LFT_ADAPTED_REQUEST_ALL_HEADERS: + type = LFT_ADAPTED_REQUEST_HEADER; + break; + case LFT_REPLY_ALL_HEADERS: + type = LFT_REPLY_HEADER; + break; +#if USE_TATI + case LFT_ADAPTATION_LAST_ALL_HEADERS: + type = LFT_ADAPTATION_LAST_HEADER; + break; +#endif +#if ICAPENT + case LFT_ICAP_REQ_ALL_HEADERS: + type = LFT_ICAP_REQ_HEADER; + break; + case LFT_ICAP_REP_ALL_HEADERS: + type = LFT_ICAP_REP_HEADER; + break; +#endif + default: + break; + } + + break; + + default: + if (data.string) + arg = data.string; + + break; + } + + os << '%'; + + switch (quote) { + + case LOG_QUOTE_QUOTES: + os << '"'; + break; + + case LOG_QUOTE_MIMEBLOB: + os << '['; + break; + + case LOG_QUOTE_URL: + os << '#'; + break; + + case LOG_QUOTE_RAW: + os << "'"; + break; + + case LOG_QUOTE_SHELL: + os << '/'; + break; + + case LOG_QUOTE_NONE: + break; + } + + if (left) + os << '-'; + + if (zero) + os << '0'; + + if (widthMin >= 0) + os << widthMin; + + if (widthMax >= 0) + os << '.' << widthMax; + + if (arg) + os << '{' << arg << '}'; + + os << label; + + if (space) + os << ' '; + } + + if (next) + next->print(os); +} + Format::Token::Token() : type(LFT_NONE), label(nullptr), widthMin(-1), diff --git a/src/format/Token.h b/src/format/Token.h index b28e658d3a3..80e88abc522 100644 --- a/src/format/Token.h +++ b/src/format/Token.h @@ -47,6 +47,9 @@ class Token */ int parse(const char *def, enum Quoting *quote); + /// writes this token and all tokens in the list into the stream + void print(std::ostream &os); + ByteCode_t type; const char *label; struct { diff --git a/src/format/forward.h b/src/format/forward.h new file mode 100644 index 00000000000..46f9854eda7 --- /dev/null +++ b/src/format/forward.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 1996-2025 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_FORMAT_FORWARD_H +#define SQUID_SRC_FORMAT_FORWARD_H + +namespace Format +{ + +class Format; + +} + +#endif /* SQUID_SRC_FORMAT_FORWARD_H */ + diff --git a/src/ip/Address.cc b/src/ip/Address.cc index 298db47a4d0..3ad9d495c27 100644 --- a/src/ip/Address.cc +++ b/src/ip/Address.cc @@ -394,6 +394,29 @@ Ip::Address::GetHostByName(const char* s) return lookupHostIP(s, false); } +/// \returns an IPv4 Address with true isAnyAddr() +const Ip::Address & +Ip::Address::AnyAddrIPv4() +{ + static Address anyAddr; + if(!anyAddr.isAnyAddr()) + anyAddr.setAnyAddr(); + if (!anyAddr.isIPv4()) + anyAddr.setIPv4(); + return anyAddr; + +} + +/// \returns an IPv6 Address with true isAnyAddr() +const Ip::Address & +Ip::Address::AnyAddrIPv6() +{ + static Address anyAddr; + if(!anyAddr.isAnyAddr()) + anyAddr.setAnyAddr(); + return anyAddr; +} + bool Ip::Address::lookupHostIP(const char *s, bool nodns) { diff --git a/src/ip/Address.h b/src/ip/Address.h index 8f49bb29cf8..877d63c663a 100644 --- a/src/ip/Address.h +++ b/src/ip/Address.h @@ -320,6 +320,12 @@ class Address /// \see isNoAddr() for more details static const Address &NoAddr() { static const Address noAddr(v6_noaddr); return noAddr; } + /// \returns an IPv4 Address with true isAnyAddr() + static const Address &AnyAddrIPv4(); + + /// \returns an IPv6 Address with true isAnyAddr() + static const Address &AnyAddrIPv6(); + public: /* XXX: When C => C++ conversion is done will be fully private. * Legacy Transition Methods. diff --git a/src/parser/BinaryPacker.cc b/src/parser/BinaryPacker.cc new file mode 100644 index 00000000000..b73afeb7150 --- /dev/null +++ b/src/parser/BinaryPacker.cc @@ -0,0 +1,91 @@ +/* + * Copyright (C) 1996-2025 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 "ip/Address.h" +#include "parser/BinaryPacker.h" + +#include + +/// helper for methods that need to store a single byte +void +BinaryPacker::packOctet_(const uint8_t value) +{ + output_.append(static_cast(value)); +} + +/// helper for methods that need to store a variable number of bytes +void +BinaryPacker::packOctets_(const void *value, const size_t size) +{ + output_.append(static_cast(value), size); +} + +/// helper for reporting to-be-serialized field +template +void +BinaryPacker::packing_(const char * const description, const Value &value, const size_t size) const +{ + debugs(24, 7, description << "[" << size << " bytes]: " << value); +} + +void +BinaryPacker::uint8(const char * const description, const uint8_t value) +{ + packing_(description, value, 1); + packOctet_(value); +} + +void +BinaryPacker::uint16(const char * const description, const uint16_t value) +{ + packing_(description, value, 2); + packOctet_(value >> 8); + packOctet_(value); +} + +void +BinaryPacker::area(const char * const description, const SBuf &blob) +{ + packing_(description, __FUNCTION__, blob.length()); + packOctets_(blob.rawContent(), blob.length()); +} + +void +BinaryPacker::inet(const char * const description, const Ip::Address &ip) +{ + if (ip.isIPv4()) { + in_addr ip4; + packing_(description, ip, sizeof(ip4)); + ip.getInAddr(ip4); + packOctets_(&ip4, sizeof(ip4)); + } else { + in6_addr ip6; + packing_(description, ip, sizeof(ip6)); + ip.getInAddr(ip6); + packOctets_(&ip6, sizeof(ip6)); + } +} + +void +BinaryPacker::pstring8(const char * const description, const SBuf &area) +{ + packing_(description, __FUNCTION__, area.length()); + Assure(area.length() <= std::numeric_limits::max()); + uint8("pstring8() length", area.length()); + packOctets_(area.rawContent(), area.length()); +} + +void +BinaryPacker::pstring16(const char * const description, const SBuf &area) +{ + packing_(description, __FUNCTION__, area.length()); + Assure(area.length() <= std::numeric_limits::max()); + uint16("pstring16() length", area.length()); + packOctets_(area.rawContent(), area.length()); +} diff --git a/src/parser/BinaryPacker.h b/src/parser/BinaryPacker.h new file mode 100644 index 00000000000..5f85f5281da --- /dev/null +++ b/src/parser/BinaryPacker.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 1996-2025 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_PARSER_BINARYPACKER_H +#define SQUID_SRC_PARSER_BINARYPACKER_H + +#include "ip/forward.h" +#include "parser/forward.h" +#include "sbuf/SBuf.h" + +/// Serializes various common types using network byte order (where applicable). +/// \sa Parser::BinaryTokenizer that parses serialized fields. +class BinaryPacker +{ +public: + /// packs a single-byte unsigned integer + void uint8(const char *description, uint8_t); + + /// packs a two-byte unsigned integer + void uint16(const char *description, uint16_t); + + /// packs all given bytes as an opaque blob + void area(const char *description, const SBuf &); + + /// packs in_addr or in6_addr structure; port information is not stored + void inet(const char *description, const Ip::Address &); + + /* + * Variable-length arrays (a.k.a. Pascal or prefix strings). + * pstringN() packs an N-bit length field followed by length bytes + */ + void pstring8(const char *description, const SBuf &); ///< up to 255 byte-long p-string + void pstring16(const char *description, const SBuf &); ///< up to 64 KiB-long p-string + + const SBuf &packed() const { return output_; } + +private: + void packOctet_(uint8_t); + void packOctets_(const void *, size_t); + template void packing_(const char *description, const Value &, size_t size) const; + +private: + /// serialized bytes accumulated so far + SBuf output_; +}; + +#endif /* SQUID_SRC_PARSER_BINARYPACKER_H */ + diff --git a/src/parser/Makefile.am b/src/parser/Makefile.am index 7ed1a0d5c56..95402f1aef5 100644 --- a/src/parser/Makefile.am +++ b/src/parser/Makefile.am @@ -10,6 +10,8 @@ include $(top_srcdir)/src/Common.am noinst_LTLIBRARIES = libparser.la libparser_la_SOURCES = \ + BinaryPacker.cc \ + BinaryPacker.h \ BinaryTokenizer.cc \ BinaryTokenizer.h \ Tokenizer.cc \ diff --git a/src/parser/forward.h b/src/parser/forward.h index 1aed43923b9..01b5192c012 100644 --- a/src/parser/forward.h +++ b/src/parser/forward.h @@ -18,5 +18,7 @@ class BinaryTokenizer; class InsufficientInput {}; } // namespace Parser +class BinaryPacker; + #endif /* SQUID_SRC_PARSER_FORWARD_H */ diff --git a/src/proxyp/Elements.cc b/src/proxyp/Elements.cc index a32338bb49b..58716b8765a 100644 --- a/src/proxyp/Elements.cc +++ b/src/proxyp/Elements.cc @@ -36,6 +36,21 @@ static Two::FieldType IntegerToFieldType(const SBuf &); } // namespace ProxyProtocol +/// magic octet prefix for PROXY protocol version 1 +const SBuf & +ProxyProtocol::One::Magic() +{ + static const auto magic = new SBuf("PROXY", 5); + return *magic; +} + +const SBuf & +ProxyProtocol::Two::Magic() +{ + static const auto magic = new SBuf("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); + return *magic; +} + const SBuf & ProxyProtocol::PseudoFieldTypeToFieldName(const Two::FieldType fieldType) { diff --git a/src/proxyp/Elements.h b/src/proxyp/Elements.h index 6fe246d1611..8274b7f247c 100644 --- a/src/proxyp/Elements.h +++ b/src/proxyp/Elements.h @@ -13,8 +13,17 @@ // https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt namespace ProxyProtocol { + +namespace One { +/// magic octet prefix for PROXY protocol version 1 +const SBuf &Magic(); +} + namespace Two { +/// magic octet prefix for PROXY protocol version 2 +const SBuf &Magic(); + /// numeric IDs of registered PROXY protocol TLV types and pseudo headers typedef enum { htUnknown = 0x00, diff --git a/src/proxyp/Header.cc b/src/proxyp/Header.cc index 30921f95146..8bdf89a8809 100644 --- a/src/proxyp/Header.cc +++ b/src/proxyp/Header.cc @@ -8,6 +8,7 @@ #include "squid.h" #include "base/EnumIterator.h" +#include "parser/BinaryPacker.h" #include "proxyp/Elements.h" #include "proxyp/Header.h" #include "sbuf/Stream.h" @@ -21,6 +22,36 @@ ProxyProtocol::Header::Header(const SBuf &ver, const Two::Command cmd): ignoreAddresses_(false) {} +void +ProxyProtocol::Header::pack(BinaryPacker &pack) const +{ + pack.area("magic", Two::Magic()); + + const auto ver = 2; // XXX: We should be using version_, but version_ should use int instead of SBuf! + Assure(ver == 2); // no support for serializing using legacy v1 format + pack.uint8("version and command", (ver << 4) | command_); + + Assure(sourceAddress.isIPv4() == destinationAddress.isIPv4()); // one family for both addresses + const auto family = sourceAddress.isIPv4() ? Two::afInet : Two::afInet6; + pack.uint8("socket family and transport protocol", (family << 4) | Two::tpStream); + + BinaryPacker tail; + + tail.inet("src_addr", sourceAddress); + tail.inet("dst_addr", destinationAddress); + tail.uint16("src_port", sourceAddress.port()); + tail.uint16("dst_port", destinationAddress.port()); + + for (const auto &tlv: tlvs) { + tail.uint8("pp2_tlv::type", tlv.type); + tail.pstring16("pp2_tlv::value", tlv.value); + } + + // Optimization TODO: This copy can be removed by packing length placeholder + // and std::moving BinaryPacker::output_ from `pack` into `tail` and back. + pack.pstring16("addresses and TLVs", tail.packed()); +} + SBuf ProxyProtocol::Header::toMime() const { diff --git a/src/proxyp/Header.h b/src/proxyp/Header.h index bcdf7fc0835..3d23b734a86 100644 --- a/src/proxyp/Header.h +++ b/src/proxyp/Header.h @@ -11,6 +11,8 @@ #include "base/RefCount.h" #include "ip/Address.h" +#include "MemBuf.h" +#include "parser/forward.h" #include "proxyp/Elements.h" #include "sbuf/SBuf.h" @@ -57,6 +59,8 @@ class Header: public RefCountable /// \returns "mix" otherwise const SBuf &addressFamily() const; + void pack(BinaryPacker &) const; + /// source address of the client connection Ip::Address sourceAddress; /// intended destination address of the client connection diff --git a/src/proxyp/Makefile.am b/src/proxyp/Makefile.am index f03c87c55df..6d1dfbd92d8 100644 --- a/src/proxyp/Makefile.am +++ b/src/proxyp/Makefile.am @@ -14,6 +14,8 @@ libproxyp_la_SOURCES = \ Elements.h \ Header.cc \ Header.h \ + OutgoingHttpConfig.cc \ + OutgoingHttpConfig.h \ Parser.cc \ Parser.h \ forward.h diff --git a/src/proxyp/OutgoingHttpConfig.cc b/src/proxyp/OutgoingHttpConfig.cc new file mode 100644 index 00000000000..c3f48fbe89a --- /dev/null +++ b/src/proxyp/OutgoingHttpConfig.cc @@ -0,0 +1,352 @@ +/* + * Copyright (C) 1996-2023 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 "acl/FilledChecklist.h" +#include "acl/Gadgets.h" +#include "acl/Tree.h" +#include "base/IoManip.h" +#include "base/TextException.h" +#include "cache_cf.h" +#include "ConfigOption.h" +#include "ConfigParser.h" +#include "format/Format.h" +#include "format/Token.h" +#include "parser/Tokenizer.h" +#include "proxyp/Header.h" +#include "proxyp/OutgoingHttpConfig.h" +#include "sbuf/Stream.h" +#include "sbuf/StringConvert.h" + +ProxyProtocol::Option::Option(const char *aName, ConfigParser &parser) + : name_(aName), quoted_(parser.LastTokenWasQuoted()), value_(nullptr) +{ + char *key = nullptr; + char *value = nullptr; + if(!parser.optionalKvPair(key, value)) + throw TextException(ToSBuf("missing ", name_, " option"), Here()); + if (name_.cmp(key) != 0) + throw TextException(ToSBuf("expected ", name_, ", but got ", key, " option"), Here()); + parseFormat(value); +} + +ProxyProtocol::Option::Option(const char *aName, const char *aValue, bool quoted) + : name_(aName), quoted_(quoted), value_(nullptr) +{ + parseFormat(aValue); +} + +ProxyProtocol::Option::~Option() +{ + delete value_; +} + +std::ostream & +ProxyProtocol::operator << (std::ostream &os, const Option &opt) +{ + os << opt.name_ << '='; + auto buf = Format::Dash; + if (opt.value_) { + SBufStream valueOs; + opt.value_->format->print(valueOs); + buf = valueOs.buf(); + } + if (opt.quoted_) + os << ConfigParser::QuoteString(SBufToString(buf)); + else + os << buf; + return os; +} + +void +ProxyProtocol::Option::parseFormat(const char *value) +{ + if (Format::Dash.cmp(value) == 0) + return; + Assure(!value_); + auto format = std::unique_ptr(new Format::Format(name_.c_str())); + if (!format->parse(value)) { + throw TextException(ToSBuf("failed to parse value ", value), Here()); + } + value_ = format.release(); +} + +SBuf +ProxyProtocol::Option::assembleValue(const AccessLogEntryPointer &al) const +{ + Assure(value_); + static MemBuf mb; + mb.reset(); + value_->assemble(mb, al, 0); + return SBuf(mb.content()); +} + +ProxyProtocol::AddrOption::AddrOption(const char *aName, ConfigParser &parser) : Option(aName, parser) +{ + if (value_ && !value_->isConstant()) { + const auto formattedValue = assembleValue(AccessLogEntryPointer()); + address_ = parseAddr(formattedValue); + } +} + +std::optional +ProxyProtocol::AddrOption::parseAddr(const SBuf &val) const +{ + const auto addr = Ip::Address::Parse(SBuf(val).c_str()); + if (!addr) + throw TextException(ToSBuf("Cannot parse '", val, "' as ", name_), Here()); + return addr; +} + +static std::nullopt_t +FormatFailure(const SBuf &what) +{ + debugs(17, DBG_IMPORTANT, "WARNING: could not process logformat for " << what << + Debug::Extra << "problem: " << CurrentException); + return std::nullopt; +} + +ProxyProtocol::AddrOption::Addr +ProxyProtocol::AddrOption::address(const AccessLogEntryPointer &al) const +{ + if(address_) + return address_; + try + { + if (!value_) + return std::nullopt; + const auto formattedValue = assembleValue(al); + return parseAddr(formattedValue); + } catch (...) { + return FormatFailure(name_); + } +} + +ProxyProtocol::PortOption::PortOption(const char *aName, ConfigParser &parser) : Option(aName, parser) +{ + if (value_ && !value_->isConstant()) { + const auto formattedValue = assembleValue(AccessLogEntryPointer()); + port_ = parsePort(formattedValue); + } +} + +uint16_t +ProxyProtocol::PortOption::parsePort(const SBuf &val) const +{ + Parser::Tokenizer tok(val); + const auto portMax = std::numeric_limits::max(); + int64_t p = -1; + if (!tok.int64(p, 10, false) || !tok.atEnd() || p > portMax) + throw TextException(ToSBuf("Cannot parse '", val, "' as ", name_, ". Expect an unsigned integer less than ", portMax), Here()); + return p; +} + +ProxyProtocol::PortOption::Port +ProxyProtocol::PortOption::port(const AccessLogEntryPointer &al) const +{ + if(port_) + return *port_; + try + { + if (!value_) + return std::nullopt; + const auto formattedValue = assembleValue(al); + return parsePort(formattedValue); + } catch (...) { + return FormatFailure(name_); + } +} + +ProxyProtocol::TlvOption::TlvOption(const char *aName, const char *aValue, const bool quoted) : Option(aName, aValue, quoted) +{ + const TlvType typeMin = 0xe0; + const TlvType typeMax = 0xef; + + int64_t t = -1; + Parser::Tokenizer tok(name_); + if (!tok.int64(t, 0, false) || (t < typeMin || t > typeMax)) + throw TextException(ToSBuf("Expected tlv type as a decimal or hex number in the [0xE0, 0xEF] range but got ", name_), Here()); + tlvType_ = static_cast(t); + + if (!value_) + tlvValue_ = Format::Dash; + if (!value_->isConstant()) + tlvValue_ = assembleValue(AccessLogEntryPointer()); +} + +ProxyProtocol::TlvOption::TlvValue +ProxyProtocol::TlvOption::tlvValue(const AccessLogEntryPointer &al) const +{ + if(tlvValue_) + return *tlvValue_; + try + { + const auto formatted = assembleValue(al); + const auto max = std::numeric_limits::max(); + if (formatted.length() > max) + throw TextException(ToSBuf("Expected tlv value size less than ", max, " but got ", formatted.length(), " bytes"), Here()); + return TlvValue(assembleValue(al)); + } catch (...) { + return FormatFailure(name_); + } +} + +ProxyProtocol::OutgoingHttpConfig::OutgoingHttpConfig(ConfigParser &parser) +{ + parseOptions(parser); + aclList = parser.optionalAclList(); +} + +void +ProxyProtocol::OutgoingHttpConfig::dump(std::ostream &os) +{ + const auto separator = " "; + os << *srcAddr << separator << *dstAddr << separator << srcPort << separator << dstPort << + AsList(tlvOptions).prefixedBy(separator).delimitedBy(separator); + if (aclList) { + os << separator; + // TODO: Use Acl::dump() after fixing the XXX in dump_acl_list(). + for (const auto &acl: ToTree(aclList).treeDump("if", &Acl::AllowOrDeny)) + os << ' ' << acl; + } +} + +void +ProxyProtocol::OutgoingHttpConfig::fill(ProxyProtocol::Header &header, const AccessLogEntryPointer &al) +{ + fillAddresses(header.sourceAddress, header.destinationAddress, al); + fillTlvs(header.tlvs, al); +} + +void +ProxyProtocol::OutgoingHttpConfig::fillAddresses(Ip::Address &src, Ip::Address &dst, const AccessLogEntryPointer &al) +{ + if (const auto err = adjustAddresses(src,dst, al)) + debugs(17, DBG_IMPORTANT, *err); + src.port(srcPort->port(al).value_or(0)); + dst.port(dstPort->port(al).value_or(0)); +} + +void +ProxyProtocol::OutgoingHttpConfig::fillTlvs(Tlvs &tlvs, const AccessLogEntryPointer &al) const +{ + for (const auto &t : tlvOptions) { + if (const auto v = t->tlvValue(al)) + tlvs.emplace_back(t->tlvType(), *v); + } +} + +/// converts the configured src_addr/dst_addr pair (having maybe unknown addresses or +/// addresses with mismatching families) into a pair of addresses with matching families. +/// \returns an error message if encountered a mismatching address family, or nullopt +std::optional +ProxyProtocol::OutgoingHttpConfig::adjustAddresses(Ip::Address &adjustedSrc, Ip::Address &adjustedDst, const AccessLogEntryPointer &al) +{ + auto src = srcAddr->address(al); + auto dst = dstAddr->address(al); + + // source and/or destination are unknown + // either configured as "-" or could not parse format codes + if (!src && !dst) { + // IPv4 by default + adjustedSrc = Ip::Address::AnyAddrIPv4(); + adjustedDst = Ip::Address::AnyAddrIPv4(); + return std::nullopt; + } else if (!src) { + adjustedSrc = dst->isIPv4() ? Ip::Address::AnyAddrIPv4() : Ip::Address::AnyAddrIPv6(); + adjustedDst = *dst; + return std::nullopt; + } else if (!dst) { + adjustedSrc = *src; + adjustedDst = src->isIPv4() ? Ip::Address::AnyAddrIPv4() : Ip::Address::AnyAddrIPv6(); + return std::nullopt; + } + + // source and destination are known + + // source and destination have the same address family + if (src->isIPv4() == dst->isIPv4()) { + adjustedSrc = *src; + adjustedDst = *dst; + return std::nullopt; + } + + // source and destination have different address family + if (src->isAnyAddr() && !dst->isAnyAddr()) { + adjustedSrc = dst->isIPv4() ? Ip::Address::AnyAddrIPv4() : Ip::Address::AnyAddrIPv6(); + adjustedDst = *dst; + } else { + adjustedSrc = *src; + adjustedDst = src->isIPv4() ? Ip::Address::AnyAddrIPv4() : Ip::Address::AnyAddrIPv6(); + } + + return ToSBuf("Address family mismatch: ", srcAddr->name_, "(", *src, ") and ", dstAddr->name_, "(", *dst, ")"); +} + +void +ProxyProtocol::OutgoingHttpConfig::parseOptions(ConfigParser &parser) +{ + // required options + srcAddr = new AddrOption("src_addr", parser); + dstAddr = new AddrOption("dst_addr", parser); + srcPort = new PortOption("src_port", parser); + dstPort = new PortOption("dst_port", parser); + + if (srcAddr->hasAddress() && dstAddr->hasAddress()) { + Ip::Address adjustedSrc, adjustedDst; + if (const auto err = adjustAddresses(adjustedSrc, adjustedDst, AccessLogEntryPointer())) + throw TextException(*err, Here()); + srcAddr->setAddress(adjustedSrc); + dstAddr->setAddress(adjustedDst); + } + + char *key = nullptr; + char *value = nullptr; + + // optional TLVs + std::vector< std::pair > parsedTlvs; // temporary storage for duplication checks + while (parser.optionalKvPair(key, value)) { + auto option = std::unique_ptr(new TlvOption(key, value, parser.LastTokenWasQuoted())); + const auto it = std::find_if(parsedTlvs.begin(), parsedTlvs.end(), [&](const auto &p) { + return p.first == option->tlvType() && p.second == SBuf(value); + }); + if (it != parsedTlvs.end()) { + throw TextException(ToSBuf("duplicate TLV option: ", key, "=", value), Here()); + } + parsedTlvs.emplace_back(option->tlvType(), value); + tlvOptions.push_back(option.release()); + } +} + +namespace Configuration { + +template <> +ProxyProtocol::OutgoingHttpConfig * +Configuration::Component::Parse(ConfigParser &parser) +{ + return new ProxyProtocol::OutgoingHttpConfig(parser); +} + +template <> +void +Configuration::Component::Print(std::ostream &os, ProxyProtocol::OutgoingHttpConfig* const & cfg) +{ + assert(cfg); + cfg->dump(os); +} + +template <> +void +Configuration::Component::Free(ProxyProtocol::OutgoingHttpConfig * const cfg) +{ + delete cfg; +} + +} // namespace Configuration + diff --git a/src/proxyp/OutgoingHttpConfig.h b/src/proxyp/OutgoingHttpConfig.h new file mode 100644 index 00000000000..96399ea5b03 --- /dev/null +++ b/src/proxyp/OutgoingHttpConfig.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 1996-2025 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_OUTGOINGHTTPCONFIG_H +#define SQUID_SRC_PROXYP_OUTGOINGHTTPCONFIG_H + +#include "acl/forward.h" +#include "format/forward.h" +#include "ip/Address.h" +#include "log/forward.h" +#include "proxyp/Elements.h" +#include "proxyp/forward.h" + +#include +#include + +class ConfigParser; + +namespace ProxyProtocol { + +/// a name=value option for the http_outgoing_proxy_protocol directive +class Option : public RefCountable +{ +public: + Option(const char *aName, ConfigParser &); + Option(const char *aName, const char *aVal, bool quoted); + virtual ~Option(); + + void dump(std::ostream &); + + SBuf name_; ///< the option name + const bool quoted_; ///< whether the option value is quoted + +protected: + /// \returns the value with expanded logformat %macros (quoted values) + SBuf assembleValue(const AccessLogEntryPointer &al) const; + + Format::Format *value_; ///< compiled value format + + friend std::ostream & operator << (std::ostream &, const Option &); + +private: + /// parses the value as a logformat string + void parseFormat(const char *); +}; + +std::ostream & operator << (std::ostream &, const Option &); + +/// an address option for http_outgoing_proxy_protocol directive +class AddrOption : public Option +{ +public: + using Pointer = RefCount; + using Addr = std::optional; + + AddrOption(const char *aName, ConfigParser &); + + Addr address(const AccessLogEntryPointer &al) const; + bool hasAddress() const { return address_.has_value(); } + void setAddress(const Ip::Address &addr) { address_ = addr; } + +protected: + std::optional parseAddr(const SBuf &) const; + + Addr address_; ///< transaction-independent source or destination address +}; + +/// a port option for http_outgoing_proxy_protocol directive +class PortOption : public Option +{ +public: + using Pointer = RefCount; + using Port = std::optional; + + PortOption(const char *aName, ConfigParser &); + + Port port(const AccessLogEntryPointer &al) const; + +protected: + uint16_t parsePort(const SBuf &val) const; + + Port port_; ///< transaction-independent source or destination address port +}; + +/// a TLV option for http_outgoing_proxy_protocol directive +class TlvOption : public Option +{ +public: + using Pointer = RefCount; + using TlvType = uint8_t; + using TlvValue = std::optional; + + TlvOption(const char *aName, const char *aVal, bool quoted); + + TlvValue tlvValue(const AccessLogEntryPointer &al) const; + TlvType tlvType() const { return tlvType_; } + +protected: + TlvType tlvType_; + TlvValue tlvValue_; ///< transaction-independent TLV value +}; + +/// an http_outgoing_proxy_protocol directive configuration +class OutgoingHttpConfig +{ +public: + using Tlvs = std::vector; + + explicit OutgoingHttpConfig(ConfigParser &); + + void dump(std::ostream &); + + void fill(ProxyProtocol::Header &header, const AccessLogEntryPointer &); + + /// restrict logging to matching transactions + ACLList *aclList = nullptr; + +private: + void parseOptions(ConfigParser &); + void fillAddresses(Ip::Address &src, Ip::Address &dst, const AccessLogEntryPointer &); + void fillTlvs(Tlvs &, const AccessLogEntryPointer &) const; + + void parseAddress(const char *optionName); + void parsePort(const char *optionName); + std::optional adjustAddresses(Ip::Address &adjustedSrc, Ip::Address &adjustedDst, const AccessLogEntryPointer &al); + + AddrOption::Pointer srcAddr; + AddrOption::Pointer dstAddr; + PortOption::Pointer srcPort; + PortOption::Pointer dstPort; + + using TlvOptions = std::vector; + TlvOptions tlvOptions; // the list TLVs +}; + +} // namespace ProxyProtocol + +#endif /* SQUID_SRC_PROXYP_OUTGOINGHTTPCONFIG_H */ + diff --git a/src/proxyp/Parser.cc b/src/proxyp/Parser.cc index 4ba40f206b3..3316f3a902b 100644 --- a/src/proxyp/Parser.cc +++ b/src/proxyp/Parser.cc @@ -29,13 +29,6 @@ namespace ProxyProtocol { namespace One { -/// magic octet prefix for PROXY protocol version 1 -static const auto & -Magic() -{ - static const auto magic = new SBuf("PROXY", 5); - return *magic; -} /// extracts PROXY protocol v1 header from the given buffer static Parsed Parse(const SBuf &buf); @@ -45,13 +38,6 @@ static void ParseAddresses(Parser::Tokenizer &tok, Header::Pointer &header); } namespace Two { -/// magic octet prefix for PROXY protocol version 2 -static const auto & -Magic() -{ - static const auto magic = new SBuf("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12); - return *magic; -} /// extracts PROXY protocol v2 header from the given buffer static Parsed Parse(const SBuf &buf); diff --git a/src/proxyp/forward.h b/src/proxyp/forward.h index 20e2290b561..151300ab8d6 100644 --- a/src/proxyp/forward.h +++ b/src/proxyp/forward.h @@ -15,6 +15,7 @@ namespace ProxyProtocol { class Header; +class OutgoingHttpConfig; typedef RefCount
HeaderPointer; diff --git a/test-suite/squidconf/outgoing-proxy-protocol-family-mismatch.conf b/test-suite/squidconf/outgoing-proxy-protocol-family-mismatch.conf new file mode 100644 index 00000000000..95057ec424c --- /dev/null +++ b/test-suite/squidconf/outgoing-proxy-protocol-family-mismatch.conf @@ -0,0 +1,12 @@ +## Copyright (C) 1996-2025 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. +## + + +acl A src 127.0.0.1 + +http_outgoing_proxy_protocol src_addr="127.0.0.1" dst_addr="::1" src_port="4444" dst_port="5555" 0xE0="%proxy_protocol::>h{224}" 0xE1="%note{myConnectionTag_}" if A + diff --git a/test-suite/squidconf/outgoing-proxy-protocol-family-mismatch.conf.instructions b/test-suite/squidconf/outgoing-proxy-protocol-family-mismatch.conf.instructions new file mode 100644 index 00000000000..c40d68011f1 --- /dev/null +++ b/test-suite/squidconf/outgoing-proxy-protocol-family-mismatch.conf.instructions @@ -0,0 +1,2 @@ +expect-failure ERROR:.configuration.failure:.Address.family.mismatch +