From 1c2b4465dbca5ae7ffacfb6150bc2e08c1a4a64f Mon Sep 17 00:00:00 2001 From: Christos Tsantilas Date: Fri, 29 May 2020 21:26:08 +0000 Subject: [PATCH] Allow upgrading from HTTP/1.1 to other protocols (#481) Support admin-authorized HTTP Upgrade-driven (RFC 7230 Section 6.7) protocol switching. The new http_upgrade_request_protocols configuration directive allows admin to control what client Upgrade offer(s) are forwarded to the server. Squid does not automatically check whether the server selection matches one of the client offers, but the admin can deny unacceptable server responses using http_reply_access. By default, Squid still does not forward any Upgrade headers, effectively blocking an upgrade attempt. Squid itself does not understand the protocols being upgraded to and participates in the upgraded communication only as a dumb TCP proxy. This is a Measurement Factory project. --- src/HttpHeaderTools.cc | 5 + src/HttpUpgradeProtocolAccess.cc | 80 +++++++++ src/HttpUpgradeProtocolAccess.h | 118 ++++++++++++++ src/Makefile.am | 17 +- src/SquidConfig.h | 3 + src/StrList.cc | 35 ++-- src/StrList.h | 13 ++ src/acl/Acl.h | 11 +- src/acl/Checklist.cc | 1 + src/adaptation/icap/ModXact.cc | 7 + src/cache_cf.cc | 38 +++++ src/cf.data.depend | 1 + src/cf.data.pre | 97 +++++++++++ src/client_side.cc | 6 + src/client_side.h | 20 +++ src/comm/Connection.cc | 10 +- src/comm/Connection.h | 7 + src/http.cc | 169 ++++++++++++++++++-- src/http.h | 7 + src/http/StateFlags.h | 10 +- src/servers/Http1Server.cc | 44 ++++- src/servers/Http1Server.h | 2 + src/ssl/PeekingPeerConnector.cc | 22 ++- src/ssl/PeekingPeerConnector.h | 3 + src/tests/stub_HttpUpgradeProtocolAccess.cc | 25 +++ src/tests/stub_cache_cf.cc | 1 + src/tests/stub_tunnel.cc | 2 +- src/tunnel.cc | 26 ++- 28 files changed, 724 insertions(+), 56 deletions(-) create mode 100644 src/HttpUpgradeProtocolAccess.cc create mode 100644 src/HttpUpgradeProtocolAccess.h create mode 100644 src/tests/stub_HttpUpgradeProtocolAccess.cc diff --git a/src/HttpHeaderTools.cc b/src/HttpHeaderTools.cc index 1cc1776333..1ea6bc4ff4 100644 --- a/src/HttpHeaderTools.cc +++ b/src/HttpHeaderTools.cc @@ -295,6 +295,10 @@ httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, HeaderManglers *hms, c HTTPMSGLOCK(checklist.reply); } + // XXX: The two "It was denied" clauses below mishandle cases with no + // matching rules, violating the "If no rules within the set have matching + // ACLs, the header field is left as is" promise in squid.conf. + // TODO: Use Acl::Answer::implicit. See HttpStateData::forwardUpgrade(). if (checklist.fastCheck().allowed()) { /* aclCheckFast returns true for allow. */ debugs(66, 7, "checklist for mangler is positive. Mangle"); @@ -302,6 +306,7 @@ httpHdrMangle(HttpHeaderEntry * e, HttpRequest * request, HeaderManglers *hms, c } else if (NULL == hm->replacement) { /* It was denied, and we don't have any replacement */ debugs(66, 7, "checklist denied, we have no replacement. Pass"); + // XXX: We said "Pass", but the caller will delete on zero retval. retval = 0; } else { /* It was denied, but we have a replacement. Replace the diff --git a/src/HttpUpgradeProtocolAccess.cc b/src/HttpUpgradeProtocolAccess.cc new file mode 100644 index 0000000000..212119a9ce --- /dev/null +++ b/src/HttpUpgradeProtocolAccess.cc @@ -0,0 +1,80 @@ +/* + * Copyright (C) 1996-2019 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/Acl.h" +#include "acl/Gadgets.h" +#include "ConfigParser.h" +#include "globals.h" +#include "HttpUpgradeProtocolAccess.h" +#include "sbuf/Stream.h" + +#include + +const SBuf HttpUpgradeProtocolAccess::ProtoOther("OTHER"); + +ProtocolView::ProtocolView(const char * const start, const size_t len): + ProtocolView(SBuf(start, len)) +{ +} + +ProtocolView::ProtocolView(const SBuf &proto): + name(proto.substr(0, proto.find('/'))), + version(proto.substr(name.length())) +{ +} + +std::ostream & +operator <<(std::ostream &os, const ProtocolView &view) +{ + os << view.name; + if (!view.version.isEmpty()) + os << view.version; + return os; +} + +/* HttpUpgradeProtocolAccess */ + +HttpUpgradeProtocolAccess::~HttpUpgradeProtocolAccess() +{ + aclDestroyAccessList(&other); +} + +void +HttpUpgradeProtocolAccess::configureGuard(ConfigParser &parser) +{ + const auto rawProto = parser.NextToken(); + if (!rawProto) + throw TextException(ToSBuf("expected a protocol name or ", ProtoOther), Here()); + + if (ProtoOther.cmp(rawProto) == 0) { + aclParseAccessLine(cfg_directive, parser, &other); + return; + } + + // To preserve ACL rules checking order, to exclude inapplicable (i.e. wrong + // protocol version) rules, and to keep things simple, we merge no rules. + acl_access *access = nullptr; + aclParseAccessLine(cfg_directive, parser, &access); + if (access) + namedGuards.emplace_back(rawProto, access); +} + +/* HttpUpgradeProtocolAccess::NamedGuard */ + +HttpUpgradeProtocolAccess::NamedGuard::NamedGuard(const char *rawProtocol, acl_access *acls): + protocol(rawProtocol), + proto(protocol), + guard(acls) +{ +} + +HttpUpgradeProtocolAccess::NamedGuard::~NamedGuard() { + aclDestroyAccessList(&guard); +} + diff --git a/src/HttpUpgradeProtocolAccess.h b/src/HttpUpgradeProtocolAccess.h new file mode 100644 index 0000000000..3721c7256b --- /dev/null +++ b/src/HttpUpgradeProtocolAccess.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 1996-2019 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_HTTP_UPGRADE_H +#define SQUID_HTTP_UPGRADE_H + +#include "acl/forward.h" +#include "sbuf/SBuf.h" + +#include + +/// a reference to a protocol name[/version] string; no 0-termination is assumed +class ProtocolView +{ +public: + ProtocolView(const char * const start, const size_t len); + explicit ProtocolView(const SBuf &proto); + + SBuf name; ///< everything up to (but excluding) the first slash('/') + SBuf version; ///< everything after the name, including the slash('/') +}; + +std::ostream &operator <<(std::ostream &, const ProtocolView &); + +// HTTP is not explicit about case sensitivity of Upgrade protocol strings, but +// there are bug reports showing different case variants used for WebSocket. We +// conservatively preserve the received case and compare case-sensitively. + +/// Either b has no version restrictions or both have the same version. +/// For example, "ws/1" is in "ws" but "ws" is not in "ws/1". +inline bool +vAinB(const ProtocolView &a, const ProtocolView &b) +{ + // Optimization: Do not assert(a.name == b.name). + return b.version.isEmpty() || (a.version == b.version); +} + +/// Allows or blocks HTTP Upgrade protocols (see http_upgrade_request_protocols) +class HttpUpgradeProtocolAccess +{ +public: + HttpUpgradeProtocolAccess() = default; + ~HttpUpgradeProtocolAccess(); + HttpUpgradeProtocolAccess(HttpUpgradeProtocolAccess &&) = delete; // no copying of any kind + + /// \returns the ACLs matching the given "name[/version]" protocol (or nil) + const acl_access *findGuard(const SBuf &proto) const; + + /// parses a single allow/deny rule + void configureGuard(ConfigParser&); + + /// iterates over all configured rules, calling the given visitor + template inline void forEach(const Visitor &) const; + + /// iterates over rules applicable to the given protocol, calling visitor; + /// breaks iteration if the visitor returns true + template inline void forApplicable(const ProtocolView &, const Visitor &) const; + +private: + /// a single configured access rule for an explicitly named protocol + class NamedGuard + { + public: + NamedGuard(const char *rawProtocol, acl_access*); + NamedGuard(const NamedGuard &&) = delete; // no copying of any kind + ~NamedGuard(); + + const SBuf protocol; ///< configured protocol name (and version) + const ProtocolView proto; ///< optimization: compiled this->protocol + acl_access *guard = nullptr; ///< configured access rule; never nil + }; + + /// maps HTTP Upgrade protocol name/version to the ACLs guarding its usage + typedef std::deque NamedGuards; + + /// pseudonym to specify rules for "all other protocols" + static const SBuf ProtoOther; + + /// rules governing upgrades to explicitly named protocols + NamedGuards namedGuards; + + /// OTHER rules governing unnamed protocols + acl_access *other = nullptr; +}; + +template +inline void +HttpUpgradeProtocolAccess::forEach(const Visitor &visitor) const +{ + for (const auto &namedGuard: namedGuards) + visitor(namedGuard.protocol, namedGuard.guard); + if (other) + visitor(ProtoOther, other); +} + +template +inline void +HttpUpgradeProtocolAccess::forApplicable(const ProtocolView &offer, const Visitor &visitor) const +{ + auto seenApplicable = false; + for (const auto &namedGuard: namedGuards) { + if (offer.name != namedGuard.proto.name) + continue; + if (vAinB(offer, namedGuard.proto) && visitor(namedGuard.protocol, namedGuard.guard)) + return; + seenApplicable = true; // may already be true + } + if (!seenApplicable && other) // OTHER is applicable if named rules were not + (void)visitor(ProtoOther, other); +} + +#endif /* SQUID_HTTP_UPGRADE_H */ + diff --git a/src/Makefile.am b/src/Makefile.am index cda8aa8415..c3ed4d642d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -319,6 +319,8 @@ squid_SOURCES = \ hier_code.h \ HierarchyLogEntry.h \ $(HTCPSOURCE) \ + HttpUpgradeProtocolAccess.cc \ + HttpUpgradeProtocolAccess.h \ http.cc \ http.h \ HttpHeaderFieldStat.h \ @@ -1859,7 +1861,6 @@ tests_test_http_range_SOURCES = \ cache_cf.h \ AuthReg.h \ RefreshPattern.h \ - cache_cf.cc \ CachePeer.cc \ CachePeer.h \ cache_manager.cc \ @@ -2017,7 +2018,9 @@ tests_test_http_range_SOURCES = \ tests/stub_libstore.cc \ Transients.cc \ tests/test_http_range.cc \ + tests/stub_cache_cf.cc \ tests/stub_external_acl.cc \ + tests/stub_HttpUpgradeProtocolAccess.cc \ tests/stub_ipc_Forwarder.cc \ tests/stub_libdiskio.cc \ tests/stub_libeui.cc \ @@ -2262,7 +2265,6 @@ tests_testHttpRequest_SOURCES = \ cache_cf.h \ AuthReg.h \ RefreshPattern.h \ - cache_cf.cc \ debug.cc \ CacheDigest.h \ tests/stub_CacheDigest.cc \ @@ -2414,6 +2416,8 @@ tests_testHttpRequest_SOURCES = \ tools.h \ tools.cc \ Transients.cc \ + tests/stub_cache_cf.cc \ + tests/stub_HttpUpgradeProtocolAccess.cc \ tests/stub_tunnel.cc \ tests/stub_libstore.cc \ MemStore.cc \ @@ -2560,7 +2564,6 @@ tests_testCacheManager_SOURCES = \ cache_cf.h \ AuthReg.h \ RefreshPattern.h \ - cache_cf.cc \ CachePeer.cc \ CachePeer.h \ CacheDigest.h \ @@ -2714,6 +2717,8 @@ tests_testCacheManager_SOURCES = \ tools.h \ tools.cc \ Transients.cc \ + tests/stub_cache_cf.cc \ + tests/stub_HttpUpgradeProtocolAccess.cc \ tests/stub_tunnel.cc \ tests/stub_libstore.cc \ MemStore.cc \ @@ -2866,7 +2871,6 @@ tests_testEvent_SOURCES = \ cache_cf.h \ AuthReg.h \ RefreshPattern.h \ - cache_cf.cc \ CachePeer.cc \ CachePeer.h \ cache_manager.cc \ @@ -3023,6 +3027,8 @@ tests_testEvent_SOURCES = \ StoreMetaUnpacker.cc \ StoreSwapLogData.cc \ String.cc \ + tests/stub_cache_cf.cc \ + tests/stub_HttpUpgradeProtocolAccess.cc \ tests/stub_libstore.cc \ tests/CapturingStoreEntry.h \ tests/testEvent.cc \ @@ -3105,7 +3111,6 @@ tests_testEventLoop_SOURCES = \ cache_cf.h \ AuthReg.h \ RefreshPattern.h \ - cache_cf.cc \ CachePeer.cc \ CachePeer.h \ carp.h \ @@ -3260,6 +3265,8 @@ tests_testEventLoop_SOURCES = \ String.cc \ StrList.h \ StrList.cc \ + tests/stub_cache_cf.cc \ + tests/stub_HttpUpgradeProtocolAccess.cc \ tests/stub_libstore.cc \ tests/testEventLoop.cc \ tests/testEventLoop.h \ diff --git a/src/SquidConfig.h b/src/SquidConfig.h index 061b2b194b..e5a5f8e2e2 100644 --- a/src/SquidConfig.h +++ b/src/SquidConfig.h @@ -49,6 +49,7 @@ class external_acl; class HeaderManglers; class RefreshPattern; class RemovalPolicySettings; +class HttpUpgradeProtocolAccess; namespace AnyP { @@ -478,6 +479,8 @@ public: HeaderWithAclList *request_header_add; ///reply_header_add access list HeaderWithAclList *reply_header_add; + /// http_upgrade_request_protocols + HttpUpgradeProtocolAccess *http_upgrade_request_protocols; ///note Notes notes; char *coredump_dir; diff --git a/src/StrList.cc b/src/StrList.cc index 1dba00f3f4..62997b718a 100644 --- a/src/StrList.cc +++ b/src/StrList.cc @@ -14,22 +14,31 @@ #include "SquidString.h" #include "StrList.h" -/** appends an item to the list */ void -strListAdd(String * str, const char *item, char del) +strListAdd(String &str, const char *item, const size_t itemSize, const char delimiter) { - assert(str && item); - const auto itemSize = strlen(item); - if (str->size()) { - char buf[3]; - buf[0] = del; - buf[1] = ' '; - buf[2] = '\0'; - Must(str->canGrowBy(2)); - str->append(buf, 2); + if (str.size()) { + const char buf[] = { delimiter, ' ' }; + const auto bufSize = sizeof(buf); + Must(str.canGrowBy(bufSize)); + str.append(buf, bufSize); } - Must(str->canGrowBy(itemSize)); - str->append(item, itemSize); + Must(str.canGrowBy(itemSize)); + str.append(item, itemSize); +} + +void +strListAdd(String *str, const char *item, const char delimiter) +{ + assert(str); + assert(item); + strListAdd(*str, item, strlen(item), delimiter); +} + +void +strListAdd(String &str, const SBuf &item, char delimiter) +{ + strListAdd(str, item.rawContent(), item.length(), delimiter); } /** returns true iff "m" is a member of the list */ diff --git a/src/StrList.h b/src/StrList.h index 3fad8af884..d0476815eb 100644 --- a/src/StrList.h +++ b/src/StrList.h @@ -13,11 +13,24 @@ #include "sbuf/forward.h" +#include + class String; +/// Appends the given item to a delimiter-separated list in str. void strListAdd(String * str, const char *item, char del); + +/// Appends the given item of a given size to a delimiter-separated list in str. +void strListAdd(String &str, const char *item, const size_t itemSize, const char del = ','); + +/// Appends the given item to a delimiter-separated list in str. +/// Use strListAdd(c-string) for c-string items with unknown length. +void strListAdd(String &str, const SBuf &item, char delimiter = ','); + int strListIsMember(const String * str, const SBuf &item, char del); int strListIsSubstr(const String * list, const char *s, char del); +/// Iterates through delimiter-separated and optionally "quoted" list members. +/// Follows HTTP #rule, including skipping OWS and empty members. 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. diff --git a/src/acl/Acl.h b/src/acl/Acl.h index 3e73e022f8..8dee1d56c2 100644 --- a/src/acl/Acl.h +++ b/src/acl/Acl.h @@ -118,7 +118,7 @@ public: // not explicit: allow "aclMatchCode to Acl::Answer" conversions (for now) Answer(const aclMatchCode aCode, int aKind = 0): code(aCode), kind(aKind) {} - Answer(): code(ACCESS_DUNNO), kind(0) {} + Answer() = default; bool operator ==(const aclMatchCode aCode) const { return code == aCode; @@ -151,8 +151,13 @@ public: /// whether Squid is uncertain about the allowed() or denied() answer bool conflicted() const { return !allowed() && !denied(); } - aclMatchCode code; ///< ACCESS_* code - int kind; ///< which custom access list verb matched + aclMatchCode code = ACCESS_DUNNO; ///< ACCESS_* code + + /// the matched custom access list verb (or zero) + int kind = 0; + + /// whether we were computed by the "negate the last explicit action" rule + bool implicit = false; }; } // namespace Acl diff --git a/src/acl/Checklist.cc b/src/acl/Checklist.cc index 976d329bd4..90576cd509 100644 --- a/src/acl/Checklist.cc +++ b/src/acl/Checklist.cc @@ -379,6 +379,7 @@ ACLChecklist::calcImplicitAnswer() implicitRuleAnswer = Acl::Answer(ACCESS_DENIED); // else we saw no rules and will respond with ACCESS_DUNNO + implicitRuleAnswer.implicit = true; debugs(28, 3, HERE << this << " NO match found, last action " << lastAction << " so returning " << implicitRuleAnswer); markFinished(implicitRuleAnswer, "implicit rule won"); diff --git a/src/adaptation/icap/ModXact.cc b/src/adaptation/icap/ModXact.cc index a4b22e22d2..a73f32c547 100644 --- a/src/adaptation/icap/ModXact.cc +++ b/src/adaptation/icap/ModXact.cc @@ -1611,6 +1611,13 @@ Adaptation::Icap::ModXact::encapsulateHead(MemBuf &icapBuf, const char *section, headClone->header.delById(Http::HdrType::PROXY_AUTHENTICATE); headClone->header.removeHopByHopEntries(); + // TODO: modify HttpHeader::removeHopByHopEntries to accept a list of + // excluded hop-by-hop headers + if (head->header.has(Http::HdrType::UPGRADE)) { + const auto upgrade = head->header.getList(Http::HdrType::UPGRADE); + headClone->header.putStr(Http::HdrType::UPGRADE, upgrade.termedBuf()); + } + // pack polished HTTP header packHead(httpBuf, headClone.getRaw()); diff --git a/src/cache_cf.cc b/src/cache_cf.cc index 76dbe74be8..87b898209b 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -33,6 +33,7 @@ #include "ftp/Elements.h" #include "globals.h" #include "HttpHeaderTools.h" +#include "HttpUpgradeProtocolAccess.h" #include "icmp/IcmpConfig.h" #include "ident/Config.h" #include "ip/Intercept.h" @@ -251,6 +252,9 @@ static void parse_on_unsupported_protocol(acl_access **access); static void dump_on_unsupported_protocol(StoreEntry *entry, const char *name, acl_access *access); static void free_on_unsupported_protocol(acl_access **access); static void ParseAclWithAction(acl_access **access, const Acl::Answer &action, const char *desc, ACL *acl = nullptr); +static void parse_http_upgrade_request_protocols(HttpUpgradeProtocolAccess **protoGuards); +static void dump_http_upgrade_request_protocols(StoreEntry *entry, const char *name, HttpUpgradeProtocolAccess *protoGuards); +static void free_http_upgrade_request_protocols(HttpUpgradeProtocolAccess **protoGuards); /* * LegacyParser is a parser for legacy code that uses the global @@ -5110,3 +5114,37 @@ free_on_unsupported_protocol(acl_access **access) free_acl_access(access); } +static void +parse_http_upgrade_request_protocols(HttpUpgradeProtocolAccess **protoGuardsPtr) +{ + assert(protoGuardsPtr); + auto &protoGuards = *protoGuardsPtr; + if (!protoGuards) + protoGuards = new HttpUpgradeProtocolAccess(); + protoGuards->configureGuard(LegacyParser); +} + +static void +dump_http_upgrade_request_protocols(StoreEntry *entry, const char *rawName, HttpUpgradeProtocolAccess *protoGuards) +{ + assert(protoGuards); + const SBuf name(rawName); + protoGuards->forEach([entry,&name](const SBuf &proto, const acl_access *acls) { + SBufList line; + line.push_back(name); + line.push_back(proto); + const auto acld = acls->treeDump("", &Acl::AllowOrDeny); + line.insert(line.end(), acld.begin(), acld.end()); + dump_SBufList(entry, line); + }); +} + +static void +free_http_upgrade_request_protocols(HttpUpgradeProtocolAccess **protoGuardsPtr) +{ + assert(protoGuardsPtr); + auto &protoGuards = *protoGuardsPtr; + delete protoGuards; + protoGuards = nullptr; +} + diff --git a/src/cf.data.depend b/src/cf.data.depend index f768a7a7bd..8107880182 100644 --- a/src/cf.data.depend +++ b/src/cf.data.depend @@ -43,6 +43,7 @@ hostdomain cache_peer hostdomaintype cache_peer http_header_access acl http_header_replace +http_upgrade_request_protocols acl HeaderWithAclList acl adaptation_access_type adaptation_service_set adaptation_service_chain acl icap_service icap_class adaptation_service_set_type icap_service ecap_service diff --git a/src/cf.data.pre b/src/cf.data.pre index 579efa728a..7a706969d3 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -10524,6 +10524,103 @@ DOC_START that the request body is needed. Delaying is the default behavior. DOC_END +NAME: http_upgrade_request_protocols +TYPE: http_upgrade_request_protocols +LOC: Config.http_upgrade_request_protocols +DEFAULT: none +DEFAULT_DOC: Upgrade header dropped, effectively blocking an upgrade attempt. +DOC_START + Controls client-initiated and server-confirmed switching from HTTP to + another protocol (or to several protocols) using HTTP Upgrade mechanism + defined in RFC 7230 Section 6.7. Squid itself does not understand the + protocols being upgraded to and participates in the upgraded + communication only as a dumb TCP proxy. Admins should not allow + upgrading to protocols that require a more meaningful proxy + participation. + + Usage: http_upgrade_request_protocols allow|deny [!]acl ... + + The required "protocol" parameter is either an all-caps word OTHER or an + explicit protocol name (e.g. "WebSocket") optionally followed by a slash + and a version token (e.g. "HTTP/3"). Explicit protocol names and + versions are case sensitive. + + When an HTTP client sends an Upgrade request header, Squid iterates over + the client-offered protocols and, for each protocol P (with an optional + version V), evaluates the first non-empty set of + http_upgrade_request_protocols rules (if any) from the following list: + + * All rules with an explicit protocol name equal to P. + * All rules that use OTHER instead of a protocol name. + + In other words, rules using OTHER are considered for protocol P if and + only if there are no rules mentioning P by name. + + If both of the above sets are empty, then Squid removes protocol P from + the Upgrade offer. + + If the client sent a versioned protocol offer P/X, then explicit rules + referring to the same-name but different-version protocol P/Y are + declared inapplicable. Inapplicable rules are not evaluated (i.e. are + ignored). However, inapplicable rules still belong to the first set of + rules for P. + + Within the applicable rule subset, individual rules are evaluated in + their configuration order. If all ACLs of an applicable "allow" rule + match, then the protocol offered by the client is forwarded to the next + hop as is. If all ACLs of an applicable "deny" rule match, then the + offer is dropped. If no applicable rules have matching ACLs, then the + offer is also dropped. The first matching rule also ends rules + evaluation for the offered protocol. + + If all client-offered protocols are removed, then Squid forwards the + client request without the Upgrade header. Squid never sends an empty + Upgrade request header. + + An Upgrade request header with a value violating HTTP syntax is dropped + and ignored without an attempt to use extractable individual protocol + offers. + + Upon receiving an HTTP 101 (Switching Protocols) control message, Squid + checks that the server listed at least one protocol name and sent a + Connection:upgrade response header. Squid does not understand individual + protocol naming and versioning concepts enough to implement stricter + checks, but an admin can restrict HTTP 101 (Switching Protocols) + responses further using http_reply_access. Responses denied by + http_reply_access rules and responses flagged by the internal Upgrade + checks result in HTTP 502 (Bad Gateway) ERR_INVALID_RESP errors and + Squid-to-server connection closures. + + If Squid sends an Upgrade request header, and the next hop (e.g., the + origin server) responds with an acceptable HTTP 101 (Switching + Protocols), then Squid forwards that message to the client and becomes + a TCP tunnel. + + The presence of an Upgrade request header alone does not preclude cache + lookups. In other words, an Upgrade request might be satisfied from the + cache, using regular HTTP caching rules. + + This clause only supports fast acl types. + See http://wiki.squid-cache.org/SquidFaq/SquidAcl for details. + + Each of the following groups of configuration lines represents a + separate configuration example: + + # never upgrade to protocol Foo; all others are OK + http_upgrade_request_protocols Foo deny all + http_upgrade_request_protocols OTHER allow all + + # only allow upgrades to protocol Bar (except for its first version) + http_upgrade_request_protocols Bar/1 deny all + http_upgrade_request_protocols Bar allow all + http_upgrade_request_protocols OTHER deny all # this rule is optional + + # only allow upgrades to protocol Baz, and only if Baz is the only offer + acl UpgradeHeaderHasMultipleOffers ... + http_upgrade_request_protocols Baz deny UpgradeHeaderHasMultipleOffers + http_upgrade_request_protocols Baz allow all +DOC_END + NAME: server_pconn_for_nonretriable TYPE: acl_access DEFAULT: none diff --git a/src/client_side.cc b/src/client_side.cc index 3d566d5652..889ea138ae 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -4089,3 +4089,9 @@ operator <<(std::ostream &os, const ConnStateData::PinnedIdleContext &pic) return os << pic.connection << ", request=" << pic.request; } +std::ostream & +operator <<(std::ostream &os, const ConnStateData::ServerConnectionContext &scc) +{ + return os << scc.conn_ << ", srv_bytes=" << scc.preReadServerBytes.length(); +} + diff --git a/src/client_side.h b/src/client_side.h index 61a90ac07c..8672c791b6 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -199,6 +199,25 @@ public: // pining related comm callbacks virtual void clientPinnedConnectionClosed(const CommCloseCbParams &io); + /// noteTakeServerConnectionControl() callback parameter + class ServerConnectionContext { + public: + ServerConnectionContext(const Comm::ConnectionPointer &conn, const HttpRequest::Pointer &req, const SBuf &post101Bytes): preReadServerBytes(post101Bytes), conn_(conn) { conn_->enterOrphanage(); } + + /// gives to-server connection to the new owner + Comm::ConnectionPointer connection() { conn_->leaveOrphanage(); return conn_; } + + SBuf preReadServerBytes; ///< post-101 bytes received from the server + + private: + friend std::ostream &operator <<(std::ostream &, const ServerConnectionContext &); + Comm::ConnectionPointer conn_; ///< to-server connection + }; + + /// Gives us the control of the Squid-to-server connection. + /// Used, for example, to initiate a TCP tunnel after protocol switching. + virtual void noteTakeServerConnectionControl(ServerConnectionContext) {} + // comm callbacks void clientReadFtpData(const CommIoCbParams &io); void connStateClosed(const CommCloseCbParams &io); @@ -470,6 +489,7 @@ void clientProcessRequest(ConnStateData *, const Http1::RequestParserPointer &, void clientPostHttpsAccept(ConnStateData *); std::ostream &operator <<(std::ostream &os, const ConnStateData::PinnedIdleContext &pic); +std::ostream &operator <<(std::ostream &, const ConnStateData::ServerConnectionContext &); #endif /* SQUID_CLIENTSIDE_H */ diff --git a/src/comm/Connection.cc b/src/comm/Connection.cc index 1fb1351328..301888e54c 100644 --- a/src/comm/Connection.cc +++ b/src/comm/Connection.cc @@ -41,12 +41,16 @@ Comm::Connection::Connection() : *rfc931 = 0; // quick init the head. the rest does not matter. } -static int64_t lost_conn = 0; Comm::Connection::~Connection() { if (fd >= 0) { - debugs(5, 4, "BUG #3329: Orphan Comm::Connection: " << *this); - debugs(5, 4, "NOTE: " << ++lost_conn << " Orphans since last started."); + if (flags & COMM_ORPHANED) { + debugs(5, 5, "closing orphan: " << *this); + } else { + static uint64_t losses = 0; + ++losses; + debugs(5, 4, "BUG #3329: Lost orphan #" << losses << ": " << *this); + } close(); } diff --git a/src/comm/Connection.h b/src/comm/Connection.h index b6a2973aff..a4f330ca13 100644 --- a/src/comm/Connection.h +++ b/src/comm/Connection.h @@ -50,6 +50,8 @@ namespace Comm #define COMM_TRANSPARENT 0x10 // arrived via TPROXY #define COMM_INTERCEPTION 0x20 // arrived via NAT #define COMM_REUSEPORT 0x40 //< needs SO_REUSEPORT +/// not registered with Comm and not owned by any connection-closing code +#define COMM_ORPHANED 0x40 /** * Store data about the physical and logical attributes of a connection. @@ -80,6 +82,11 @@ public: */ ConnectionPointer copyDetails() const; + /// close the still-open connection when its last reference is gone + void enterOrphanage() { flags |= COMM_ORPHANED; } + /// resume relying on owner(s) to initiate an explicit connection closure + void leaveOrphanage() { flags &= ~COMM_ORPHANED; } + /** Close any open socket. */ void close(); diff --git a/src/http.cc b/src/http.cc index 916fd410c4..4c0f337810 100644 --- a/src/http.cc +++ b/src/http.cc @@ -41,6 +41,7 @@ #include "HttpHeaderTools.h" #include "HttpReply.h" #include "HttpRequest.h" +#include "HttpUpgradeProtocolAccess.h" #include "log/access_log.h" #include "MemBuf.h" #include "MemObject.h" @@ -137,6 +138,8 @@ HttpStateData::~HttpStateData() cbdataReferenceDone(_peer); + delete upgradeHeaderOut; + debugs(11,5, HERE << "HttpStateData " << this << " destroyed; " << serverConnection); } @@ -797,11 +800,18 @@ HttpStateData::handle1xx(HttpReply *reply) Must(!flags.handling1xx); flags.handling1xx = true; - if (!request->canHandle1xx() || request->forcedBodyContinuation) { - debugs(11, 2, "ignoring 1xx because it is " << (request->forcedBodyContinuation ? "already sent" : "not supported by client")); - proceedAfter1xx(); - return; - } + const auto statusCode = reply->sline.status(); + + // drop1xx() needs to handle HTTP 101 (Switching Protocols) responses + // specially because they indicate that the server has stopped speaking HTTP + Must(!flags.serverSwitchedProtocols); + flags.serverSwitchedProtocols = (statusCode == Http::scSwitchingProtocols); + + if (statusCode == Http::scContinue && request->forcedBodyContinuation) + return drop1xx("we have sent it already"); + + if (!request->canHandle1xx()) + return drop1xx("the client does not support it"); #if USE_HTTP_VIOLATIONS // check whether the 1xx response forwarding is allowed by squid.conf @@ -811,14 +821,16 @@ HttpStateData::handle1xx(HttpReply *reply) ch.reply = reply; ch.syncAle(originalRequest().getRaw(), nullptr); HTTPMSGLOCK(ch.reply); - if (!ch.fastCheck().allowed()) { // TODO: support slow lookups? - debugs(11, 3, HERE << "ignoring denied 1xx"); - proceedAfter1xx(); - return; - } + if (!ch.fastCheck().allowed()) // TODO: support slow lookups? + return drop1xx("http_reply_access blocked it"); } #endif // USE_HTTP_VIOLATIONS + if (flags.serverSwitchedProtocols) { + if (const auto reason = blockSwitchingProtocols(*reply)) + return drop1xx(reason); + } + debugs(11, 2, HERE << "forwarding 1xx to client"); // the Sink will use this to call us back after writing 1xx to the client @@ -833,11 +845,75 @@ HttpStateData::handle1xx(HttpReply *reply) // for similar reasons without a 1xx response. } +/// if possible, safely ignores the received 1xx control message +/// otherwise, terminates the server connection +void +HttpStateData::drop1xx(const char *reason) +{ + if (flags.serverSwitchedProtocols) { + debugs(11, 2, "bad 101 because " << reason); + const auto err = new ErrorState(ERR_INVALID_RESP, Http::scBadGateway, request.getRaw(), fwd->al); + fwd->fail(err); + closeServer(); + mustStop("prohibited HTTP/101 response"); + return; + } + + debugs(11, 2, "ignoring 1xx because " << reason); + proceedAfter1xx(); +} + +/// \retval nil if the HTTP/101 (Switching Protocols) reply should be forwarded +/// \retval reason why an attempt to switch protocols should be stopped +const char * +HttpStateData::blockSwitchingProtocols(const HttpReply &reply) const +{ + if (!upgradeHeaderOut) + return "Squid offered no Upgrade at all, but server switched to a tunnel"; + + // See RFC 7230 section 6.7 for the corresponding MUSTs + + if (!reply.header.has(Http::HdrType::UPGRADE)) + return "server did not send an Upgrade header field"; + + if (!reply.header.hasListMember(Http::HdrType::CONNECTION, "upgrade", ',')) + return "server did not send 'Connection: upgrade'"; + + const auto acceptedProtos = reply.header.getList(Http::HdrType::UPGRADE); + const char *pos = nullptr; + const char *accepted = nullptr; + int acceptedLen = 0; + while (strListGetItem(&acceptedProtos, ',', &accepted, &acceptedLen, &pos)) { + debugs(11, 5, "server accepted at least" << Raw(nullptr, accepted, acceptedLen)); + return nullptr; // OK: let the client validate server's selection + } + + return "server sent an essentially empty Upgrade header field"; +} + /// restores state and resumes processing after 1xx is ignored or forwarded void HttpStateData::proceedAfter1xx() { Must(flags.handling1xx); + + if (flags.serverSwitchedProtocols) { + // pass server connection ownership to request->clientConnectionManager + ConnStateData::ServerConnectionContext scc(serverConnection, request, inBuf); + typedef UnaryMemFunT MyDialer; + AsyncCall::Pointer call = asyncCall(11, 3, "ConnStateData::noteTakeServerConnectionControl", + MyDialer(request->clientConnectionManager, + &ConnStateData::noteTakeServerConnectionControl, scc)); + ScheduleCallHere(call); + fwd->unregister(serverConnection); + comm_remove_close_handler(serverConnection->fd, closeHandler); + closeHandler = nullptr; + serverConnection = nullptr; + doneWithFwd = "switched protocols"; + mustStop(doneWithFwd); + return; + } + debugs(11, 2, "continuing with " << payloadSeen << " bytes in buffer after 1xx"); CallJobHere(11, 3, this, HttpStateData, HttpStateData::processReply); } @@ -1956,6 +2032,66 @@ HttpStateData::httpBuildRequestHeader(HttpRequest * request, strConnection.clean(); } +/// copies from-client Upgrade info into the given to-server header while +/// honoring configuration filters and following HTTP requirements +void +HttpStateData::forwardUpgrade(HttpHeader &hdrOut) +{ + if (!Config.http_upgrade_request_protocols) + return; // forward nothing by default + + /* RFC 7230 section 6.7 paragraph 10: + * A server MUST ignore an Upgrade header field that is received in + * an HTTP/1.0 request. + */ + if (request->http_ver == Http::ProtocolVersion(1,0)) + return; + + const auto &hdrIn = request->header; + if (!hdrIn.has(Http::HdrType::UPGRADE)) + return; + const auto upgradeIn = hdrIn.getList(Http::HdrType::UPGRADE); + + String upgradeOut; + + ACLFilledChecklist ch(nullptr, request.getRaw()); + ch.al = fwd->al; + const char *pos = nullptr; + const char *offeredStr = nullptr; + int offeredStrLen = 0; + while (strListGetItem(&upgradeIn, ',', &offeredStr, &offeredStrLen, &pos)) { + const ProtocolView offeredProto(offeredStr, offeredStrLen); + debugs(11, 5, "checks all rules applicable to " << offeredProto); + Config.http_upgrade_request_protocols->forApplicable(offeredProto, [&ch, offeredStr, offeredStrLen, &upgradeOut] (const SBuf &cfgProto, const acl_access *guard) { + debugs(11, 5, "checks " << cfgProto << " rule(s)"); + ch.changeAcl(guard); + const auto answer = ch.fastCheck(); + if (answer.implicit) + return false; // keep looking for an explicit rule match + if (answer.allowed()) + strListAdd(upgradeOut, offeredStr, offeredStrLen); + // else drop the offer (explicitly denied cases and ACL errors) + return true; // stop after an explicit rule match or an error + }); + } + + if (upgradeOut.size()) { + hdrOut.putStr(Http::HdrType::UPGRADE, upgradeOut.termedBuf()); + + /* RFC 7230 section 6.7 paragraph 10: + * When Upgrade is sent, the sender MUST also send a Connection header + * field that contains an "upgrade" connection option, in + * order to prevent Upgrade from being accidentally forwarded by + * intermediaries that might not implement the listed protocols. + * + * NP: Squid does not truly implement the protocol(s) in this Upgrade. + * For now we are treating an explicit blind tunnel as "implemented" + * regardless of the security implications. + */ + hdrOut.putStr(Http::HdrType::CONNECTION, "upgrade"); + } +} + /** * Decides whether a particular header may be cloned from the received Clients request * to our outgoing fetch request. @@ -1989,10 +2125,13 @@ copyOneHeaderFromClientsideRequestToUpstreamRequest(const HttpHeaderEntry *e, co case Http::HdrType::KEEP_ALIVE: /** \par Keep-Alive: */ case Http::HdrType::PROXY_AUTHENTICATE: /** \par Proxy-Authenticate: */ case Http::HdrType::TRAILER: /** \par Trailer: */ - case Http::HdrType::UPGRADE: /** \par Upgrade: */ case Http::HdrType::TRANSFER_ENCODING: /** \par Transfer-Encoding: */ break; + /// \par Upgrade is hop-by-hop but forwardUpgrade() may send a filtered one + case Http::HdrType::UPGRADE: + break; + /** \par OTHER headers I haven't bothered to track down yet. */ case Http::HdrType::AUTHORIZATION: @@ -2185,6 +2324,7 @@ HttpStateData::buildRequestPrefix(MemBuf * mb) /* build and pack headers */ { HttpHeader hdr(hoRequest); + forwardUpgrade(hdr); httpBuildRequestHeader(request.getRaw(), entry, fwd->al, &hdr, flags); if (request->flags.pinned && request->flags.connectionAuth) @@ -2192,6 +2332,13 @@ HttpStateData::buildRequestPrefix(MemBuf * mb) else if (hdr.has(Http::HdrType::AUTHORIZATION)) request->flags.authSent = true; + // The late placement of this check supports reply_header_add mangling, + // but also complicates optimizing upgradeHeaderOut-like lookups. + if (hdr.has(Http::HdrType::UPGRADE)) { + assert(!upgradeHeaderOut); + upgradeHeaderOut = new String(hdr.getList(Http::HdrType::UPGRADE)); + } + hdr.packInto(mb); hdr.clean(); } diff --git a/src/http.h b/src/http.h index 77cc3763a1..47ec720213 100644 --- a/src/http.h +++ b/src/http.h @@ -17,6 +17,7 @@ class FwdState; class HttpHeader; +class String; class HttpStateData : public Client { @@ -69,12 +70,16 @@ public: bool ignoreCacheControl; bool surrogateNoStore; + /// Upgrade header value sent to the origin server or cache peer. + String *upgradeHeaderOut = nullptr; + void processSurrogateControl(HttpReply *); protected: void processReply(); void proceedAfter1xx(); void handle1xx(HttpReply *msg); + void drop1xx(const char *reason); private: /** @@ -135,8 +140,10 @@ private: void httpTimeout(const CommTimeoutCbParams ¶ms); mb_size_t buildRequestPrefix(MemBuf * mb); + void forwardUpgrade(HttpHeader&); static bool decideIfWeDoRanges (HttpRequest * orig_request); bool peerSupportsConnectionPinning() const; + const char *blockSwitchingProtocols(const HttpReply&) const; /// Parser being used at present to parse the HTTP/ICY server response. Http1::ResponseParserPointer hp; diff --git a/src/http/StateFlags.h b/src/http/StateFlags.h index e4d3eda109..499cfc7068 100644 --- a/src/http/StateFlags.h +++ b/src/http/StateFlags.h @@ -18,7 +18,15 @@ public: unsigned int front_end_https = 0; ///< send "Front-End-Https: On" header (off/on/auto=2) bool keepalive = false; ///< whether to keep the connection persistent bool only_if_cached = false; - bool handling1xx = false; ///< we are ignoring or forwarding 1xx response + + /// Whether we are processing an HTTP 1xx control message. + bool handling1xx = false; + + /// Whether we received an HTTP 101 (Switching Protocols) control message. + /// Implies true handling1xx, but the serverSwitchedProtocols state is + /// permanent/final while handling of other control messages usually stops. + bool serverSwitchedProtocols = false; + bool headers_parsed = false; /// Whether the next TCP hop is a cache_peer, including originserver diff --git a/src/servers/Http1Server.cc b/src/servers/Http1Server.cc index d6d577e1af..8a9439bb65 100644 --- a/src/servers/Http1Server.cc +++ b/src/servers/Http1Server.cc @@ -233,6 +233,17 @@ Http::One::Server::proceedAfterBodyContinuation(Http::StreamPointer context) clientProcessRequest(this, parser_, context.getRaw()); } +int +Http::One::Server::pipelinePrefetchMax() const +{ + const auto context = pipeline.back(); + const auto request = (context && context->http) ? context->http->request : nullptr; + if (request && request->header.has(Http::HdrType::UPGRADE)) + return 0; + + return ConnStateData::pipelinePrefetchMax(); +} + void Http::One::Server::processParsedRequest(Http::StreamPointer &context) { @@ -335,12 +346,25 @@ Http::One::Server::writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &ca const ClientHttpRequest *http = context->http; + // remember Upgrade header; removeHopByHopEntries() will remove it + String upgradeHeader; + const auto switching = (rep->sline.status() == Http::scSwitchingProtocols); + if (switching) + upgradeHeader = rep->header.getList(Http::HdrType::UPGRADE); + // apply selected clientReplyContext::buildReplyHeader() mods // it is not clear what headers are required for control messages rep->header.removeHopByHopEntries(); // paranoid: ContentLengthInterpreter has cleaned non-generated replies rep->removeIrrelevantContentLength(); - rep->header.putStr(Http::HdrType::CONNECTION, "keep-alive"); + + if (switching && /* paranoid: */ upgradeHeader.size()) { + rep->header.putStr(Http::HdrType::UPGRADE, upgradeHeader.termedBuf()); + rep->header.putStr(Http::HdrType::CONNECTION, "upgrade, keep-alive"); + } else { + rep->header.putStr(Http::HdrType::CONNECTION, "keep-alive"); + } + httpHdrMangleList(&rep->header, http->request, http->al, ROR_REPLY); MemBuf *mb = rep->pack(); @@ -354,6 +378,24 @@ Http::One::Server::writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &ca return true; } +void switchToTunnel(HttpRequest *request, const Comm::ConnectionPointer &clientConn, const Comm::ConnectionPointer &srvConn, const SBuf &preReadServerData); + +void +Http::One::Server::noteTakeServerConnectionControl(ServerConnectionContext server) +{ + const auto context = pipeline.front(); + assert(context); + const auto http = context->http; + assert(http); + assert(http->request); + + stopReading(); + Must(!writer); + + switchToTunnel(http->request, clientConnection, + server.connection(), server.preReadServerBytes); +} + ConnStateData * Http::NewServer(MasterXactionPointer &xact) { diff --git a/src/servers/Http1Server.h b/src/servers/Http1Server.h index 88e3ef0f3a..244fee9077 100644 --- a/src/servers/Http1Server.h +++ b/src/servers/Http1Server.h @@ -33,7 +33,9 @@ protected: virtual void processParsedRequest(Http::StreamPointer &context); virtual void handleReply(HttpReply *rep, StoreIOBuffer receivedData); virtual bool writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call); + virtual int pipelinePrefetchMax() const; virtual time_t idleTimeout() const; + virtual void noteTakeServerConnectionControl(ServerConnectionContext); /* BodyPipe API */ virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer); diff --git a/src/ssl/PeekingPeerConnector.cc b/src/ssl/PeekingPeerConnector.cc index 6973b79ee3..e48e39b35b 100644 --- a/src/ssl/PeekingPeerConnector.cc +++ b/src/ssl/PeekingPeerConnector.cc @@ -23,7 +23,7 @@ CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeekingPeerConnector); -void switchToTunnel(HttpRequest *request, Comm::ConnectionPointer & clientConn, Comm::ConnectionPointer &srvConn); +void switchToTunnel(HttpRequest *request, const Comm::ConnectionPointer &clientConn, const Comm::ConnectionPointer &srvConn, const SBuf &preReadServerData); void Ssl::PeekingPeerConnector::cbCheckForPeekAndSpliceDone(Acl::Answer answer, void *data) @@ -252,12 +252,28 @@ Ssl::PeekingPeerConnector::noteNegotiationDone(ErrorState *error) bail(new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al)); throw TextException("from-client connection gone", Here()); } - switchToTunnel(request.getRaw(), clientConn, serverConn); - tunnelInsteadOfNegotiating(); + startTunneling(); } } } +void +Ssl::PeekingPeerConnector::startTunneling() +{ + // switchToTunnel() drains any already buffered from-server data (rBufData) + fd_table[serverConn->fd].useDefaultIo(); + // tunnelStartShoveling() drains any buffered from-client data (inBuf) + fd_table[clientConn->fd].useDefaultIo(); + + // TODO: Encapsulate this frequently repeated logic into a method. + const auto session = fd_table[serverConn->fd].ssl; + auto b = SSL_get_rbio(session.get()); + auto srvBio = static_cast(BIO_get_data(b)); + + switchToTunnel(request.getRaw(), clientConn, serverConn, srvBio->rBufData()); + tunnelInsteadOfNegotiating(); +} + void Ssl::PeekingPeerConnector::noteWantWrite() { diff --git a/src/ssl/PeekingPeerConnector.h b/src/ssl/PeekingPeerConnector.h index d69ff7826c..1987db8333 100644 --- a/src/ssl/PeekingPeerConnector.h +++ b/src/ssl/PeekingPeerConnector.h @@ -63,6 +63,9 @@ public: /// connection manager members void serverCertificateVerified(); + /// Abruptly stops TLS negotiation and starts tunneling. + void startTunneling(); + /// A wrapper function for checkForPeekAndSpliceDone for use with acl static void cbCheckForPeekAndSpliceDone(Acl::Answer answer, void *data); diff --git a/src/tests/stub_HttpUpgradeProtocolAccess.cc b/src/tests/stub_HttpUpgradeProtocolAccess.cc new file mode 100644 index 0000000000..71e006f917 --- /dev/null +++ b/src/tests/stub_HttpUpgradeProtocolAccess.cc @@ -0,0 +1,25 @@ +/* + * Copyright (C) 1996-2020 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 "ConfigParser.h" + +#define STUB_API "HttpUpgradeProtocolAccess.cc" +#include "STUB.h" + +#include "HttpUpgradeProtocolAccess.h" +ProtocolView::ProtocolView(const char * const, const size_t) STUB +ProtocolView::ProtocolView(SBuf const &) STUB +std::ostream &operator <<(std::ostream &os, const ProtocolView &) STUB_RETVAL(os) +HttpUpgradeProtocolAccess::~HttpUpgradeProtocolAccess() STUB +const acl_access *HttpUpgradeProtocolAccess::findGuard(const SBuf &) const STUB_RETVAL(nullptr) +void HttpUpgradeProtocolAccess::configureGuard(ConfigParser &) STUB +const SBuf HttpUpgradeProtocolAccess::ProtoOther("STUB-OTHER"); +HttpUpgradeProtocolAccess::NamedGuard::~NamedGuard() STUB_NOP +HttpUpgradeProtocolAccess::NamedGuard::NamedGuard(const char *, acl_access *): protocol("STUB-UNDEF"), proto(protocol) STUB_NOP + diff --git a/src/tests/stub_cache_cf.cc b/src/tests/stub_cache_cf.cc index 70a557fe49..40edc5bfd9 100644 --- a/src/tests/stub_cache_cf.cc +++ b/src/tests/stub_cache_cf.cc @@ -25,6 +25,7 @@ void parse_wordlist(wordlist ** list) STUB void requirePathnameExists(const char *name, const char *path) STUB_NOP void parse_time_t(time_t * var) STUB void ConfigParser::ParseUShort(unsigned short *var) STUB +void ConfigParser::ParseWordList(wordlist **) STUB void dump_acl_access(StoreEntry * entry, const char *name, acl_access * head) STUB void dump_acl_list(StoreEntry*, ACLList*) STUB diff --git a/src/tests/stub_tunnel.cc b/src/tests/stub_tunnel.cc index 69f1facde8..545c967134 100644 --- a/src/tests/stub_tunnel.cc +++ b/src/tests/stub_tunnel.cc @@ -16,5 +16,5 @@ class ClientHttpRequest; void tunnelStart(ClientHttpRequest *) STUB -void switchToTunnel(HttpRequest *request, Comm::ConnectionPointer &clientConn, Comm::ConnectionPointer &srvConn) STUB +void switchToTunnel(HttpRequest *, const Comm::ConnectionPointer &, const Comm::ConnectionPointer &, const SBuf &) STUB diff --git a/src/tunnel.cc b/src/tunnel.cc index 02472665ad..dac7411844 100644 --- a/src/tunnel.cc +++ b/src/tunnel.cc @@ -1068,8 +1068,8 @@ TunnelStateData::connectToPeer(const Comm::ConnectionPointer &conn) if (const auto p = conn->getPeer()) { if (p->secure.encryptTransport) return advanceDestination("secure connection to peer", conn, [this,&conn] { - secureConnectionToPeer(conn); - }); + secureConnectionToPeer(conn); + }); } connectedToPeer(conn); @@ -1339,9 +1339,15 @@ TunnelStateData::notifyConnOpener() } } -#if USE_OPENSSL +/** + * Sets up a TCP tunnel through Squid and starts shoveling traffic. + * \param request the request that initiated/caused this tunnel + * \param clientConn the already accepted client-to-Squid TCP connection + * \param srvConn the already established Squid-to-server TCP connection + * \param preReadServerData server-sent bytes to be forwarded to the client + */ void -switchToTunnel(HttpRequest *request, Comm::ConnectionPointer &clientConn, Comm::ConnectionPointer &srvConn) +switchToTunnel(HttpRequest *request, const Comm::ConnectionPointer &clientConn, const Comm::ConnectionPointer &srvConn, const SBuf &preReadServerData) { Must(Comm::IsConnOpen(clientConn)); Must(Comm::IsConnOpen(srvConn)); @@ -1361,9 +1367,6 @@ switchToTunnel(HttpRequest *request, Comm::ConnectionPointer &clientConn, Comm:: TunnelStateData *tunnelState = new TunnelStateData(context->http); - // tunnelStartShoveling() drains any buffered from-client data (inBuf) - fd_table[clientConn->fd].useDefaultIo(); - request->hier.resetPeerNotes(srvConn, tunnelState->getHost()); tunnelState->server.conn = srvConn; @@ -1383,15 +1386,8 @@ switchToTunnel(HttpRequest *request, Comm::ConnectionPointer &clientConn, Comm:: else request->prepForDirect(); - // we drain any already buffered from-server data below (rBufData) - fd_table[srvConn->fd].useDefaultIo(); + tunnelState->preReadServerData = preReadServerData; - auto ssl = fd_table[srvConn->fd].ssl.get(); - assert(ssl); - BIO *b = SSL_get_rbio(ssl); - Ssl::ServerBio *srvBio = static_cast(BIO_get_data(b)); - tunnelState->preReadServerData = srvBio->rBufData(); tunnelStartShoveling(tunnelState); } -#endif //USE_OPENSSL -- 2.39.5