blob: d6a1da24839a08896662c613f11b1b8c40ef2d5e [file] [log] [blame]
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include "net/third_party/quiche/src/quic/core/quic_versions.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_system_event_loop.h"
#include "net/third_party/quiche/src/quic/tools/fake_proof_verifier.h"
#include "net/third_party/quiche/src/quic/tools/quic_client.h"
#include "net/third_party/quiche/src/quic/tools/quic_url.h"
DEFINE_QUIC_COMMAND_LINE_FLAG(std::string,
host,
"",
"The IP or hostname to connect to.");
DEFINE_QUIC_COMMAND_LINE_FLAG(int32_t, port, 0, "The port to connect to.");
namespace quic {
enum class Feature {
// A version negotiation response is elicited and acted on.
kVersionNegotiation,
// The handshake completes successfully.
kHandshake,
// Stream data is being exchanged and ACK'ed.
kStreamData,
// The connection close procedcure completes with a zero error code.
kConnectionClose,
// An H3 transaction succeeded.
kHttp3,
// TODO(nharper): Add Retry to list of tested features.
};
char MatrixLetter(Feature f) {
switch (f) {
case Feature::kVersionNegotiation:
return 'V';
case Feature::kHandshake:
return 'H';
case Feature::kStreamData:
return 'D';
case Feature::kConnectionClose:
return 'C';
case Feature::kHttp3:
return '3';
}
}
std::set<Feature> AttemptRequest(QuicSocketAddress addr,
std::string authority,
QuicServerId server_id,
ParsedQuicVersionVector versions) {
std::set<Feature> features;
auto proof_verifier = std::make_unique<FakeProofVerifier>();
QuicEpollServer epoll_server;
auto client = std::make_unique<QuicClient>(
addr, server_id, versions, &epoll_server, std::move(proof_verifier));
if (!client->Initialize()) {
return features;
}
if (!client->Connect()) {
QuicErrorCode error = client->session()->error();
if (error == QUIC_INVALID_VERSION) {
// QuicFramer::ProcessPacket returns RaiseError(QUIC_INVALID_VERSION) if
// it receives a packet containing a version in the header that is not our
// version. It might be possible that we didn't actually process a VN
// packet here.
features.insert(Feature::kVersionNegotiation);
return features;
}
return features;
}
if (!client->session()->IsCryptoHandshakeConfirmed()) {
return features;
}
features.insert(Feature::kHandshake);
// Construct and send a request.
spdy::SpdyHeaderBlock header_block;
header_block[":method"] = "GET";
header_block[":scheme"] = "https";
header_block[":authority"] = authority;
header_block[":path"] = "/";
client->set_store_response(true);
client->SendRequest(header_block, "", /*fin=*/true);
// TODO(nharper): After some period of time, time out and don't report
// success.
while (client->WaitForEvents()) {
}
if (!client->connected()) {
return features;
}
if (client->latest_response_code() != -1) {
features.insert(Feature::kHttp3);
}
// TODO(nharper): Properly check that we actually sent stream data and
// received ACKs for it.
features.insert(Feature::kStreamData);
// TODO(nharper): Check that we sent/received (which one?) a CONNECTION_CLOSE
// with error code 0.
features.insert(Feature::kConnectionClose);
return features;
}
std::set<Feature> ServerSupport(std::string host, int port) {
// Configure version list.
QuicVersionInitializeSupportForIetfDraft();
ParsedQuicVersion version =
ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99);
ParsedQuicVersionVector versions = {version};
QuicEnableVersion(version);
// Build the client, and try to connect.
QuicSocketAddress addr = tools::LookupAddress(host, QuicStrCat(port));
QuicServerId server_id(host, port, false);
std::string authority = QuicStrCat(host, ":", port);
ParsedQuicVersionVector versions_with_negotiation = versions;
versions_with_negotiation.insert(versions_with_negotiation.begin(),
QuicVersionReservedForNegotiation());
auto supported_features =
AttemptRequest(addr, authority, server_id, versions_with_negotiation);
if (!supported_features.empty()) {
supported_features.insert(Feature::kVersionNegotiation);
} else {
supported_features = AttemptRequest(addr, authority, server_id, versions);
}
return supported_features;
}
} // namespace quic
int main(int argc, char* argv[]) {
QuicSystemEventLoop event_loop("quic_client");
const char* usage = "Usage: quic_client_interop_test [options] [url]";
std::vector<std::string> args =
quic::QuicParseCommandLineFlags(usage, argc, argv);
if (args.size() > 1) {
quic::QuicPrintCommandLineFlagHelp(usage);
exit(1);
}
std::string host = GetQuicFlag(FLAGS_host);
int port = GetQuicFlag(FLAGS_port);
if (!args.empty()) {
quic::QuicUrl url(args[0], "https");
if (host.empty()) {
host = url.host();
}
if (port == 0) {
port = url.port();
}
}
if (port == 0) {
port = 443;
}
if (host.empty()) {
quic::QuicPrintCommandLineFlagHelp(usage);
exit(1);
}
auto supported_features = quic::ServerSupport(host, port);
std::cout << "Supported features: ";
for (auto feature : supported_features) {
std::cout << MatrixLetter(feature);
}
std::cout << std::endl;
}