|  | // 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 "quic/masque/masque_client_session.h" | 
|  |  | 
|  | #include <string> | 
|  |  | 
|  | #include "absl/algorithm/container.h" | 
|  | #include "absl/container/flat_hash_map.h" | 
|  | #include "absl/container/flat_hash_set.h" | 
|  | #include "absl/strings/str_cat.h" | 
|  | #include "url/url_canon.h" | 
|  | #include "quic/core/http/spdy_utils.h" | 
|  | #include "quic/core/quic_data_reader.h" | 
|  | #include "quic/core/quic_utils.h" | 
|  | #include "quic/platform/api/quic_socket_address.h" | 
|  | #include "quic/tools/quic_url.h" | 
|  | #include "common/platform/api/quiche_url_utils.h" | 
|  |  | 
|  | namespace quic { | 
|  |  | 
|  | 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, | 
|  | QuicClientPushPromiseIndex* push_promise_index, Owner* owner) | 
|  | : QuicSpdyClientSession(config, supported_versions, connection, server_id, | 
|  | crypto_config, push_promise_index), | 
|  | masque_mode_(masque_mode), | 
|  | uri_template_(uri_template), | 
|  | owner_(owner), | 
|  | compression_engine_(this) {} | 
|  |  | 
|  | void MasqueClientSession::OnMessageReceived(absl::string_view message) { | 
|  | if (masque_mode_ == MasqueMode::kLegacy) { | 
|  | QUIC_DVLOG(1) << "Received DATAGRAM frame of length " << message.length(); | 
|  | QuicConnectionId client_connection_id, server_connection_id; | 
|  | QuicSocketAddress target_server_address; | 
|  | std::vector<char> packet; | 
|  | bool version_present; | 
|  | if (!compression_engine_.DecompressDatagram( | 
|  | message, &client_connection_id, &server_connection_id, | 
|  | &target_server_address, &packet, &version_present)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto connection_id_registration = | 
|  | client_connection_id_registrations_.find(client_connection_id); | 
|  | if (connection_id_registration == | 
|  | client_connection_id_registrations_.end()) { | 
|  | QUIC_DLOG(ERROR) << "MasqueClientSession failed to dispatch " | 
|  | << client_connection_id; | 
|  | return; | 
|  | } | 
|  | EncapsulatedClientSession* encapsulated_client_session = | 
|  | connection_id_registration->second; | 
|  | encapsulated_client_session->ProcessPacket( | 
|  | absl::string_view(packet.data(), packet.size()), target_server_address); | 
|  |  | 
|  | QUIC_DVLOG(1) << "Sent " << packet.size() << " bytes to connection for " | 
|  | << client_connection_id; | 
|  | return; | 
|  | } | 
|  | QUICHE_DCHECK_EQ(masque_mode_, MasqueMode::kOpen); | 
|  | QuicSpdySession::OnMessageReceived(message); | 
|  | } | 
|  |  | 
|  | void MasqueClientSession::OnMessageAcked(QuicMessageId message_id, | 
|  | QuicTime /*receive_timestamp*/) { | 
|  | QUIC_DVLOG(1) << "Received ack for DATAGRAM frame " << message_id; | 
|  | } | 
|  |  | 
|  | void MasqueClientSession::OnMessageLost(QuicMessageId message_id) { | 
|  | QUIC_DVLOG(1) << "We believe DATAGRAM frame " << message_id << " was lost"; | 
|  | } | 
|  |  | 
|  | const MasqueClientSession::ConnectUdpClientState* | 
|  | MasqueClientSession::GetOrCreateConnectUdpClientState( | 
|  | const QuicSocketAddress& target_server_address, | 
|  | EncapsulatedClientSession* encapsulated_client_session) { | 
|  | for (const ConnectUdpClientState& client_state : connect_udp_client_states_) { | 
|  | if (client_state.target_server_address() == target_server_address && | 
|  | client_state.encapsulated_client_session() == | 
|  | encapsulated_client_session) { | 
|  | // Found existing CONNECT-UDP request. | 
|  | return &client_state; | 
|  | } | 
|  | } | 
|  | // No CONNECT-UDP request found, create a new one. | 
|  |  | 
|  | 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) | 
|  | << "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; | 
|  | parameters["target_host"] = target_server_address.host().ToString(); | 
|  | parameters["target_port"] = absl::StrCat(target_server_address.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_server_address; | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | 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_server_address; | 
|  | 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_server_address | 
|  | << " on stream " << stream->id() << " scheme=\"" << scheme | 
|  | << "\" authority=\"" << authority << "\" path=\"" | 
|  | << canonicalized_path << "\""; | 
|  |  | 
|  | // Send the request. | 
|  | spdy::Http2HeaderBlock headers; | 
|  | headers[":method"] = "CONNECT"; | 
|  | headers[":protocol"] = "connect-udp"; | 
|  | headers[":scheme"] = scheme; | 
|  | headers[":authority"] = authority; | 
|  | headers[":path"] = canonicalized_path; | 
|  | headers["connect-udp-version"] = "6"; | 
|  | if (http_datagram_support() == HttpDatagramSupport::kDraft00) { | 
|  | SpdyUtils::AddDatagramFlowIdHeader(&headers, stream->id()); | 
|  | } | 
|  | 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; | 
|  | } | 
|  |  | 
|  | absl::optional<QuicDatagramContextId> context_id; | 
|  | connect_udp_client_states_.push_back( | 
|  | ConnectUdpClientState(stream, encapsulated_client_session, this, | 
|  | context_id, target_server_address)); | 
|  | return &connect_udp_client_states_.back(); | 
|  | } | 
|  |  | 
|  | void MasqueClientSession::SendPacket( | 
|  | QuicConnectionId client_connection_id, | 
|  | QuicConnectionId server_connection_id, absl::string_view packet, | 
|  | const QuicSocketAddress& target_server_address, | 
|  | EncapsulatedClientSession* encapsulated_client_session) { | 
|  | if (masque_mode_ == MasqueMode::kLegacy) { | 
|  | compression_engine_.CompressAndSendPacket(packet, client_connection_id, | 
|  | server_connection_id, | 
|  | target_server_address); | 
|  | return; | 
|  | } | 
|  | 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; | 
|  | } | 
|  |  | 
|  | MessageStatus message_status = SendHttp3Datagram( | 
|  | connect_udp->stream()->id(), connect_udp->context_id(), packet); | 
|  |  | 
|  | QUIC_DVLOG(1) << "Sent packet to " << target_server_address | 
|  | << " compressed with stream ID " << connect_udp->stream()->id() | 
|  | << " context ID " | 
|  | << (connect_udp->context_id().has_value() | 
|  | ? absl::StrCat(connect_udp->context_id().value()) | 
|  | : "none") | 
|  | << " and got message status " | 
|  | << MessageStatusToString(message_status); | 
|  | } | 
|  |  | 
|  | void MasqueClientSession::RegisterConnectionId( | 
|  | QuicConnectionId client_connection_id, | 
|  | EncapsulatedClientSession* encapsulated_client_session) { | 
|  | QUIC_DLOG(INFO) << "Registering " << client_connection_id | 
|  | << " to encapsulated client"; | 
|  | QUICHE_DCHECK( | 
|  | client_connection_id_registrations_.find(client_connection_id) == | 
|  | client_connection_id_registrations_.end() || | 
|  | client_connection_id_registrations_[client_connection_id] == | 
|  | encapsulated_client_session); | 
|  | client_connection_id_registrations_[client_connection_id] = | 
|  | encapsulated_client_session; | 
|  | } | 
|  |  | 
|  | void MasqueClientSession::UnregisterConnectionId( | 
|  | QuicConnectionId client_connection_id, | 
|  | EncapsulatedClientSession* encapsulated_client_session) { | 
|  | QUIC_DLOG(INFO) << "Unregistering " << client_connection_id; | 
|  | if (masque_mode_ == MasqueMode::kLegacy) { | 
|  | if (client_connection_id_registrations_.find(client_connection_id) != | 
|  | client_connection_id_registrations_.end()) { | 
|  | client_connection_id_registrations_.erase(client_connection_id); | 
|  | owner_->UnregisterClientConnectionId(client_connection_id); | 
|  | compression_engine_.UnregisterClientConnectionId(client_connection_id); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | 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 state for stream ID " << it->stream()->id() | 
|  | << " context ID " | 
|  | << (it->context_id().has_value() | 
|  | ? absl::StrCat(it->context_id().value()) | 
|  | : "none"); | 
|  | 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::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); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 state for context ID " | 
|  | << (it->context_id().has_value() | 
|  | ? absl::StrCat(it->context_id().value()) | 
|  | : "none"); | 
|  | 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; | 
|  | } | 
|  | } | 
|  |  | 
|  | QuicSpdyClientSession::OnStreamClosed(stream_id); | 
|  | } | 
|  |  | 
|  | bool MasqueClientSession::OnSettingsFrame(const SettingsFrame& frame) { | 
|  | QUIC_DLOG(INFO) << "Received SETTINGS: " << frame; | 
|  | if (!QuicSpdyClientSession::OnSettingsFrame(frame)) { | 
|  | QUIC_DLOG(ERROR) << "Failed to parse received settings"; | 
|  | return false; | 
|  | } | 
|  | if (!SupportsH3Datagram()) { | 
|  | QUIC_DLOG(ERROR) << "Refusing to use MASQUE without HTTP/3 Datagrams"; | 
|  | return false; | 
|  | } | 
|  | 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, | 
|  | absl::optional<QuicDatagramContextId> context_id, | 
|  | const QuicSocketAddress& target_server_address) | 
|  | : stream_(stream), | 
|  | encapsulated_client_session_(encapsulated_client_session), | 
|  | masque_session_(masque_session), | 
|  | context_id_(context_id), | 
|  | target_server_address_(target_server_address) { | 
|  | QUICHE_DCHECK_NE(masque_session_, nullptr); | 
|  | this->stream()->RegisterHttp3DatagramRegistrationVisitor(this); | 
|  | this->stream()->RegisterHttp3DatagramContextId( | 
|  | this->context_id(), DatagramFormatType::UDP_PAYLOAD, | 
|  | /*format_additional_data=*/absl::string_view(), this); | 
|  | } | 
|  |  | 
|  | MasqueClientSession::ConnectUdpClientState::~ConnectUdpClientState() { | 
|  | if (stream() != nullptr) { | 
|  | stream()->UnregisterHttp3DatagramContextId(context_id()); | 
|  | stream()->UnregisterHttp3DatagramRegistrationVisitor(); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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_; | 
|  | context_id_ = other.context_id_; | 
|  | target_server_address_ = other.target_server_address_; | 
|  | other.stream_ = nullptr; | 
|  | if (stream() != nullptr) { | 
|  | stream()->MoveHttp3DatagramRegistration(this); | 
|  | stream()->MoveHttp3DatagramContextIdRegistration(context_id(), this); | 
|  | } | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | void MasqueClientSession::ConnectUdpClientState::OnHttp3Datagram( | 
|  | QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, | 
|  | absl::string_view payload) { | 
|  | QUICHE_DCHECK_EQ(stream_id, stream()->id()); | 
|  | QUICHE_DCHECK(context_id == context_id_); | 
|  | encapsulated_client_session_->ProcessPacket(payload, target_server_address_); | 
|  | QUIC_DVLOG(1) << "Sent " << payload.size() | 
|  | << " bytes to connection for stream ID " << stream_id | 
|  | << " context ID " | 
|  | << (context_id.has_value() ? absl::StrCat(context_id.value()) | 
|  | : "none"); | 
|  | } | 
|  |  | 
|  | void MasqueClientSession::ConnectUdpClientState::OnContextReceived( | 
|  | QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, | 
|  | DatagramFormatType format_type, absl::string_view format_additional_data) { | 
|  | if (stream_id != stream_->id()) { | 
|  | QUIC_BUG(MASQUE client bad datagram context registration) | 
|  | << "Registered stream ID " << stream_id << ", expected " | 
|  | << stream_->id(); | 
|  | return; | 
|  | } | 
|  | if (format_type != DatagramFormatType::UDP_PAYLOAD) { | 
|  | QUIC_DLOG(INFO) << "Ignoring unexpected datagram format type " | 
|  | << DatagramFormatTypeToString(format_type); | 
|  | return; | 
|  | } | 
|  | if (!format_additional_data.empty()) { | 
|  | QUIC_DLOG(ERROR) | 
|  | << "Received non-empty format additional data for context ID " | 
|  | << (context_id_.has_value() ? context_id_.value() : 0) | 
|  | << " on stream ID " << stream()->id(); | 
|  | masque_session_->ResetStream(stream()->id(), QUIC_STREAM_CANCELLED); | 
|  | return; | 
|  | } | 
|  | if (context_id != context_id_) { | 
|  | QUIC_DLOG(INFO) | 
|  | << "Ignoring unexpected context ID " | 
|  | << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none") | 
|  | << " instead of " | 
|  | << (context_id_.has_value() ? absl::StrCat(context_id_.value()) | 
|  | : "none") | 
|  | << " on stream ID " << stream_->id(); | 
|  | return; | 
|  | } | 
|  | // Do nothing since the client registers first and we currently ignore | 
|  | // extensions. | 
|  | } | 
|  |  | 
|  | void MasqueClientSession::ConnectUdpClientState::OnContextClosed( | 
|  | QuicStreamId stream_id, absl::optional<QuicDatagramContextId> context_id, | 
|  | ContextCloseCode close_code, absl::string_view close_details) { | 
|  | if (stream_id != stream_->id()) { | 
|  | QUIC_BUG(MASQUE client bad datagram context registration) | 
|  | << "Closed context on stream ID " << stream_id << ", expected " | 
|  | << stream_->id(); | 
|  | return; | 
|  | } | 
|  | if (context_id != context_id_) { | 
|  | QUIC_DLOG(INFO) | 
|  | << "Ignoring unexpected close of context ID " | 
|  | << (context_id.has_value() ? absl::StrCat(context_id.value()) : "none") | 
|  | << " instead of " | 
|  | << (context_id_.has_value() ? absl::StrCat(context_id_.value()) | 
|  | : "none") | 
|  | << " on stream ID " << stream_->id(); | 
|  | return; | 
|  | } | 
|  | QUIC_DLOG(INFO) << "Received datagram context close with close code " | 
|  | << close_code << " close details \"" << close_details | 
|  | << "\" on stream ID " << stream_->id() << ", closing stream"; | 
|  | masque_session_->ResetStream(stream_->id(), QUIC_STREAM_CANCELLED); | 
|  | } | 
|  |  | 
|  | }  // namespace quic |