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_