[email protected] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 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. |
| 4 | |
rtenneti | 16142b6 | 2014-09-03 21:52:11 | [diff] [blame] | 5 | // A binary wrapper for QuicClient. |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 6 | // Connects to a host using QUIC, sends a request to the provided URL, and |
| 7 | // displays the response. |
[email protected] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 8 | // |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 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: |
| 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] | 0001689 | 2013-10-08 19:00:49 | [diff] [blame] | 40 | |
| 41 | #include <iostream> |
[email protected] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 42 | |
| 43 | #include "base/at_exit.h" |
| 44 | #include "base/command_line.h" |
| 45 | #include "base/logging.h" |
[email protected] | ebfa6db3 | 2013-05-24 07:32:12 | [diff] [blame] | 46 | #include "base/strings/string_number_conversions.h" |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 47 | #include "base/strings/string_split.h" |
| 48 | #include "base/strings/string_util.h" |
[email protected] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 49 | #include "net/base/ip_endpoint.h" |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 50 | #include "net/base/net_errors.h" |
| 51 | #include "net/base/net_log.h" |
[email protected] | 9dd3ff0f | 2014-03-26 09:51:28 | [diff] [blame] | 52 | #include "net/base/privacy_mode.h" |
rtenneti | f4bdb54 | 2015-01-21 14:33:05 | [diff] [blame] | 53 | #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] | 4887809 | 2013-07-26 14:51:56 | [diff] [blame] | 56 | #include "net/quic/quic_protocol.h" |
[email protected] | fb35b0a | 2014-04-15 21:06:49 | [diff] [blame] | 57 | #include "net/quic/quic_server_id.h" |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 58 | #include "net/quic/quic_utils.h" |
[email protected] | 7d56135 | 2014-06-20 09:09:21 | [diff] [blame] | 59 | #include "net/tools/epoll_server/epoll_server.h" |
[email protected] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 60 | #include "net/tools/quic/quic_client.h" |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 61 | #include "net/tools/quic/spdy_utils.h" |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 62 | #include "net/tools/quic/synchronous_host_resolver.h" |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 63 | #include "url/gurl.h" |
[email protected] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 64 | |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 65 | using base::StringPiece; |
rtenneti | f4bdb54 | 2015-01-21 14:33:05 | [diff] [blame] | 66 | using net::CertVerifier; |
| 67 | using net::ProofVerifierChromium; |
| 68 | using net::TransportSecurityState; |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 69 | using std::cout; |
| 70 | using std::cerr; |
| 71 | using std::map; |
| 72 | using std::string; |
| 73 | using std::vector; |
| 74 | using std::endl; |
| 75 | |
rtenneti | 16142b6 | 2014-09-03 21:52:11 | [diff] [blame] | 76 | // The IP or hostname the quic client will connect to. |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 77 | string FLAGS_host = ""; |
| 78 | // The port to connect to. |
| 79 | int32 FLAGS_port = 80; |
| 80 | // If set, send a POST with this body. |
| 81 | string FLAGS_body = ""; |
| 82 | // A semicolon separated list of key:value pairs to add to request headers. |
| 83 | string FLAGS_headers = ""; |
| 84 | // Set to true for a quieter output experience. |
| 85 | bool FLAGS_quiet = false; |
| 86 | // QUIC version to speak, e.g. 21. If not set, then all available versions are |
| 87 | // offered in the handshake. |
| 88 | int32 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. |
| 91 | bool 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. |
| 94 | bool FLAGS_redirect_is_success = true; |
[email protected] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 95 | |
| 96 | int main(int argc, char *argv[]) { |
[email protected] | 7ceb3588 | 2014-06-03 00:01:07 | [diff] [blame] | 97 | base::CommandLine::Init(argc, argv); |
| 98 | base::CommandLine* line = base::CommandLine::ForCurrentProcess(); |
rjshade | abbfd37e | 2014-08-29 16:04:39 | [diff] [blame] | 99 | const base::CommandLine::StringVector& urls = line->GetArgs(); |
[email protected] | e827d0d | 2014-02-19 00:49:53 | [diff] [blame] | 100 | |
| 101 | logging::LoggingSettings settings; |
| 102 | settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; |
| 103 | CHECK(logging::InitLogging(settings)); |
| 104 | |
rjshade | abbfd37e | 2014-08-29 16:04:39 | [diff] [blame] | 105 | if (line->HasSwitch("h") || line->HasSwitch("help") || urls.empty()) { |
[email protected] | 0001689 | 2013-10-08 19:00:49 | [diff] [blame] | 106 | const char* help_str = |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 107 | "Usage: quic_client [options] <url>\n" |
[email protected] | 0001689 | 2013-10-08 19:00:49 | [diff] [blame] | 108 | "\n" |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 109 | "<url> with scheme must be provided (e.g. http://www.google.com)\n\n" |
[email protected] | 0001689 | 2013-10-08 19:00:49 | [diff] [blame] | 110 | "Options:\n" |
| 111 | "-h, --help show this help message and exit\n" |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 112 | "--host=<host> specify the IP address of the hostname to " |
| 113 | "connect to\n" |
[email protected] | 0001689 | 2013-10-08 19:00:49 | [diff] [blame] | 114 | "--port=<port> specify the port to connect to\n" |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 115 | "--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" |
rtenneti | 31e9fd6 | 2014-09-16 05:22:15 | [diff] [blame] | 119 | "--quic-version=<quic version> specify QUIC version to speak\n" |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 120 | "--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] | 0001689 | 2013-10-08 19:00:49 | [diff] [blame] | 125 | exit(0); |
| 126 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 127 | if (line->HasSwitch("host")) { |
| 128 | FLAGS_host = line->GetSwitchValueASCII("host"); |
| 129 | } |
[email protected] | ebfa6db3 | 2013-05-24 07:32:12 | [diff] [blame] | 130 | if (line->HasSwitch("port")) { |
dougk | c2a5ab10 | 2015-02-19 01:07:47 | [diff] [blame] | 131 | if (!base::StringToInt(line->GetSwitchValueASCII("port"), &FLAGS_port)) { |
dougk | 5ec46dd | 2015-02-18 23:54:37 | [diff] [blame] | 132 | std::cerr << "--port must be an integer\n"; |
| 133 | return 1; |
[email protected] | ebfa6db3 | 2013-05-24 07:32:12 | [diff] [blame] | 134 | } |
| 135 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 136 | if (line->HasSwitch("body")) { |
| 137 | FLAGS_body = line->GetSwitchValueASCII("body"); |
[email protected] | ebfa6db3 | 2013-05-24 07:32:12 | [diff] [blame] | 138 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 139 | if (line->HasSwitch("headers")) { |
| 140 | FLAGS_headers = line->GetSwitchValueASCII("headers"); |
[email protected] | ebfa6db3 | 2013-05-24 07:32:12 | [diff] [blame] | 141 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 142 | if (line->HasSwitch("quiet")) { |
| 143 | FLAGS_quiet = true; |
[email protected] | e4c3ea6 | 2014-03-15 00:45:14 | [diff] [blame] | 144 | } |
rtenneti | 16142b6 | 2014-09-03 21:52:11 | [diff] [blame] | 145 | 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 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 152 | if (line->HasSwitch("version_mismatch_ok")) { |
| 153 | FLAGS_version_mismatch_ok = true; |
rtenneti | 31e9fd6 | 2014-09-16 05:22:15 | [diff] [blame] | 154 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 155 | 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] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 165 | |
| 166 | base::AtExitManager exit_manager; |
| 167 | |
rtenneti | 16142b6 | 2014-09-03 21:52:11 | [diff] [blame] | 168 | // Determine IP address to connect to from supplied hostname. |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 169 | net::IPAddressNumber ip_addr; |
[email protected] | a5b9817 | 2014-06-18 07:01:59 | [diff] [blame] | 170 | |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 171 | // 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; |
dougk | 5ec46dd | 2015-02-18 23:54:37 | [diff] [blame] | 175 | if (host.empty()) { |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 176 | host = url.host(); |
dougk | 5ec46dd | 2015-02-18 23:54:37 | [diff] [blame] | 177 | } |
| 178 | if (!net::ParseIPLiteralToNumber(host, &ip_addr)) { |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 179 | net::AddressList addresses; |
| 180 | int rv = net::tools::SynchronousHostResolver::Resolve(host, &addresses); |
| 181 | if (rv != net::OK) { |
| 182 | LOG(ERROR) << "Unable to resolve '" << host << "' : " |
rch | bd93154 | 2015-03-23 02:25:39 | [diff] [blame] | 183 | << net::ErrorToShortString(rv); |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 184 | return 1; |
| 185 | } |
| 186 | ip_addr = addresses[0].address(); |
dougk | 5ec46dd | 2015-02-18 23:54:37 | [diff] [blame] | 187 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 188 | |
| 189 | string host_port = net::IPAddressToStringWithPort(ip_addr, FLAGS_port); |
| 190 | VLOG(1) << "Resolved " << host << " to " << host_port << endl; |
rtenneti | 16142b6 | 2014-09-03 21:52:11 | [diff] [blame] | 191 | |
| 192 | // Build the client, and try to connect. |
rtenneti | f4bdb54 | 2015-01-21 14:33:05 | [diff] [blame] | 193 | bool is_https = (FLAGS_port == 443); |
rtenneti | 16142b6 | 2014-09-03 21:52:11 | [diff] [blame] | 194 | net::EpollServer epoll_server; |
rtenneti | f4bdb54 | 2015-01-21 14:33:05 | [diff] [blame] | 195 | net::QuicServerId server_id(host, FLAGS_port, is_https, |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 196 | 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); |
rtenneti | f4bdb54 | 2015-01-21 14:33:05 | [diff] [blame] | 204 | 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 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 214 | if (!client.Initialize()) { |
| 215 | cerr << "Failed to initialize client." << endl; |
rtenneti | 16142b6 | 2014-09-03 21:52:11 | [diff] [blame] | 216 | return 1; |
| 217 | } |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 218 | 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] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 231 | |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 232 | // 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; |
rtenneti | b346cb0 | 2015-03-25 19:50:06 | [diff] [blame^] | 272 | cout << endl; |
| 273 | cout << "Response:" << endl; |
rtenneti | 48fc02900 | 2015-01-13 21:09:07 | [diff] [blame] | 274 | 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] | 519e4988 | 2013-03-27 08:45:32 | [diff] [blame] | 294 | } |