blob: e790b548ea842a85f8a12fb78c4c474ee758a7f0 [file] [log] [blame]
rch86a45622019-05-15 19:19:04 -07001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// A binary wrapper for QuicClient.
6// Connects to a host using QUIC, sends a request to the provided URL, and
7// displays the response.
8//
9// Some usage examples:
10//
11// Standard request/response:
12// quic_client www.google.com
13// quic_client www.google.com --quiet
14// quic_client www.google.com --port=443
15//
16// Use a specific version:
17// quic_client www.google.com --quic_version=23
18//
19// Send a POST instead of a GET:
20// quic_client www.google.com --body="this is a POST body"
21//
22// Append additional headers to the request:
23// quic_client www.google.com --headers="Header-A: 1234; Header-B: 5678"
24//
25// Connect to a host different to the URL being requested:
26// quic_client mail.google.com --host=www.google.com
27//
28// Connect to a specific IP:
29// IP=`dig www.google.com +short | head -1`
30// quic_client www.google.com --host=${IP}
31//
32// Send repeated requests and change ephemeral port between requests
33// quic_client www.google.com --num_requests=10
34//
35// Try to connect to a host which does not speak QUIC:
36// quic_client www.example.com
37//
38// This tool is available as a built binary at:
39// /google/data/ro/teams/quic/tools/quic_client
40// After submitting changes to this file, you will need to follow the
41// instructions at go/quic_client_binary_update
42
43#include "net/third_party/quiche/src/quic/tools/quic_toy_client.h"
44
rch86a45622019-05-15 19:19:04 -070045#include <iostream>
46#include <memory>
47#include <string>
bnc463f2352019-10-10 04:49:34 -070048#include <utility>
rch86a45622019-05-15 19:19:04 -070049#include <vector>
50
51#include "net/third_party/quiche/src/quic/core/quic_packets.h"
52#include "net/third_party/quiche/src/quic/core/quic_server_id.h"
53#include "net/third_party/quiche/src/quic/core/quic_utils.h"
54#include "net/third_party/quiche/src/quic/core/quic_versions.h"
55#include "net/third_party/quiche/src/quic/platform/api/quic_default_proof_providers.h"
rch86a45622019-05-15 19:19:04 -070056#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
rch86a45622019-05-15 19:19:04 -070057#include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h"
rch86a45622019-05-15 19:19:04 -070058#include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h"
59#include "net/third_party/quiche/src/quic/tools/quic_url.h"
QUICHE team5015e2e2019-12-11 09:38:06 -080060#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
61#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
rch86a45622019-05-15 19:19:04 -070062
63namespace {
64
rch86a45622019-05-15 19:19:04 -070065using quic::QuicUrl;
QUICHE team5015e2e2019-12-11 09:38:06 -080066using quiche::QuicheStringPiece;
67using quiche::QuicheTextUtils;
rch86a45622019-05-15 19:19:04 -070068
69} // namespace
70
71DEFINE_QUIC_COMMAND_LINE_FLAG(
72 std::string,
73 host,
74 "",
75 "The IP or hostname to connect to. If not provided, the host "
76 "will be derived from the provided URL.");
77
78DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
79
80DEFINE_QUIC_COMMAND_LINE_FLAG(std::string,
81 body,
82 "",
83 "If set, send a POST with this body.");
84
85DEFINE_QUIC_COMMAND_LINE_FLAG(
86 std::string,
87 body_hex,
88 "",
89 "If set, contents are converted from hex to ascii, before "
90 "sending as body of a POST. e.g. --body_hex=\"68656c6c6f\"");
91
92DEFINE_QUIC_COMMAND_LINE_FLAG(
93 std::string,
94 headers,
95 "",
96 "A semicolon separated list of key:value pairs to "
97 "add to request headers.");
98
99DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
100 quiet,
101 false,
102 "Set to true for a quieter output experience.");
103
104DEFINE_QUIC_COMMAND_LINE_FLAG(
105 std::string,
106 quic_version,
107 "",
108 "QUIC version to speak, e.g. 21. If not set, then all available "
109 "versions are offered in the handshake. Also supports wire versions "
110 "such as Q043 or T099.");
111
rcha702be22019-08-30 15:20:12 -0700112DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
113 quic_ietf_draft,
114 false,
115 "Use the IETF draft version. This also enables "
116 "required internal QUIC flags.");
rch86a45622019-05-15 19:19:04 -0700117
118DEFINE_QUIC_COMMAND_LINE_FLAG(
119 bool,
120 version_mismatch_ok,
121 false,
122 "If true, a version mismatch in the handshake is not considered a "
123 "failure. Useful for probing a server to determine if it speaks "
124 "any version of QUIC.");
125
126DEFINE_QUIC_COMMAND_LINE_FLAG(
127 bool,
128 force_version_negotiation,
129 false,
130 "If true, start by proposing a version that is reserved for version "
131 "negotiation.");
132
133DEFINE_QUIC_COMMAND_LINE_FLAG(
134 bool,
135 redirect_is_success,
136 true,
137 "If true, an HTTP response code of 3xx is considered to be a "
138 "successful response, otherwise a failure.");
139
140DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t,
141 initial_mtu,
142 0,
143 "Initial MTU of the connection.");
144
145DEFINE_QUIC_COMMAND_LINE_FLAG(
146 int32_t,
147 num_requests,
148 1,
149 "How many sequential requests to make on a single connection.");
150
151DEFINE_QUIC_COMMAND_LINE_FLAG(bool,
152 disable_certificate_verification,
153 false,
154 "If true, don't verify the server certificate.");
155
156DEFINE_QUIC_COMMAND_LINE_FLAG(
157 bool,
158 drop_response_body,
159 false,
160 "If true, drop response body immediately after it is received.");
161
rch20a05e22019-09-23 09:36:57 -0700162DEFINE_QUIC_COMMAND_LINE_FLAG(
163 bool,
164 disable_port_changes,
165 false,
166 "If true, do not change local port after each request.");
167
rch86a45622019-05-15 19:19:04 -0700168namespace quic {
169
rchd9142502019-05-17 14:31:14 -0700170QuicToyClient::QuicToyClient(ClientFactory* client_factory)
rch86a45622019-05-15 19:19:04 -0700171 : client_factory_(client_factory) {}
172
rchd9142502019-05-17 14:31:14 -0700173int QuicToyClient::SendRequestsAndPrintResponses(
rch86a45622019-05-15 19:19:04 -0700174 std::vector<std::string> urls) {
175 QuicUrl url(urls[0], "https");
176 std::string host = GetQuicFlag(FLAGS_host);
177 if (host.empty()) {
178 host = url.host();
179 }
180 int port = GetQuicFlag(FLAGS_port);
181 if (port == 0) {
182 port = url.port();
183 }
184
185 quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();
186
187 std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
rcha702be22019-08-30 15:20:12 -0700188 if (GetQuicFlag(FLAGS_quic_ietf_draft)) {
189 quic::QuicVersionInitializeSupportForIetfDraft();
190 versions = {{quic::PROTOCOL_TLS1_3, quic::QUIC_VERSION_99}};
191 quic::QuicEnableVersion(versions[0]);
192
193 } else if (!quic_version_string.empty()) {
rch86a45622019-05-15 19:19:04 -0700194 quic::ParsedQuicVersion parsed_quic_version =
195 quic::ParseQuicVersionString(quic_version_string);
196 if (parsed_quic_version.transport_version ==
197 quic::QUIC_VERSION_UNSUPPORTED) {
198 return 1;
199 }
rcha702be22019-08-30 15:20:12 -0700200 versions = {parsed_quic_version};
rch86a45622019-05-15 19:19:04 -0700201 quic::QuicEnableVersion(parsed_quic_version);
202 }
203
204 if (GetQuicFlag(FLAGS_force_version_negotiation)) {
205 versions.insert(versions.begin(),
206 quic::QuicVersionReservedForNegotiation());
207 }
208
209 const int32_t num_requests(GetQuicFlag(FLAGS_num_requests));
210 std::unique_ptr<quic::ProofVerifier> proof_verifier;
211 if (GetQuicFlag(FLAGS_disable_certificate_verification)) {
vasilvv0fc587f2019-09-06 13:33:08 -0700212 proof_verifier = std::make_unique<FakeProofVerifier>();
rch86a45622019-05-15 19:19:04 -0700213 } else {
nharper6039aff2019-08-26 17:41:13 -0700214 proof_verifier = quic::CreateDefaultProofVerifier(url.host());
rch86a45622019-05-15 19:19:04 -0700215 }
216
217 // Build the client, and try to connect.
218 std::unique_ptr<QuicSpdyClientBase> client = client_factory_->CreateClient(
wub4ea2ddf2019-06-10 15:55:10 -0700219 url.host(), host, port, versions, std::move(proof_verifier));
rch86a45622019-05-15 19:19:04 -0700220
dschinazi0e48f122019-09-04 11:56:03 -0700221 if (client == nullptr) {
222 std::cerr << "Failed to create client." << std::endl;
223 return 1;
224 }
225
rch86a45622019-05-15 19:19:04 -0700226 int32_t initial_mtu = GetQuicFlag(FLAGS_initial_mtu);
227 client->set_initial_max_packet_length(
228 initial_mtu != 0 ? initial_mtu : quic::kDefaultMaxPacketSize);
229 client->set_drop_response_body(GetQuicFlag(FLAGS_drop_response_body));
230 if (!client->Initialize()) {
231 std::cerr << "Failed to initialize client." << std::endl;
232 return 1;
233 }
234 if (!client->Connect()) {
235 quic::QuicErrorCode error = client->session()->error();
236 if (error == quic::QUIC_INVALID_VERSION) {
237 std::cerr << "Server talks QUIC, but none of the versions supported by "
238 << "this client: " << ParsedQuicVersionVectorToString(versions)
239 << std::endl;
240 // 0: No error.
241 // 20: Failed to connect due to QUIC_INVALID_VERSION.
242 return GetQuicFlag(FLAGS_version_mismatch_ok) ? 0 : 20;
243 }
244 std::cerr << "Failed to connect to " << host << ":" << port
245 << ". Error: " << quic::QuicErrorCodeToString(error) << std::endl;
246 return 1;
247 }
248 std::cerr << "Connected to " << host << ":" << port << std::endl;
249
250 // Construct the string body from flags, if provided.
251 std::string body = GetQuicFlag(FLAGS_body);
252 if (!GetQuicFlag(FLAGS_body_hex).empty()) {
253 DCHECK(GetQuicFlag(FLAGS_body).empty())
254 << "Only set one of --body and --body_hex.";
QUICHE team5015e2e2019-12-11 09:38:06 -0800255 body = QuicheTextUtils::HexDecode(GetQuicFlag(FLAGS_body_hex));
rch86a45622019-05-15 19:19:04 -0700256 }
257
258 // Construct a GET or POST request for supplied URL.
259 spdy::SpdyHeaderBlock header_block;
260 header_block[":method"] = body.empty() ? "GET" : "POST";
261 header_block[":scheme"] = url.scheme();
262 header_block[":authority"] = url.HostPort();
263 header_block[":path"] = url.PathParamsQuery();
264
265 // Append any additional headers supplied on the command line.
266 const std::string headers = GetQuicFlag(FLAGS_headers);
QUICHE team5015e2e2019-12-11 09:38:06 -0800267 for (QuicheStringPiece sp : QuicheTextUtils::Split(headers, ';')) {
268 QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&sp);
rch86a45622019-05-15 19:19:04 -0700269 if (sp.empty()) {
270 continue;
271 }
QUICHE team5015e2e2019-12-11 09:38:06 -0800272 std::vector<QuicheStringPiece> kv = QuicheTextUtils::Split(sp, ':');
273 QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]);
274 QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]);
rch86a45622019-05-15 19:19:04 -0700275 header_block[kv[0]] = kv[1];
276 }
277
278 // Make sure to store the response, for later output.
279 client->set_store_response(true);
280
281 for (int i = 0; i < num_requests; ++i) {
282 // Send the request.
283 client->SendRequestAndWaitForResponse(header_block, body, /*fin=*/true);
284
285 // Print request and response details.
286 if (!GetQuicFlag(FLAGS_quiet)) {
287 std::cout << "Request:" << std::endl;
288 std::cout << "headers:" << header_block.DebugString();
289 if (!GetQuicFlag(FLAGS_body_hex).empty()) {
290 // Print the user provided hex, rather than binary body.
291 std::cout << "body:\n"
QUICHE team5015e2e2019-12-11 09:38:06 -0800292 << QuicheTextUtils::HexDump(QuicheTextUtils::HexDecode(
293 GetQuicFlag(FLAGS_body_hex)))
rch86a45622019-05-15 19:19:04 -0700294 << std::endl;
295 } else {
296 std::cout << "body: " << body << std::endl;
297 }
298 std::cout << std::endl;
299
300 if (!client->preliminary_response_headers().empty()) {
301 std::cout << "Preliminary response headers: "
302 << client->preliminary_response_headers() << std::endl;
303 std::cout << std::endl;
304 }
305
306 std::cout << "Response:" << std::endl;
307 std::cout << "headers: " << client->latest_response_headers()
308 << std::endl;
309 std::string response_body = client->latest_response_body();
310 if (!GetQuicFlag(FLAGS_body_hex).empty()) {
311 // Assume response is binary data.
312 std::cout << "body:\n"
QUICHE team5015e2e2019-12-11 09:38:06 -0800313 << QuicheTextUtils::HexDump(response_body) << std::endl;
rch86a45622019-05-15 19:19:04 -0700314 } else {
315 std::cout << "body: " << response_body << std::endl;
316 }
317 std::cout << "trailers: " << client->latest_response_trailers()
318 << std::endl;
319 }
320
321 if (!client->connected()) {
322 std::cerr << "Request caused connection failure. Error: "
323 << quic::QuicErrorCodeToString(client->session()->error())
324 << std::endl;
325 return 1;
326 }
327
nharperbfb95de2019-07-23 07:18:53 -0700328 int response_code = client->latest_response_code();
rch86a45622019-05-15 19:19:04 -0700329 if (response_code >= 200 && response_code < 300) {
330 std::cout << "Request succeeded (" << response_code << ")." << std::endl;
331 } else if (response_code >= 300 && response_code < 400) {
332 if (GetQuicFlag(FLAGS_redirect_is_success)) {
333 std::cout << "Request succeeded (redirect " << response_code << ")."
334 << std::endl;
335 } else {
336 std::cout << "Request failed (redirect " << response_code << ")."
337 << std::endl;
338 return 1;
339 }
340 } else {
341 std::cout << "Request failed (" << response_code << ")." << std::endl;
342 return 1;
343 }
344
345 // Change the ephemeral port if there are more requests to do.
rch20a05e22019-09-23 09:36:57 -0700346 if (!GetQuicFlag(FLAGS_disable_port_changes) && i + 1 < num_requests) {
rch86a45622019-05-15 19:19:04 -0700347 if (!client->ChangeEphemeralPort()) {
348 std::cerr << "Failed to change ephemeral port." << std::endl;
349 return 1;
350 }
351 }
352 }
353
354 return 0;
355}
356
357} // namespace quic