rch | fdfdc8ed | 2015-03-18 00:35:20 | [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 | |
| 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: |
rjshade | c86dbfa | 2015-11-12 20:16:25 | [diff] [blame] | 22 | // quic_client http://www.google.com --quic_version=23 --host=${IP} |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 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} |
| 40 | |
| 41 | #include <iostream> |
| 42 | |
| 43 | #include "base/at_exit.h" |
| 44 | #include "base/command_line.h" |
| 45 | #include "base/logging.h" |
tfarina | 2f78bb2 | 2015-09-08 19:16:31 | [diff] [blame] | 46 | #include "base/message_loop/message_loop.h" |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 47 | #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" |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 51 | #include "net/base/net_errors.h" |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 52 | #include "net/base/privacy_mode.h" |
| 53 | #include "net/cert/cert_verifier.h" |
rtenneti | 052774e | 2015-11-24 21:00:12 | [diff] [blame^] | 54 | #include "net/cert/multi_log_ct_verifier.h" |
rch | a9d12ce1 | 2015-03-19 23:06:49 | [diff] [blame] | 55 | #include "net/http/http_request_info.h" |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 56 | #include "net/http/transport_security_state.h" |
eroman | 87c53d6 | 2015-04-02 06:51:07 | [diff] [blame] | 57 | #include "net/log/net_log.h" |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 58 | #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" |
bnc | 7ecc112 | 2015-09-28 13:22:49 | [diff] [blame] | 62 | #include "net/spdy/spdy_header_block.h" |
rch | a9d12ce1 | 2015-03-19 23:06:49 | [diff] [blame] | 63 | #include "net/spdy/spdy_http_utils.h" |
| 64 | #include "net/tools/quic/quic_simple_client.h" |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 65 | #include "net/tools/quic/synchronous_host_resolver.h" |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 66 | #include "url/gurl.h" |
| 67 | |
| 68 | using base::StringPiece; |
| 69 | using net::CertVerifier; |
rtenneti | 052774e | 2015-11-24 21:00:12 | [diff] [blame^] | 70 | using net::CTVerifier; |
| 71 | using net::MultiLogCTVerifier; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 72 | using net::ProofVerifierChromium; |
| 73 | using net::TransportSecurityState; |
| 74 | using std::cout; |
| 75 | using std::cerr; |
| 76 | using std::map; |
| 77 | using std::string; |
| 78 | using std::vector; |
| 79 | using std::endl; |
| 80 | |
| 81 | // The IP or hostname the quic client will connect to. |
| 82 | string FLAGS_host = ""; |
| 83 | // The port to connect to. |
rch | f114d98 | 2015-10-21 01:34:56 | [diff] [blame] | 84 | int32 FLAGS_port = 0; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 85 | // If set, send a POST with this body. |
| 86 | string FLAGS_body = ""; |
| 87 | // A semicolon separated list of key:value pairs to add to request headers. |
| 88 | string FLAGS_headers = ""; |
| 89 | // Set to true for a quieter output experience. |
| 90 | bool FLAGS_quiet = false; |
| 91 | // QUIC version to speak, e.g. 21. If not set, then all available versions are |
| 92 | // offered in the handshake. |
| 93 | int32 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. |
| 96 | bool 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. |
| 99 | bool FLAGS_redirect_is_success = true; |
rtenneti | 6bd660b | 2015-07-18 00:19:53 | [diff] [blame] | 100 | // Initial MTU of the connection. |
| 101 | int32 FLAGS_initial_mtu = 0; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 102 | |
rch | e22c2fb | 2015-11-17 00:22:32 | [diff] [blame] | 103 | class 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 | |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 121 | int 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 " |
rtenneti | 6bd660b | 2015-07-18 00:19:53 | [diff] [blame] | 148 | "is considered to be a successful response, otherwise a failure\n" |
| 149 | "--initial_mtu=<initial_mtu> specify the initial MTU of the connection" |
rch | e22c2fb | 2015-11-17 00:22:32 | [diff] [blame] | 150 | "\n" |
| 151 | "--disable-certificate-verification do not verify certificates\n"; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 152 | 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 | } |
rtenneti | 6bd660b | 2015-07-18 00:19:53 | [diff] [blame] | 186 | 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 | } |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 193 | |
| 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 |
rtenneti | 6bd660b | 2015-07-18 00:19:53 | [diff] [blame] | 199 | << " redirect_is_success: " << FLAGS_redirect_is_success |
| 200 | << " initial_mtu: " << FLAGS_initial_mtu; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 201 | |
| 202 | base::AtExitManager exit_manager; |
rch | a9d12ce1 | 2015-03-19 23:06:49 | [diff] [blame] | 203 | base::MessageLoopForIO message_loop; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 204 | |
| 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; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 212 | if (host.empty()) { |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 213 | host = url.host(); |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 214 | } |
rch | f114d98 | 2015-10-21 01:34:56 | [diff] [blame] | 215 | int port = FLAGS_port; |
| 216 | if (port == 0) { |
| 217 | port = url.EffectiveIntPort(); |
| 218 | } |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 219 | if (!net::ParseIPLiteralToNumber(host, &ip_addr)) { |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 220 | net::AddressList addresses; |
| 221 | int rv = net::tools::SynchronousHostResolver::Resolve(host, &addresses); |
| 222 | if (rv != net::OK) { |
| 223 | LOG(ERROR) << "Unable to resolve '" << host << "' : " |
rch | bd93154 | 2015-03-23 02:25:39 | [diff] [blame] | 224 | << net::ErrorToShortString(rv); |
rch | da78df5a | 2015-03-22 05:16:37 | [diff] [blame] | 225 | return 1; |
| 226 | } |
| 227 | ip_addr = addresses[0].address(); |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 228 | } |
| 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. |
rch | f114d98 | 2015-10-21 01:34:56 | [diff] [blame] | 234 | net::QuicServerId server_id(url.host(), url.EffectiveIntPort(), |
rch | 1fe2eeb | 2015-10-26 14:45:57 | [diff] [blame] | 235 | net::PRIVACY_MODE_DISABLED); |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 236 | 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 | } |
rtenneti | 5a87ce4 | 2015-11-10 05:35:31 | [diff] [blame] | 241 | // For secure QUIC we need to verify the cert chain. |
| 242 | scoped_ptr<CertVerifier> cert_verifier(CertVerifier::CreateDefault()); |
rch | e22c2fb | 2015-11-17 00:22:32 | [diff] [blame] | 243 | if (line->HasSwitch("disable-certificate-verification")) { |
| 244 | cert_verifier.reset(new FakeCertVerifier()); |
| 245 | } |
rtenneti | 5a87ce4 | 2015-11-10 05:35:31 | [diff] [blame] | 246 | scoped_ptr<TransportSecurityState> transport_security_state( |
| 247 | new TransportSecurityState); |
rtenneti | 052774e | 2015-11-24 21:00:12 | [diff] [blame^] | 248 | scoped_ptr<CTVerifier> ct_verifier(new MultiLogCTVerifier()); |
rch | 1fe2eeb | 2015-10-26 14:45:57 | [diff] [blame] | 249 | ProofVerifierChromium* proof_verifier = new ProofVerifierChromium( |
rtenneti | 052774e | 2015-11-24 21:00:12 | [diff] [blame^] | 250 | cert_verifier.get(), nullptr, transport_security_state.get(), |
| 251 | ct_verifier.get()); |
rch | 1fe2eeb | 2015-10-26 14:45:57 | [diff] [blame] | 252 | net::tools::QuicSimpleClient client(net::IPEndPoint(ip_addr, port), server_id, |
| 253 | versions, proof_verifier); |
rtenneti | bb2780c | 2015-07-22 19:16:08 | [diff] [blame] | 254 | client.set_initial_max_packet_length( |
| 255 | FLAGS_initial_mtu != 0 ? FLAGS_initial_mtu : net::kDefaultMaxPacketSize); |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 256 | 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) { |
rtenneti | 85816fdf | 2015-05-25 03:01:10 | [diff] [blame] | 263 | cout << "Server talks QUIC, but none of the versions supported by " |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 264 | << "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. |
rch | a9d12ce1 | 2015-03-19 23:06:49 | [diff] [blame] | 275 | net::HttpRequestInfo request; |
| 276 | request.method = FLAGS_body.empty() ? "GET" : "POST"; |
| 277 | request.url = url; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 278 | |
| 279 | // Append any additional headers supplied on the command line. |
brettw | 8cc24ae2 | 2015-07-06 23:53:00 | [diff] [blame] | 280 | for (const std::string& header : |
| 281 | base::SplitString(FLAGS_headers, ";", base::KEEP_WHITESPACE, |
| 282 | base::SPLIT_WANT_NONEMPTY)) { |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 283 | string sp; |
brettw | 8cc24ae2 | 2015-07-06 23:53:00 | [diff] [blame] | 284 | base::TrimWhitespaceASCII(header, base::TRIM_ALL, &sp); |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 285 | if (sp.empty()) { |
| 286 | continue; |
| 287 | } |
brettw | 3a2c690 | 2015-07-06 19:43:29 | [diff] [blame] | 288 | vector<string> kv = |
| 289 | base::SplitString(sp, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 290 | 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); |
rch | a9d12ce1 | 2015-03-19 23:06:49 | [diff] [blame] | 295 | request.extra_headers.SetHeader(key, value); |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 296 | } |
| 297 | |
| 298 | // Make sure to store the response, for later output. |
| 299 | client.set_store_response(true); |
| 300 | |
| 301 | // Send the request. |
rch | a9d12ce1 | 2015-03-19 23:06:49 | [diff] [blame] | 302 | net::SpdyHeaderBlock header_block; |
| 303 | net::CreateSpdyHeadersFromHttpRequest(request, request.extra_headers, |
rtenneti | 0b073d0 | 2015-07-28 04:27:56 | [diff] [blame] | 304 | net::HTTP2, /*direct=*/true, |
rch | a9d12ce1 | 2015-03-19 23:06:49 | [diff] [blame] | 305 | &header_block); |
| 306 | client.SendRequestAndWaitForResponse(request, FLAGS_body, /*fin=*/true); |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 307 | |
| 308 | // Print request and response details. |
| 309 | if (!FLAGS_quiet) { |
| 310 | cout << "Request:" << endl; |
| 311 | cout << "headers:" << endl; |
bnc | 7ecc112 | 2015-09-28 13:22:49 | [diff] [blame] | 312 | for (const auto& kv : header_block) { |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 313 | cout << " " << kv.first << ": " << kv.second << endl; |
| 314 | } |
| 315 | cout << "body: " << FLAGS_body << endl; |
rch | a9d12ce1 | 2015-03-19 23:06:49 | [diff] [blame] | 316 | cout << endl; |
| 317 | cout << "Response:" << endl; |
rch | fdfdc8ed | 2015-03-18 00:35:20 | [diff] [blame] | 318 | 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 | } |