Introduce MASQUE, part 2: client code This CL introduces the client code for MASQUE as defined by <https://tools.ietf.org/html/draft-schinazi-masque>. Most of the work here is plumbing in order to override the right methods of the QUIC codebase. The meat of the MASQUE protocol work is in the parent cl/278956073. gfe-relnote: n/a, adds unused code PiperOrigin-RevId: 285443244 Change-Id: I76c66a4d89b8b70aada4f15f03ac5ec139ada22f
diff --git a/quic/masque/masque_client_bin.cc b/quic/masque/masque_client_bin.cc new file mode 100644 index 0000000..8bdeef5 --- /dev/null +++ b/quic/masque/masque_client_bin.cc
@@ -0,0 +1,93 @@ +// 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. + +// This file is reponsible for the masque_client binary. It allows testing +// our MASQUE client code by connecting to a MASQUE proxy and then sending +// HTTP/3 requests to web servers tunnelled over that MASQUE connection. +// e.g.: masque_client $PROXY_HOST:$PROXY_PORT $URL1 $URL2 + +#include <memory> + +#include "net/third_party/quiche/src/quic/core/quic_server_id.h" +#include "net/third_party/quiche/src/quic/masque/masque_client_tools.h" +#include "net/third_party/quiche/src/quic/masque/masque_encapsulated_epoll_client.h" +#include "net/third_party/quiche/src/quic/masque/masque_epoll_client.h" +#include "net/third_party/quiche/src/quic/masque/masque_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_default_proof_providers.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h" +#include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h" +#include "net/third_party/quiche/src/quic/tools/quic_url.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_str_cat.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h" + +DEFINE_QUIC_COMMAND_LINE_FLAG(bool, + disable_certificate_verification, + false, + "If true, don't verify the server certificate."); + +namespace quic { + +namespace { + +int RunMasqueClient(int argc, char* argv[]) { + QuicSystemEventLoop event_loop("masque_client"); + const char* usage = "Usage: masque_client [options] <url>"; + + // The first non-flag argument is the MASQUE server. All subsequent ones are + // interpreted as URLs to fetch via the MASQUE server. + std::vector<std::string> urls = QuicParseCommandLineFlags(usage, argc, argv); + if (urls.empty()) { + QuicPrintCommandLineFlagHelp(usage); + return 1; + } + + const bool disable_certificate_verification = + GetQuicFlag(FLAGS_disable_certificate_verification); + QuicEpollServer epoll_server; + + QuicUrl masque_url(urls[0], "https"); + if (masque_url.host().empty()) { + masque_url = QuicUrl(quiche::QuicheStrCat("https://", urls[0]), "https"); + } + if (masque_url.host().empty()) { + QUIC_LOG(ERROR) << "Failed to parse MASQUE server address " << urls[0]; + return 1; + } + std::unique_ptr<ProofVerifier> proof_verifier; + if (disable_certificate_verification) { + proof_verifier = std::make_unique<FakeProofVerifier>(); + } else { + proof_verifier = CreateDefaultProofVerifier(masque_url.host()); + } + std::unique_ptr<MasqueEpollClient> masque_client = + MasqueEpollClient::Create(masque_url.host(), masque_url.port(), + &epoll_server, std::move(proof_verifier)); + if (masque_client == nullptr) { + return 1; + } + + std::cerr << "MASQUE is connected " << masque_client->connection_id() + << std::endl; + + for (size_t i = 1; i < urls.size(); ++i) { + if (!tools::SendEncapsulatedMasqueRequest( + masque_client.get(), &epoll_server, urls[i], + disable_certificate_verification)) { + return 1; + } + } + + return 0; +} + +} // namespace + +} // namespace quic + +int main(int argc, char* argv[]) { + return quic::RunMasqueClient(argc, argv); +}
diff --git a/quic/masque/masque_client_session.cc b/quic/masque/masque_client_session.cc new file mode 100644 index 0000000..2d7c790 --- /dev/null +++ b/quic/masque/masque_client_session.cc
@@ -0,0 +1,95 @@ +// 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 "net/third_party/quiche/src/quic/masque/masque_client_session.h" + +namespace quic { + +MasqueClientSession::MasqueClientSession( + 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), + owner_(owner), + compression_engine_(this) {} + +void MasqueClientSession::OnMessageReceived(quiche::QuicheStringPiece message) { + QUIC_DVLOG(1) << "Received DATAGRAM frame of length " << message.length(); + + QuicConnectionId client_connection_id, server_connection_id; + QuicSocketAddress server_address; + std::string packet; + bool version_present; + if (!compression_engine_.DecompressDatagram( + message, &client_connection_id, &server_connection_id, + &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(packet, server_address); + + QUIC_DVLOG(1) << "Sent " << packet.length() << " bytes to connection for " + << client_connection_id; +} + +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"; +} + +void MasqueClientSession::SendPacket(QuicConnectionId client_connection_id, + QuicConnectionId server_connection_id, + quiche::QuicheStringPiece packet, + const QuicSocketAddress& server_address) { + compression_engine_.CompressAndSendPacket( + packet, client_connection_id, server_connection_id, server_address); +} + +void MasqueClientSession::RegisterConnectionId( + QuicConnectionId client_connection_id, + EncapsulatedClientSession* encapsulated_client_session) { + QUIC_DLOG(INFO) << "Registering " << client_connection_id + << " to encapsulated client"; + 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) { + QUIC_DLOG(INFO) << "Unregistering " << client_connection_id; + 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); + } +} + +} // namespace quic
diff --git a/quic/masque/masque_client_session.h b/quic/masque/masque_client_session.h new file mode 100644 index 0000000..acf2111 --- /dev/null +++ b/quic/masque/masque_client_session.h
@@ -0,0 +1,99 @@ +// 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. + +#ifndef QUICHE_QUIC_MASQUE_MASQUE_CLIENT_SESSION_H_ +#define QUICHE_QUIC_MASQUE_MASQUE_CLIENT_SESSION_H_ + +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h" +#include "net/third_party/quiche/src/quic/masque/masque_compression_engine.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_export.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" + +namespace quic { + +// QUIC client session for connection to MASQUE proxy. This session establishes +// a connection to a MASQUE proxy and handles sending and receiving DATAGRAM +// frames for operation of the MASQUE protocol. Multiple end-to-end encapsulated +// sessions can then coexist inside this session. Once these are created, they +// need to be registered with this session. +class QUIC_NO_EXPORT MasqueClientSession : public QuicSpdyClientSession { + public: + // Interface meant to be implemented by the owner of the + // MasqueClientSession instance. + class QUIC_NO_EXPORT Owner { + public: + virtual ~Owner() {} + + // Notifies the owner that the client connection ID is no longer in use. + virtual void UnregisterClientConnectionId( + QuicConnectionId client_connection_id) = 0; + }; + // Interface meant to be implemented by encapsulated client sessions, i.e. + // the end-to-end QUIC client sessions that run inside MASQUE encapsulation. + class QUIC_NO_EXPORT EncapsulatedClientSession { + public: + virtual ~EncapsulatedClientSession() {} + + // Process packet that was just decapsulated. + virtual void ProcessPacket(quiche::QuicheStringPiece packet, + QuicSocketAddress server_address) = 0; + }; + + // Takes ownership of |connection|, but not of |crypto_config| or + // |push_promise_index| or |owner|. All pointers must be non-null. Caller + // must ensure that |push_promise_index| and |owner| stay valid for the + // lifetime of the newly created MasqueClientSession. + MasqueClientSession(const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection, + const QuicServerId& server_id, + QuicCryptoClientConfig* crypto_config, + QuicClientPushPromiseIndex* push_promise_index, + Owner* owner); + + // Disallow copy and assign. + MasqueClientSession(const MasqueClientSession&) = delete; + MasqueClientSession& operator=(const MasqueClientSession&) = delete; + + // From QuicSession. + void OnMessageReceived(quiche::QuicheStringPiece message) override; + + void OnMessageAcked(QuicMessageId message_id, + QuicTime receive_timestamp) override; + + void OnMessageLost(QuicMessageId message_id) override; + + // Send encapsulated packet. + void SendPacket(QuicConnectionId client_connection_id, + QuicConnectionId server_connection_id, + quiche::QuicheStringPiece packet, + const QuicSocketAddress& server_address); + + // Register encapsulated client. This allows clients that are encapsulated + // within this MASQUE session to indicate they own a given client connection + // ID so incoming packets with that connection ID are routed back to them. + // Callers must not register a second different |encapsulated_client_session| + // with the same |client_connection_id|. Every call must be matched with a + // call to UnregisterConnectionId. + void RegisterConnectionId( + QuicConnectionId client_connection_id, + EncapsulatedClientSession* encapsulated_client_session); + + // Unregister encapsulated client. |client_connection_id| must match a + // value previously passed to RegisterConnectionId. + void UnregisterConnectionId(QuicConnectionId client_connection_id); + + private: + QuicUnorderedMap<QuicConnectionId, + EncapsulatedClientSession*, + QuicConnectionIdHash> + client_connection_id_registrations_; + Owner* owner_; // Unowned; + MasqueCompressionEngine compression_engine_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_MASQUE_MASQUE_CLIENT_SESSION_H_
diff --git a/quic/masque/masque_client_tools.cc b/quic/masque/masque_client_tools.cc new file mode 100644 index 0000000..7b96abe --- /dev/null +++ b/quic/masque/masque_client_tools.cc
@@ -0,0 +1,107 @@ +// 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 "net/third_party/quiche/src/quic/masque/masque_client_tools.h" +#include "net/third_party/quiche/src/quic/masque/masque_encapsulated_epoll_client.h" +#include "net/third_party/quiche/src/quic/masque/masque_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_default_proof_providers.h" +#include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h" +#include "net/third_party/quiche/src/quic/tools/quic_url.h" + +namespace quic { +namespace tools { + +bool SendEncapsulatedMasqueRequest(MasqueEpollClient* masque_client, + QuicEpollServer* epoll_server, + std::string url_string, + bool disable_certificate_verification) { + const QuicUrl url(url_string, "https"); + std::unique_ptr<ProofVerifier> proof_verifier; + if (disable_certificate_verification) { + proof_verifier = std::make_unique<FakeProofVerifier>(); + } else { + proof_verifier = CreateDefaultProofVerifier(url.host()); + } + + // Build the client, and try to connect. + const QuicSocketAddress addr = + LookupAddress(url.host(), quiche::QuicheStrCat(url.port())); + if (!addr.IsInitialized()) { + QUIC_LOG(ERROR) << "Unable to resolve address: " << url.host(); + return false; + } + const QuicServerId server_id(url.host(), url.port()); + auto client = std::make_unique<MasqueEncapsulatedEpollClient>( + addr, server_id, epoll_server, std::move(proof_verifier), masque_client); + + if (client == nullptr) { + QUIC_LOG(ERROR) << "Failed to create MasqueEncapsulatedEpollClient for " + << url_string; + return false; + } + + client->set_initial_max_packet_length(kMasqueMaxEncapsulatedPacketSize); + client->set_drop_response_body(false); + if (!client->Initialize()) { + QUIC_LOG(ERROR) << "Failed to initialize MasqueEncapsulatedEpollClient for " + << url_string; + return false; + } + + if (!client->Connect()) { + QuicErrorCode error = client->session()->error(); + QUIC_LOG(ERROR) << "Failed to connect with client " + << client->session()->connection()->client_connection_id() + << " server " << client->session()->connection_id() + << " to " << url.HostPort() + << ". Error: " << QuicErrorCodeToString(error); + return false; + } + + QUIC_LOG(INFO) << "Connected client " + << client->session()->connection()->client_connection_id() + << " server " << client->session()->connection_id() << " for " + << url_string; + + // Construct the string body from flags, if provided. + // TODO(dschinazi) Add support for HTTP POST and non-empty bodies. + const std::string body = ""; + + // Construct a GET or POST request for supplied URL. + spdy::SpdyHeaderBlock header_block; + header_block[":method"] = "GET"; + header_block[":scheme"] = url.scheme(); + header_block[":authority"] = url.HostPort(); + header_block[":path"] = url.PathParamsQuery(); + + // Make sure to store the response, for later output. + client->set_store_response(true); + + // Send the MASQUE init request. + client->SendRequestAndWaitForResponse(header_block, body, + /*fin=*/true); + + if (!client->connected()) { + QUIC_LOG(ERROR) << "Request for " << url_string + << " caused connection failure. Error: " + << QuicErrorCodeToString(client->session()->error()); + return false; + } + + const int response_code = client->latest_response_code(); + if (response_code < 200 || response_code >= 300) { + QUIC_LOG(ERROR) << "Request for " << url_string + << " failed with HTTP response code " << response_code; + return false; + } + + const std::string response_body = client->latest_response_body(); + QUIC_LOG(INFO) << "Request succeeded for " << url_string << std::endl + << response_body; + + return true; +} + +} // namespace tools +} // namespace quic
diff --git a/quic/masque/masque_client_tools.h b/quic/masque/masque_client_tools.h new file mode 100644 index 0000000..4ddc2d7 --- /dev/null +++ b/quic/masque/masque_client_tools.h
@@ -0,0 +1,25 @@ +// 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. + +#ifndef QUICHE_QUIC_MASQUE_MASQUE_CLIENT_TOOLS_H_ +#define QUICHE_QUIC_MASQUE_MASQUE_CLIENT_TOOLS_H_ + +#include "net/third_party/quiche/src/quic/masque/masque_epoll_client.h" + +namespace quic { +namespace tools { + +// Sends an HTTP GET request for |url_string|, proxied over the MASQUE +// connection represented by |masque_client|. A valid and owned |epoll_server| +// is required. |disable_certificate_verification| allows disabling verification +// of the HTTP server's TLS certificate. +bool SendEncapsulatedMasqueRequest(MasqueEpollClient* masque_client, + QuicEpollServer* epoll_server, + std::string url_string, + bool disable_certificate_verification); + +} // namespace tools +} // namespace quic + +#endif // QUICHE_QUIC_MASQUE_MASQUE_CLIENT_TOOLS_H_
diff --git a/quic/masque/masque_compression_engine.cc b/quic/masque/masque_compression_engine.cc index fc8957b..77f7296 100644 --- a/quic/masque/masque_compression_engine.cc +++ b/quic/masque/masque_compression_engine.cc
@@ -13,8 +13,8 @@ #include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/core/quic_versions.h" #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h" -#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" -#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h" namespace quic { @@ -213,7 +213,7 @@ return false; } } - QuicStringPiece packet_payload = reader->ReadRemainingPayload(); + quiche::QuicheStringPiece packet_payload = reader->ReadRemainingPayload(); if (!writer.WriteStringPiece(packet_payload)) { QUIC_BUG << "Failed to write packet_payload"; return false; @@ -222,13 +222,13 @@ } void MasqueCompressionEngine::CompressAndSendPacket( - QuicStringPiece packet, + quiche::QuicheStringPiece packet, QuicConnectionId client_connection_id, QuicConnectionId server_connection_id, const QuicSocketAddress& server_address) { QUIC_DVLOG(2) << "Compressing client " << client_connection_id << " server " << server_connection_id << "\n" - << QuicTextUtils::HexDump(packet); + << quiche::QuicheTextUtils::HexDump(packet); DCHECK(server_address.IsInitialized()); if (packet.empty()) { QUIC_BUG << "Tried to send empty packet"; @@ -450,7 +450,7 @@ return false; } } - QuicStringPiece payload = reader->ReadRemainingPayload(); + quiche::QuicheStringPiece payload = reader->ReadRemainingPayload(); if (!writer.WriteStringPiece(payload)) { QUIC_BUG << "Failed to write payload"; return false; @@ -459,7 +459,7 @@ } bool MasqueCompressionEngine::DecompressDatagram( - QuicStringPiece datagram, + quiche::QuicheStringPiece datagram, QuicConnectionId* client_connection_id, QuicConnectionId* server_connection_id, QuicSocketAddress* server_address, @@ -512,7 +512,7 @@ QUIC_DVLOG(2) << "Decompressed client " << context.client_connection_id << " server " << context.server_connection_id << "\n" - << QuicTextUtils::HexDump(*packet); + << quiche::QuicheTextUtils::HexDump(*packet); return true; }
diff --git a/quic/masque/masque_compression_engine.h b/quic/masque/masque_compression_engine.h index 09c49a6..f170ddc 100644 --- a/quic/masque/masque_compression_engine.h +++ b/quic/masque/masque_compression_engine.h
@@ -11,7 +11,7 @@ #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h" #include "net/third_party/quiche/src/quic/platform/api/quic_export.h" #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" -#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h" +#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h" namespace quic { @@ -47,7 +47,7 @@ // hand off the uncompressed packet to an encapsulated session that will treat // it as having come from the provided |server_address|. // The connection IDs are the one used by the encapsulated |packet|. - void CompressAndSendPacket(QuicStringPiece packet, + void CompressAndSendPacket(quiche::QuicheStringPiece packet, QuicConnectionId client_connection_id, QuicConnectionId server_connection_id, const QuicSocketAddress& server_address); @@ -58,7 +58,7 @@ // |server_address| will be filled with the |server_address| passed to // CompressAndSendPacket. |version_present| will contain whether the // encapsulated |packet| contains a Version field. - bool DecompressDatagram(QuicStringPiece datagram, + bool DecompressDatagram(quiche::QuicheStringPiece datagram, QuicConnectionId* client_connection_id, QuicConnectionId* server_connection_id, QuicSocketAddress* server_address,
diff --git a/quic/masque/masque_encapsulated_client_session.cc b/quic/masque/masque_encapsulated_client_session.cc new file mode 100644 index 0000000..818ff76 --- /dev/null +++ b/quic/masque/masque_encapsulated_client_session.cc
@@ -0,0 +1,41 @@ +// 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 "net/third_party/quiche/src/quic/masque/masque_encapsulated_client_session.h" + +namespace quic { + +MasqueEncapsulatedClientSession::MasqueEncapsulatedClientSession( + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection, + const QuicServerId& server_id, + QuicCryptoClientConfig* crypto_config, + QuicClientPushPromiseIndex* push_promise_index, + MasqueClientSession* masque_client_session) + : QuicSpdyClientSession(config, + supported_versions, + connection, + server_id, + crypto_config, + push_promise_index), + masque_client_session_(masque_client_session) {} + +void MasqueEncapsulatedClientSession::ProcessPacket( + quiche::QuicheStringPiece packet, + QuicSocketAddress server_address) { + QuicTime now = connection()->clock()->ApproximateNow(); + QuicReceivedPacket received_packet(packet.data(), packet.length(), now); + connection()->ProcessUdpPacket(connection()->self_address(), server_address, + received_packet); +} + +void MasqueEncapsulatedClientSession::OnConnectionClosed( + const QuicConnectionCloseFrame& /*frame*/, + ConnectionCloseSource /*source*/) { + masque_client_session_->UnregisterConnectionId( + connection()->client_connection_id()); +} + +} // namespace quic
diff --git a/quic/masque/masque_encapsulated_client_session.h b/quic/masque/masque_encapsulated_client_session.h new file mode 100644 index 0000000..75ccc1f --- /dev/null +++ b/quic/masque/masque_encapsulated_client_session.h
@@ -0,0 +1,58 @@ +// 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. + +#ifndef QUICHE_QUIC_MASQUE_MASQUE_ENCAPSULATED_CLIENT_SESSION_H_ +#define QUICHE_QUIC_MASQUE_MASQUE_ENCAPSULATED_CLIENT_SESSION_H_ + +#include "net/third_party/quiche/src/quic/core/http/quic_spdy_client_session.h" +#include "net/third_party/quiche/src/quic/masque/masque_client_session.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_export.h" + +namespace quic { + +// QUIC client session for QUIC encapsulated in MASQUE. This client session is +// maintained end-to-end between the client and the web-server (the MASQUE +// session does not have access to the cryptographic keys for the end-to-end +// session), but its packets are sent encapsulated inside DATAGRAM frames in a +// MASQUE session, as opposed to regular QUIC packets. Multiple encapsulated +// sessions can coexist inside a MASQUE session. +class QUIC_NO_EXPORT MasqueEncapsulatedClientSession + : public QuicSpdyClientSession, + public MasqueClientSession::EncapsulatedClientSession { + public: + // Takes ownership of |connection|, but not of |crypto_config| or + // |push_promise_index| or |masque_client_session|. All pointers must be + // non-null. Caller must ensure that |push_promise_index| and + // |masque_client_session| stay valid for the lifetime of the newly created + // MasqueEncapsulatedClientSession. + MasqueEncapsulatedClientSession( + const QuicConfig& config, + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection, + const QuicServerId& server_id, + QuicCryptoClientConfig* crypto_config, + QuicClientPushPromiseIndex* push_promise_index, + MasqueClientSession* masque_client_session); + + // Disallow copy and assign. + MasqueEncapsulatedClientSession(const MasqueEncapsulatedClientSession&) = + delete; + MasqueEncapsulatedClientSession& operator=( + const MasqueEncapsulatedClientSession&) = delete; + + // From MasqueClientSession::EncapsulatedClientSession. + void ProcessPacket(quiche::QuicheStringPiece packet, + QuicSocketAddress server_address) override; + + // From QuicSession. + void OnConnectionClosed(const QuicConnectionCloseFrame& frame, + ConnectionCloseSource source) override; + + private: + MasqueClientSession* masque_client_session_; // Unowned. +}; + +} // namespace quic + +#endif // QUICHE_QUIC_MASQUE_MASQUE_ENCAPSULATED_CLIENT_SESSION_H_
diff --git a/quic/masque/masque_encapsulated_epoll_client.cc b/quic/masque/masque_encapsulated_epoll_client.cc new file mode 100644 index 0000000..7c9749a --- /dev/null +++ b/quic/masque/masque_encapsulated_epoll_client.cc
@@ -0,0 +1,125 @@ +// 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 "net/third_party/quiche/src/quic/masque/masque_encapsulated_epoll_client.h" +#include "net/third_party/quiche/src/quic/core/quic_utils.h" +#include "net/third_party/quiche/src/quic/masque/masque_client_session.h" +#include "net/third_party/quiche/src/quic/masque/masque_encapsulated_client_session.h" +#include "net/third_party/quiche/src/quic/masque/masque_epoll_client.h" +#include "net/third_party/quiche/src/quic/masque/masque_utils.h" + +namespace quic { + +namespace { + +// Custom packet writer that allows getting all of a connection's outgoing +// packets. +class MasquePacketWriter : public QuicPacketWriter { + public: + explicit MasquePacketWriter(MasqueEncapsulatedEpollClient* client) + : client_(client) {} + WriteResult WritePacket(const char* buffer, + size_t buf_len, + const QuicIpAddress& /*self_address*/, + const QuicSocketAddress& peer_address, + PerPacketOptions* /*options*/) override { + DCHECK(peer_address.IsInitialized()); + QUIC_DVLOG(1) << "MasquePacketWriter trying to write " << buf_len + << " bytes to " << peer_address; + quiche::QuicheStringPiece packet(buffer, buf_len); + client_->masque_client()->masque_client_session()->SendPacket( + client_->session()->connection()->client_connection_id(), + client_->session()->connection()->connection_id(), packet, + peer_address); + return WriteResult(WRITE_STATUS_OK, buf_len); + } + + bool IsWriteBlocked() const override { return false; } + + void SetWritable() override {} + + QuicByteCount GetMaxPacketSize( + const QuicSocketAddress& /*peer_address*/) const override { + return kMasqueMaxEncapsulatedPacketSize; + } + + bool SupportsReleaseTime() const override { return false; } + + bool IsBatchMode() const override { return false; } + char* GetNextWriteLocation( + const QuicIpAddress& /*self_address*/, + const QuicSocketAddress& /*peer_address*/) override { + return nullptr; + } + + WriteResult Flush() override { return WriteResult(WRITE_STATUS_OK, 0); } + + private: + MasqueEncapsulatedEpollClient* client_; // Unowned. +}; + +// Custom network helper that allows injecting a custom packet writer in order +// to get all of a connection's outgoing packets. +class MasqueClientEpollNetworkHelper : public QuicClientEpollNetworkHelper { + public: + MasqueClientEpollNetworkHelper(QuicEpollServer* epoll_server, + MasqueEncapsulatedEpollClient* client) + : QuicClientEpollNetworkHelper(epoll_server, client), client_(client) {} + QuicPacketWriter* CreateQuicPacketWriter() override { + return new MasquePacketWriter(client_); + } + + private: + MasqueEncapsulatedEpollClient* client_; // Unowned. +}; + +} // namespace + +MasqueEncapsulatedEpollClient::MasqueEncapsulatedEpollClient( + QuicSocketAddress server_address, + const QuicServerId& server_id, + QuicEpollServer* epoll_server, + std::unique_ptr<ProofVerifier> proof_verifier, + MasqueEpollClient* masque_client) + : QuicClient( + server_address, + server_id, + MasqueSupportedVersions(), + MasqueEncapsulatedConfig(), + epoll_server, + std::make_unique<MasqueClientEpollNetworkHelper>(epoll_server, this), + std::move(proof_verifier)), + masque_client_(masque_client) {} + +MasqueEncapsulatedEpollClient::~MasqueEncapsulatedEpollClient() { + masque_client_->masque_client_session()->UnregisterConnectionId( + client_connection_id_); +} + +std::unique_ptr<QuicSession> +MasqueEncapsulatedEpollClient::CreateQuicClientSession( + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) { + QUIC_DLOG(INFO) << "Creating MASQUE encapsulated session for " + << connection->connection_id(); + return std::make_unique<MasqueEncapsulatedClientSession>( + *config(), supported_versions, connection, server_id(), crypto_config(), + push_promise_index(), masque_client_->masque_client_session()); +} + +MasqueEncapsulatedClientSession* +MasqueEncapsulatedEpollClient::masque_encapsulated_client_session() { + return static_cast<MasqueEncapsulatedClientSession*>(QuicClient::session()); +} + +QuicConnectionId MasqueEncapsulatedEpollClient::GetClientConnectionId() { + if (client_connection_id_.IsEmpty()) { + client_connection_id_ = QuicUtils::CreateRandomConnectionId(); + masque_client_->masque_client_session()->RegisterConnectionId( + client_connection_id_, masque_encapsulated_client_session()); + } + return client_connection_id_; +} + +} // namespace quic
diff --git a/quic/masque/masque_encapsulated_epoll_client.h b/quic/masque/masque_encapsulated_epoll_client.h new file mode 100644 index 0000000..c8bff21 --- /dev/null +++ b/quic/masque/masque_encapsulated_epoll_client.h
@@ -0,0 +1,50 @@ +// 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. + +#ifndef QUICHE_QUIC_MASQUE_MASQUE_ENCAPSULATED_EPOLL_CLIENT_H_ +#define QUICHE_QUIC_MASQUE_MASQUE_ENCAPSULATED_EPOLL_CLIENT_H_ + +#include "net/third_party/quiche/src/quic/masque/masque_encapsulated_client_session.h" +#include "net/third_party/quiche/src/quic/masque/masque_epoll_client.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_export.h" +#include "net/third_party/quiche/src/quic/tools/quic_client.h" + +namespace quic { + +// QUIC client for QUIC encapsulated in MASQUE. +class QUIC_NO_EXPORT MasqueEncapsulatedEpollClient : public QuicClient { + public: + MasqueEncapsulatedEpollClient(QuicSocketAddress server_address, + const QuicServerId& server_id, + QuicEpollServer* epoll_server, + std::unique_ptr<ProofVerifier> proof_verifier, + MasqueEpollClient* masque_client); + ~MasqueEncapsulatedEpollClient() override; + + // Disallow copy and assign. + MasqueEncapsulatedEpollClient(const MasqueEncapsulatedEpollClient&) = delete; + MasqueEncapsulatedEpollClient& operator=( + const MasqueEncapsulatedEpollClient&) = delete; + + // From QuicClient. + std::unique_ptr<QuicSession> CreateQuicClientSession( + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) override; + + QuicConnectionId GetClientConnectionId() override; + + // MASQUE client that this client is encapsulated in. + MasqueEpollClient* masque_client() { return masque_client_; } + + // Client session for this client. + MasqueEncapsulatedClientSession* masque_encapsulated_client_session(); + + private: + MasqueEpollClient* masque_client_; // Unowned. + QuicConnectionId client_connection_id_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_MASQUE_MASQUE_ENCAPSULATED_EPOLL_CLIENT_H_
diff --git a/quic/masque/masque_epoll_client.cc b/quic/masque/masque_epoll_client.cc new file mode 100644 index 0000000..a0954b6 --- /dev/null +++ b/quic/masque/masque_epoll_client.cc
@@ -0,0 +1,132 @@ +// 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 "net/third_party/quiche/src/quic/masque/masque_epoll_client.h" +#include "net/third_party/quiche/src/quic/masque/masque_client_session.h" +#include "net/third_party/quiche/src/quic/masque/masque_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h" + +namespace quic { + +MasqueEpollClient::MasqueEpollClient( + QuicSocketAddress server_address, + const QuicServerId& server_id, + QuicEpollServer* epoll_server, + std::unique_ptr<ProofVerifier> proof_verifier, + const std::string& authority) + : QuicClient(server_address, + server_id, + MasqueSupportedVersions(), + epoll_server, + std::move(proof_verifier)), + epoll_server_(epoll_server), + authority_(authority) {} + +std::unique_ptr<QuicSession> MasqueEpollClient::CreateQuicClientSession( + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) { + QUIC_DLOG(INFO) << "Creating MASQUE session for " + << connection->connection_id(); + return std::make_unique<MasqueClientSession>( + *config(), supported_versions, connection, server_id(), crypto_config(), + push_promise_index(), this); +} + +MasqueClientSession* MasqueEpollClient::masque_client_session() { + return static_cast<MasqueClientSession*>(QuicClient::session()); +} + +QuicConnectionId MasqueEpollClient::connection_id() { + return masque_client_session()->connection_id(); +} + +// static +std::unique_ptr<MasqueEpollClient> MasqueEpollClient::Create( + const std::string& host, + int port, + QuicEpollServer* epoll_server, + std::unique_ptr<ProofVerifier> proof_verifier) { + // Build the masque_client, and try to connect. + QuicSocketAddress addr = + tools::LookupAddress(host, quiche::QuicheStrCat(port)); + if (!addr.IsInitialized()) { + QUIC_LOG(ERROR) << "Unable to resolve address: " << host; + return nullptr; + } + QuicServerId server_id(host, port); + // Use QuicWrapUnique(new MasqueEpollClient(...)) instead of + // std::make_unique<MasqueEpollClient>(...) because the constructor for + // MasqueEpollClient is private and therefore not accessible from make_unique. + auto masque_client = QuicWrapUnique(new MasqueEpollClient( + addr, server_id, epoll_server, std::move(proof_verifier), + quiche::QuicheStrCat(host, ":", port))); + + if (masque_client == nullptr) { + QUIC_LOG(ERROR) << "Failed to create masque_client"; + return nullptr; + } + + masque_client->set_initial_max_packet_length(kDefaultMaxPacketSize); + masque_client->set_drop_response_body(false); + if (!masque_client->Initialize()) { + QUIC_LOG(ERROR) << "Failed to initialize masque_client"; + return nullptr; + } + if (!masque_client->Connect()) { + QuicErrorCode error = masque_client->session()->error(); + QUIC_LOG(ERROR) << "Failed to connect to " << host << ":" << port + << ". Error: " << QuicErrorCodeToString(error); + return nullptr; + } + + std::string body = "foo"; + + // Construct a GET or POST request for supplied URL. + spdy::SpdyHeaderBlock header_block; + header_block[":method"] = "POST"; + header_block[":scheme"] = "https"; + header_block[":authority"] = masque_client->authority_; + header_block[":path"] = "/.well-known/masque/init"; + + // Make sure to store the response, for later output. + masque_client->set_store_response(true); + + // Send the MASQUE init command. + masque_client->SendRequestAndWaitForResponse(header_block, body, + /*fin=*/true); + + if (!masque_client->connected()) { + QUIC_LOG(ERROR) << "MASQUE init request caused connection failure. Error: " + << QuicErrorCodeToString(masque_client->session()->error()); + return nullptr; + } + + const int response_code = masque_client->latest_response_code(); + if (response_code != 200) { + QUIC_LOG(ERROR) << "MASQUE init request failed with HTTP response code " + << response_code; + return nullptr; + } + return masque_client; +} + +void MasqueEpollClient::UnregisterClientConnectionId( + QuicConnectionId client_connection_id) { + std::string body(client_connection_id.data(), client_connection_id.length()); + + // Construct a GET or POST request for supplied URL. + spdy::SpdyHeaderBlock header_block; + header_block[":method"] = "POST"; + header_block[":scheme"] = "https"; + header_block[":authority"] = authority_; + header_block[":path"] = "/.well-known/masque/unregister"; + + // Make sure to store the response, for later output. + set_store_response(true); + + // Send the MASQUE unregister command. + SendRequest(header_block, body, /*fin=*/true); +} + +} // namespace quic
diff --git a/quic/masque/masque_epoll_client.h b/quic/masque/masque_epoll_client.h new file mode 100644 index 0000000..a8485c2 --- /dev/null +++ b/quic/masque/masque_epoll_client.h
@@ -0,0 +1,58 @@ +// 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. + +#ifndef QUICHE_QUIC_MASQUE_MASQUE_EPOLL_CLIENT_H_ +#define QUICHE_QUIC_MASQUE_MASQUE_EPOLL_CLIENT_H_ + +#include "net/third_party/quiche/src/quic/masque/masque_client_session.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_export.h" +#include "net/third_party/quiche/src/quic/tools/quic_client.h" + +namespace quic { + +// QUIC client that implements MASQUE. +class QUIC_NO_EXPORT MasqueEpollClient : public QuicClient, + public MasqueClientSession::Owner { + public: + // Constructs a MasqueEpollClient, performs a synchronous DNS lookup. + static std::unique_ptr<MasqueEpollClient> Create( + const std::string& host, + int port, + QuicEpollServer* epoll_server, + std::unique_ptr<ProofVerifier> proof_verifier); + + // From QuicClient. + std::unique_ptr<QuicSession> CreateQuicClientSession( + const ParsedQuicVersionVector& supported_versions, + QuicConnection* connection) override; + + // Client session for this client. + MasqueClientSession* masque_client_session(); + + // Convenience accessor for the underlying connection ID. + QuicConnectionId connection_id(); + + // Send a MASQUE client connection ID unregister command to the server. + void UnregisterClientConnectionId( + QuicConnectionId client_connection_id) override; + + private: + // Constructor is private, use Create() instead. + MasqueEpollClient(QuicSocketAddress server_address, + const QuicServerId& server_id, + QuicEpollServer* epoll_server, + std::unique_ptr<ProofVerifier> proof_verifier, + const std::string& authority); + + // Disallow copy and assign. + MasqueEpollClient(const MasqueEpollClient&) = delete; + MasqueEpollClient& operator=(const MasqueEpollClient&) = delete; + + QuicEpollServer* epoll_server_; // Unowned. + std::string authority_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_MASQUE_MASQUE_EPOLL_CLIENT_H_