blob: 4159735928068d720ed44b71b181c196de929d9c [file] [log] [blame]
// Copyright (c) 2012 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.
// A binary wrapper for QuicClient.
// Connects to a host using QUIC, sends a request to the provided URL, and
// displays the response.
//
// Some usage examples:
//
// Standard request/response:
// quic_client www.google.com
// quic_client www.google.com --quiet
// quic_client www.google.com --port=443
//
// Use a specific version:
// quic_client www.google.com --quic_version=23
//
// Send a POST instead of a GET:
// quic_client www.google.com --body="this is a POST body"
//
// Append additional headers to the request:
// quic_client www.google.com --headers="Header-A: 1234; Header-B: 5678"
//
// Connect to a host different to the URL being requested:
// quic_client mail.google.com --host=www.google.com
//
// Connect to a specific IP:
// IP=`dig www.google.com +short | head -1`
// quic_client www.google.com --host=${IP}
//
// Send repeated requests and change ephemeral port between requests
// quic_client www.google.com --num_requests=10
//
// Try to connect to a host which does not speak QUIC:
// quic_client www.example.com
//
// This tool is available as a built binary at:
// /google/data/ro/teams/quic/tools/quic_client
// After submitting changes to this file, you will need to follow the
// instructions at go/quic_client_binary_update
#include "net/third_party/quiche/src/quic/tools/quic_toy_client.h"
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "net/third_party/quiche/src/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quic/core/quic_server_id.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/core/quic_versions.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_socket_address.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.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 {
using quic::QuicStringPiece;
using quic::QuicTextUtils;
using quic::QuicUrl;
} // namespace
DEFINE_QUIC_COMMAND_LINE_FLAG(
std::string,
host,
"",
"The IP or hostname to connect to. If not provided, the host "
"will be derived from the provided URL.");
DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
DEFINE_QUIC_COMMAND_LINE_FLAG(std::string,
body,
"",
"If set, send a POST with this body.");
DEFINE_QUIC_COMMAND_LINE_FLAG(
std::string,
body_hex,
"",
"If set, contents are converted from hex to ascii, before "
"sending as body of a POST. e.g. --body_hex=\"68656c6c6f\"");
DEFINE_QUIC_COMMAND_LINE_FLAG(
std::string,
headers,
"",
"A semicolon separated list of key:value pairs to "
"add to request headers.");
DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
quiet,
false,
"Set to true for a quieter output experience.");
DEFINE_QUIC_COMMAND_LINE_FLAG(
std::string,
quic_version,
"",
"QUIC version to speak, e.g. 21. If not set, then all available "
"versions are offered in the handshake. Also supports wire versions "
"such as Q043 or T099.");
DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
quic_ietf_draft,
false,
"Use the IETF draft version. This also enables "
"required internal QUIC flags.");
DEFINE_QUIC_COMMAND_LINE_FLAG(
bool,
version_mismatch_ok,
false,
"If true, a version mismatch in the handshake is not considered a "
"failure. Useful for probing a server to determine if it speaks "
"any version of QUIC.");
DEFINE_QUIC_COMMAND_LINE_FLAG(
bool,
force_version_negotiation,
false,
"If true, start by proposing a version that is reserved for version "
"negotiation.");
DEFINE_QUIC_COMMAND_LINE_FLAG(
bool,
redirect_is_success,
true,
"If true, an HTTP response code of 3xx is considered to be a "
"successful response, otherwise a failure.");
DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t,
initial_mtu,
0,
"Initial MTU of the connection.");
DEFINE_QUIC_COMMAND_LINE_FLAG(
int32_t,
num_requests,
1,
"How many sequential requests to make on a single connection.");
DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
disable_certificate_verification,
false,
"If true, don't verify the server certificate.");
DEFINE_QUIC_COMMAND_LINE_FLAG(
bool,
drop_response_body,
false,
"If true, drop response body immediately after it is received.");
DEFINE_QUIC_COMMAND_LINE_FLAG(
bool,
disable_port_changes,
false,
"If true, do not change local port after each request.");
namespace quic {
QuicToyClient::QuicToyClient(ClientFactory* client_factory)
: client_factory_(client_factory) {}
int QuicToyClient::SendRequestsAndPrintResponses(
std::vector<std::string> urls) {
QuicUrl url(urls[0], "https");
std::string host = GetQuicFlag(FLAGS_host);
if (host.empty()) {
host = url.host();
}
int port = GetQuicFlag(FLAGS_port);
if (port == 0) {
port = url.port();
}
quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();
std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
if (GetQuicFlag(FLAGS_quic_ietf_draft)) {
quic::QuicVersionInitializeSupportForIetfDraft();
versions = {{quic::PROTOCOL_TLS1_3, quic::QUIC_VERSION_99}};
quic::QuicEnableVersion(versions[0]);
} else if (!quic_version_string.empty()) {
if (quic_version_string[0] == 'T') {
// ParseQuicVersionString checks quic_supports_tls_handshake.
SetQuicReloadableFlag(quic_supports_tls_handshake, true);
}
quic::ParsedQuicVersion parsed_quic_version =
quic::ParseQuicVersionString(quic_version_string);
if (parsed_quic_version.transport_version ==
quic::QUIC_VERSION_UNSUPPORTED) {
return 1;
}
versions = {parsed_quic_version};
quic::QuicEnableVersion(parsed_quic_version);
}
if (GetQuicFlag(FLAGS_force_version_negotiation)) {
versions.insert(versions.begin(),
quic::QuicVersionReservedForNegotiation());
}
const int32_t num_requests(GetQuicFlag(FLAGS_num_requests));
std::unique_ptr<quic::ProofVerifier> proof_verifier;
if (GetQuicFlag(FLAGS_disable_certificate_verification)) {
proof_verifier = std::make_unique<FakeProofVerifier>();
} else {
proof_verifier = quic::CreateDefaultProofVerifier(url.host());
}
// Build the client, and try to connect.
std::unique_ptr<QuicSpdyClientBase> client = client_factory_->CreateClient(
url.host(), host, port, versions, std::move(proof_verifier));
if (client == nullptr) {
std::cerr << "Failed to create client." << std::endl;
return 1;
}
int32_t initial_mtu = GetQuicFlag(FLAGS_initial_mtu);
client->set_initial_max_packet_length(
initial_mtu != 0 ? initial_mtu : quic::kDefaultMaxPacketSize);
client->set_drop_response_body(GetQuicFlag(FLAGS_drop_response_body));
if (!client->Initialize()) {
std::cerr << "Failed to initialize client." << std::endl;
return 1;
}
if (!client->Connect()) {
quic::QuicErrorCode error = client->session()->error();
if (error == quic::QUIC_INVALID_VERSION) {
std::cerr << "Server talks QUIC, but none of the versions supported by "
<< "this client: " << ParsedQuicVersionVectorToString(versions)
<< std::endl;
// 0: No error.
// 20: Failed to connect due to QUIC_INVALID_VERSION.
return GetQuicFlag(FLAGS_version_mismatch_ok) ? 0 : 20;
}
std::cerr << "Failed to connect to " << host << ":" << port
<< ". Error: " << quic::QuicErrorCodeToString(error) << std::endl;
return 1;
}
std::cerr << "Connected to " << host << ":" << port << std::endl;
// Construct the string body from flags, if provided.
std::string body = GetQuicFlag(FLAGS_body);
if (!GetQuicFlag(FLAGS_body_hex).empty()) {
DCHECK(GetQuicFlag(FLAGS_body).empty())
<< "Only set one of --body and --body_hex.";
body = QuicTextUtils::HexDecode(GetQuicFlag(FLAGS_body_hex));
}
// Construct a GET or POST request for supplied URL.
spdy::SpdyHeaderBlock header_block;
header_block[":method"] = body.empty() ? "GET" : "POST";
header_block[":scheme"] = url.scheme();
header_block[":authority"] = url.HostPort();
header_block[":path"] = url.PathParamsQuery();
// Append any additional headers supplied on the command line.
const std::string headers = GetQuicFlag(FLAGS_headers);
for (QuicStringPiece sp : QuicTextUtils::Split(headers, ';')) {
QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&sp);
if (sp.empty()) {
continue;
}
std::vector<QuicStringPiece> kv = QuicTextUtils::Split(sp, ':');
QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]);
QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]);
header_block[kv[0]] = kv[1];
}
// Make sure to store the response, for later output.
client->set_store_response(true);
for (int i = 0; i < num_requests; ++i) {
// Send the request.
client->SendRequestAndWaitForResponse(header_block, body, /*fin=*/true);
// Print request and response details.
if (!GetQuicFlag(FLAGS_quiet)) {
std::cout << "Request:" << std::endl;
std::cout << "headers:" << header_block.DebugString();
if (!GetQuicFlag(FLAGS_body_hex).empty()) {
// Print the user provided hex, rather than binary body.
std::cout << "body:\n"
<< QuicTextUtils::HexDump(
QuicTextUtils::HexDecode(GetQuicFlag(FLAGS_body_hex)))
<< std::endl;
} else {
std::cout << "body: " << body << std::endl;
}
std::cout << std::endl;
if (!client->preliminary_response_headers().empty()) {
std::cout << "Preliminary response headers: "
<< client->preliminary_response_headers() << std::endl;
std::cout << std::endl;
}
std::cout << "Response:" << std::endl;
std::cout << "headers: " << client->latest_response_headers()
<< std::endl;
std::string response_body = client->latest_response_body();
if (!GetQuicFlag(FLAGS_body_hex).empty()) {
// Assume response is binary data.
std::cout << "body:\n"
<< QuicTextUtils::HexDump(response_body) << std::endl;
} else {
std::cout << "body: " << response_body << std::endl;
}
std::cout << "trailers: " << client->latest_response_trailers()
<< std::endl;
}
if (!client->connected()) {
std::cerr << "Request caused connection failure. Error: "
<< quic::QuicErrorCodeToString(client->session()->error())
<< std::endl;
return 1;
}
int response_code = client->latest_response_code();
if (response_code >= 200 && response_code < 300) {
std::cout << "Request succeeded (" << response_code << ")." << std::endl;
} else if (response_code >= 300 && response_code < 400) {
if (GetQuicFlag(FLAGS_redirect_is_success)) {
std::cout << "Request succeeded (redirect " << response_code << ")."
<< std::endl;
} else {
std::cout << "Request failed (redirect " << response_code << ")."
<< std::endl;
return 1;
}
} else {
std::cout << "Request failed (" << response_code << ")." << std::endl;
return 1;
}
// Change the ephemeral port if there are more requests to do.
if (!GetQuicFlag(FLAGS_disable_port_changes) && i + 1 < num_requests) {
if (!client->ChangeEphemeralPort()) {
std::cerr << "Failed to change ephemeral port." << std::endl;
return 1;
}
}
}
return 0;
}
} // namespace quic