| // 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 "quiche/quic/masque/masque_client_tools.h" | |
| #include <memory> | |
| #include <optional> | |
| #include <ostream> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "quiche/quic/core/crypto/proof_verifier.h" | |
| #include "quiche/quic/core/io/quic_event_loop.h" | |
| #include "quiche/quic/core/quic_error_codes.h" | |
| #include "quiche/quic/masque/masque_client.h" | |
| #include "quiche/quic/masque/masque_client_session.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_logging.h" | |
| #include "quiche/quic/platform/api/quic_socket_address.h" | |
| #include "quiche/quic/tools/fake_proof_verifier.h" | |
| #include "quiche/quic/tools/quic_name_lookup.h" | |
| #include "quiche/quic/tools/quic_url.h" | |
| #include "quiche/common/http/http_header_block.h" | |
| #include "quiche/common/platform/api/quiche_logging.h" | |
| #include "quiche/common/quiche_ip_address.h" | |
| namespace quic { | |
| namespace tools { | |
| namespace { | |
| // Helper class to ensure a fake address gets properly removed when this goes | |
| // out of scope. | |
| class FakeAddressRemover { | |
| public: | |
| FakeAddressRemover() = default; | |
| void IngestFakeAddress(const quiche::QuicheIpAddress& fake_address, | |
| MasqueClientSession* masque_client_session) { | |
| QUICHE_CHECK(masque_client_session != nullptr); | |
| QUICHE_CHECK(!fake_address_.has_value()); | |
| fake_address_ = fake_address; | |
| masque_client_session_ = masque_client_session; | |
| } | |
| ~FakeAddressRemover() { | |
| if (fake_address_.has_value()) { | |
| masque_client_session_->RemoveFakeAddress(*fake_address_); | |
| } | |
| } | |
| private: | |
| std::optional<quiche::QuicheIpAddress> fake_address_; | |
| MasqueClientSession* masque_client_session_ = nullptr; | |
| }; | |
| } // namespace | |
| std::unique_ptr<MasqueEncapsulatedClient> | |
| CreateAndConnectMasqueEncapsulatedClient( | |
| MasqueClient* masque_client, MasqueMode masque_mode, | |
| QuicEventLoop* event_loop, std::string url_string, | |
| bool disable_certificate_verification, int address_family_for_lookup, | |
| bool dns_on_client, bool is_also_underlying) { | |
| if (!masque_client->masque_client_session()->SupportsH3Datagram()) { | |
| QUIC_LOG(ERROR) << "Refusing to use MASQUE without datagram support"; | |
| return nullptr; | |
| } | |
| QuicUrl url(url_string, "https"); | |
| if (url.host().empty() && !absl::StrContains(url_string, "://")) { | |
| url = QuicUrl(absl::StrCat("https://", url_string)); | |
| } | |
| if (url.host().empty()) { | |
| QUIC_LOG(ERROR) << "Failed to parse URL \"" << url_string << "\""; | |
| return nullptr; | |
| } | |
| std::unique_ptr<ProofVerifier> proof_verifier; | |
| if (disable_certificate_verification) { | |
| proof_verifier = std::make_unique<FakeProofVerifier>(); | |
| } else { | |
| proof_verifier = CreateDefaultProofVerifier(url.host()); | |
| if (!proof_verifier) { | |
| QUICHE_LOG(ERROR) << "The default proof verifier is not supported. Pass " | |
| "in --disable_certificate_verification."; | |
| return nullptr; | |
| } | |
| } | |
| // Build the client, and try to connect. | |
| QuicSocketAddress addr; | |
| FakeAddressRemover fake_address_remover; | |
| if (dns_on_client) { | |
| addr = LookupAddress(address_family_for_lookup, url.host(), | |
| absl::StrCat(url.port())); | |
| if (!addr.IsInitialized()) { | |
| QUIC_LOG(ERROR) << "Unable to resolve address: " << url.host(); | |
| return nullptr; | |
| } | |
| } else { | |
| quiche::QuicheIpAddress fake_address = | |
| masque_client->masque_client_session()->GetFakeAddress(url.host()); | |
| fake_address_remover.IngestFakeAddress( | |
| fake_address, masque_client->masque_client_session()); | |
| addr = QuicSocketAddress(fake_address, url.port()); | |
| QUICHE_CHECK(addr.IsInitialized()); | |
| } | |
| const QuicServerId server_id(url.host(), url.port()); | |
| std::unique_ptr<MasqueEncapsulatedClient> client; | |
| if (is_also_underlying) { | |
| client = MasqueEncapsulatedClient::Create( | |
| addr, server_id, url_string, masque_mode, event_loop, | |
| std::move(proof_verifier), masque_client); | |
| } else { | |
| client = std::make_unique<MasqueEncapsulatedClient>( | |
| addr, server_id, event_loop, std::move(proof_verifier), masque_client); | |
| } | |
| if (client == nullptr) { | |
| QUIC_LOG(ERROR) << "Failed to create MasqueEncapsulatedClient for " | |
| << url_string; | |
| return nullptr; | |
| } | |
| if (!client->Prepare( | |
| MaxPacketSizeForEncapsulatedConnections(masque_client))) { | |
| QUIC_LOG(ERROR) << "Failed to prepare MasqueEncapsulatedClient for " | |
| << url_string; | |
| return nullptr; | |
| } | |
| QUIC_LOG(INFO) << "Connected client " | |
| << client->session()->connection()->client_connection_id() | |
| << " server " << client->session()->connection_id() << " for " | |
| << url_string; | |
| return client; | |
| } | |
| bool SendRequestOnMasqueEncapsulatedClient(MasqueEncapsulatedClient& client, | |
| std::string url_string) { | |
| const QuicUrl url(url_string, "https"); | |
| // 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 request for supplied URL. | |
| quiche::HttpHeaderBlock 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 |