// 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 "quic/tools/quic_toy_client.h" #include #include #include #include #include #include "absl/strings/escaping.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "quic/core/quic_packets.h" #include "quic/core/quic_server_id.h" #include "quic/core/quic_utils.h" #include "quic/core/quic_versions.h" #include "quic/platform/api/quic_default_proof_providers.h" #include "quic/platform/api/quic_ip_address.h" #include "quic/platform/api/quic_socket_address.h" #include "quic/platform/api/quic_system_event_loop.h" #include "quic/tools/fake_proof_verifier.h" #include "quic/tools/quic_url.h" #include "common/platform/api/quiche_text_utils.h" namespace { using quic::QuicUrl; using quiche::QuicheTextUtils; } // 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, ip_version_for_host_lookup, "", "Only used if host address lookup is needed. " "4=ipv4; 6=ipv6; otherwise=don't care."); 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( std::string, connection_options, "", "Connection options as ASCII tags separated by commas, " "e.g. \"ABCD,EFGH\""); DEFINE_QUIC_COMMAND_LINE_FLAG( std::string, client_connection_options, "", "Client connection options as ASCII tags separated by commas, " "e.g. \"ABCD,EFGH\""); 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, multi_packet_chlo, false, "If true, add a transport parameter to make the ClientHello span two " "packets. Only works with QUIC+TLS."); 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."); DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, server_connection_id_length, -1, "Length of the server connection ID used."); DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, client_connection_id_length, -1, "Length of the client connection ID used."); namespace quic { QuicToyClient::QuicToyClient(ClientFactory* client_factory) : client_factory_(client_factory) {} int QuicToyClient::SendRequestsAndPrintResponses( std::vector 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(); if (GetQuicFlag(FLAGS_quic_ietf_draft)) { quic::QuicVersionInitializeSupportForIetfDraft(); versions = {}; for (const ParsedQuicVersion& version : AllSupportedVersions()) { if (version.HasIetfQuicFrames() && version.handshake_protocol == quic::PROTOCOL_TLS1_3) { versions.push_back(version); } } } std::string quic_version_string = GetQuicFlag(FLAGS_quic_version); if (!quic_version_string.empty()) { versions = quic::ParseQuicVersionVectorString(quic_version_string); } if (versions.empty()) { std::cerr << "No known version selected." << std::endl; return 1; } for (const quic::ParsedQuicVersion& version : versions) { quic::QuicEnableVersion(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 proof_verifier; if (GetQuicFlag(FLAGS_disable_certificate_verification)) { proof_verifier = std::make_unique(); } else { proof_verifier = quic::CreateDefaultProofVerifier(url.host()); } QuicConfig config; std::string connection_options_string = GetQuicFlag(FLAGS_connection_options); if (!connection_options_string.empty()) { config.SetConnectionOptionsToSend( ParseQuicTagVector(connection_options_string)); } std::string client_connection_options_string = GetQuicFlag(FLAGS_client_connection_options); if (!client_connection_options_string.empty()) { config.SetClientConnectionOptions( ParseQuicTagVector(client_connection_options_string)); } if (GetQuicFlag(FLAGS_multi_packet_chlo)) { // Make the ClientHello span multiple packets by adding a custom transport // parameter. constexpr auto kCustomParameter = static_cast(0x173E); std::string custom_value(2000, '?'); config.custom_transport_parameters_to_send()[kCustomParameter] = custom_value; } int address_family_for_lookup = AF_UNSPEC; if (GetQuicFlag(FLAGS_ip_version_for_host_lookup) == "4") { address_family_for_lookup = AF_INET; } else if (GetQuicFlag(FLAGS_ip_version_for_host_lookup) == "6") { address_family_for_lookup = AF_INET6; } // Build the client, and try to connect. std::unique_ptr client = client_factory_->CreateClient( url.host(), host, address_family_for_lookup, port, versions, config, 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)); const int32_t server_connection_id_length = GetQuicFlag(FLAGS_server_connection_id_length); if (server_connection_id_length >= 0) { client->set_server_connection_id_length(server_connection_id_length); } const int32_t client_connection_id_length = GetQuicFlag(FLAGS_client_connection_id_length); if (client_connection_id_length >= 0) { client->set_client_connection_id_length(client_connection_id_length); } 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 << "Failed to negotiate version with " << host << ":" << port << ". " << client->session()->error_details() << 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 << ". " << quic::QuicErrorCodeToString(error) << " " << client->session()->error_details() << 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()) { QUICHE_DCHECK(GetQuicFlag(FLAGS_body).empty()) << "Only set one of --body and --body_hex."; body = absl::HexStringToBytes(GetQuicFlag(FLAGS_body_hex)); } // Construct a GET or POST request for supplied URL. spdy::Http2HeaderBlock 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 (absl::string_view sp : absl::StrSplit(headers, ';')) { QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&sp); if (sp.empty()) { continue; } std::vector kv = absl::StrSplit(sp, ':'); QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]); QuicheTextUtils::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" << QuicheTextUtils::HexDump( absl::HexStringToBytes(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" << QuicheTextUtils::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