| // 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 |