blob: bf31ecb3b134c45959a07f03d7a07d8e5161072e [file] [log] [blame]
// 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/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/platform/api/quiche_logging.h"
#include "quiche/common/quiche_ip_address.h"
#include "quiche/spdy/core/http2_header_block.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;
}
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.
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.
spdy::Http2HeaderBlock 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