| // 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 responsible 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 <cstddef> | |
| #include <cstdint> | |
| #include <fstream> | |
| #include <iostream> | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/escaping.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "openssl/curve25519.h" | |
| #include "quiche/quic/core/crypto/certificate_view.h" | |
| #include "quiche/quic/core/crypto/client_proof_source.h" | |
| #include "quiche/quic/core/crypto/proof_verifier.h" | |
| #include "quiche/quic/core/http/quic_spdy_client_stream.h" | |
| #include "quiche/quic/core/io/quic_default_event_loop.h" | |
| #include "quiche/quic/core/io/quic_event_loop.h" | |
| #include "quiche/quic/core/quic_default_clock.h" | |
| #include "quiche/quic/core/quic_time.h" | |
| #include "quiche/quic/core/quic_udp_socket.h" | |
| #include "quiche/quic/masque/masque_client.h" | |
| #include "quiche/quic/masque/masque_client_session.h" | |
| #include "quiche/quic/masque/masque_client_tools.h" | |
| #include "quiche/quic/masque/masque_encapsulated_client.h" | |
| #include "quiche/quic/masque/masque_utils.h" | |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" | |
| #include "quiche/quic/platform/api/quic_default_proof_providers.h" | |
| #include "quiche/quic/platform/api/quic_ip_address.h" | |
| #include "quiche/quic/platform/api/quic_logging.h" | |
| #include "quiche/quic/tools/fake_proof_verifier.h" | |
| #include "quiche/common/capsule.h" | |
| #include "quiche/common/platform/api/quiche_command_line_flags.h" | |
| #include "quiche/common/platform/api/quiche_googleurl.h" | |
| #include "quiche/common/platform/api/quiche_logging.h" | |
| #include "quiche/common/platform/api/quiche_reference_counted.h" | |
| #include "quiche/common/platform/api/quiche_system_event_loop.h" | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| bool, disable_certificate_verification, false, | |
| "If true, don't verify the server certificate."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG(int, address_family, 0, | |
| "IP address family to use. Must be 0, 4 or 6. " | |
| "Defaults to 0 which means any."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, masque_mode, "", | |
| "Allows setting MASQUE mode, currently only valid value is \"open\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, proxy_headers, "", | |
| "A list of HTTP headers to add to request to the MASQUE proxy. " | |
| "Separated with colons and semicolons. " | |
| "For example: \"name1:value1;name2:value2\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, concealed_auth, "", | |
| "Enables HTTP Concealed Authentication. Pass in the string \"new\" to " | |
| "generate new keys. Otherwise, pass in the key ID in ASCII followed by a " | |
| "colon and the 32-byte private key as hex. For example: \"kid:0123...f\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| bool, bring_up_tun, false, | |
| "If set to true, no URLs need to be specified and instead a TUN device " | |
| "is brought up with the assigned IP from the MASQUE CONNECT-IP server."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| bool, dns_on_client, false, | |
| "If set to true, masque_client will perform DNS for encapsulated URLs and " | |
| "send the IP litteral in the CONNECT request. If set to false, " | |
| "masque_client send the hostname in the CONNECT request."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| bool, bind_use_uncompressed_context, false, | |
| "If set, an uncompressed context will be created for the client." | |
| "Otherwise, a compressed context will be requested."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| bool, bring_up_tap, false, | |
| "If set to true, no URLs need to be specified and instead a TAP device " | |
| "is brought up for a MASQUE CONNECT-ETHERNET session."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, client_cert_file, "", | |
| "Path to the PEM-encoded client certificate chain."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, client_cert_key_file, "", | |
| "Path to the PEM/PKCS8-encoded client certificate private key."); | |
| namespace quic { | |
| namespace { | |
| using ::quiche::AddressAssignCapsule; | |
| using ::quiche::AddressRequestCapsule; | |
| using ::quiche::RouteAdvertisementCapsule; | |
| class MasqueTunSession : public MasqueClientSession::EncapsulatedIpSession, | |
| public QuicSocketEventListener { | |
| public: | |
| MasqueTunSession(QuicEventLoop* event_loop, MasqueClientSession* session) | |
| : event_loop_(event_loop), session_(session) {} | |
| ~MasqueTunSession() override = default; | |
| // MasqueClientSession::EncapsulatedIpSession | |
| void ProcessIpPacket(absl::string_view packet) override { | |
| QUIC_LOG(INFO) << " Received IP packets of length " << packet.length(); | |
| if (fd_ == -1) { | |
| // TUN not open, early return | |
| return; | |
| } | |
| if (write(fd_, packet.data(), packet.size()) == -1) { | |
| QUIC_LOG(FATAL) << "Failed to write"; | |
| } | |
| } | |
| void CloseIpSession(const std::string& details) override { | |
| QUIC_LOG(ERROR) << "Was asked to close IP session: " << details; | |
| } | |
| bool OnAddressAssignCapsule(const AddressAssignCapsule& capsule) override { | |
| for (auto assigned_address : capsule.assigned_addresses) { | |
| if (assigned_address.ip_prefix.address().IsIPv4()) { | |
| QUIC_LOG(INFO) << "MasqueTunSession saving local IPv4 address " | |
| << assigned_address.ip_prefix.address(); | |
| local_address_ = assigned_address.ip_prefix.address(); | |
| break; | |
| } | |
| } | |
| // Bring up the TUN | |
| QUIC_LOG(ERROR) << "Bringing up tun with address " << local_address_; | |
| fd_ = CreateTunInterface(local_address_, false); | |
| if (fd_ < 0) { | |
| QUIC_LOG(FATAL) << "Failed to create TUN interface"; | |
| } | |
| if (!event_loop_->RegisterSocket(fd_, kSocketEventReadable, this)) { | |
| QUIC_LOG(FATAL) << "Failed to register TUN fd with the event loop"; | |
| } | |
| return true; | |
| } | |
| bool OnAddressRequestCapsule( | |
| const AddressRequestCapsule& /*capsule*/) override { | |
| // Always ignore the address request capsule from the server. | |
| return true; | |
| } | |
| bool OnRouteAdvertisementCapsule( | |
| const RouteAdvertisementCapsule& /*capsule*/) override { | |
| // Consider installing routes. | |
| return true; | |
| } | |
| bool OnDnsAssignCapsule(const DnsAssignCapsule& capsule) override { | |
| QUIC_LOG(INFO) << "Received capsule " << capsule.ToString(); | |
| return true; | |
| } | |
| bool OnPref64Capsule(const Pref64Capsule& capsule) override { | |
| QUIC_LOG(INFO) << "Received capsule " << capsule.ToString(); | |
| return true; | |
| } | |
| // QuicSocketEventListener | |
| void OnSocketEvent(QuicEventLoop* /*event_loop*/, QuicUdpSocketFd fd, | |
| QuicSocketEventMask events) override { | |
| if ((events & kSocketEventReadable) == 0) { | |
| QUIC_DVLOG(1) << "Ignoring OnEvent fd " << fd << " event mask " << events; | |
| return; | |
| } | |
| char datagram[kMasqueIpPacketBufferSize]; | |
| while (true) { | |
| ssize_t read_size = read(fd, datagram, sizeof(datagram)); | |
| if (read_size < 0) { | |
| break; | |
| } | |
| // Packet received from the TUN. Write it to the MASQUE CONNECT-IP | |
| // session. | |
| session_->SendIpPacket(absl::string_view(datagram, read_size), this); | |
| } | |
| if (!event_loop_->SupportsEdgeTriggered()) { | |
| if (!event_loop_->RearmSocket(fd, kSocketEventReadable)) { | |
| QUIC_BUG(MasqueServerSession_ConnectIp_OnSocketEvent_Rearm) | |
| << "Failed to re-arm socket " << fd << " for reading"; | |
| } | |
| } | |
| } | |
| private: | |
| QuicEventLoop* event_loop_; | |
| MasqueClientSession* session_; | |
| QuicIpAddress local_address_; | |
| int fd_ = -1; | |
| }; | |
| class MasqueTapSession | |
| : public MasqueClientSession::EncapsulatedEthernetSession, | |
| public QuicSocketEventListener { | |
| public: | |
| MasqueTapSession(QuicEventLoop* event_loop, MasqueClientSession* session) | |
| : event_loop_(event_loop), session_(session) {} | |
| ~MasqueTapSession() override = default; | |
| void CreateInterface(void) { | |
| QUIC_LOG(ERROR) << "Bringing up TAP"; | |
| fd_ = CreateTapInterface(); | |
| if (fd_ < 0) { | |
| QUIC_LOG(FATAL) << "Failed to create TAP interface"; | |
| } | |
| if (!event_loop_->RegisterSocket(fd_, kSocketEventReadable, this)) { | |
| QUIC_LOG(FATAL) << "Failed to register TAP fd with the event loop"; | |
| } | |
| } | |
| // MasqueClientSession::EncapsulatedEthernetSession | |
| void ProcessEthernetFrame(absl::string_view frame) override { | |
| QUIC_LOG(INFO) << " Received Ethernet frame of length " << frame.length(); | |
| if (fd_ == -1) { | |
| // TAP not open, early return | |
| return; | |
| } | |
| if (write(fd_, frame.data(), frame.size()) == -1) { | |
| QUIC_LOG(FATAL) << "Failed to write"; | |
| } | |
| } | |
| void CloseEthernetSession(const std::string& details) override { | |
| QUIC_LOG(ERROR) << "Was asked to close Ethernet session: " << details; | |
| } | |
| // QuicSocketEventListener | |
| void OnSocketEvent(QuicEventLoop* /*event_loop*/, QuicUdpSocketFd fd, | |
| QuicSocketEventMask events) override { | |
| if ((events & kSocketEventReadable) == 0) { | |
| QUIC_DVLOG(1) << "Ignoring OnEvent fd " << fd << " event mask " << events; | |
| return; | |
| } | |
| char datagram[kMasqueEthernetFrameBufferSize]; | |
| while (true) { | |
| ssize_t read_size = read(fd, datagram, sizeof(datagram)); | |
| if (read_size < 0) { | |
| break; | |
| } | |
| // Frame received from the TAP. Write it to the MASQUE CONNECT-ETHERNET | |
| // session. | |
| session_->SendEthernetFrame(absl::string_view(datagram, read_size), this); | |
| } | |
| if (!event_loop_->SupportsEdgeTriggered()) { | |
| if (!event_loop_->RearmSocket(fd, kSocketEventReadable)) { | |
| QUIC_BUG(MasqueServerSession_ConnectIp_OnSocketEvent_Rearm) | |
| << "Failed to re-arm socket " << fd << " for reading"; | |
| } | |
| } | |
| } | |
| private: | |
| QuicEventLoop* event_loop_; | |
| MasqueClientSession* session_; | |
| std::string local_mac_address_; // string, uint8_t[6], or new wrapper type? | |
| int fd_ = -1; | |
| }; | |
| std::unique_ptr<ClientProofSource> CreateClientProofSource( | |
| const std::string& client_cert_file, | |
| const std::string& client_cert_key_file) { | |
| if (client_cert_file.empty() || client_cert_key_file.empty()) { | |
| std::cerr << "Both client cert and client cert key need to be set." | |
| << std::endl; | |
| return nullptr; | |
| } | |
| std::ifstream cert_stream(client_cert_file, std::ios::binary); | |
| std::vector<std::string> certs = | |
| CertificateView::LoadPemFromStream(&cert_stream); | |
| if (certs.empty()) { | |
| std::cerr << "Failed to load client certs." << std::endl; | |
| return nullptr; | |
| } | |
| std::ifstream key_stream(client_cert_key_file, std::ios::binary); | |
| std::unique_ptr<CertificatePrivateKey> private_key = | |
| CertificatePrivateKey::LoadPemFromStream(&key_stream); | |
| if (private_key == nullptr) { | |
| std::cerr << "Failed to load client cert key." << std::endl; | |
| return nullptr; | |
| } | |
| auto proof_source = std::make_unique<DefaultClientProofSource>(); | |
| if (!proof_source->AddCertAndKey( | |
| {"*"}, | |
| quiche::QuicheReferenceCountedPointer<ClientProofSource::Chain>( | |
| new ClientProofSource::Chain(certs)), | |
| std::move(*private_key))) { | |
| std::cerr << "Failed to add client cert and key." << std::endl; | |
| return nullptr; | |
| } | |
| return proof_source; | |
| } | |
| int RunMasqueClient(int argc, char* argv[]) { | |
| const char* usage = | |
| "Usage: masque_client [options] <proxy-url> <urls>..\n" | |
| " <proxy-url> is the URI template of the MASQUE server,\n" | |
| " or host:port to use the default template"; | |
| // The first non-flag argument is the URI template of the MASQUE server. | |
| // All subsequent ones are interpreted as URLs to fetch via the MASQUE server. | |
| // Note that the URI template expansion currently only supports string | |
| // replacement of {target_host} and {target_port}, not | |
| // {?target_host,target_port}. | |
| std::vector<std::string> urls = | |
| quiche::QuicheParseCommandLineFlags(usage, argc, argv); | |
| std::string concealed_auth_param = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_concealed_auth); | |
| std::string concealed_auth_key_id; | |
| std::string concealed_auth_private_key; | |
| std::string concealed_auth_public_key; | |
| if (!concealed_auth_param.empty()) { | |
| static constexpr size_t kEd25519Rfc8032PrivateKeySize = 32; | |
| uint8_t public_key[ED25519_PUBLIC_KEY_LEN]; | |
| uint8_t private_key[ED25519_PRIVATE_KEY_LEN]; | |
| const bool is_new_key_pair = concealed_auth_param == "new"; | |
| if (is_new_key_pair) { | |
| ED25519_keypair(public_key, private_key); | |
| QUIC_LOG(INFO) << "Generated new Concealed Authentication key pair"; | |
| } else { | |
| std::vector<absl::string_view> concealed_auth_param_split = | |
| absl::StrSplit(concealed_auth_param, absl::MaxSplits(':', 1)); | |
| std::string private_key_seed; | |
| if (concealed_auth_param_split.size() != 2) { | |
| QUIC_LOG(ERROR) | |
| << "Concealed authentication parameter is missing a colon"; | |
| return 1; | |
| } | |
| concealed_auth_key_id = concealed_auth_param_split[0]; | |
| if (concealed_auth_key_id.empty()) { | |
| QUIC_LOG(ERROR) << "Concealed authentication key ID cannot be empty"; | |
| return 1; | |
| } | |
| if (!absl::HexStringToBytes(concealed_auth_param_split[1], | |
| &private_key_seed)) { | |
| QUIC_LOG(ERROR) << "Concealed authentication key hex value is invalid"; | |
| return 1; | |
| } | |
| if (private_key_seed.size() != kEd25519Rfc8032PrivateKeySize) { | |
| QUIC_LOG(ERROR) | |
| << "Invalid Concealed authentication private key length " | |
| << private_key_seed.size(); | |
| return 1; | |
| } | |
| ED25519_keypair_from_seed( | |
| public_key, private_key, | |
| reinterpret_cast<uint8_t*>(private_key_seed.data())); | |
| QUIC_LOG(INFO) << "Loaded Concealed Authentication key pair"; | |
| } | |
| // Note that Ed25519 private keys are 32 bytes long per RFC 8032. However, | |
| // to reduce CPU costs, BoringSSL represents private keys in memory as the | |
| // concatenation of the 32-byte private key and the corresponding 32-byte | |
| // public key - which makes for a total of 64 bytes. The private key log | |
| // below relies on this BoringSSL implementation detail to extract the | |
| // RFC 8032 private key because BoringSSL does not provide a supported way | |
| // to access it. This is required to allow us to print the private key in a | |
| // format that can be passed back in to BoringSSL from the command-line. See | |
| // curve25519.h for details. The rest of our concealed authentication code | |
| // uses the BoringSSL representation without relying on this implementation | |
| // detail. | |
| static_assert(kEd25519Rfc8032PrivateKeySize <= | |
| static_cast<size_t>(ED25519_PRIVATE_KEY_LEN)); | |
| std::string private_key_hexstr = absl::BytesToHexString(absl::string_view( | |
| reinterpret_cast<char*>(private_key), kEd25519Rfc8032PrivateKeySize)); | |
| std::string public_key_hexstr = absl::BytesToHexString(absl::string_view( | |
| reinterpret_cast<char*>(public_key), ED25519_PUBLIC_KEY_LEN)); | |
| if (is_new_key_pair) { | |
| std::cout << "Generated new Concealed Authentication key pair." | |
| << std::endl; | |
| std::cout << "Private key: " << private_key_hexstr << std::endl; | |
| std::cout << "Public key: " << public_key_hexstr << std::endl; | |
| return 0; | |
| } | |
| QUIC_LOG(INFO) << "Private key: " << private_key_hexstr; | |
| QUIC_LOG(INFO) << "Public key: " << public_key_hexstr; | |
| concealed_auth_private_key = std::string( | |
| reinterpret_cast<char*>(private_key), ED25519_PRIVATE_KEY_LEN); | |
| concealed_auth_public_key = std::string(reinterpret_cast<char*>(public_key), | |
| ED25519_PUBLIC_KEY_LEN); | |
| } | |
| bool bring_up_tun = quiche::GetQuicheCommandLineFlag(FLAGS_bring_up_tun); | |
| bool bring_up_tap = quiche::GetQuicheCommandLineFlag(FLAGS_bring_up_tap); | |
| if (urls.empty() && !bring_up_tun && !bring_up_tap) { | |
| quiche::QuichePrintCommandLineFlagHelp(usage); | |
| return 1; | |
| } | |
| if (bring_up_tun && bring_up_tap) { | |
| quiche::QuichePrintCommandLineFlagHelp(usage); | |
| return 1; | |
| } | |
| quiche::QuicheSystemEventLoop system_event_loop("masque_client"); | |
| const bool disable_certificate_verification = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_disable_certificate_verification); | |
| MasqueMode masque_mode = MasqueMode::kOpen; | |
| std::string mode_string = quiche::GetQuicheCommandLineFlag(FLAGS_masque_mode); | |
| if (!mode_string.empty()) { | |
| if (mode_string == "open") { | |
| masque_mode = MasqueMode::kOpen; | |
| } else if (mode_string == "connectip" || mode_string == "connect-ip") { | |
| masque_mode = MasqueMode::kConnectIp; | |
| } else if (mode_string == "connectethernet" || | |
| mode_string == "connect-ethernet") { | |
| masque_mode = MasqueMode::kConnectEthernet; | |
| } else if (mode_string == "connectudpbind" || | |
| mode_string == "connect-udp-bind") { | |
| masque_mode = MasqueMode::kConnectUdpBind; | |
| } else { | |
| QUIC_LOG(ERROR) << "Invalid masque_mode \"" << mode_string << "\""; | |
| return 1; | |
| } | |
| } | |
| const int address_family = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_address_family); | |
| int address_family_for_lookup; | |
| if (address_family == 0) { | |
| address_family_for_lookup = AF_UNSPEC; | |
| } else if (address_family == 4) { | |
| address_family_for_lookup = AF_INET; | |
| } else if (address_family == 6) { | |
| address_family_for_lookup = AF_INET6; | |
| } else { | |
| QUIC_LOG(ERROR) << "Invalid address_family " << address_family; | |
| return 1; | |
| } | |
| const bool dns_on_client = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_dns_on_client); | |
| std::string client_cert_file = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_client_cert_file); | |
| std::string client_cert_key_file = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_client_cert_key_file); | |
| std::unique_ptr<QuicEventLoop> event_loop = | |
| GetDefaultEventLoop()->Create(QuicDefaultClock::Get()); | |
| std::vector<std::unique_ptr<MasqueClient>> masque_clients; | |
| for (absl::string_view uri_template_sv : absl::StrSplit(urls[0], ',')) { | |
| std::string uri_template = std::string(uri_template_sv); | |
| if (!absl::StrContains(uri_template, '/')) { | |
| // If an authority is passed in instead of a URI template, use the default | |
| // URI template. | |
| uri_template = | |
| absl::StrCat("https://", uri_template, | |
| "/.well-known/masque/udp/{target_host}/{target_port}/"); | |
| } | |
| url::Parsed parsed_uri_template; | |
| url::ParseStandardURL(uri_template.c_str(), uri_template.length(), | |
| &parsed_uri_template); | |
| if (!parsed_uri_template.scheme.is_nonempty() || | |
| !parsed_uri_template.host.is_nonempty() || | |
| !parsed_uri_template.path.is_nonempty()) { | |
| QUIC_LOG(ERROR) << "Failed to parse MASQUE URI template \"" | |
| << uri_template << "\""; | |
| return 1; | |
| } | |
| std::unique_ptr<MasqueClient> masque_client; | |
| if (masque_clients.empty()) { | |
| std::string host = uri_template.substr(parsed_uri_template.host.begin, | |
| parsed_uri_template.host.len); | |
| std::unique_ptr<ProofVerifier> proof_verifier; | |
| if (disable_certificate_verification) { | |
| proof_verifier = std::make_unique<FakeProofVerifier>(); | |
| } else { | |
| proof_verifier = CreateDefaultProofVerifier(host); | |
| if (!proof_verifier) { | |
| QUICHE_LOG(ERROR) | |
| << "The default proof verifier is not supported. Pass " | |
| "in --disable_certificate_verification."; | |
| return 1; | |
| } | |
| } | |
| std::unique_ptr<ClientProofSource> proof_source; | |
| if (!client_cert_file.empty() || !client_cert_key_file.empty()) { | |
| proof_source = | |
| CreateClientProofSource(client_cert_file, client_cert_key_file); | |
| if (proof_source == nullptr) { | |
| return 1; | |
| } | |
| } | |
| masque_client = MasqueClient::Create( | |
| uri_template, masque_mode, event_loop.get(), | |
| std::move(proof_verifier), std::move(proof_source)); | |
| } else { | |
| masque_client = tools::CreateAndConnectMasqueEncapsulatedClient( | |
| masque_clients.back().get(), masque_mode, event_loop.get(), | |
| uri_template, disable_certificate_verification, | |
| address_family_for_lookup, dns_on_client, | |
| /*is_also_underlying=*/true); | |
| } | |
| if (masque_client == nullptr) { | |
| return 1; | |
| } | |
| QUIC_LOG(INFO) << "MASQUE[" << masque_clients.size() << "] to " | |
| << uri_template << " is connected " | |
| << masque_client->connection_id() << " in " << masque_mode | |
| << " mode"; | |
| masque_client->masque_client_session()->set_additional_headers( | |
| quiche::GetQuicheCommandLineFlag(FLAGS_proxy_headers)); | |
| if (!concealed_auth_param.empty()) { | |
| masque_client->masque_client_session()->EnableConcealedAuth( | |
| concealed_auth_key_id, concealed_auth_private_key, | |
| concealed_auth_public_key); | |
| } | |
| masque_clients.push_back(std::move(masque_client)); | |
| } | |
| std::unique_ptr<MasqueClient> masque_client = | |
| std::move(masque_clients.back()); | |
| masque_clients.pop_back(); | |
| if (bring_up_tun) { | |
| QUIC_LOG(INFO) << "Bringing up tun"; | |
| MasqueTunSession tun_session(event_loop.get(), | |
| masque_client->masque_client_session()); | |
| masque_client->masque_client_session()->SendIpPacket( | |
| absl::string_view("asdf"), &tun_session); | |
| while (true) { | |
| event_loop->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(50)); | |
| } | |
| QUICHE_NOTREACHED(); | |
| } | |
| if (bring_up_tap) { | |
| MasqueTapSession tap_session(event_loop.get(), | |
| masque_client->masque_client_session()); | |
| tap_session.CreateInterface(); | |
| while (true) { | |
| event_loop->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(50)); | |
| } | |
| QUICHE_NOTREACHED(); | |
| } | |
| if (masque_mode == MasqueMode::kConnectUdpBind) { | |
| bool uncompressed_context = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_bind_use_uncompressed_context); | |
| masque_client->masque_client_session()->set_bind_use_uncompressed_context( | |
| uncompressed_context); | |
| } | |
| for (size_t i = 1; i < urls.size(); ++i) { | |
| if (absl::StartsWith(urls[i], "/")) { | |
| QuicSpdyClientStream* stream = | |
| masque_client->masque_client_session()->SendGetRequest(urls[i]); | |
| while (stream->time_to_response_complete().IsInfinite()) { | |
| event_loop->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(50)); | |
| } | |
| // Print the response body to stdout. | |
| std::cout << std::endl << stream->data() << std::endl; | |
| } else { | |
| // For bind, DNS has to be done on client in the encapsulated client. | |
| std::unique_ptr<MasqueEncapsulatedClient> encapsulated_client = | |
| tools::CreateAndConnectMasqueEncapsulatedClient( | |
| masque_client.get(), masque_mode, event_loop.get(), urls[i], | |
| disable_certificate_verification, address_family_for_lookup, | |
| /*dns_on_client=*/dns_on_client || | |
| (masque_mode == MasqueMode::kConnectUdpBind), | |
| /*is_also_underlying=*/false); | |
| if (!encapsulated_client || !tools::SendRequestOnMasqueEncapsulatedClient( | |
| *encapsulated_client, urls[i])) { | |
| return 1; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| } // namespace | |
| } // namespace quic | |
| int main(int argc, char* argv[]) { return quic::RunMasqueClient(argc, argv); } |