blob: 956b612e0120a3562a42d6c5a3ccace88e961077 [file] [log] [blame]
rchfdfdc8ed2015-03-18 00:35:201// 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// TODO(rtenneti): make --host optional by getting IP Address of URL's host.
12//
13// Get IP address of the www.google.com
14// IP=`dig www.google.com +short | head -1`
15//
16// Standard request/response:
17// quic_client http://www.google.com --host=${IP}
18// quic_client http://www.google.com --quiet --host=${IP}
19// quic_client https://www.google.com --port=443 --host=${IP}
20//
21// Use a specific version:
rjshadec86dbfa2015-11-12 20:16:2522// quic_client http://www.google.com --quic_version=23 --host=${IP}
rchfdfdc8ed2015-03-18 00:35:2023//
24// Send a POST instead of a GET:
25// quic_client http://www.google.com --body="this is a POST body" --host=${IP}
26//
27// Append additional headers to the request:
28// quic_client http://www.google.com --host=${IP}
29// --headers="Header-A: 1234; Header-B: 5678"
30//
31// Connect to a host different to the URL being requested:
32// Get IP address of the www.google.com
33// IP=`dig www.google.com +short | head -1`
34// quic_client mail.google.com --host=${IP}
35//
36// Try to connect to a host which does not speak QUIC:
37// Get IP address of the www.example.com
38// IP=`dig www.example.com +short | head -1`
39// quic_client http://www.example.com --host=${IP}
40
41#include <iostream>
42
43#include "base/at_exit.h"
44#include "base/command_line.h"
45#include "base/logging.h"
Alexander Timin4f9c35c2018-11-01 20:15:2046#include "base/message_loop/message_loop.h"
Gabriel Charette44db1422018-08-06 11:19:3347#include "base/task/task_scheduler/task_scheduler.h"
rchda78df5a2015-03-22 05:16:3748#include "net/base/net_errors.h"
rchfdfdc8ed2015-03-18 00:35:2049#include "net/base/privacy_mode.h"
50#include "net/cert/cert_verifier.h"
rch71bd2332017-02-04 00:06:4751#include "net/cert/ct_log_verifier.h"
Ryan Sleevi8a9c9c12018-05-09 02:36:2352#include "net/cert/ct_policy_enforcer.h"
rtenneti052774e2015-11-24 21:00:1253#include "net/cert/multi_log_ct_verifier.h"
rchfdfdc8ed2015-03-18 00:35:2054#include "net/http/transport_security_state.h"
Ryan Hamiltona3ee93a72018-08-01 22:03:0855#include "net/quic/crypto/proof_verifier_chromium.h"
Bence Béky94658bf2018-05-11 19:22:5856#include "net/spdy/spdy_http_utils.h"
Ryan Hamilton56b10c5d2018-05-11 13:40:1657#include "net/third_party/quic/core/quic_error_codes.h"
58#include "net/third_party/quic/core/quic_packets.h"
59#include "net/third_party/quic/core/quic_server_id.h"
60#include "net/third_party/quic/platform/api/quic_socket_address.h"
61#include "net/third_party/quic/platform/api/quic_str_cat.h"
62#include "net/third_party/quic/platform/api/quic_string_piece.h"
63#include "net/third_party/quic/platform/api/quic_text_utils.h"
Ryan Hamilton2e003eea2018-05-02 00:24:2964#include "net/third_party/spdy/core/spdy_header_block.h"
rcha9d12ce12015-03-19 23:06:4965#include "net/tools/quic/quic_simple_client.h"
rchda78df5a2015-03-22 05:16:3766#include "net/tools/quic/synchronous_host_resolver.h"
rchfdfdc8ed2015-03-18 00:35:2067#include "url/gurl.h"
68
rchfdfdc8ed2015-03-18 00:35:2069using net::CertVerifier;
rtenneti052774e2015-11-24 21:00:1270using net::CTVerifier;
71using net::MultiLogCTVerifier;
Ryan Hamilton8d9ee76e2018-05-29 23:52:5272using quic::ProofVerifier;
rchfdfdc8ed2015-03-18 00:35:2073using net::ProofVerifierChromium;
Ryan Hamilton8d9ee76e2018-05-29 23:52:5274using quic::QuicStringPiece;
75using quic::QuicTextUtils;
rchfdfdc8ed2015-03-18 00:35:2076using net::TransportSecurityState;
Ryan Hamilton0239aac2018-05-19 00:03:1377using spdy::SpdyHeaderBlock;
rchfdfdc8ed2015-03-18 00:35:2078using std::cout;
79using std::cerr;
rchfdfdc8ed2015-03-18 00:35:2080using std::endl;
rch872e00e2016-12-02 02:48:1881using std::string;
rchfdfdc8ed2015-03-18 00:35:2082
83// The IP or hostname the quic client will connect to.
84string FLAGS_host = "";
85// The port to connect to.
Avi Drissman13fc8932015-12-20 04:40:4686int32_t FLAGS_port = 0;
rchfdfdc8ed2015-03-18 00:35:2087// If set, send a POST with this body.
88string FLAGS_body = "";
danzhb7551342015-12-18 02:06:4089// If set, contents are converted from hex to ascii, before sending as body of
90// a POST. e.g. --body_hex=\"68656c6c6f\"
91string FLAGS_body_hex = "";
rchfdfdc8ed2015-03-18 00:35:2092// A semicolon separated list of key:value pairs to add to request headers.
93string FLAGS_headers = "";
94// Set to true for a quieter output experience.
95bool FLAGS_quiet = false;
96// QUIC version to speak, e.g. 21. If not set, then all available versions are
97// offered in the handshake.
Avi Drissman13fc8932015-12-20 04:40:4698int32_t FLAGS_quic_version = -1;
rchfdfdc8ed2015-03-18 00:35:2099// If true, a version mismatch in the handshake is not considered a failure.
100// Useful for probing a server to determine if it speaks any version of QUIC.
101bool FLAGS_version_mismatch_ok = false;
102// If true, an HTTP response code of 3xx is considered to be a successful
103// response, otherwise a failure.
104bool FLAGS_redirect_is_success = true;
rtenneti6bd660b2015-07-18 00:19:53105// Initial MTU of the connection.
Avi Drissman13fc8932015-12-20 04:40:46106int32_t FLAGS_initial_mtu = 0;
rchfdfdc8ed2015-03-18 00:35:20107
Ryan Hamilton8d9ee76e2018-05-29 23:52:52108class FakeProofVerifier : public quic::ProofVerifier {
rche22c2fb2015-11-17 00:22:32109 public:
Ryan Hamilton8d9ee76e2018-05-29 23:52:52110 quic::QuicAsyncStatus VerifyProof(
shigeki.ohtsubf71e632016-10-23 15:43:54111 const string& hostname,
112 const uint16_t port,
113 const string& server_config,
Ryan Hamilton8d9ee76e2018-05-29 23:52:52114 quic::QuicTransportVersion quic_version,
115 quic::QuicStringPiece chlo_hash,
rch872e00e2016-12-02 02:48:18116 const std::vector<string>& certs,
shigeki.ohtsubf71e632016-10-23 15:43:54117 const string& cert_sct,
118 const string& signature,
Ryan Hamilton8d9ee76e2018-05-29 23:52:52119 const quic::ProofVerifyContext* context,
shigeki.ohtsubf71e632016-10-23 15:43:54120 string* error_details,
Ryan Hamilton8d9ee76e2018-05-29 23:52:52121 std::unique_ptr<quic::ProofVerifyDetails>* details,
122 std::unique_ptr<quic::ProofVerifierCallback> callback) override {
123 return quic::QUIC_SUCCESS;
rche22c2fb2015-11-17 00:22:32124 }
125
Ryan Hamilton8d9ee76e2018-05-29 23:52:52126 quic::QuicAsyncStatus VerifyCertChain(
shigeki.ohtsubf71e632016-10-23 15:43:54127 const std::string& hostname,
128 const std::vector<std::string>& certs,
Ryan Hamilton8d9ee76e2018-05-29 23:52:52129 const quic::ProofVerifyContext* verify_context,
shigeki.ohtsubf71e632016-10-23 15:43:54130 std::string* error_details,
Ryan Hamilton8d9ee76e2018-05-29 23:52:52131 std::unique_ptr<quic::ProofVerifyDetails>* verify_details,
132 std::unique_ptr<quic::ProofVerifierCallback> callback) override {
133 return quic::QUIC_SUCCESS;
shigeki.ohtsubf71e632016-10-23 15:43:54134 }
Ryan Hamilton8bb19a12018-07-23 20:29:24135
136 std::unique_ptr<quic::ProofVerifyContext> CreateDefaultContext() override {
137 return nullptr;
138 }
rche22c2fb2015-11-17 00:22:32139};
140
rjshaded5ced072015-12-18 19:26:02141int main(int argc, char* argv[]) {
rchfdfdc8ed2015-03-18 00:35:20142 base::CommandLine::Init(argc, argv);
143 base::CommandLine* line = base::CommandLine::ForCurrentProcess();
144 const base::CommandLine::StringVector& urls = line->GetArgs();
Francois Dorayb3b1d34a2018-04-05 14:31:23145 base::TaskScheduler::CreateAndStartWithDefaultParams("quic_client");
rchfdfdc8ed2015-03-18 00:35:20146
147 logging::LoggingSettings settings;
148 settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
149 CHECK(logging::InitLogging(settings));
150
151 if (line->HasSwitch("h") || line->HasSwitch("help") || urls.empty()) {
152 const char* help_str =
153 "Usage: quic_client [options] <url>\n"
154 "\n"
155 "<url> with scheme must be provided (e.g. http://www.google.com)\n\n"
156 "Options:\n"
157 "-h, --help show this help message and exit\n"
158 "--host=<host> specify the IP address of the hostname to "
159 "connect to\n"
160 "--port=<port> specify the port to connect to\n"
161 "--body=<body> specify the body to post\n"
danzhb7551342015-12-18 02:06:40162 "--body_hex=<body_hex> specify the body_hex to be printed out\n"
rchfdfdc8ed2015-03-18 00:35:20163 "--headers=<headers> specify a semicolon separated list of "
164 "key:value pairs to add to request headers\n"
165 "--quiet specify for a quieter output experience\n"
166 "--quic-version=<quic version> specify QUIC version to speak\n"
167 "--version_mismatch_ok if specified a version mismatch in the "
168 "handshake is not considered a failure\n"
169 "--redirect_is_success if specified an HTTP response code of 3xx "
rtenneti6bd660b2015-07-18 00:19:53170 "is considered to be a successful response, otherwise a failure\n"
171 "--initial_mtu=<initial_mtu> specify the initial MTU of the connection"
rche22c2fb2015-11-17 00:22:32172 "\n"
173 "--disable-certificate-verification do not verify certificates\n";
rchfdfdc8ed2015-03-18 00:35:20174 cout << help_str;
175 exit(0);
176 }
177 if (line->HasSwitch("host")) {
178 FLAGS_host = line->GetSwitchValueASCII("host");
179 }
180 if (line->HasSwitch("port")) {
181 if (!base::StringToInt(line->GetSwitchValueASCII("port"), &FLAGS_port)) {
182 std::cerr << "--port must be an integer\n";
183 return 1;
184 }
185 }
186 if (line->HasSwitch("body")) {
187 FLAGS_body = line->GetSwitchValueASCII("body");
188 }
danzhb7551342015-12-18 02:06:40189 if (line->HasSwitch("body_hex")) {
190 FLAGS_body_hex = line->GetSwitchValueASCII("body_hex");
191 }
rchfdfdc8ed2015-03-18 00:35:20192 if (line->HasSwitch("headers")) {
193 FLAGS_headers = line->GetSwitchValueASCII("headers");
194 }
195 if (line->HasSwitch("quiet")) {
196 FLAGS_quiet = true;
197 }
198 if (line->HasSwitch("quic-version")) {
199 int quic_version;
200 if (base::StringToInt(line->GetSwitchValueASCII("quic-version"),
201 &quic_version)) {
202 FLAGS_quic_version = quic_version;
203 }
204 }
205 if (line->HasSwitch("version_mismatch_ok")) {
206 FLAGS_version_mismatch_ok = true;
207 }
208 if (line->HasSwitch("redirect_is_success")) {
209 FLAGS_redirect_is_success = true;
210 }
rtenneti6bd660b2015-07-18 00:19:53211 if (line->HasSwitch("initial_mtu")) {
212 if (!base::StringToInt(line->GetSwitchValueASCII("initial_mtu"),
213 &FLAGS_initial_mtu)) {
214 std::cerr << "--initial_mtu must be an integer\n";
215 return 1;
216 }
217 }
rchfdfdc8ed2015-03-18 00:35:20218
219 VLOG(1) << "server host: " << FLAGS_host << " port: " << FLAGS_port
220 << " body: " << FLAGS_body << " headers: " << FLAGS_headers
221 << " quiet: " << FLAGS_quiet
222 << " quic-version: " << FLAGS_quic_version
223 << " version_mismatch_ok: " << FLAGS_version_mismatch_ok
rtenneti6bd660b2015-07-18 00:19:53224 << " redirect_is_success: " << FLAGS_redirect_is_success
225 << " initial_mtu: " << FLAGS_initial_mtu;
rchfdfdc8ed2015-03-18 00:35:20226
227 base::AtExitManager exit_manager;
rcha9d12ce12015-03-19 23:06:49228 base::MessageLoopForIO message_loop;
rchfdfdc8ed2015-03-18 00:35:20229
230 // Determine IP address to connect to from supplied hostname.
Ryan Hamilton8d9ee76e2018-05-29 23:52:52231 quic::QuicIpAddress ip_addr;
rchfdfdc8ed2015-03-18 00:35:20232
rchfdfdc8ed2015-03-18 00:35:20233 GURL url(urls[0]);
234 string host = FLAGS_host;
rchfdfdc8ed2015-03-18 00:35:20235 if (host.empty()) {
rchda78df5a2015-03-22 05:16:37236 host = url.host();
rchfdfdc8ed2015-03-18 00:35:20237 }
rchf114d982015-10-21 01:34:56238 int port = FLAGS_port;
239 if (port == 0) {
240 port = url.EffectiveIntPort();
241 }
rch2239d362016-12-29 01:23:00242 if (!ip_addr.FromString(host)) {
rchda78df5a2015-03-22 05:16:37243 net::AddressList addresses;
rch750447f92016-01-31 02:54:53244 int rv = net::SynchronousHostResolver::Resolve(host, &addresses);
rchda78df5a2015-03-22 05:16:37245 if (rv != net::OK) {
rjshaded5ced072015-12-18 19:26:02246 LOG(ERROR) << "Unable to resolve '" << host
247 << "' : " << net::ErrorToShortString(rv);
rchda78df5a2015-03-22 05:16:37248 return 1;
249 }
rch2239d362016-12-29 01:23:00250 ip_addr =
Ryan Hamilton8d9ee76e2018-05-29 23:52:52251 quic::QuicIpAddress(quic::QuicIpAddressImpl(addresses[0].address()));
rchfdfdc8ed2015-03-18 00:35:20252 }
253
Ryan Hamilton8d9ee76e2018-05-29 23:52:52254 string host_port = quic::QuicStrCat(ip_addr.ToString(), ":", port);
rchfdfdc8ed2015-03-18 00:35:20255 VLOG(1) << "Resolved " << host << " to " << host_port << endl;
256
257 // Build the client, and try to connect.
Ryan Hamilton8d9ee76e2018-05-29 23:52:52258 quic::QuicServerId server_id(url.host(), url.EffectiveIntPort(),
259 net::PRIVACY_MODE_DISABLED);
Ryan Hamiltona5e002ea2018-06-27 00:59:13260 quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();
rchfdfdc8ed2015-03-18 00:35:20261 if (FLAGS_quic_version != -1) {
262 versions.clear();
Ryan Hamilton8d9ee76e2018-05-29 23:52:52263 versions.push_back(quic::ParsedQuicVersion(
264 quic::PROTOCOL_QUIC_CRYPTO,
265 static_cast<quic::QuicTransportVersion>(FLAGS_quic_version)));
rchfdfdc8ed2015-03-18 00:35:20266 }
rtenneti5a87ce42015-11-10 05:35:31267 // For secure QUIC we need to verify the cert chain.
danakja9850e12016-04-18 22:28:08268 std::unique_ptr<CertVerifier> cert_verifier(CertVerifier::CreateDefault());
danakja9850e12016-04-18 22:28:08269 std::unique_ptr<TransportSecurityState> transport_security_state(
rtenneti5a87ce42015-11-10 05:35:31270 new TransportSecurityState);
rch71bd2332017-02-04 00:06:47271 std::unique_ptr<MultiLogCTVerifier> ct_verifier(new MultiLogCTVerifier());
Ryan Sleevi8a9c9c12018-05-09 02:36:23272 std::unique_ptr<net::CTPolicyEnforcer> ct_policy_enforcer(
273 new net::DefaultCTPolicyEnforcer());
Ryan Hamilton8d9ee76e2018-05-29 23:52:52274 std::unique_ptr<quic::ProofVerifier> proof_verifier;
shigeki.ohtsubf71e632016-10-23 15:43:54275 if (line->HasSwitch("disable-certificate-verification")) {
276 proof_verifier.reset(new FakeProofVerifier());
277 } else {
278 proof_verifier.reset(new ProofVerifierChromium(
279 cert_verifier.get(), ct_policy_enforcer.get(),
280 transport_security_state.get(), ct_verifier.get()));
281 }
Ryan Hamilton8d9ee76e2018-05-29 23:52:52282 net::QuicSimpleClient client(quic::QuicSocketAddress(ip_addr, port),
283 server_id, versions, std::move(proof_verifier));
rtennetibb2780c2015-07-22 19:16:08284 client.set_initial_max_packet_length(
Ryan Hamilton8d9ee76e2018-05-29 23:52:52285 FLAGS_initial_mtu != 0 ? FLAGS_initial_mtu : quic::kDefaultMaxPacketSize);
rchfdfdc8ed2015-03-18 00:35:20286 if (!client.Initialize()) {
287 cerr << "Failed to initialize client." << endl;
288 return 1;
289 }
290 if (!client.Connect()) {
Ryan Hamilton8d9ee76e2018-05-29 23:52:52291 quic::QuicErrorCode error = client.session()->error();
292 if (FLAGS_version_mismatch_ok && error == quic::QUIC_INVALID_VERSION) {
rtenneti85816fdf2015-05-25 03:01:10293 cout << "Server talks QUIC, but none of the versions supported by "
Dan Zhangfefaf5b2017-12-11 17:06:24294 << "this client: " << ParsedQuicVersionVectorToString(versions)
Michael Warres74ee3ce2017-10-09 15:26:37295 << endl;
rchfdfdc8ed2015-03-18 00:35:20296 // Version mismatch is not deemed a failure.
297 return 0;
298 }
299 cerr << "Failed to connect to " << host_port
Ryan Hamilton8d9ee76e2018-05-29 23:52:52300 << ". Error: " << quic::QuicErrorCodeToString(error) << endl;
rchfdfdc8ed2015-03-18 00:35:20301 return 1;
302 }
303 cout << "Connected to " << host_port << endl;
304
danzhb7551342015-12-18 02:06:40305 // Construct the string body from flags, if provided.
306 string body = FLAGS_body;
307 if (!FLAGS_body_hex.empty()) {
308 DCHECK(FLAGS_body.empty()) << "Only set one of --body and --body_hex.";
Ryan Hamilton8d9ee76e2018-05-29 23:52:52309 body = quic::QuicTextUtils::HexDecode(FLAGS_body_hex);
danzhb7551342015-12-18 02:06:40310 }
311
rchfdfdc8ed2015-03-18 00:35:20312 // Construct a GET or POST request for supplied URL.
rch2239d362016-12-29 01:23:00313 SpdyHeaderBlock header_block;
314 header_block[":method"] = body.empty() ? "GET" : "POST";
315 header_block[":scheme"] = url.scheme();
316 header_block[":authority"] = url.host();
317 header_block[":path"] = url.path();
rchfdfdc8ed2015-03-18 00:35:20318
319 // Append any additional headers supplied on the command line.
Ryan Hamilton8d9ee76e2018-05-29 23:52:52320 for (quic::QuicStringPiece sp :
321 quic::QuicTextUtils::Split(FLAGS_headers, ';')) {
322 quic::QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&sp);
rchfdfdc8ed2015-03-18 00:35:20323 if (sp.empty()) {
324 continue;
325 }
Ryan Hamilton8d9ee76e2018-05-29 23:52:52326 std::vector<quic::QuicStringPiece> kv = quic::QuicTextUtils::Split(sp, ':');
327 quic::QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[0]);
328 quic::QuicTextUtils::RemoveLeadingAndTrailingWhitespace(&kv[1]);
rch2239d362016-12-29 01:23:00329 header_block[kv[0]] = kv[1];
rchfdfdc8ed2015-03-18 00:35:20330 }
331
332 // Make sure to store the response, for later output.
333 client.set_store_response(true);
334
335 // Send the request.
rchc29e68c2016-09-27 17:57:39336 client.SendRequestAndWaitForResponse(header_block, body, /*fin=*/true);
rchfdfdc8ed2015-03-18 00:35:20337
338 // Print request and response details.
339 if (!FLAGS_quiet) {
340 cout << "Request:" << endl;
jri86a3c602015-12-15 06:01:03341 cout << "headers:" << header_block.DebugString();
fayanga31a74b2015-12-28 17:27:14342 if (!FLAGS_body_hex.empty()) {
343 // Print the user provided hex, rather than binary body.
ckrasic6567aa52016-07-08 09:24:35344 cout << "body:\n"
Ryan Hamilton8d9ee76e2018-05-29 23:52:52345 << quic::QuicTextUtils::HexDump(
346 quic::QuicTextUtils::HexDecode(FLAGS_body_hex))
rch16fe57a2016-05-10 21:34:33347 << endl;
fayanga31a74b2015-12-28 17:27:14348 } else {
349 cout << "body: " << body << endl;
350 }
rcha9d12ce12015-03-19 23:06:49351 cout << endl;
352 cout << "Response:" << endl;
rchfdfdc8ed2015-03-18 00:35:20353 cout << "headers: " << client.latest_response_headers() << endl;
fayanga31a74b2015-12-28 17:27:14354 string response_body = client.latest_response_body();
355 if (!FLAGS_body_hex.empty()) {
356 // Assume response is binary data.
Ryan Hamilton8d9ee76e2018-05-29 23:52:52357 cout << "body:\n" << quic::QuicTextUtils::HexDump(response_body) << endl;
fayanga31a74b2015-12-28 17:27:14358 } else {
359 cout << "body: " << response_body << endl;
360 }
rch2239d362016-12-29 01:23:00361 cout << "trailers: " << client.latest_response_trailers() << endl;
rchfdfdc8ed2015-03-18 00:35:20362 }
363
364 size_t response_code = client.latest_response_code();
365 if (response_code >= 200 && response_code < 300) {
366 cout << "Request succeeded (" << response_code << ")." << endl;
367 return 0;
368 } else if (response_code >= 300 && response_code < 400) {
369 if (FLAGS_redirect_is_success) {
370 cout << "Request succeeded (redirect " << response_code << ")." << endl;
371 return 0;
372 } else {
373 cout << "Request failed (redirect " << response_code << ")." << endl;
374 return 1;
375 }
376 } else {
377 cerr << "Request failed (" << response_code << ")." << endl;
378 return 1;
379 }
380}