blob: 8154fc8da9800c3c3b3ef2cf8769e1531976be99 [file] [log] [blame]
[email protected]519e49882013-03-27 08:45:321// 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
rtenneti16142b62014-09-03 21:52:115// A binary wrapper for QuicClient.
rtenneti48fc029002015-01-13 21:09:076// Connects to a host using QUIC, sends a request to the provided URL, and
7// displays the response.
[email protected]519e49882013-03-27 08:45:328//
rtenneti48fc029002015-01-13 21:09:079// 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:
22// quic_client http://www.google.com --version=23 --host=${IP}
23//
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}
[email protected]00016892013-10-08 19:00:4940
41#include <iostream>
[email protected]519e49882013-03-27 08:45:3242
43#include "base/at_exit.h"
44#include "base/command_line.h"
45#include "base/logging.h"
[email protected]ebfa6db32013-05-24 07:32:1246#include "base/strings/string_number_conversions.h"
rtenneti48fc029002015-01-13 21:09:0747#include "base/strings/string_split.h"
48#include "base/strings/string_util.h"
[email protected]519e49882013-03-27 08:45:3249#include "net/base/ip_endpoint.h"
rchda78df5a2015-03-22 05:16:3750#include "net/base/net_errors.h"
51#include "net/base/net_log.h"
[email protected]9dd3ff0f2014-03-26 09:51:2852#include "net/base/privacy_mode.h"
rtennetif4bdb542015-01-21 14:33:0553#include "net/cert/cert_verifier.h"
54#include "net/http/transport_security_state.h"
55#include "net/quic/crypto/proof_verifier_chromium.h"
[email protected]48878092013-07-26 14:51:5656#include "net/quic/quic_protocol.h"
[email protected]fb35b0a2014-04-15 21:06:4957#include "net/quic/quic_server_id.h"
rtenneti48fc029002015-01-13 21:09:0758#include "net/quic/quic_utils.h"
[email protected]7d561352014-06-20 09:09:2159#include "net/tools/epoll_server/epoll_server.h"
[email protected]519e49882013-03-27 08:45:3260#include "net/tools/quic/quic_client.h"
rtenneti48fc029002015-01-13 21:09:0761#include "net/tools/quic/spdy_utils.h"
rchda78df5a2015-03-22 05:16:3762#include "net/tools/quic/synchronous_host_resolver.h"
rtenneti48fc029002015-01-13 21:09:0763#include "url/gurl.h"
[email protected]519e49882013-03-27 08:45:3264
rtenneti48fc029002015-01-13 21:09:0765using base::StringPiece;
rtennetif4bdb542015-01-21 14:33:0566using net::CertVerifier;
67using net::ProofVerifierChromium;
68using net::TransportSecurityState;
rtenneti48fc029002015-01-13 21:09:0769using std::cout;
70using std::cerr;
71using std::map;
72using std::string;
73using std::vector;
74using std::endl;
75
rtenneti16142b62014-09-03 21:52:1176// The IP or hostname the quic client will connect to.
rtenneti48fc029002015-01-13 21:09:0777string FLAGS_host = "";
78// The port to connect to.
79int32 FLAGS_port = 80;
80// If set, send a POST with this body.
81string FLAGS_body = "";
82// A semicolon separated list of key:value pairs to add to request headers.
83string FLAGS_headers = "";
84// Set to true for a quieter output experience.
85bool FLAGS_quiet = false;
86// QUIC version to speak, e.g. 21. If not set, then all available versions are
87// offered in the handshake.
88int32 FLAGS_quic_version = -1;
89// If true, a version mismatch in the handshake is not considered a failure.
90// Useful for probing a server to determine if it speaks any version of QUIC.
91bool FLAGS_version_mismatch_ok = false;
92// If true, an HTTP response code of 3xx is considered to be a successful
93// response, otherwise a failure.
94bool FLAGS_redirect_is_success = true;
[email protected]519e49882013-03-27 08:45:3295
96int main(int argc, char *argv[]) {
[email protected]7ceb35882014-06-03 00:01:0797 base::CommandLine::Init(argc, argv);
98 base::CommandLine* line = base::CommandLine::ForCurrentProcess();
rjshadeabbfd37e2014-08-29 16:04:3999 const base::CommandLine::StringVector& urls = line->GetArgs();
[email protected]e827d0d2014-02-19 00:49:53100
101 logging::LoggingSettings settings;
102 settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
103 CHECK(logging::InitLogging(settings));
104
rjshadeabbfd37e2014-08-29 16:04:39105 if (line->HasSwitch("h") || line->HasSwitch("help") || urls.empty()) {
[email protected]00016892013-10-08 19:00:49106 const char* help_str =
rtenneti48fc029002015-01-13 21:09:07107 "Usage: quic_client [options] <url>\n"
[email protected]00016892013-10-08 19:00:49108 "\n"
rtenneti48fc029002015-01-13 21:09:07109 "<url> with scheme must be provided (e.g. http://www.google.com)\n\n"
[email protected]00016892013-10-08 19:00:49110 "Options:\n"
111 "-h, --help show this help message and exit\n"
rtenneti48fc029002015-01-13 21:09:07112 "--host=<host> specify the IP address of the hostname to "
113 "connect to\n"
[email protected]00016892013-10-08 19:00:49114 "--port=<port> specify the port to connect to\n"
rtenneti48fc029002015-01-13 21:09:07115 "--body=<body> specify the body to post\n"
116 "--headers=<headers> specify a semicolon separated list of "
117 "key:value pairs to add to request headers\n"
118 "--quiet specify for a quieter output experience\n"
rtenneti31e9fd62014-09-16 05:22:15119 "--quic-version=<quic version> specify QUIC version to speak\n"
rtenneti48fc029002015-01-13 21:09:07120 "--version_mismatch_ok if specified a version mismatch in the "
121 "handshake is not considered a failure\n"
122 "--redirect_is_success if specified an HTTP response code of 3xx "
123 "is considered to be a successful response, otherwise a failure\n";
124 cout << help_str;
[email protected]00016892013-10-08 19:00:49125 exit(0);
126 }
rtenneti48fc029002015-01-13 21:09:07127 if (line->HasSwitch("host")) {
128 FLAGS_host = line->GetSwitchValueASCII("host");
129 }
[email protected]ebfa6db32013-05-24 07:32:12130 if (line->HasSwitch("port")) {
dougkc2a5ab102015-02-19 01:07:47131 if (!base::StringToInt(line->GetSwitchValueASCII("port"), &FLAGS_port)) {
dougk5ec46dd2015-02-18 23:54:37132 std::cerr << "--port must be an integer\n";
133 return 1;
[email protected]ebfa6db32013-05-24 07:32:12134 }
135 }
rtenneti48fc029002015-01-13 21:09:07136 if (line->HasSwitch("body")) {
137 FLAGS_body = line->GetSwitchValueASCII("body");
[email protected]ebfa6db32013-05-24 07:32:12138 }
rtenneti48fc029002015-01-13 21:09:07139 if (line->HasSwitch("headers")) {
140 FLAGS_headers = line->GetSwitchValueASCII("headers");
[email protected]ebfa6db32013-05-24 07:32:12141 }
rtenneti48fc029002015-01-13 21:09:07142 if (line->HasSwitch("quiet")) {
143 FLAGS_quiet = true;
[email protected]e4c3ea62014-03-15 00:45:14144 }
rtenneti16142b62014-09-03 21:52:11145 if (line->HasSwitch("quic-version")) {
146 int quic_version;
147 if (base::StringToInt(line->GetSwitchValueASCII("quic-version"),
148 &quic_version)) {
149 FLAGS_quic_version = quic_version;
150 }
151 }
rtenneti48fc029002015-01-13 21:09:07152 if (line->HasSwitch("version_mismatch_ok")) {
153 FLAGS_version_mismatch_ok = true;
rtenneti31e9fd62014-09-16 05:22:15154 }
rtenneti48fc029002015-01-13 21:09:07155 if (line->HasSwitch("redirect_is_success")) {
156 FLAGS_redirect_is_success = true;
157 }
158
159 VLOG(1) << "server host: " << FLAGS_host << " port: " << FLAGS_port
160 << " body: " << FLAGS_body << " headers: " << FLAGS_headers
161 << " quiet: " << FLAGS_quiet
162 << " quic-version: " << FLAGS_quic_version
163 << " version_mismatch_ok: " << FLAGS_version_mismatch_ok
164 << " redirect_is_success: " << FLAGS_redirect_is_success;
[email protected]519e49882013-03-27 08:45:32165
166 base::AtExitManager exit_manager;
167
rtenneti16142b62014-09-03 21:52:11168 // Determine IP address to connect to from supplied hostname.
rtenneti48fc029002015-01-13 21:09:07169 net::IPAddressNumber ip_addr;
[email protected]a5b98172014-06-18 07:01:59170
rtenneti48fc029002015-01-13 21:09:07171 // TODO(rtenneti): GURL's doesn't support default_protocol argument, thus
172 // protocol is required in the URL.
173 GURL url(urls[0]);
174 string host = FLAGS_host;
dougk5ec46dd2015-02-18 23:54:37175 if (host.empty()) {
rchda78df5a2015-03-22 05:16:37176 host = url.host();
dougk5ec46dd2015-02-18 23:54:37177 }
178 if (!net::ParseIPLiteralToNumber(host, &ip_addr)) {
rchda78df5a2015-03-22 05:16:37179 net::AddressList addresses;
180 int rv = net::tools::SynchronousHostResolver::Resolve(host, &addresses);
181 if (rv != net::OK) {
182 LOG(ERROR) << "Unable to resolve '" << host << "' : "
rchbd931542015-03-23 02:25:39183 << net::ErrorToShortString(rv);
rchda78df5a2015-03-22 05:16:37184 return 1;
185 }
186 ip_addr = addresses[0].address();
dougk5ec46dd2015-02-18 23:54:37187 }
rtenneti48fc029002015-01-13 21:09:07188
189 string host_port = net::IPAddressToStringWithPort(ip_addr, FLAGS_port);
190 VLOG(1) << "Resolved " << host << " to " << host_port << endl;
rtenneti16142b62014-09-03 21:52:11191
192 // Build the client, and try to connect.
rtennetif4bdb542015-01-21 14:33:05193 bool is_https = (FLAGS_port == 443);
rtenneti16142b62014-09-03 21:52:11194 net::EpollServer epoll_server;
rtennetif4bdb542015-01-21 14:33:05195 net::QuicServerId server_id(host, FLAGS_port, is_https,
rtenneti48fc029002015-01-13 21:09:07196 net::PRIVACY_MODE_DISABLED);
197 net::QuicVersionVector versions = net::QuicSupportedVersions();
198 if (FLAGS_quic_version != -1) {
199 versions.clear();
200 versions.push_back(static_cast<net::QuicVersion>(FLAGS_quic_version));
201 }
202 net::tools::QuicClient client(net::IPEndPoint(ip_addr, FLAGS_port), server_id,
203 versions, &epoll_server);
rtennetif4bdb542015-01-21 14:33:05204 scoped_ptr<CertVerifier> cert_verifier;
205 scoped_ptr<TransportSecurityState> transport_security_state;
206 if (is_https) {
207 // For secure QUIC we need to verify the cert chain.a
208 cert_verifier.reset(CertVerifier::CreateDefault());
209 transport_security_state.reset(new TransportSecurityState);
210 // TODO(rtenneti): Fix "Proof invalid: Missing context" error.
211 client.SetProofVerifier(new ProofVerifierChromium(
212 cert_verifier.get(), transport_security_state.get()));
213 }
rtenneti48fc029002015-01-13 21:09:07214 if (!client.Initialize()) {
215 cerr << "Failed to initialize client." << endl;
rtenneti16142b62014-09-03 21:52:11216 return 1;
217 }
rtenneti48fc029002015-01-13 21:09:07218 if (!client.Connect()) {
219 net::QuicErrorCode error = client.session()->error();
220 if (FLAGS_version_mismatch_ok && error == net::QUIC_INVALID_VERSION) {
221 cout << "Server talks QUIC, but none of the versions supoorted by "
222 << "this client: " << QuicVersionVectorToString(versions) << endl;
223 // Version mismatch is not deemed a failure.
224 return 0;
225 }
226 cerr << "Failed to connect to " << host_port
227 << ". Error: " << net::QuicUtils::ErrorToString(error) << endl;
228 return 1;
229 }
230 cout << "Connected to " << host_port << endl;
[email protected]519e49882013-03-27 08:45:32231
rtenneti48fc029002015-01-13 21:09:07232 // Construct a GET or POST request for supplied URL.
233 net::BalsaHeaders headers;
234 headers.SetRequestFirstlineFromStringPieces(
235 FLAGS_body.empty() ? "GET" : "POST", url.spec(), "HTTP/1.1");
236
237 // Append any additional headers supplied on the command line.
238 vector<string> headers_tokenized;
239 Tokenize(FLAGS_headers, ";", &headers_tokenized);
240 for (size_t i = 0; i < headers_tokenized.size(); ++i) {
241 string sp;
242 base::TrimWhitespaceASCII(headers_tokenized[i], base::TRIM_ALL, &sp);
243 if (sp.empty()) {
244 continue;
245 }
246 vector<string> kv;
247 base::SplitString(sp, ':', &kv);
248 CHECK_EQ(2u, kv.size());
249 string key;
250 base::TrimWhitespaceASCII(kv[0], base::TRIM_ALL, &key);
251 string value;
252 base::TrimWhitespaceASCII(kv[1], base::TRIM_ALL, &value);
253 headers.AppendHeader(key, value);
254 }
255
256 // Make sure to store the response, for later output.
257 client.set_store_response(true);
258
259 // Send the request.
260 map<string, string> header_block =
261 net::tools::SpdyUtils::RequestHeadersToSpdy4Headers(headers);
262 client.SendRequestAndWaitForResponse(headers, FLAGS_body, /*fin=*/true);
263
264 // Print request and response details.
265 if (!FLAGS_quiet) {
266 cout << "Request:" << endl;
267 cout << "headers:" << endl;
268 for (const std::pair<string, string>& kv : header_block) {
269 cout << " " << kv.first << ": " << kv.second << endl;
270 }
271 cout << "body: " << FLAGS_body << endl;
rtennetib346cb02015-03-25 19:50:06272 cout << endl;
273 cout << "Response:" << endl;
rtenneti48fc029002015-01-13 21:09:07274 cout << "headers: " << client.latest_response_headers() << endl;
275 cout << "body: " << client.latest_response_body() << endl;
276 }
277
278 size_t response_code = client.latest_response_code();
279 if (response_code >= 200 && response_code < 300) {
280 cout << "Request succeeded (" << response_code << ")." << endl;
281 return 0;
282 } else if (response_code >= 300 && response_code < 400) {
283 if (FLAGS_redirect_is_success) {
284 cout << "Request succeeded (redirect " << response_code << ")." << endl;
285 return 0;
286 } else {
287 cout << "Request failed (redirect " << response_code << ")." << endl;
288 return 1;
289 }
290 } else {
291 cerr << "Request failed (" << response_code << ")." << endl;
292 return 1;
293 }
[email protected]519e49882013-03-27 08:45:32294}