1 // 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.
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.
9 // Some usage examples:
11 // TODO(rtenneti): make --host optional by getting IP Address of URL's host.
13 // Get IP address of the www.google.com
14 // IP=`dig www.google.com +short | head -1`
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}
21 // Use a specific version:
22 // quic_client http://www.google.com --version=23 --host=${IP}
24 // Send a POST instead of a GET:
25 // quic_client http://www.google.com --body="this is a POST body" --host=${IP}
27 // Append additional headers to the request:
28 // quic_client http://www.google.com --host=${IP}
29 // --headers="Header-A: 1234; Header-B: 5678"
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}
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}
43 #include "base/at_exit.h"
44 #include "base/command_line.h"
45 #include "base/logging.h"
46 #include "base/strings/string_number_conversions.h"
47 #include "base/strings/string_split.h"
48 #include "base/strings/string_util.h"
49 #include "net/base/ip_endpoint.h"
50 #include "net/base/net_errors.h"
51 #include "net/base/privacy_mode.h"
52 #include "net/cert/cert_verifier.h"
53 #include "net/http/transport_security_state.h"
54 #include "net/log/net_log.h"
55 #include "net/quic/crypto/proof_verifier_chromium.h"
56 #include "net/quic/quic_protocol.h"
57 #include "net/quic/quic_server_id.h"
58 #include "net/quic/quic_utils.h"
59 #include "net/tools/epoll_server/epoll_server.h"
60 #include "net/tools/quic/quic_client.h"
61 #include "net/tools/quic/spdy_balsa_utils.h"
62 #include "net/tools/quic/synchronous_host_resolver.h"
65 using base::StringPiece
;
66 using net::CertVerifier
;
67 using net::ProofVerifierChromium
;
68 using net::TransportSecurityState
;
75 // The IP or hostname the quic client will connect to.
76 string FLAGS_host
= "";
77 // The port to connect to.
78 int32 FLAGS_port
= 80;
79 // If set, send a POST with this body.
80 string FLAGS_body
= "";
81 // A semicolon separated list of key:value pairs to add to request headers.
82 string FLAGS_headers
= "";
83 // Set to true for a quieter output experience.
84 bool FLAGS_quiet
= false;
85 // QUIC version to speak, e.g. 21. If not set, then all available versions are
86 // offered in the handshake.
87 int32 FLAGS_quic_version
= -1;
88 // If true, a version mismatch in the handshake is not considered a failure.
89 // Useful for probing a server to determine if it speaks any version of QUIC.
90 bool FLAGS_version_mismatch_ok
= false;
91 // If true, an HTTP response code of 3xx is considered to be a successful
92 // response, otherwise a failure.
93 bool FLAGS_redirect_is_success
= true;
94 // Initial MTU of the connection.
95 int32 FLAGS_initial_mtu
= 0;
97 int main(int argc
, char *argv
[]) {
98 base::CommandLine::Init(argc
, argv
);
99 base::CommandLine
* line
= base::CommandLine::ForCurrentProcess();
100 const base::CommandLine::StringVector
& urls
= line
->GetArgs();
102 logging::LoggingSettings settings
;
103 settings
.logging_dest
= logging::LOG_TO_SYSTEM_DEBUG_LOG
;
104 CHECK(logging::InitLogging(settings
));
106 if (line
->HasSwitch("h") || line
->HasSwitch("help") || urls
.empty()) {
107 const char* help_str
=
108 "Usage: quic_client [options] <url>\n"
110 "<url> with scheme must be provided (e.g. http://www.google.com)\n\n"
112 "-h, --help show this help message and exit\n"
113 "--host=<host> specify the IP address of the hostname to "
115 "--port=<port> specify the port to connect to\n"
116 "--body=<body> specify the body to post\n"
117 "--headers=<headers> specify a semicolon separated list of "
118 "key:value pairs to add to request headers\n"
119 "--quiet specify for a quieter output experience\n"
120 "--quic-version=<quic version> specify QUIC version to speak\n"
121 "--version_mismatch_ok if specified a version mismatch in the "
122 "handshake is not considered a failure\n"
123 "--redirect_is_success if specified an HTTP response code of 3xx "
124 "is considered to be a successful response, otherwise a failure\n"
125 "--initial_mtu=<initial_mtu> specify the initial MTU of the connection"
130 if (line
->HasSwitch("host")) {
131 FLAGS_host
= line
->GetSwitchValueASCII("host");
133 if (line
->HasSwitch("port")) {
134 if (!base::StringToInt(line
->GetSwitchValueASCII("port"), &FLAGS_port
)) {
135 std::cerr
<< "--port must be an integer\n";
139 if (line
->HasSwitch("body")) {
140 FLAGS_body
= line
->GetSwitchValueASCII("body");
142 if (line
->HasSwitch("headers")) {
143 FLAGS_headers
= line
->GetSwitchValueASCII("headers");
145 if (line
->HasSwitch("quiet")) {
148 if (line
->HasSwitch("quic-version")) {
150 if (base::StringToInt(line
->GetSwitchValueASCII("quic-version"),
152 FLAGS_quic_version
= quic_version
;
155 if (line
->HasSwitch("version_mismatch_ok")) {
156 FLAGS_version_mismatch_ok
= true;
158 if (line
->HasSwitch("redirect_is_success")) {
159 FLAGS_redirect_is_success
= true;
161 if (line
->HasSwitch("initial_mtu")) {
162 if (!base::StringToInt(line
->GetSwitchValueASCII("initial_mtu"),
163 &FLAGS_initial_mtu
)) {
164 std::cerr
<< "--initial_mtu must be an integer\n";
169 VLOG(1) << "server host: " << FLAGS_host
<< " port: " << FLAGS_port
170 << " body: " << FLAGS_body
<< " headers: " << FLAGS_headers
171 << " quiet: " << FLAGS_quiet
172 << " quic-version: " << FLAGS_quic_version
173 << " version_mismatch_ok: " << FLAGS_version_mismatch_ok
174 << " redirect_is_success: " << FLAGS_redirect_is_success
175 << " initial_mtu: " << FLAGS_initial_mtu
;
177 base::AtExitManager exit_manager
;
179 // Determine IP address to connect to from supplied hostname.
180 net::IPAddressNumber ip_addr
;
182 // TODO(rtenneti): GURL's doesn't support default_protocol argument, thus
183 // protocol is required in the URL.
185 string host
= FLAGS_host
;
189 if (!net::ParseIPLiteralToNumber(host
, &ip_addr
)) {
190 net::AddressList addresses
;
191 int rv
= net::tools::SynchronousHostResolver::Resolve(host
, &addresses
);
193 LOG(ERROR
) << "Unable to resolve '" << host
<< "' : "
194 << net::ErrorToShortString(rv
);
197 ip_addr
= addresses
[0].address();
200 string host_port
= net::IPAddressToStringWithPort(ip_addr
, FLAGS_port
);
201 VLOG(1) << "Resolved " << host
<< " to " << host_port
<< endl
;
203 // Build the client, and try to connect.
204 bool is_https
= (FLAGS_port
== 443);
205 net::EpollServer epoll_server
;
206 net::QuicServerId
server_id(url
.host(), FLAGS_port
, is_https
,
207 net::PRIVACY_MODE_DISABLED
);
208 net::QuicVersionVector versions
= net::QuicSupportedVersions();
209 if (FLAGS_quic_version
!= -1) {
211 versions
.push_back(static_cast<net::QuicVersion
>(FLAGS_quic_version
));
213 net::tools::QuicClient
client(net::IPEndPoint(ip_addr
, FLAGS_port
), server_id
,
214 versions
, &epoll_server
);
215 scoped_ptr
<CertVerifier
> cert_verifier
;
216 scoped_ptr
<TransportSecurityState
> transport_security_state
;
217 client
.set_initial_max_packet_length(
218 FLAGS_initial_mtu
!= 0 ? FLAGS_initial_mtu
: net::kDefaultMaxPacketSize
);
220 // For secure QUIC we need to verify the cert chain.
221 cert_verifier
.reset(CertVerifier::CreateDefault());
222 transport_security_state
.reset(new TransportSecurityState
);
223 client
.SetProofVerifier(new ProofVerifierChromium(
224 cert_verifier
.get(), nullptr, transport_security_state
.get()));
226 if (!client
.Initialize()) {
227 cerr
<< "Failed to initialize client." << endl
;
230 if (!client
.Connect()) {
231 net::QuicErrorCode error
= client
.session()->error();
232 if (FLAGS_version_mismatch_ok
&& error
== net::QUIC_INVALID_VERSION
) {
233 cout
<< "Server talks QUIC, but none of the versions supported by "
234 << "this client: " << QuicVersionVectorToString(versions
) << endl
;
235 // Version mismatch is not deemed a failure.
238 cerr
<< "Failed to connect to " << host_port
239 << ". Error: " << net::QuicUtils::ErrorToString(error
) << endl
;
242 cout
<< "Connected to " << host_port
<< endl
;
244 // Construct a GET or POST request for supplied URL.
245 net::BalsaHeaders headers
;
246 headers
.SetRequestFirstlineFromStringPieces(
247 FLAGS_body
.empty() ? "GET" : "POST", url
.spec(), "HTTP/1.1");
249 // Append any additional headers supplied on the command line.
250 for (const std::string
& header
:
251 base::SplitString(FLAGS_headers
, ";", base::KEEP_WHITESPACE
,
252 base::SPLIT_WANT_NONEMPTY
)) {
254 base::TrimWhitespaceASCII(header
, base::TRIM_ALL
, &sp
);
259 base::SplitString(sp
, ":", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
260 CHECK_EQ(2u, kv
.size());
262 base::TrimWhitespaceASCII(kv
[0], base::TRIM_ALL
, &key
);
264 base::TrimWhitespaceASCII(kv
[1], base::TRIM_ALL
, &value
);
265 headers
.AppendHeader(key
, value
);
268 // Make sure to store the response, for later output.
269 client
.set_store_response(true);
272 net::SpdyHeaderBlock header_block
=
273 net::tools::SpdyBalsaUtils::RequestHeadersToSpdyHeaders(
274 headers
, client
.session()->connection()->version());
275 client
.SendRequestAndWaitForResponse(headers
, FLAGS_body
, /*fin=*/true);
277 // Print request and response details.
279 cout
<< "Request:" << endl
;
280 cout
<< "headers:" << endl
;
281 for (const std::pair
<string
, string
>& kv
: header_block
) {
282 cout
<< " " << kv
.first
<< ": " << kv
.second
<< endl
;
284 cout
<< "body: " << FLAGS_body
<< endl
;
286 cout
<< "Response:" << endl
;
287 cout
<< "headers: " << client
.latest_response_headers() << endl
;
288 cout
<< "body: " << client
.latest_response_body() << endl
;
291 size_t response_code
= client
.latest_response_code();
292 if (response_code
>= 200 && response_code
< 300) {
293 cout
<< "Request succeeded (" << response_code
<< ")." << endl
;
295 } else if (response_code
>= 300 && response_code
< 400) {
296 if (FLAGS_redirect_is_success
) {
297 cout
<< "Request succeeded (redirect " << response_code
<< ")." << endl
;
300 cout
<< "Request failed (redirect " << response_code
<< ")." << endl
;
304 cerr
<< "Request failed (" << response_code
<< ")." << endl
;