blob: 5dbb26d9e1c7af05079b5d9338ad8fb2c68b5b16 [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"
tfarina2f78bb22015-09-08 19:16:3146#include "base/message_loop/message_loop.h"
rchfdfdc8ed2015-03-18 00:35:2047#include "base/strings/string_number_conversions.h"
48#include "base/strings/string_split.h"
49#include "base/strings/string_util.h"
50#include "net/base/ip_endpoint.h"
rchda78df5a2015-03-22 05:16:3751#include "net/base/net_errors.h"
rchfdfdc8ed2015-03-18 00:35:2052#include "net/base/privacy_mode.h"
53#include "net/cert/cert_verifier.h"
rtenneti052774e2015-11-24 21:00:1254#include "net/cert/multi_log_ct_verifier.h"
rcha9d12ce12015-03-19 23:06:4955#include "net/http/http_request_info.h"
rchfdfdc8ed2015-03-18 00:35:2056#include "net/http/transport_security_state.h"
eroman87c53d62015-04-02 06:51:0757#include "net/log/net_log.h"
rchfdfdc8ed2015-03-18 00:35:2058#include "net/quic/crypto/proof_verifier_chromium.h"
59#include "net/quic/quic_protocol.h"
60#include "net/quic/quic_server_id.h"
61#include "net/quic/quic_utils.h"
bnc7ecc1122015-09-28 13:22:4962#include "net/spdy/spdy_header_block.h"
rcha9d12ce12015-03-19 23:06:4963#include "net/spdy/spdy_http_utils.h"
64#include "net/tools/quic/quic_simple_client.h"
rchda78df5a2015-03-22 05:16:3765#include "net/tools/quic/synchronous_host_resolver.h"
rchfdfdc8ed2015-03-18 00:35:2066#include "url/gurl.h"
67
68using base::StringPiece;
69using net::CertVerifier;
rtenneti052774e2015-11-24 21:00:1270using net::CTVerifier;
71using net::MultiLogCTVerifier;
rchfdfdc8ed2015-03-18 00:35:2072using net::ProofVerifierChromium;
73using net::TransportSecurityState;
74using std::cout;
75using std::cerr;
76using std::map;
77using std::string;
78using std::vector;
79using std::endl;
80
81// The IP or hostname the quic client will connect to.
82string FLAGS_host = "";
83// The port to connect to.
rchf114d982015-10-21 01:34:5684int32 FLAGS_port = 0;
rchfdfdc8ed2015-03-18 00:35:2085// If set, send a POST with this body.
86string FLAGS_body = "";
87// A semicolon separated list of key:value pairs to add to request headers.
88string FLAGS_headers = "";
89// Set to true for a quieter output experience.
90bool FLAGS_quiet = false;
91// QUIC version to speak, e.g. 21. If not set, then all available versions are
92// offered in the handshake.
93int32 FLAGS_quic_version = -1;
94// If true, a version mismatch in the handshake is not considered a failure.
95// Useful for probing a server to determine if it speaks any version of QUIC.
96bool FLAGS_version_mismatch_ok = false;
97// If true, an HTTP response code of 3xx is considered to be a successful
98// response, otherwise a failure.
99bool FLAGS_redirect_is_success = true;
rtenneti6bd660b2015-07-18 00:19:53100// Initial MTU of the connection.
101int32 FLAGS_initial_mtu = 0;
rchfdfdc8ed2015-03-18 00:35:20102
rche22c2fb2015-11-17 00:22:32103class FakeCertVerifier : public net::CertVerifier {
104 public:
105 int Verify(net::X509Certificate* cert,
106 const std::string& hostname,
107 const std::string& ocsp_response,
108 int flags,
109 net::CRLSet* crl_set,
110 net::CertVerifyResult* verify_result,
111 const net::CompletionCallback& callback,
112 scoped_ptr<net::CertVerifier::Request>* out_req,
113 const net::BoundNetLog& net_log) override {
114 return net::OK;
115 }
116
117 // Returns true if this CertVerifier supports stapled OCSP responses.
118 bool SupportsOCSPStapling() override { return false; }
119};
120
rchfdfdc8ed2015-03-18 00:35:20121int main(int argc, char *argv[]) {
122 base::CommandLine::Init(argc, argv);
123 base::CommandLine* line = base::CommandLine::ForCurrentProcess();
124 const base::CommandLine::StringVector& urls = line->GetArgs();
125
126 logging::LoggingSettings settings;
127 settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
128 CHECK(logging::InitLogging(settings));
129
130 if (line->HasSwitch("h") || line->HasSwitch("help") || urls.empty()) {
131 const char* help_str =
132 "Usage: quic_client [options] <url>\n"
133 "\n"
134 "<url> with scheme must be provided (e.g. http://www.google.com)\n\n"
135 "Options:\n"
136 "-h, --help show this help message and exit\n"
137 "--host=<host> specify the IP address of the hostname to "
138 "connect to\n"
139 "--port=<port> specify the port to connect to\n"
140 "--body=<body> specify the body to post\n"
141 "--headers=<headers> specify a semicolon separated list of "
142 "key:value pairs to add to request headers\n"
143 "--quiet specify for a quieter output experience\n"
144 "--quic-version=<quic version> specify QUIC version to speak\n"
145 "--version_mismatch_ok if specified a version mismatch in the "
146 "handshake is not considered a failure\n"
147 "--redirect_is_success if specified an HTTP response code of 3xx "
rtenneti6bd660b2015-07-18 00:19:53148 "is considered to be a successful response, otherwise a failure\n"
149 "--initial_mtu=<initial_mtu> specify the initial MTU of the connection"
rche22c2fb2015-11-17 00:22:32150 "\n"
151 "--disable-certificate-verification do not verify certificates\n";
rchfdfdc8ed2015-03-18 00:35:20152 cout << help_str;
153 exit(0);
154 }
155 if (line->HasSwitch("host")) {
156 FLAGS_host = line->GetSwitchValueASCII("host");
157 }
158 if (line->HasSwitch("port")) {
159 if (!base::StringToInt(line->GetSwitchValueASCII("port"), &FLAGS_port)) {
160 std::cerr << "--port must be an integer\n";
161 return 1;
162 }
163 }
164 if (line->HasSwitch("body")) {
165 FLAGS_body = line->GetSwitchValueASCII("body");
166 }
167 if (line->HasSwitch("headers")) {
168 FLAGS_headers = line->GetSwitchValueASCII("headers");
169 }
170 if (line->HasSwitch("quiet")) {
171 FLAGS_quiet = true;
172 }
173 if (line->HasSwitch("quic-version")) {
174 int quic_version;
175 if (base::StringToInt(line->GetSwitchValueASCII("quic-version"),
176 &quic_version)) {
177 FLAGS_quic_version = quic_version;
178 }
179 }
180 if (line->HasSwitch("version_mismatch_ok")) {
181 FLAGS_version_mismatch_ok = true;
182 }
183 if (line->HasSwitch("redirect_is_success")) {
184 FLAGS_redirect_is_success = true;
185 }
rtenneti6bd660b2015-07-18 00:19:53186 if (line->HasSwitch("initial_mtu")) {
187 if (!base::StringToInt(line->GetSwitchValueASCII("initial_mtu"),
188 &FLAGS_initial_mtu)) {
189 std::cerr << "--initial_mtu must be an integer\n";
190 return 1;
191 }
192 }
rchfdfdc8ed2015-03-18 00:35:20193
194 VLOG(1) << "server host: " << FLAGS_host << " port: " << FLAGS_port
195 << " body: " << FLAGS_body << " headers: " << FLAGS_headers
196 << " quiet: " << FLAGS_quiet
197 << " quic-version: " << FLAGS_quic_version
198 << " version_mismatch_ok: " << FLAGS_version_mismatch_ok
rtenneti6bd660b2015-07-18 00:19:53199 << " redirect_is_success: " << FLAGS_redirect_is_success
200 << " initial_mtu: " << FLAGS_initial_mtu;
rchfdfdc8ed2015-03-18 00:35:20201
202 base::AtExitManager exit_manager;
rcha9d12ce12015-03-19 23:06:49203 base::MessageLoopForIO message_loop;
rchfdfdc8ed2015-03-18 00:35:20204
205 // Determine IP address to connect to from supplied hostname.
206 net::IPAddressNumber ip_addr;
207
208 // TODO(rtenneti): GURL's doesn't support default_protocol argument, thus
209 // protocol is required in the URL.
210 GURL url(urls[0]);
211 string host = FLAGS_host;
rchfdfdc8ed2015-03-18 00:35:20212 if (host.empty()) {
rchda78df5a2015-03-22 05:16:37213 host = url.host();
rchfdfdc8ed2015-03-18 00:35:20214 }
rchf114d982015-10-21 01:34:56215 int port = FLAGS_port;
216 if (port == 0) {
217 port = url.EffectiveIntPort();
218 }
rchfdfdc8ed2015-03-18 00:35:20219 if (!net::ParseIPLiteralToNumber(host, &ip_addr)) {
rchda78df5a2015-03-22 05:16:37220 net::AddressList addresses;
221 int rv = net::tools::SynchronousHostResolver::Resolve(host, &addresses);
222 if (rv != net::OK) {
223 LOG(ERROR) << "Unable to resolve '" << host << "' : "
rchbd931542015-03-23 02:25:39224 << net::ErrorToShortString(rv);
rchda78df5a2015-03-22 05:16:37225 return 1;
226 }
227 ip_addr = addresses[0].address();
rchfdfdc8ed2015-03-18 00:35:20228 }
229
230 string host_port = net::IPAddressToStringWithPort(ip_addr, FLAGS_port);
231 VLOG(1) << "Resolved " << host << " to " << host_port << endl;
232
233 // Build the client, and try to connect.
rchf114d982015-10-21 01:34:56234 net::QuicServerId server_id(url.host(), url.EffectiveIntPort(),
rch1fe2eeb2015-10-26 14:45:57235 net::PRIVACY_MODE_DISABLED);
rchfdfdc8ed2015-03-18 00:35:20236 net::QuicVersionVector versions = net::QuicSupportedVersions();
237 if (FLAGS_quic_version != -1) {
238 versions.clear();
239 versions.push_back(static_cast<net::QuicVersion>(FLAGS_quic_version));
240 }
rtenneti5a87ce42015-11-10 05:35:31241 // For secure QUIC we need to verify the cert chain.
242 scoped_ptr<CertVerifier> cert_verifier(CertVerifier::CreateDefault());
rche22c2fb2015-11-17 00:22:32243 if (line->HasSwitch("disable-certificate-verification")) {
244 cert_verifier.reset(new FakeCertVerifier());
245 }
rtenneti5a87ce42015-11-10 05:35:31246 scoped_ptr<TransportSecurityState> transport_security_state(
247 new TransportSecurityState);
rtenneti052774e2015-11-24 21:00:12248 scoped_ptr<CTVerifier> ct_verifier(new MultiLogCTVerifier());
rch1fe2eeb2015-10-26 14:45:57249 ProofVerifierChromium* proof_verifier = new ProofVerifierChromium(
rtenneti052774e2015-11-24 21:00:12250 cert_verifier.get(), nullptr, transport_security_state.get(),
251 ct_verifier.get());
rch1fe2eeb2015-10-26 14:45:57252 net::tools::QuicSimpleClient client(net::IPEndPoint(ip_addr, port), server_id,
253 versions, proof_verifier);
rtennetibb2780c2015-07-22 19:16:08254 client.set_initial_max_packet_length(
255 FLAGS_initial_mtu != 0 ? FLAGS_initial_mtu : net::kDefaultMaxPacketSize);
rchfdfdc8ed2015-03-18 00:35:20256 if (!client.Initialize()) {
257 cerr << "Failed to initialize client." << endl;
258 return 1;
259 }
260 if (!client.Connect()) {
261 net::QuicErrorCode error = client.session()->error();
262 if (FLAGS_version_mismatch_ok && error == net::QUIC_INVALID_VERSION) {
rtenneti85816fdf2015-05-25 03:01:10263 cout << "Server talks QUIC, but none of the versions supported by "
rchfdfdc8ed2015-03-18 00:35:20264 << "this client: " << QuicVersionVectorToString(versions) << endl;
265 // Version mismatch is not deemed a failure.
266 return 0;
267 }
268 cerr << "Failed to connect to " << host_port
269 << ". Error: " << net::QuicUtils::ErrorToString(error) << endl;
270 return 1;
271 }
272 cout << "Connected to " << host_port << endl;
273
274 // Construct a GET or POST request for supplied URL.
rcha9d12ce12015-03-19 23:06:49275 net::HttpRequestInfo request;
276 request.method = FLAGS_body.empty() ? "GET" : "POST";
277 request.url = url;
rchfdfdc8ed2015-03-18 00:35:20278
279 // Append any additional headers supplied on the command line.
brettw8cc24ae22015-07-06 23:53:00280 for (const std::string& header :
281 base::SplitString(FLAGS_headers, ";", base::KEEP_WHITESPACE,
282 base::SPLIT_WANT_NONEMPTY)) {
rchfdfdc8ed2015-03-18 00:35:20283 string sp;
brettw8cc24ae22015-07-06 23:53:00284 base::TrimWhitespaceASCII(header, base::TRIM_ALL, &sp);
rchfdfdc8ed2015-03-18 00:35:20285 if (sp.empty()) {
286 continue;
287 }
brettw3a2c6902015-07-06 19:43:29288 vector<string> kv =
289 base::SplitString(sp, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
rchfdfdc8ed2015-03-18 00:35:20290 CHECK_EQ(2u, kv.size());
291 string key;
292 base::TrimWhitespaceASCII(kv[0], base::TRIM_ALL, &key);
293 string value;
294 base::TrimWhitespaceASCII(kv[1], base::TRIM_ALL, &value);
rcha9d12ce12015-03-19 23:06:49295 request.extra_headers.SetHeader(key, value);
rchfdfdc8ed2015-03-18 00:35:20296 }
297
298 // Make sure to store the response, for later output.
299 client.set_store_response(true);
300
301 // Send the request.
rcha9d12ce12015-03-19 23:06:49302 net::SpdyHeaderBlock header_block;
303 net::CreateSpdyHeadersFromHttpRequest(request, request.extra_headers,
rtenneti0b073d02015-07-28 04:27:56304 net::HTTP2, /*direct=*/true,
rcha9d12ce12015-03-19 23:06:49305 &header_block);
306 client.SendRequestAndWaitForResponse(request, FLAGS_body, /*fin=*/true);
rchfdfdc8ed2015-03-18 00:35:20307
308 // Print request and response details.
309 if (!FLAGS_quiet) {
310 cout << "Request:" << endl;
311 cout << "headers:" << endl;
bnc7ecc1122015-09-28 13:22:49312 for (const auto& kv : header_block) {
rchfdfdc8ed2015-03-18 00:35:20313 cout << " " << kv.first << ": " << kv.second << endl;
314 }
315 cout << "body: " << FLAGS_body << endl;
rcha9d12ce12015-03-19 23:06:49316 cout << endl;
317 cout << "Response:" << endl;
rchfdfdc8ed2015-03-18 00:35:20318 cout << "headers: " << client.latest_response_headers() << endl;
319 cout << "body: " << client.latest_response_body() << endl;
320 }
321
322 size_t response_code = client.latest_response_code();
323 if (response_code >= 200 && response_code < 300) {
324 cout << "Request succeeded (" << response_code << ")." << endl;
325 return 0;
326 } else if (response_code >= 300 && response_code < 400) {
327 if (FLAGS_redirect_is_success) {
328 cout << "Request succeeded (redirect " << response_code << ")." << endl;
329 return 0;
330 } else {
331 cout << "Request failed (redirect " << response_code << ")." << endl;
332 return 1;
333 }
334 } else {
335 cerr << "Request failed (" << response_code << ")." << endl;
336 return 1;
337 }
338}