| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "quiche/quic/masque/masque_client_session.h" |
| |
| #include <cstdint> |
| #include <cstring> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_replace.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| #include "openssl/curve25519.h" |
| #include "quiche/quic/core/crypto/quic_crypto_client_config.h" |
| #include "quiche/quic/core/frames/quic_connection_close_frame.h" |
| #include "quiche/quic/core/http/http_frames.h" |
| #include "quiche/quic/core/http/quic_spdy_client_session.h" |
| #include "quiche/quic/core/http/quic_spdy_client_stream.h" |
| #include "quiche/quic/core/http/quic_spdy_stream.h" |
| #include "quiche/quic/core/quic_config.h" |
| #include "quiche/quic/core/quic_connection.h" |
| #include "quiche/quic/core/quic_data_reader.h" |
| #include "quiche/quic/core/quic_data_writer.h" |
| #include "quiche/quic/core/quic_error_codes.h" |
| #include "quiche/quic/core/quic_server_id.h" |
| #include "quiche/quic/core/quic_time.h" |
| #include "quiche/quic/core/quic_types.h" |
| #include "quiche/quic/core/quic_utils.h" |
| #include "quiche/quic/core/quic_versions.h" |
| #include "quiche/quic/masque/masque_utils.h" |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" |
| #include "quiche/quic/platform/api/quic_logging.h" |
| #include "quiche/quic/platform/api/quic_socket_address.h" |
| #include "quiche/quic/tools/quic_url.h" |
| #include "quiche/common/capsule.h" |
| #include "quiche/common/http/http_header_block.h" |
| #include "quiche/common/platform/api/quiche_googleurl.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| #include "quiche/common/platform/api/quiche_url_utils.h" |
| #include "quiche/common/quiche_ip_address.h" |
| #include "quiche/common/quiche_random.h" |
| #include "quiche/common/quiche_text_utils.h" |
| |
| namespace quic { |
| |
| namespace { |
| |
| using ::quiche::AddressAssignCapsule; |
| using ::quiche::AddressRequestCapsule; |
| using ::quiche::RouteAdvertisementCapsule; |
| using ContextId = MasqueClientSession::ContextId; |
| |
| constexpr MasqueClientSession::ContextId kConnectIpPayloadContextId = 0; |
| constexpr MasqueClientSession::ContextId kConnectEthernetPayloadContextId = 0; |
| } // namespace |
| |
| MasqueClientSession::MasqueClientSession( |
| MasqueMode masque_mode, const std::string& uri_template, |
| const QuicConfig& config, const ParsedQuicVersionVector& supported_versions, |
| QuicConnection* connection, const QuicServerId& server_id, |
| QuicCryptoClientConfig* crypto_config, Owner* owner) |
| : QuicSpdyClientSession(config, supported_versions, connection, server_id, |
| crypto_config), |
| masque_mode_(masque_mode), |
| uri_template_(uri_template), |
| owner_(owner) { |
| QUICHE_CHECK(!QuicUrl(uri_template_).host().empty()); |
| // We don't currently use `masque_mode_` but will in the future. To silence |
| // clang's `-Wunused-private-field` warning for this when building QUICHE for |
| // Chrome, add a use of it here. |
| (void)masque_mode_; |
| } |
| |
| MasqueClientSession::MasqueClientSession( |
| const QuicConfig& config, const ParsedQuicVersionVector& supported_versions, |
| QuicConnection* connection, const QuicServerId& server_id, |
| QuicCryptoClientConfig* crypto_config, Owner* owner) |
| : QuicSpdyClientSession(config, supported_versions, connection, server_id, |
| crypto_config), |
| owner_(owner) {} |
| |
| void MasqueClientSession::OnDatagramAcked(QuicDatagramId datagram_id, |
| QuicTime /*receive_timestamp*/) { |
| QUIC_DVLOG(1) << "Received ack for DATAGRAM frame " << datagram_id; |
| } |
| |
| void MasqueClientSession::OnDatagramLost(QuicDatagramId datagram_id) { |
| QUIC_DVLOG(1) << "We believe DATAGRAM frame " << datagram_id << " was lost"; |
| } |
| |
| MasqueClientSession::ConnectUdpClientState* |
| MasqueClientSession::GetOrCreateConnectUdpClientState( |
| const QuicSocketAddress& target_server_address, |
| EncapsulatedClientSession* encapsulated_client_session) { |
| for (ConnectUdpClientState& client_state : connect_udp_client_states_) { |
| if (client_state.target_server_address() == target_server_address && |
| client_state.encapsulated_client_session() == |
| encapsulated_client_session && |
| IsBind() == client_state.is_bind()) { |
| // Found existing CONNECT-UDP request. |
| return &client_state; |
| } |
| } |
| |
| QUIC_DLOG(INFO) << "No existing state non bind state found, create one"; |
| // No CONNECT-UDP request found, create a new one. |
| std::string target_host, target_port; |
| |
| if (IsBind()) { |
| target_host = "*"; |
| target_port = "*"; |
| } else { |
| auto it = |
| fake_addresses_.find(target_server_address.host().ToPackedString()); |
| if (it != fake_addresses_.end()) { |
| target_host = it->second; |
| } else { |
| target_host = target_server_address.host().ToString(); |
| } |
| target_port = absl::StrCat(target_server_address.port()); |
| } |
| QUICHE_CHECK(!target_host.empty()); |
| |
| url::Parsed parsed_uri_template; |
| url::ParseStandardURL(uri_template_.c_str(), uri_template_.length(), |
| &parsed_uri_template); |
| if (!parsed_uri_template.path.is_nonempty()) { |
| QUIC_BUG(bad URI template path) |
| << connection_id() << ": Cannot parse path from URI template \"" |
| << uri_template_ << "\""; |
| return nullptr; |
| } |
| std::string path = uri_template_.substr(parsed_uri_template.path.begin, |
| parsed_uri_template.path.len); |
| if (parsed_uri_template.query.is_valid()) { |
| absl::StrAppend(&path, "?", |
| uri_template_.substr(parsed_uri_template.query.begin, |
| parsed_uri_template.query.len)); |
| } |
| absl::flat_hash_map<std::string, std::string> parameters; |
| // TODO(abhisinghx): Remove this hack. |
| // ExpandURITemplate, does not encode * as %2A. |
| // Therefore to meet the spec, we need to do it manually. |
| if (IsBind()) { |
| parameters["target_host"] = "HackyStringForBind"; |
| parameters["target_port"] = "HackyStringForBind"; |
| } else { |
| parameters["target_host"] = target_host; |
| parameters["target_port"] = target_port; |
| } |
| |
| std::string expanded_path; |
| absl::flat_hash_set<std::string> vars_found; |
| bool expanded = |
| quiche::ExpandURITemplate(path, parameters, &expanded_path, &vars_found); |
| if (!expanded || vars_found.find("target_host") == vars_found.end() || |
| vars_found.find("target_port") == vars_found.end()) { |
| QUIC_DLOG(ERROR) << "Failed to expand URI template \"" << uri_template_ |
| << "\" for " << target_host << " port " |
| << target_server_address.port(); |
| return nullptr; |
| } |
| if (IsBind()) { |
| expanded_path = |
| absl::StrReplaceAll(expanded_path, {{"HackyStringForBind", "%2A"}}); |
| } |
| |
| url::Component expanded_path_component(0, expanded_path.length()); |
| url::RawCanonOutput<1024> canonicalized_path_output; |
| url::Component canonicalized_path_component; |
| bool canonicalized = url::CanonicalizePath( |
| expanded_path.c_str(), expanded_path_component, |
| &canonicalized_path_output, &canonicalized_path_component); |
| if (!canonicalized || !canonicalized_path_component.is_nonempty()) { |
| QUIC_DLOG(ERROR) << "Failed to canonicalize URI template \"" |
| << uri_template_ << "\" for " << target_host << " port " |
| << target_server_address.port(); |
| return nullptr; |
| } |
| std::string canonicalized_path( |
| canonicalized_path_output.data() + canonicalized_path_component.begin, |
| canonicalized_path_component.len); |
| |
| QuicSpdyClientStream* stream = CreateOutgoingBidirectionalStream(); |
| if (stream == nullptr) { |
| // Stream flow control limits prevented us from opening a new stream. |
| QUIC_DLOG(ERROR) << "Failed to open CONNECT-UDP stream"; |
| return nullptr; |
| } |
| |
| QuicUrl url(uri_template_); |
| std::string scheme = url.scheme(); |
| std::string authority = url.HostPort(); |
| |
| QUIC_DLOG(INFO) << "Sending CONNECT-UDP request for " << target_host |
| << " port " << target_server_address.port() << " on stream " |
| << stream->id() << " scheme=\"" << scheme << "\" authority=\"" |
| << authority << "\" path=\"" << canonicalized_path << "\""; |
| |
| // Send the request. |
| quiche::HttpHeaderBlock headers; |
| headers[":method"] = "CONNECT"; |
| headers[":protocol"] = "connect-udp"; |
| headers[":scheme"] = scheme; |
| headers[":authority"] = authority; |
| headers[":path"] = canonicalized_path; |
| if (IsBind()) { |
| headers["connect-udp-bind"] = "?1"; |
| } |
| AddAdditionalHeaders(headers, url); |
| QUIC_DVLOG(1) << "Sending request headers: " << headers.DebugString(); |
| size_t bytes_sent = |
| stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false); |
| if (bytes_sent == 0) { |
| QUIC_DLOG(ERROR) << "Failed to send CONNECT-UDP request"; |
| return nullptr; |
| } |
| |
| connect_udp_client_states_.push_back(ConnectUdpClientState( |
| stream, encapsulated_client_session, this, target_server_address)); |
| if (IsBind()) { |
| QUIC_DLOG(INFO) << "Creating bind state"; |
| connect_udp_client_states_.back().set_is_bind(true); |
| } |
| return &connect_udp_client_states_.back(); |
| } |
| |
| const MasqueClientSession::ConnectIpClientState* |
| MasqueClientSession::GetOrCreateConnectIpClientState( |
| MasqueClientSession::EncapsulatedIpSession* encapsulated_ip_session) { |
| for (const ConnectIpClientState& client_state : connect_ip_client_states_) { |
| if (client_state.encapsulated_ip_session() == encapsulated_ip_session) { |
| // Found existing CONNECT-IP request. |
| return &client_state; |
| } |
| } |
| // No CONNECT-IP request found, create a new one. |
| QuicSpdyClientStream* stream = CreateOutgoingBidirectionalStream(); |
| if (stream == nullptr) { |
| // Stream flow control limits prevented us from opening a new stream. |
| QUIC_DLOG(ERROR) << "Failed to open CONNECT-IP stream"; |
| return nullptr; |
| } |
| |
| QuicUrl url(uri_template_); |
| std::string scheme = url.scheme(); |
| std::string authority = url.HostPort(); |
| std::string path = "/.well-known/masque/ip/*/*/"; |
| |
| QUIC_DLOG(INFO) << "Sending CONNECT-IP request on stream " << stream->id() |
| << " scheme=\"" << scheme << "\" authority=\"" << authority |
| << "\" path=\"" << path << "\""; |
| |
| // Send the request. |
| quiche::HttpHeaderBlock headers; |
| headers[":method"] = "CONNECT"; |
| headers[":protocol"] = "connect-ip"; |
| headers[":scheme"] = scheme; |
| headers[":authority"] = authority; |
| headers[":path"] = path; |
| headers["connect-ip-version"] = "3"; |
| AddAdditionalHeaders(headers, url); |
| QUIC_DVLOG(1) << "Sending request headers: " << headers.DebugString(); |
| size_t bytes_sent = |
| stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false); |
| if (bytes_sent == 0) { |
| QUIC_DLOG(ERROR) << "Failed to send CONNECT-IP request"; |
| return nullptr; |
| } |
| |
| connect_ip_client_states_.push_back( |
| ConnectIpClientState(stream, encapsulated_ip_session, this)); |
| return &connect_ip_client_states_.back(); |
| } |
| |
| const MasqueClientSession::ConnectEthernetClientState* |
| MasqueClientSession::GetOrCreateConnectEthernetClientState( |
| MasqueClientSession::EncapsulatedEthernetSession* |
| encapsulated_ethernet_session) { |
| for (const ConnectEthernetClientState& client_state : |
| connect_ethernet_client_states_) { |
| if (client_state.encapsulated_ethernet_session() == |
| encapsulated_ethernet_session) { |
| // Found existing CONNECT-ETHERNET request. |
| return &client_state; |
| } |
| } |
| // No CONNECT-ETHERNET request found, create a new one. |
| QuicSpdyClientStream* stream = CreateOutgoingBidirectionalStream(); |
| if (stream == nullptr) { |
| // Stream flow control limits prevented us from opening a new stream. |
| QUIC_DLOG(ERROR) << "Failed to open CONNECT-ETHERNET stream"; |
| return nullptr; |
| } |
| |
| QuicUrl url(uri_template_); |
| std::string scheme = url.scheme(); |
| std::string authority = url.HostPort(); |
| std::string path = "/.well-known/masque/ethernet/"; |
| |
| QUIC_DLOG(INFO) << "Sending CONNECT-ETHERNET request on stream " |
| << stream->id() << " scheme=\"" << scheme << "\" authority=\"" |
| << authority << "\" path=\"" << path << "\""; |
| |
| // Send the request. |
| quiche::HttpHeaderBlock headers; |
| headers[":method"] = "CONNECT"; |
| headers[":protocol"] = "connect-ethernet"; |
| headers[":scheme"] = scheme; |
| headers[":authority"] = authority; |
| headers[":path"] = path; |
| AddAdditionalHeaders(headers, url); |
| QUIC_DVLOG(1) << "Sending request headers: " << headers.DebugString(); |
| size_t bytes_sent = |
| stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/false); |
| if (bytes_sent == 0) { |
| QUIC_DLOG(ERROR) << "Failed to send CONNECT-ETHERNET request"; |
| return nullptr; |
| } |
| |
| connect_ethernet_client_states_.push_back( |
| ConnectEthernetClientState(stream, encapsulated_ethernet_session, this)); |
| return &connect_ethernet_client_states_.back(); |
| } |
| |
| void MasqueClientSession::SendIpPacket( |
| absl::string_view packet, |
| MasqueClientSession::EncapsulatedIpSession* encapsulated_ip_session) { |
| const ConnectIpClientState* connect_ip = |
| GetOrCreateConnectIpClientState(encapsulated_ip_session); |
| if (connect_ip == nullptr) { |
| QUIC_DLOG(ERROR) << "Failed to create CONNECT-IP request"; |
| return; |
| } |
| |
| std::string http_payload; |
| http_payload.resize( |
| QuicDataWriter::GetVarInt62Len(kConnectIpPayloadContextId) + |
| packet.size()); |
| QuicDataWriter writer(http_payload.size(), http_payload.data()); |
| if (!writer.WriteVarInt62(kConnectIpPayloadContextId)) { |
| QUIC_BUG(IP context write fail) << "Failed to write CONNECT-IP context ID"; |
| return; |
| } |
| if (!writer.WriteStringPiece(packet)) { |
| QUIC_BUG(IP packet write fail) << "Failed to write CONNECT-IP packet"; |
| return; |
| } |
| DatagramStatus message_status = |
| SendHttp3Datagram(connect_ip->stream()->id(), http_payload); |
| |
| QUIC_DVLOG(1) << "Sent encapsulated IP packet of length " << packet.size() |
| << " with stream ID " << connect_ip->stream()->id() |
| << " and got message status " |
| << DatagramStatusToString(message_status); |
| } |
| |
| void MasqueClientSession::SendEthernetFrame( |
| absl::string_view frame, MasqueClientSession::EncapsulatedEthernetSession* |
| encapsulated_ethernet_session) { |
| const ConnectEthernetClientState* connect_ethernet = |
| GetOrCreateConnectEthernetClientState(encapsulated_ethernet_session); |
| if (connect_ethernet == nullptr) { |
| QUIC_DLOG(ERROR) << "Failed to create CONNECT-ETHERNET request"; |
| return; |
| } |
| |
| std::string http_payload; |
| http_payload.resize( |
| QuicDataWriter::GetVarInt62Len(kConnectEthernetPayloadContextId) + |
| frame.size()); |
| QuicDataWriter writer(http_payload.size(), http_payload.data()); |
| if (!writer.WriteVarInt62(kConnectEthernetPayloadContextId)) { |
| QUIC_BUG(IP context write fail) |
| << "Failed to write CONNECT-ETHERNET context ID"; |
| return; |
| } |
| if (!writer.WriteStringPiece(frame)) { |
| QUIC_BUG(IP packet write fail) << "Failed to write CONNECT-ETHERNET frame"; |
| return; |
| } |
| DatagramStatus message_status = |
| SendHttp3Datagram(connect_ethernet->stream()->id(), http_payload); |
| |
| QUIC_DVLOG(1) << "Sent encapsulated Ethernet frame of length " << frame.size() |
| << " with stream ID " << connect_ethernet->stream()->id() |
| << " and got message status " |
| << DatagramStatusToString(message_status); |
| } |
| |
| void MasqueClientSession::SendConnectUdpBindPacket( |
| absl::string_view packet, const QuicSocketAddress& target_server_address, |
| EncapsulatedClientSession* encapsulated_client_session) { |
| QUICHE_DCHECK(masque_mode_ == MasqueMode::kConnectUdpBind); |
| ConnectUdpClientState* connect_udp = GetOrCreateConnectUdpClientState( |
| QuicSocketAddress(), encapsulated_client_session); |
| std::optional<std::string> bind_packet = connect_udp->PrepareBindPacket( |
| packet, target_server_address, bind_use_uncompressed_context_); |
| if (!bind_packet.has_value()) { |
| QUIC_DLOG(ERROR) << "Failed to prepare bind packet"; |
| return; |
| } |
| QUIC_DVLOG(1) << "Sending bind packet: " << bind_packet.value() |
| << " of length " << bind_packet.value().length() << " to " |
| << target_server_address; |
| |
| SendHttp3Datagram(connect_udp->stream()->id(), bind_packet.value()); |
| } |
| |
| void MasqueClientSession::SendPacket( |
| absl::string_view packet, const QuicSocketAddress& target_server_address, |
| EncapsulatedClientSession* encapsulated_client_session) { |
| const ConnectUdpClientState* connect_udp = GetOrCreateConnectUdpClientState( |
| target_server_address, encapsulated_client_session); |
| if (connect_udp == nullptr) { |
| QUIC_DLOG(ERROR) << "Failed to create CONNECT-UDP request"; |
| return; |
| } |
| if (masque_mode_ == MasqueMode::kConnectUdpBind) { |
| QUIC_LOG(ERROR) << "Should not be called in CONNECT-UDP Bind mode"; |
| return; |
| } |
| |
| std::string http_payload; |
| http_payload.resize(1 + packet.size()); |
| http_payload[0] = 0; |
| memcpy(&http_payload[1], packet.data(), packet.size()); |
| DatagramStatus message_status = |
| SendHttp3Datagram(connect_udp->stream()->id(), http_payload); |
| |
| QUIC_DVLOG(1) << "Sent packet to " << target_server_address |
| << " compressed with stream ID " << connect_udp->stream()->id() |
| << " and got message status " |
| << DatagramStatusToString(message_status); |
| QUIC_DVLOG(2) << "Contents of outgoing HTTP Datagram of length " |
| << http_payload.size() << ":" << std::endl |
| << quiche::QuicheTextUtils::HexDump(http_payload); |
| } |
| |
| // Send Capsule for CONNECT-UDP Bind. |
| void MasqueClientSession::SendBindCapsule( |
| const quiche::Capsule& capsule, |
| EncapsulatedClientSession* encapsulated_client_session) { |
| for (auto& state : connect_udp_client_states_) { |
| if (state.is_bind() && |
| state.encapsulated_client_session() == encapsulated_client_session) { |
| state.stream()->WriteCapsule(capsule); |
| } |
| } |
| } |
| |
| void MasqueClientSession::CloseConnectUdpStream( |
| EncapsulatedClientSession* encapsulated_client_session) { |
| for (auto it = connect_udp_client_states_.begin(); |
| it != connect_udp_client_states_.end();) { |
| if (it->encapsulated_client_session() == encapsulated_client_session) { |
| QUIC_DLOG(INFO) << "Removing CONNECT-UDP state for stream ID " |
| << it->stream()->id(); |
| auto* stream = it->stream(); |
| it = connect_udp_client_states_.erase(it); |
| if (!stream->write_side_closed()) { |
| stream->Reset(QUIC_STREAM_CANCELLED); |
| } |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| void MasqueClientSession::CloseConnectIpStream( |
| EncapsulatedIpSession* encapsulated_ip_session) { |
| for (auto it = connect_ip_client_states_.begin(); |
| it != connect_ip_client_states_.end();) { |
| if (it->encapsulated_ip_session() == encapsulated_ip_session) { |
| QUIC_DLOG(INFO) << "Removing CONNECT-IP state for stream ID " |
| << it->stream()->id(); |
| auto* stream = it->stream(); |
| it = connect_ip_client_states_.erase(it); |
| if (!stream->write_side_closed()) { |
| stream->Reset(QUIC_STREAM_CANCELLED); |
| } |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| void MasqueClientSession::CloseConnectEthernetStream( |
| EncapsulatedEthernetSession* encapsulated_ethernet_session) { |
| for (auto it = connect_ethernet_client_states_.begin(); |
| it != connect_ethernet_client_states_.end();) { |
| if (it->encapsulated_ethernet_session() == encapsulated_ethernet_session) { |
| QUIC_DLOG(INFO) << "Removing CONNECT-ETHERNET state for stream ID " |
| << it->stream()->id(); |
| auto* stream = it->stream(); |
| it = connect_ethernet_client_states_.erase(it); |
| if (!stream->write_side_closed()) { |
| stream->Reset(QUIC_STREAM_CANCELLED); |
| } |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| void MasqueClientSession::OnConnectionClosed( |
| const QuicConnectionCloseFrame& frame, ConnectionCloseSource source) { |
| QuicSpdyClientSession::OnConnectionClosed(frame, source); |
| // Close all encapsulated sessions. |
| for (const auto& client_state : connect_udp_client_states_) { |
| client_state.encapsulated_client_session()->CloseConnection( |
| QUIC_CONNECTION_CANCELLED, "Underlying MASQUE connection was closed", |
| ConnectionCloseBehavior::SILENT_CLOSE); |
| } |
| for (const auto& client_state : connect_ip_client_states_) { |
| client_state.encapsulated_ip_session()->CloseIpSession( |
| "Underlying MASQUE connection was closed"); |
| } |
| } |
| |
| void MasqueClientSession::OnStreamClosed(QuicStreamId stream_id) { |
| if (QuicUtils::IsBidirectionalStreamId(stream_id, version()) && |
| QuicUtils::IsClientInitiatedStreamId(transport_version(), stream_id)) { |
| QuicSpdyClientStream* stream = |
| reinterpret_cast<QuicSpdyClientStream*>(GetActiveStream(stream_id)); |
| if (stream != nullptr) { |
| QUIC_DLOG(INFO) << "Stream " << stream_id |
| << " closed, got response headers:" |
| << stream->response_headers().DebugString(); |
| } |
| } |
| for (auto it = connect_udp_client_states_.begin(); |
| it != connect_udp_client_states_.end();) { |
| if (it->stream()->id() == stream_id) { |
| QUIC_DLOG(INFO) << "Stream " << stream_id |
| << " was closed, removing CONNECT-UDP state"; |
| auto* encapsulated_client_session = it->encapsulated_client_session(); |
| it = connect_udp_client_states_.erase(it); |
| encapsulated_client_session->CloseConnection( |
| QUIC_CONNECTION_CANCELLED, |
| "Underlying MASQUE CONNECT-UDP stream was closed", |
| ConnectionCloseBehavior::SILENT_CLOSE); |
| } else { |
| ++it; |
| } |
| } |
| for (auto it = connect_ip_client_states_.begin(); |
| it != connect_ip_client_states_.end();) { |
| if (it->stream()->id() == stream_id) { |
| QUIC_DLOG(INFO) << "Stream " << stream_id |
| << " was closed, removing CONNECT-IP state"; |
| auto* encapsulated_ip_session = it->encapsulated_ip_session(); |
| it = connect_ip_client_states_.erase(it); |
| encapsulated_ip_session->CloseIpSession( |
| "Underlying MASQUE CONNECT-IP stream was closed"); |
| } else { |
| ++it; |
| } |
| } |
| |
| QuicSpdyClientSession::OnStreamClosed(stream_id); |
| } |
| |
| bool MasqueClientSession::OnSettingsFrame(const SettingsFrame& frame) { |
| QUIC_DLOG(INFO) << connection_id() << " Received SETTINGS: " << frame; |
| if (!QuicSpdyClientSession::OnSettingsFrame(frame)) { |
| QUIC_DLOG(ERROR) << "Failed to parse received settings"; |
| return false; |
| } |
| if (!SupportsH3Datagram()) { |
| QUIC_DLOG(ERROR) << "Warning: MasqueClientSession without HTTP/3 Datagrams"; |
| } |
| QUIC_DLOG(INFO) << "Using HTTP Datagram: " << http_datagram_support(); |
| owner_->OnSettingsReceived(); |
| return true; |
| } |
| |
| MasqueClientSession::ConnectUdpClientState::ConnectUdpClientState( |
| QuicSpdyClientStream* stream, |
| EncapsulatedClientSession* encapsulated_client_session, |
| MasqueClientSession* masque_session, |
| const QuicSocketAddress& target_server_address) |
| : stream_(stream), |
| encapsulated_client_session_(encapsulated_client_session), |
| masque_session_(masque_session), |
| target_server_address_(target_server_address) { |
| QUICHE_DCHECK_NE(masque_session_, nullptr); |
| this->stream()->RegisterHttp3DatagramVisitor(this); |
| if (masque_session->IsBind()) { |
| this->stream()->RegisterConnectUdpBindVisitor(this); |
| } |
| } |
| |
| MasqueClientSession::ConnectUdpClientState::~ConnectUdpClientState() { |
| if (stream() != nullptr) { |
| stream()->UnregisterHttp3DatagramVisitor(); |
| if (masque_session_->IsBind()) { |
| this->stream()->UnregisterConnectUdpBindVisitor(); |
| } |
| } |
| } |
| |
| MasqueClientSession::ConnectUdpClientState::ConnectUdpClientState( |
| MasqueClientSession::ConnectUdpClientState&& other) { |
| *this = std::move(other); |
| } |
| |
| MasqueClientSession::ConnectUdpClientState& |
| MasqueClientSession::ConnectUdpClientState::operator=( |
| MasqueClientSession::ConnectUdpClientState&& other) { |
| stream_ = other.stream_; |
| encapsulated_client_session_ = other.encapsulated_client_session_; |
| masque_session_ = other.masque_session_; |
| target_server_address_ = other.target_server_address_; |
| other.stream_ = nullptr; |
| other.is_bind_ = is_bind_; |
| other.bind_context_ip_map_ = std::move(bind_context_ip_map_); |
| if (stream() != nullptr) { |
| stream()->ReplaceHttp3DatagramVisitor(this); |
| if (masque_session_->IsBind()) { |
| stream()->ReplaceConnectUdpBindVisitor(this); |
| } |
| } |
| return *this; |
| } |
| |
| void MasqueClientSession::ConnectUdpClientState::OnHttp3Datagram( |
| QuicStreamId stream_id, absl::string_view payload) { |
| QUIC_DVLOG(1) << "Received HTTP Datagram of length " << payload.size(); |
| QUICHE_DCHECK_EQ(stream_id, stream()->id()); |
| QuicDataReader reader(payload); |
| if (!is_bind_) { |
| ContextId context_id; |
| if (!reader.ReadVarInt62(&context_id)) { |
| QUIC_DLOG(ERROR) << "Failed to read context ID"; |
| return; |
| } |
| if (context_id != 0) { |
| QUIC_DLOG(ERROR) << "Ignoring HTTP Datagram with unexpected context ID " |
| << context_id; |
| return; |
| } |
| } else { |
| ContextId context_id; |
| QuicSocketAddress target_address; |
| if (!reader.ReadVarInt62(&context_id)) { |
| QUIC_DLOG(ERROR) << "Failed to read context ID"; |
| return; |
| } |
| if (!bind_context_ip_map_.contains(context_id)) { |
| // Non existent context id. |
| QUIC_DLOG(ERROR) << "Ignoring HTTP Datagram with non existent context id " |
| << context_id; |
| // Drop the packet. |
| return; |
| } |
| if (bind_context_ip_map_[context_id] == quiche::QuicheSocketAddress()) { |
| // Uncompressed Context. |
| // Parse IP and port. |
| uint8_t ip_version; |
| if (!reader.ReadUInt8(&ip_version)) { |
| QUIC_DLOG(ERROR) << "Failed to read IP version"; |
| return; |
| } |
| auto address_size = ip_version == 4 |
| ? quiche::QuicheIpAddress::kIPv4AddressSize |
| : quiche::QuicheIpAddress::kIPv6AddressSize; |
| absl::string_view ip_address_bytes; |
| if (!reader.ReadStringPiece(&ip_address_bytes, address_size)) { |
| QUIC_DLOG(ERROR) << "Failed to read IP address"; |
| return; |
| } |
| quiche::QuicheIpAddress ip_address; |
| if (!ip_address.FromPackedString(ip_address_bytes.data(), address_size)) { |
| QUIC_DLOG(ERROR) << "Failed to parse IP address"; |
| return; |
| } |
| uint16_t port; |
| if (!reader.ReadUInt16(&port)) { |
| QUIC_DLOG(ERROR) << "Failed to read port"; |
| return; |
| } |
| target_address = QuicSocketAddress(ip_address, port); |
| } else { |
| target_address = bind_context_ip_map_[context_id]; |
| } |
| } |
| |
| absl::string_view http_payload = reader.ReadRemainingPayload(); |
| encapsulated_client_session_->ProcessPacket(http_payload, |
| target_server_address_); |
| QUIC_DVLOG(1) << "Sent " << http_payload.size() |
| << " bytes to connection for stream ID " << stream_id; |
| } |
| |
| ContextId MasqueClientSession::ConnectUdpClientState::CreateCompressedContext( |
| const QuicSocketAddress& target_address) { |
| bind_context_ip_map_[next_available_context_id_] = target_address; |
| quiche::Capsule capsule = quiche::Capsule::CompressionAssign(); |
| capsule.compression_assign_capsule().context_id = next_available_context_id_; |
| capsule.compression_assign_capsule().ip_address_port = target_address; |
| |
| masque_session_->SendBindCapsule(capsule, encapsulated_client_session_); |
| // TODO(abhisinghx): Verify Acks Before using it |
| return AllocateContextId(); |
| } |
| |
| ContextId |
| MasqueClientSession::ConnectUdpClientState::CreateUncompressedContext() { |
| bind_context_ip_map_[next_available_context_id_] = |
| quiche::QuicheSocketAddress(); |
| quiche::Capsule capsule = quiche::Capsule::CompressionAssign(); |
| capsule.compression_assign_capsule().context_id = next_available_context_id_; |
| capsule.compression_assign_capsule().ip_address_port = |
| quiche::QuicheSocketAddress(); |
| QUIC_DLOG(INFO) << "Requesting open context " << next_available_context_id_; |
| masque_session_->SendBindCapsule(capsule, encapsulated_client_session_); |
| // TODO(abhisinghx): Verify capsule ack before using it. |
| // It is fine to pre-emptively send UDP packets. |
| return AllocateContextId(); |
| } |
| |
| ContextId MasqueClientSession::ConnectUdpClientState::AllocateContextId() { |
| next_available_context_id_ += 2; |
| return next_available_context_id_ - 2; |
| } |
| |
| void MasqueClientSession::ConnectUdpClientState::CloseContext( |
| ContextId context_id) { |
| bind_context_ip_map_.erase(context_id); |
| quiche::Capsule capsule = quiche::Capsule::CompressionClose(); |
| capsule.compression_close_capsule().context_id = context_id; |
| QUIC_DLOG(INFO) << "Closing context " << context_id; |
| masque_session_->SendBindCapsule(capsule, encapsulated_client_session_); |
| } |
| |
| bool MasqueClientSession::ConnectUdpClientState::OnCompressionAssignCapsule( |
| const quiche::CompressionAssignCapsule& capsule) { |
| if (!is_bind_) { |
| QUIC_DLOG(ERROR) << "Received CompressionAssignCapsule " |
| << capsule.ToString() << " when bind is not set"; |
| return false; |
| } |
| QUIC_DLOG(INFO) << "CompressionAssignCapsule received: " |
| << capsule.ToString(); |
| if (!bind_context_ip_map_.contains(capsule.context_id)) { |
| // Server is asking to create a new context. |
| if (capsule.ip_address_port == quiche::QuicheSocketAddress()) { |
| // Server is not allowed to create open context. |
| QUIC_DLOG(ERROR) << "Server is not allowed to create open context"; |
| return false; |
| } else { |
| // Compressed context requested by server. |
| if (capsule.context_id % 2 == 0) { |
| // Even contexts are client-allocated. |
| QUIC_DLOG(ERROR) << "Server is not allowed to create even contexts"; |
| return false; |
| } |
| bind_context_ip_map_[capsule.context_id] = capsule.ip_address_port; |
| // Send acknowledgement. |
| |
| quiche::Capsule to_send = quiche::Capsule::CompressionAssign(); |
| to_send.compression_assign_capsule() = capsule; |
| masque_session_->SendBindCapsule(to_send, encapsulated_client_session_); |
| return true; |
| } |
| } else if (bind_context_ip_map_[capsule.context_id] != |
| capsule.ip_address_port) { |
| // Context already exists but the target address is different. |
| QUIC_DLOG(ERROR) << "Context already exists with different target address " |
| << bind_context_ip_map_[capsule.context_id] << " and " |
| << capsule.ip_address_port; |
| return false; |
| } else { |
| // This is an ack of client's request, ignore it. |
| QUIC_DLOG(INFO) << "Ignoring ack of client's request"; |
| return true; |
| } |
| } |
| |
| bool MasqueClientSession::ConnectUdpClientState::OnCompressionCloseCapsule( |
| const quiche::CompressionCloseCapsule& capsule) { |
| if (!is_bind_) { |
| QUIC_DLOG(ERROR) << "Received CompressionCloseCapsule " |
| << capsule.ToString() << " when bind is not set"; |
| return false; |
| } |
| QUIC_DLOG(INFO) << "CompressionCloseCapsule received: " << capsule.ToString(); |
| // If we have this context id in the map, we can close it and send ack. |
| if (bind_context_ip_map_.contains(capsule.context_id)) { |
| CloseContext(capsule.context_id); |
| } |
| return true; |
| } |
| |
| std::optional<ContextId> |
| MasqueClientSession::ConnectUdpClientState::GetContextForAddress( |
| const QuicSocketAddress& target_server_address) { |
| std::optional<ContextId> uncompressed_context_id; |
| for (const auto& [context_id, address] : bind_context_ip_map_) { |
| if (address == target_server_address) { |
| return context_id; |
| } |
| if (address == quiche::QuicheSocketAddress()) { |
| uncompressed_context_id = context_id; |
| } |
| } |
| return uncompressed_context_id; |
| } |
| |
| std::optional<std::string> |
| MasqueClientSession::ConnectUdpClientState::PrepareBindPacket( |
| absl::string_view packet, const QuicSocketAddress& target_address, |
| bool use_uncompressed_context) { |
| std::string payload_out; |
| // Find existing or open context for this address. |
| std::optional<ContextId> context_id_opt = |
| GetContextForAddress(target_address); |
| ContextId context_id; |
| if (!context_id_opt.has_value()) { |
| // No context exists for this address, create a new compressed context. |
| context_id = use_uncompressed_context |
| ? CreateUncompressedContext() |
| : CreateCompressedContext(target_address); |
| } else { |
| context_id = context_id_opt.value(); |
| } |
| size_t total_size = |
| quiche::QuicheDataWriter::GetVarInt62Len(context_id) + packet.size(); |
| if (bind_context_ip_map_[context_id] == target_address) { |
| // No need to append target info, compress it. |
| payload_out.resize(total_size); |
| QuicDataWriter writer(total_size, payload_out.data()); |
| if (!writer.WriteVarInt62(context_id)) { |
| QUIC_DLOG(ERROR) << "Failed to write compressed bind packet context id " |
| << context_id; |
| return std::nullopt; |
| } |
| if (!writer.WriteStringPiece(packet)) { |
| QUIC_DLOG(ERROR) |
| << "Failed to write compressed bind packet payload for context id " |
| << context_id; |
| return std::nullopt; |
| } |
| } else { |
| // Append target info. |
| total_size += (sizeof(uint8_t) // Ip version. |
| + target_address.host().ToPackedString().size() // Ip addr. |
| + sizeof(uint16_t)); // Port. |
| payload_out.resize(total_size); |
| QuicDataWriter writer(total_size, payload_out.data()); |
| if (!writer.WriteVarInt62(context_id) || |
| !writer.WriteUInt8( |
| target_address.host().AddressFamilyToInt() == AF_INET ? 4 : 6) || |
| !writer.WriteStringPiece(target_address.host().ToPackedString()) || |
| !writer.WriteUInt16(target_address.port()) || |
| !writer.WriteStringPiece(packet)) { |
| QUIC_DLOG(ERROR) |
| << "Failed to write uncompressed bind packet for context id " |
| << context_id; |
| return std::nullopt; |
| } |
| } |
| return payload_out; |
| } |
| |
| MasqueClientSession::ConnectIpClientState::ConnectIpClientState( |
| QuicSpdyClientStream* stream, |
| EncapsulatedIpSession* encapsulated_ip_session, |
| MasqueClientSession* masque_session) |
| : stream_(stream), |
| encapsulated_ip_session_(encapsulated_ip_session), |
| masque_session_(masque_session) { |
| QUICHE_DCHECK_NE(masque_session_, nullptr); |
| this->stream()->RegisterHttp3DatagramVisitor(this); |
| this->stream()->RegisterConnectIpVisitor(this); |
| } |
| |
| MasqueClientSession::ConnectIpClientState::~ConnectIpClientState() { |
| if (stream() != nullptr) { |
| stream()->UnregisterHttp3DatagramVisitor(); |
| stream()->UnregisterConnectIpVisitor(); |
| } |
| } |
| |
| MasqueClientSession::ConnectIpClientState::ConnectIpClientState( |
| MasqueClientSession::ConnectIpClientState&& other) { |
| *this = std::move(other); |
| } |
| |
| MasqueClientSession::ConnectIpClientState& |
| MasqueClientSession::ConnectIpClientState::operator=( |
| MasqueClientSession::ConnectIpClientState&& other) { |
| stream_ = other.stream_; |
| encapsulated_ip_session_ = other.encapsulated_ip_session_; |
| masque_session_ = other.masque_session_; |
| other.stream_ = nullptr; |
| if (stream() != nullptr) { |
| stream()->ReplaceHttp3DatagramVisitor(this); |
| stream()->ReplaceConnectIpVisitor(this); |
| } |
| return *this; |
| } |
| |
| void MasqueClientSession::ConnectIpClientState::OnHttp3Datagram( |
| QuicStreamId stream_id, absl::string_view payload) { |
| QUICHE_DCHECK_EQ(stream_id, stream()->id()); |
| QuicDataReader reader(payload); |
| ContextId context_id; |
| if (!reader.ReadVarInt62(&context_id)) { |
| QUIC_DLOG(ERROR) << "Failed to read context ID"; |
| return; |
| } |
| if (context_id != kConnectIpPayloadContextId) { |
| QUIC_DLOG(ERROR) << "Ignoring HTTP Datagram with unexpected context ID " |
| << context_id; |
| return; |
| } |
| absl::string_view http_payload = reader.ReadRemainingPayload(); |
| encapsulated_ip_session_->ProcessIpPacket(http_payload); |
| QUIC_DVLOG(1) << "Sent " << http_payload.size() |
| << " IP bytes to connection for stream ID " << stream_id; |
| } |
| |
| bool MasqueClientSession::ConnectIpClientState::OnAddressAssignCapsule( |
| const AddressAssignCapsule& capsule) { |
| return encapsulated_ip_session_->OnAddressAssignCapsule(capsule); |
| } |
| |
| bool MasqueClientSession::ConnectIpClientState::OnAddressRequestCapsule( |
| const AddressRequestCapsule& capsule) { |
| return encapsulated_ip_session_->OnAddressRequestCapsule(capsule); |
| } |
| |
| bool MasqueClientSession::ConnectIpClientState::OnRouteAdvertisementCapsule( |
| const RouteAdvertisementCapsule& capsule) { |
| return encapsulated_ip_session_->OnRouteAdvertisementCapsule(capsule); |
| } |
| |
| void MasqueClientSession::ConnectIpClientState::OnHeadersWritten() {} |
| |
| // ConnectEthernetClientState |
| |
| MasqueClientSession::ConnectEthernetClientState::ConnectEthernetClientState( |
| QuicSpdyClientStream* stream, |
| EncapsulatedEthernetSession* encapsulated_ethernet_session, |
| MasqueClientSession* masque_session) |
| : stream_(stream), |
| encapsulated_ethernet_session_(encapsulated_ethernet_session), |
| masque_session_(masque_session) { |
| QUICHE_DCHECK_NE(masque_session_, nullptr); |
| this->stream()->RegisterHttp3DatagramVisitor(this); |
| } |
| |
| MasqueClientSession::ConnectEthernetClientState::~ConnectEthernetClientState() { |
| if (stream() != nullptr) { |
| stream()->UnregisterHttp3DatagramVisitor(); |
| } |
| } |
| |
| MasqueClientSession::ConnectEthernetClientState::ConnectEthernetClientState( |
| MasqueClientSession::ConnectEthernetClientState&& other) { |
| *this = std::move(other); |
| } |
| |
| MasqueClientSession::ConnectEthernetClientState& |
| MasqueClientSession::ConnectEthernetClientState::operator=( |
| MasqueClientSession::ConnectEthernetClientState&& other) { |
| stream_ = other.stream_; |
| encapsulated_ethernet_session_ = other.encapsulated_ethernet_session_; |
| masque_session_ = other.masque_session_; |
| other.stream_ = nullptr; |
| if (stream() != nullptr) { |
| stream()->ReplaceHttp3DatagramVisitor(this); |
| } |
| return *this; |
| } |
| |
| void MasqueClientSession::ConnectEthernetClientState::OnHttp3Datagram( |
| QuicStreamId stream_id, absl::string_view payload) { |
| QUICHE_DCHECK_EQ(stream_id, stream()->id()); |
| QuicDataReader reader(payload); |
| ContextId context_id; |
| if (!reader.ReadVarInt62(&context_id)) { |
| QUIC_DLOG(ERROR) << "Failed to read context ID"; |
| return; |
| } |
| if (context_id != kConnectEthernetPayloadContextId) { |
| QUIC_DLOG(ERROR) << "Ignoring HTTP Datagram with unexpected context ID " |
| << context_id; |
| return; |
| } |
| absl::string_view http_payload = reader.ReadRemainingPayload(); |
| encapsulated_ethernet_session_->ProcessEthernetFrame(http_payload); |
| QUIC_DVLOG(1) << "Sent " << http_payload.size() |
| << " ETHERNET bytes to connection for stream ID " << stream_id; |
| } |
| |
| // End ConnectEthernetClientState |
| |
| quiche::QuicheIpAddress MasqueClientSession::GetFakeAddress( |
| absl::string_view hostname) { |
| QUICHE_CHECK(!hostname.empty()); |
| quiche::QuicheIpAddress address; |
| uint8_t address_bytes[16] = {0xFD}; |
| quiche::QuicheRandom::GetInstance()->RandBytes(&address_bytes[1], |
| sizeof(address_bytes) - 1); |
| address.FromPackedString(reinterpret_cast<const char*>(address_bytes), |
| sizeof(address_bytes)); |
| std::string address_bytes_string(reinterpret_cast<const char*>(address_bytes), |
| sizeof(address_bytes)); |
| fake_addresses_[address_bytes_string] = std::string(hostname); |
| return address; |
| } |
| |
| void MasqueClientSession::RemoveFakeAddress( |
| const quiche::QuicheIpAddress& fake_address) { |
| fake_addresses_.erase(fake_address.ToPackedString()); |
| } |
| |
| void MasqueClientSession::EnableConcealedAuth(absl::string_view key_id, |
| absl::string_view private_key, |
| absl::string_view public_key) { |
| QUICHE_CHECK(!key_id.empty()); |
| QUICHE_CHECK_EQ(private_key.size(), |
| static_cast<size_t>(ED25519_PRIVATE_KEY_LEN)); |
| QUICHE_CHECK_EQ(public_key.size(), |
| static_cast<size_t>(ED25519_PUBLIC_KEY_LEN)); |
| concealed_auth_key_id_ = key_id; |
| concealed_auth_private_key_ = private_key; |
| concealed_auth_public_key_ = public_key; |
| } |
| |
| QuicSpdyClientStream* MasqueClientSession::SendGetRequest( |
| absl::string_view path) { |
| QuicSpdyClientStream* stream = CreateOutgoingBidirectionalStream(); |
| if (stream == nullptr) { |
| // Stream flow control limits prevented us from opening a new stream. |
| QUIC_DLOG(ERROR) << "Failed to open GET stream"; |
| return nullptr; |
| } |
| |
| QuicUrl url(uri_template_); |
| std::string scheme = url.scheme(); |
| std::string authority = url.HostPort(); |
| |
| QUIC_DLOG(INFO) << "Sending GET request on stream " << stream->id() |
| << " scheme=\"" << scheme << "\" authority=\"" << authority |
| << "\" path=\"" << path << "\""; |
| |
| // Send the request. |
| quiche::HttpHeaderBlock headers; |
| headers[":method"] = "GET"; |
| headers[":scheme"] = scheme; |
| headers[":authority"] = authority; |
| headers[":path"] = path; |
| AddAdditionalHeaders(headers, url); |
| QUIC_DVLOG(1) << "Sending request headers: " << headers.DebugString(); |
| // Setting the stream visitor is required to enable reading of the response |
| // body from the stream. |
| stream->set_visitor(this); |
| size_t bytes_sent = |
| stream->SendRequest(std::move(headers), /*body=*/"", /*fin=*/true); |
| if (bytes_sent == 0) { |
| QUIC_DLOG(ERROR) << "Failed to send GET request"; |
| return nullptr; |
| } |
| return stream; |
| } |
| |
| void MasqueClientSession::OnClose(QuicSpdyStream* stream) { |
| QUIC_DVLOG(1) << "Closing stream " << stream->id(); |
| } |
| |
| std::optional<std::string> MasqueClientSession::ComputeConcealedAuthHeader( |
| const QuicUrl& url) { |
| if (concealed_auth_private_key_.empty()) { |
| return std::nullopt; |
| } |
| std::string scheme = url.scheme(); |
| std::string host = url.host(); |
| uint16_t port = url.port(); |
| std::string realm = ""; |
| std::string key_exporter_output; |
| std::string key_exporter_context = ComputeConcealedAuthContext( |
| kEd25519SignatureScheme, concealed_auth_key_id_, |
| concealed_auth_public_key_, scheme, host, port, realm); |
| QUIC_DVLOG(1) << "key_exporter_context: " |
| << absl::WebSafeBase64Escape(key_exporter_context); |
| QUICHE_DCHECK(!key_exporter_context.empty()); |
| if (!GetMutableCryptoStream()->ExportKeyingMaterial( |
| kConcealedAuthLabel, key_exporter_context, kConcealedAuthExporterSize, |
| &key_exporter_output)) { |
| QUIC_LOG(FATAL) << "Concealed auth TLS exporter failed"; |
| return std::nullopt; |
| } |
| QUICHE_CHECK_EQ(key_exporter_output.size(), kConcealedAuthExporterSize); |
| std::string signature_input = |
| key_exporter_output.substr(0, kConcealedAuthSignatureInputSize); |
| QUIC_DVLOG(1) << "signature_input: " |
| << absl::WebSafeBase64Escape(signature_input); |
| std::string verification = key_exporter_output.substr( |
| kConcealedAuthSignatureInputSize, kConcealedAuthVerificationSize); |
| std::string data_covered_by_signature = |
| ConcealedAuthDataCoveredBySignature(signature_input); |
| QUIC_DVLOG(1) << "data_covered_by_signature: " |
| << absl::WebSafeBase64Escape(data_covered_by_signature); |
| uint8_t signature[ED25519_SIGNATURE_LEN]; |
| if (ED25519_sign( |
| signature, |
| reinterpret_cast<const uint8_t*>(data_covered_by_signature.data()), |
| data_covered_by_signature.size(), |
| reinterpret_cast<const uint8_t*>( |
| concealed_auth_private_key_.data())) != 1) { |
| QUIC_LOG(FATAL) << "Concealed auth signature failed"; |
| return std::nullopt; |
| } |
| return absl::StrCat( |
| "Concealed k=", absl::WebSafeBase64Escape(concealed_auth_key_id_), |
| ", a=", absl::WebSafeBase64Escape(concealed_auth_public_key_), ", p=", |
| absl::WebSafeBase64Escape(absl::string_view( |
| reinterpret_cast<const char*>(signature), sizeof(signature))), |
| ", s=", kEd25519SignatureScheme, |
| ", v=", absl::WebSafeBase64Escape(verification)); |
| } |
| |
| void MasqueClientSession::AddAdditionalHeaders(quiche::HttpHeaderBlock& headers, |
| const QuicUrl& url) { |
| std::optional<std::string> concealed_auth_header = |
| ComputeConcealedAuthHeader(url); |
| if (concealed_auth_header.has_value()) { |
| headers["authorization"] = *concealed_auth_header; |
| } |
| if (additional_headers_.empty()) { |
| return; |
| } |
| for (absl::string_view sp : absl::StrSplit(additional_headers_, ';')) { |
| quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&sp); |
| if (sp.empty()) { |
| continue; |
| } |
| std::vector<absl::string_view> kv = |
| absl::StrSplit(sp, absl::MaxSplits(':', 1)); |
| quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]); |
| quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]); |
| headers[kv[0]] = kv[1]; |
| } |
| } |
| |
| } // namespace quic |