| // 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 <string> |
| |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "url/third_party/mozilla/url_parse.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_server_id.h" |
| #include "quiche/quic/masque/masque_client.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_default_proof_providers.h" |
| #include "quiche/quic/platform/api/quic_flags.h" |
| #include "quiche/quic/platform/api/quic_socket_address.h" |
| #include "quiche/quic/tools/fake_proof_verifier.h" |
| #include "quiche/common/platform/api/quiche_command_line_flags.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( |
| 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"); |
| |
| 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; |
| } |
| |
| // 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[1501]; |
| 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; |
| }; |
| |
| int RunMasqueClient(int argc, char* argv[]) { |
| quiche::QuicheSystemEventLoop system_event_loop("masque_client"); |
| const char* usage = "Usage: masque_client [options] <url>"; |
| |
| // 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); |
| bool bring_up_tun = quiche::GetQuicheCommandLineFlag(FLAGS_bring_up_tun); |
| if (urls.empty() && !bring_up_tun) { |
| quiche::QuichePrintCommandLineFlagHelp(usage); |
| return 1; |
| } |
| |
| const bool disable_certificate_verification = |
| quiche::GetQuicheCommandLineFlag(FLAGS_disable_certificate_verification); |
| std::unique_ptr<QuicEventLoop> event_loop = |
| GetDefaultEventLoop()->Create(QuicDefaultClock::Get()); |
| |
| std::string uri_template = urls[0]; |
| 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()) { |
| std::cerr << "Failed to parse MASQUE URI template \"" << urls[0] << "\"" |
| << std::endl; |
| return 1; |
| } |
| 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); |
| } |
| 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 { |
| std::cerr << "Invalid masque_mode \"" << mode_string << "\"" << std::endl; |
| 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 { |
| std::cerr << "Invalid address_family " << address_family << std::endl; |
| return 1; |
| } |
| std::unique_ptr<MasqueClient> masque_client = MasqueClient::Create( |
| uri_template, masque_mode, event_loop.get(), std::move(proof_verifier)); |
| if (masque_client == nullptr) { |
| return 1; |
| } |
| |
| std::cerr << "MASQUE is connected " << masque_client->connection_id() |
| << " in " << masque_mode << " mode" << std::endl; |
| |
| if (bring_up_tun) { |
| std::cerr << "Bringing up tun" << std::endl; |
| 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(); |
| } |
| |
| for (size_t i = 1; i < urls.size(); ++i) { |
| if (!tools::SendEncapsulatedMasqueRequest( |
| masque_client.get(), event_loop.get(), urls[i], |
| disable_certificate_verification, address_family_for_lookup)) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| } // namespace |
| |
| } // namespace quic |
| |
| int main(int argc, char* argv[]) { return quic::RunMasqueClient(argc, argv); } |