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_