|  | // Copyright 2025 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. | 
|  |  | 
|  | // This binary contains minimal code to send an HTTP/1.1 over TLS over TCP | 
|  | // request. It will be refactored to allow layering, with the goal of being able | 
|  | // to use MASQUE over HTTP/2, and CONNECT in our MASQUE code. | 
|  |  | 
|  | #include <fcntl.h> | 
|  | #include <netdb.h> | 
|  | #include <poll.h> | 
|  | #include <stdbool.h> | 
|  | #include <sys/socket.h> | 
|  |  | 
|  | #include <cerrno> | 
|  | #include <cstdint> | 
|  | #include <cstdio> | 
|  | #include <cstring> | 
|  | #include <iostream> | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/status/status.h" | 
|  | #include "absl/status/statusor.h" | 
|  | #include "absl/strings/match.h" | 
|  | #include "absl/strings/str_cat.h" | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "openssl/base.h" | 
|  | #include "openssl/bio.h" | 
|  | #include "openssl/pool.h" | 
|  | #include "openssl/ssl.h" | 
|  | #include "openssl/stack.h" | 
|  | #include "quiche/quic/core/connecting_client_socket.h" | 
|  | #include "quiche/quic/core/crypto/proof_verifier.h" | 
|  | #include "quiche/quic/core/io/event_loop_socket_factory.h" | 
|  | #include "quiche/quic/core/io/quic_default_event_loop.h" | 
|  | #include "quiche/quic/core/io/quic_event_loop.h" | 
|  | #include "quiche/quic/core/quic_default_clock.h" | 
|  | #include "quiche/quic/core/quic_time.h" | 
|  | #include "quiche/quic/core/quic_types.h" | 
|  | #include "quiche/quic/masque/masque_h2_connection.h" | 
|  | #include "quiche/quic/platform/api/quic_default_proof_providers.h" | 
|  | #include "quiche/quic/platform/api/quic_socket_address.h" | 
|  | #include "quiche/quic/tools/fake_proof_verifier.h" | 
|  | #include "quiche/quic/tools/quic_name_lookup.h" | 
|  | #include "quiche/quic/tools/quic_url.h" | 
|  | #include "quiche/common/http/http_header_block.h" | 
|  | #include "quiche/common/platform/api/quiche_command_line_flags.h" | 
|  | #include "quiche/common/platform/api/quiche_logging.h" | 
|  | #include "quiche/common/platform/api/quiche_system_event_loop.h" | 
|  | #include "quiche/common/quiche_mem_slice.h" | 
|  | #include "quiche/common/quiche_text_utils.h" | 
|  | #include "quiche/common/simple_buffer_allocator.h" | 
|  |  | 
|  | DEFINE_QUICHE_COMMAND_LINE_FLAG( | 
|  | bool, disable_certificate_verification, false, | 
|  | "If true, don't verify the server certificate."); | 
|  |  | 
|  | DEFINE_QUICHE_COMMAND_LINE_FLAG(int, address_family, 0, | 
|  | "IP address family to use. Must be 0, 4 or 6. " | 
|  | "Defaults to 0 which means any."); | 
|  |  | 
|  | DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, client_cert_file, "", | 
|  | "Path to the client certificate chain."); | 
|  |  | 
|  | DEFINE_QUICHE_COMMAND_LINE_FLAG( | 
|  | std::string, client_cert_key_file, "", | 
|  | "Path to the pkcs8 client certificate private key."); | 
|  |  | 
|  | namespace quic { | 
|  | namespace { | 
|  |  | 
|  | std::optional<bssl::UniquePtr<SSL_CTX>> CreateSslCtx( | 
|  | const std::string &client_cert_file, | 
|  | const std::string &client_cert_key_file) { | 
|  | if (client_cert_file.empty() != client_cert_key_file.empty()) { | 
|  | QUICHE_LOG(ERROR) << "Both private key and certificate chain are required " | 
|  | "when using client certificates"; | 
|  | return std::nullopt; | 
|  | } | 
|  | bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method())); | 
|  |  | 
|  | if (!client_cert_key_file.empty() && | 
|  | !SSL_CTX_use_PrivateKey_file(ctx.get(), client_cert_key_file.c_str(), | 
|  | SSL_FILETYPE_PEM)) { | 
|  | QUICHE_LOG(ERROR) << "Failed to load client certificate private key: " | 
|  | << client_cert_key_file; | 
|  | return std::nullopt; | 
|  | } | 
|  | if (!client_cert_file.empty() && !SSL_CTX_use_certificate_chain_file( | 
|  | ctx.get(), client_cert_file.c_str())) { | 
|  | QUICHE_LOG(ERROR) << "Failed to load client certificate chain: " | 
|  | << client_cert_file; | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | SSL_CTX_set_min_proto_version(ctx.get(), TLS1_2_VERSION); | 
|  | SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_VERSION); | 
|  |  | 
|  | return ctx; | 
|  | } | 
|  |  | 
|  | class MasqueTlsTcpClientHandler : public ConnectingClientSocket::AsyncVisitor, | 
|  | public MasqueH2Connection::Visitor { | 
|  | public: | 
|  | explicit MasqueTlsTcpClientHandler(QuicEventLoop *event_loop, SSL_CTX *ctx, | 
|  | QuicUrl url, | 
|  | bool disable_certificate_verification, | 
|  | int address_family_for_lookup) | 
|  | : event_loop_(event_loop), | 
|  | ctx_(ctx), | 
|  | socket_factory_(std::make_unique<EventLoopSocketFactory>( | 
|  | event_loop_, quiche::SimpleBufferAllocator::Get())), | 
|  | url_(url), | 
|  | disable_certificate_verification_(disable_certificate_verification), | 
|  | address_family_for_lookup_(address_family_for_lookup) {} | 
|  |  | 
|  | ~MasqueTlsTcpClientHandler() override { | 
|  | if (socket_) { | 
|  | socket_->Disconnect(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool Start() { | 
|  | if (disable_certificate_verification_) { | 
|  | proof_verifier_ = std::make_unique<FakeProofVerifier>(); | 
|  | } else { | 
|  | proof_verifier_ = CreateDefaultProofVerifier(url_.host()); | 
|  | } | 
|  | socket_address_ = tools::LookupAddress( | 
|  | address_family_for_lookup_, url_.host(), absl::StrCat(url_.port())); | 
|  | if (!socket_address_.IsInitialized()) { | 
|  | QUICHE_LOG(ERROR) << "Failed to resolve address for \"" << url_.host() | 
|  | << "\""; | 
|  | return false; | 
|  | } | 
|  | socket_ = socket_factory_->CreateTcpClientSocket(socket_address_, | 
|  | /*receive_buffer_size=*/0, | 
|  | /*send_buffer_size=*/0, | 
|  | /*async_visitor=*/this); | 
|  | if (!socket_) { | 
|  | QUICHE_LOG(ERROR) << "Failed to create TCP socket for " | 
|  | << socket_address_; | 
|  | return false; | 
|  | } | 
|  | socket_->ConnectAsync(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static enum ssl_verify_result_t VerifyCallback(SSL *ssl, uint8_t *out_alert) { | 
|  | return static_cast<MasqueTlsTcpClientHandler *>(SSL_get_app_data(ssl)) | 
|  | ->VerifyCertificate(out_alert); | 
|  | } | 
|  |  | 
|  | enum ssl_verify_result_t VerifyCertificate(uint8_t *out_alert) { | 
|  | const STACK_OF(CRYPTO_BUFFER) *cert_chain = | 
|  | SSL_get0_peer_certificates(ssl_.get()); | 
|  | if (cert_chain == nullptr) { | 
|  | QUICHE_LOG(ERROR) << "No certificate chain"; | 
|  | *out_alert = SSL_AD_INTERNAL_ERROR; | 
|  | return ssl_verify_invalid; | 
|  | } | 
|  | std::vector<std::string> certs; | 
|  | for (CRYPTO_BUFFER *cert : cert_chain) { | 
|  | certs.push_back( | 
|  | std::string(reinterpret_cast<const char *>(CRYPTO_BUFFER_data(cert)), | 
|  | CRYPTO_BUFFER_len(cert))); | 
|  | } | 
|  | const uint8_t *ocsp_response_raw; | 
|  | size_t ocsp_response_len; | 
|  | SSL_get0_ocsp_response(ssl_.get(), &ocsp_response_raw, &ocsp_response_len); | 
|  | std::string ocsp_response(reinterpret_cast<const char *>(ocsp_response_raw), | 
|  | ocsp_response_len); | 
|  | const uint8_t *sct_list_raw; | 
|  | size_t sct_list_len; | 
|  | SSL_get0_signed_cert_timestamp_list(ssl_.get(), &sct_list_raw, | 
|  | &sct_list_len); | 
|  | std::string cert_sct(reinterpret_cast<const char *>(sct_list_raw), | 
|  | sct_list_len); | 
|  | std::string error_details; | 
|  | std::unique_ptr<ProofVerifyDetails> details; | 
|  | QuicAsyncStatus verify_status = proof_verifier_->VerifyCertChain( | 
|  | url_.host(), url_.port(), certs, ocsp_response, cert_sct, | 
|  | /*context=*/nullptr, &error_details, &details, out_alert, | 
|  | /*callback=*/nullptr); | 
|  | if (verify_status != QUIC_SUCCESS) { | 
|  | // TODO(dschinazi) properly handle QUIC_PENDING. | 
|  | QUICHE_LOG(ERROR) << "Failed to verify certificate" | 
|  | << (verify_status == QUIC_PENDING ? " (pending)" : "") | 
|  | << ": " << error_details; | 
|  | return ssl_verify_invalid; | 
|  | } | 
|  | QUICHE_LOG(INFO) << "Successfully verified certificate"; | 
|  | return ssl_verify_ok; | 
|  | } | 
|  |  | 
|  | // From ConnectingClientSocket::AsyncVisitor. | 
|  | void ConnectComplete(absl::Status status) override { | 
|  | if (!status.ok()) { | 
|  | QUICHE_LOG(ERROR) << "Failed to TCP connect to " << socket_address_ | 
|  | << ": " << status; | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  |  | 
|  | QUICHE_LOG(INFO) << "TCP connected to " << socket_address_; | 
|  |  | 
|  | ssl_.reset((SSL_new(ctx_))); | 
|  |  | 
|  | if (SSL_set_app_data(ssl_.get(), this) != 1) { | 
|  | QUICHE_LOG(FATAL) << "SSL_set_app_data failed"; | 
|  | return; | 
|  | } | 
|  | SSL_set_custom_verify(ssl_.get(), SSL_VERIFY_PEER, &VerifyCallback); | 
|  |  | 
|  | if (SSL_set_tlsext_host_name(ssl_.get(), url_.host().c_str()) != 1) { | 
|  | QUICHE_LOG(FATAL) << "SSL_set_tlsext_host_name failed"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | static constexpr uint8_t kAlpnProtocols[] = { | 
|  | 0x02, 'h', '2',                                // h2 | 
|  | 0x08, 'h', 't', 't', 'p', '/', '1', '.', '1',  // http/1.1 | 
|  | }; | 
|  | if (SSL_set_alpn_protos(ssl_.get(), kAlpnProtocols, | 
|  | sizeof(kAlpnProtocols)) != 0) { | 
|  | QUICHE_LOG(FATAL) << "SSL_set_alpn_protos failed"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | BIO *tls_io = nullptr; | 
|  | if (BIO_new_bio_pair(&transport_io_, kBioBufferSize, &tls_io, | 
|  | kBioBufferSize) != 1) { | 
|  | QUICHE_LOG(FATAL) << "BIO_new_bio_pair failed"; | 
|  | return; | 
|  | } | 
|  | SSL_set_bio(ssl_.get(), tls_io, tls_io); | 
|  | BIO_free(tls_io); | 
|  |  | 
|  | int ret = SSL_connect(ssl_.get()); | 
|  | if (ret != 1) { | 
|  | int ssl_err = SSL_get_error(ssl_.get(), ret); | 
|  | if (ssl_err == SSL_ERROR_WANT_READ) { | 
|  | QUICHE_DVLOG(1) << "SSL_connect will require another read"; | 
|  | SendToTransport(); | 
|  | socket_->ReceiveAsync(kBioBufferSize); | 
|  | return; | 
|  | } | 
|  | PrintSSLError("Error while TLS connecting", ssl_err, ret); | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | QUICHE_LOG(INFO) << "TLS connected"; | 
|  |  | 
|  | tls_connected_ = true; | 
|  | MaybeSendRequest(); | 
|  | socket_->ReceiveAsync(kBioBufferSize); | 
|  | } | 
|  |  | 
|  | void ReceiveComplete(absl::StatusOr<quiche::QuicheMemSlice> data) override { | 
|  | if (!data.ok()) { | 
|  | QUICHE_LOG(ERROR) << "Failed to receive transport data: " | 
|  | << data.status(); | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | if (data->empty()) { | 
|  | QUICHE_LOG(INFO) << "Transport read closed"; | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | QUICHE_DVLOG(1) << "Transport received " << data->length() << " bytes"; | 
|  | int write_ret = BIO_write(transport_io_, data->data(), data->length()); | 
|  | if (write_ret < 0) { | 
|  | QUICHE_LOG(ERROR) << "Failed to write data from transport to TLS"; | 
|  | int ssl_err = SSL_get_error(ssl_.get(), write_ret); | 
|  | PrintSSLError("Error while writing data from transport to TLS", ssl_err, | 
|  | write_ret); | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | if (write_ret != static_cast<int>(data->length())) { | 
|  | QUICHE_LOG(ERROR) << "Short write from transport to TLS: " << write_ret | 
|  | << " != " << data->length(); | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | QUICHE_DVLOG(1) << "Wrote " << data->length() | 
|  | << " bytes from transport to TLS"; | 
|  | if (h2_selected_) { | 
|  | h2_connection_->OnTransportReadable(); | 
|  | socket_->ReceiveAsync(kBioBufferSize); | 
|  | return; | 
|  | } | 
|  | int handshake_ret = SSL_do_handshake(ssl_.get()); | 
|  | if (handshake_ret != 1) { | 
|  | int ssl_err = SSL_get_error(ssl_.get(), handshake_ret); | 
|  | if (ssl_err == SSL_ERROR_WANT_READ) { | 
|  | SendToTransport(); | 
|  | socket_->ReceiveAsync(kBioBufferSize); | 
|  | return; | 
|  | } | 
|  | PrintSSLError("Error while performing TLS handshake", ssl_err, | 
|  | handshake_ret); | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | tls_connected_ = true; | 
|  | MaybeSendRequest(); | 
|  | uint8_t buffer[kBioBufferSize] = {}; | 
|  | while (true) { | 
|  | int ssl_read_ret = SSL_read(ssl_.get(), buffer, sizeof(buffer) - 1); | 
|  | if (ssl_read_ret < 0) { | 
|  | int ssl_err = SSL_get_error(ssl_.get(), ssl_read_ret); | 
|  | if (ssl_err == SSL_ERROR_WANT_READ) { | 
|  | SendToTransport(); | 
|  | socket_->ReceiveAsync(kBioBufferSize); | 
|  | return; | 
|  | } | 
|  | PrintSSLError("Error while reading from TLS", ssl_err, ssl_read_ret); | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | if (ssl_read_ret == 0) { | 
|  | QUICHE_LOG(INFO) << "TLS read closed"; | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | if (!h2_selected_) { | 
|  | QUICHE_DVLOG(1) << "TLS read " << ssl_read_ret | 
|  | << " bytes of h1 response"; | 
|  | std::cout << buffer << std::endl; | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void SendComplete(absl::Status status) override { | 
|  | if (!status.ok()) { | 
|  | QUICHE_LOG(ERROR) << "Transport send failed: " << status; | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | SendToTransport(); | 
|  | } | 
|  |  | 
|  | bool IsDone() const { return done_; } | 
|  |  | 
|  | // From MasqueH2Connection::Visitor. | 
|  | void OnConnectionReady(MasqueH2Connection * /*connection*/) override {} | 
|  | void OnConnectionFinished(MasqueH2Connection * /*connection*/) override { | 
|  | done_ = true; | 
|  | } | 
|  |  | 
|  | void OnRequest(MasqueH2Connection * /*connection*/, int32_t /*stream_id*/, | 
|  | const quiche::HttpHeaderBlock & /*headers*/, | 
|  | const std::string & /*body*/) override { | 
|  | QUICHE_LOG(FATAL) << "Client cannot receive requests"; | 
|  | } | 
|  |  | 
|  | void OnResponse(MasqueH2Connection *connection, int32_t stream_id, | 
|  | const quiche::HttpHeaderBlock &headers, | 
|  | const std::string &body) override { | 
|  | if (connection != h2_connection_.get()) { | 
|  | QUICHE_LOG(FATAL) << "Unexpected connection"; | 
|  | } | 
|  | if (stream_id != stream_id_) { | 
|  | QUICHE_LOG(FATAL) << "Unexpected stream id"; | 
|  | } | 
|  | QUICHE_LOG(INFO) << "Received h2 response headers: " | 
|  | << headers.DebugString() << " body: " << body; | 
|  | done_ = true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | void MaybeSendRequest() { | 
|  | if (request_sent_ || done_ || !tls_connected_) { | 
|  | return; | 
|  | } | 
|  | const uint8_t *alpn_data; | 
|  | unsigned alpn_len; | 
|  | SSL_get0_alpn_selected(ssl_.get(), &alpn_data, &alpn_len); | 
|  | if (alpn_len != 0) { | 
|  | std::string alpn(reinterpret_cast<const char *>(alpn_data), alpn_len); | 
|  | if (alpn == "h2") { | 
|  | h2_selected_ = true; | 
|  | } | 
|  | QUICHE_DVLOG(1) << "ALPN selected: " | 
|  | << std::string(reinterpret_cast<const char *>(alpn_data), | 
|  | alpn_len); | 
|  | } else { | 
|  | QUICHE_DVLOG(1) << "No ALPN selected"; | 
|  | } | 
|  | QUICHE_LOG(INFO) << "Using " << (h2_selected_ ? "h2" : "http/1.1"); | 
|  | if (h2_selected_) { | 
|  | SendH2Request(); | 
|  | } else { | 
|  | SendH1Request(); | 
|  | } | 
|  | request_sent_ = true; | 
|  | } | 
|  |  | 
|  | void SendToTransport() { | 
|  | char buffer[kBioBufferSize]; | 
|  | int read_ret = BIO_read(transport_io_, buffer, sizeof(buffer)); | 
|  | if (read_ret == 0) { | 
|  | QUICHE_LOG(ERROR) << "TCP closed while TLS waiting for handshake read"; | 
|  | } else if (read_ret < 0) { | 
|  | int ssl_err = SSL_get_error(ssl_.get(), read_ret); | 
|  | if (ssl_err == SSL_ERROR_WANT_READ) { | 
|  | QUICHE_DVLOG(1) << "TLS needs more bytes from underlying socket"; | 
|  | } else if (ssl_err == SSL_ERROR_SYSCALL && errno == 0) { | 
|  | QUICHE_DVLOG(1) << "TLS recoverable failure from underlying socket"; | 
|  | } else { | 
|  | PrintSSLError("Error while reading from transport_io_", ssl_err, | 
|  | read_ret); | 
|  | } | 
|  | } else { | 
|  | QUICHE_DVLOG(1) << "TLS wrote " << read_ret << " bytes to transport"; | 
|  | socket_->SendAsync(std::string(buffer, read_ret)); | 
|  | } | 
|  | } | 
|  |  | 
|  | int WriteDataToTls(absl::string_view data) { | 
|  | QUICHE_DVLOG(2) << "Writing " << data.size() | 
|  | << " app bytes to TLS:" << std::endl | 
|  | << quiche::QuicheTextUtils::HexDump(data); | 
|  | int ssl_write_ret = SSL_write(ssl_.get(), data.data(), data.size()); | 
|  | if (ssl_write_ret <= 0) { | 
|  | int ssl_err = SSL_get_error(ssl_.get(), ssl_write_ret); | 
|  | PrintSSLError("Error while writing request to TLS", ssl_err, | 
|  | ssl_write_ret); | 
|  | done_ = true; | 
|  | return -1; | 
|  | } else { | 
|  | if (ssl_write_ret == static_cast<int>(data.size())) { | 
|  | QUICHE_DVLOG(1) << "Wrote " << data.size() << " bytes to TLS"; | 
|  | } else { | 
|  | QUICHE_DVLOG(1) << "Wrote " << ssl_write_ret << " / " << data.size() | 
|  | << "bytes to TLS"; | 
|  | } | 
|  | SendToTransport(); | 
|  | } | 
|  | return ssl_write_ret; | 
|  | } | 
|  |  | 
|  | void SendH1Request() { | 
|  | std::string request = absl::StrCat("GET ", url_.path(), | 
|  | " HTTP/1.1\r\nHost: ", url_.HostPort(), | 
|  | "\r\nConnection: close\r\n\r\n"); | 
|  | QUICHE_DVLOG(1) << "Sending h1 request of length " << request.size() | 
|  | << " to TLS"; | 
|  | int write_res = WriteDataToTls(request); | 
|  | if (write_res < 0) { | 
|  | QUICHE_LOG(ERROR) << "Failed to write request to TLS"; | 
|  | done_ = true; | 
|  | return; | 
|  | } else if (write_res != static_cast<int>(request.size())) { | 
|  | QUICHE_LOG(ERROR) << "Request TLS short write " << write_res << " < " | 
|  | << request.size(); | 
|  | done_ = true; | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | void SendH2Request() { | 
|  | h2_connection_ = | 
|  | std::make_unique<MasqueH2Connection>(ssl_.get(), | 
|  | /*is_server=*/false, this); | 
|  | h2_connection_->OnTransportReadable(); | 
|  | quiche::HttpHeaderBlock headers; | 
|  | headers[":method"] = "GET"; | 
|  | headers[":scheme"] = url_.scheme(); | 
|  | headers[":authority"] = url_.HostPort(); | 
|  | headers[":path"] = url_.path(); | 
|  | headers["host"] = url_.HostPort(); | 
|  | stream_id_ = h2_connection_->SendRequest(headers, std::string()); | 
|  | h2_connection_->AttemptToSend(); | 
|  | if (stream_id_ >= 0) { | 
|  | QUICHE_LOG(INFO) << "Wrote h2 request to stream " << stream_id_ | 
|  | << ", now sending to transport"; | 
|  | SendToTransport(); | 
|  | } else { | 
|  | QUICHE_LOG(ERROR) << "Failed to send h2 request"; | 
|  | done_ = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | static constexpr size_t kBioBufferSize = 16384; | 
|  | QuicEventLoop *event_loop_;  // Not owned. | 
|  | SSL_CTX *ctx_;               // Not owned. | 
|  | std::unique_ptr<EventLoopSocketFactory> socket_factory_; | 
|  | QuicUrl url_; | 
|  | bool disable_certificate_verification_; | 
|  | int address_family_for_lookup_; | 
|  | std::unique_ptr<ProofVerifier> proof_verifier_; | 
|  | QuicSocketAddress socket_address_; | 
|  | std::unique_ptr<ConnectingClientSocket> socket_; | 
|  | BIO *transport_io_ = nullptr; | 
|  | bssl::UniquePtr<SSL> ssl_; | 
|  | bool tls_connected_ = false; | 
|  | bool h2_selected_ = false; | 
|  | bool request_sent_ = false; | 
|  | bool done_ = false; | 
|  | int32_t stream_id_ = -1; | 
|  | std::unique_ptr<MasqueH2Connection> h2_connection_; | 
|  | }; | 
|  |  | 
|  | int RunMasqueTcpClient(int argc, char *argv[]) { | 
|  | const char *usage = "Usage: masque_tcp_client <url>"; | 
|  | std::vector<std::string> urls = | 
|  | quiche::QuicheParseCommandLineFlags(usage, argc, argv); | 
|  | if (urls.size() != 1) { | 
|  | quiche::QuichePrintCommandLineFlagHelp(usage); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | quiche::QuicheSystemEventLoop system_event_loop("masque_client"); | 
|  | const bool disable_certificate_verification = | 
|  | quiche::GetQuicheCommandLineFlag(FLAGS_disable_certificate_verification); | 
|  |  | 
|  | std::optional<bssl::UniquePtr<SSL_CTX>> ssl_ctx = CreateSslCtx( | 
|  | quiche::GetQuicheCommandLineFlag(FLAGS_client_cert_file), | 
|  | quiche::GetQuicheCommandLineFlag(FLAGS_client_cert_key_file)); | 
|  | if (!ssl_ctx.has_value()) { | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | const int address_family = | 
|  | quiche::GetQuicheCommandLineFlag(FLAGS_address_family); | 
|  | int address_family_for_lookup; | 
|  | if (address_family == 0) { | 
|  | address_family_for_lookup = AF_UNSPEC; | 
|  | } else if (address_family == 4) { | 
|  | address_family_for_lookup = AF_INET; | 
|  | } else if (address_family == 6) { | 
|  | address_family_for_lookup = AF_INET6; | 
|  | } else { | 
|  | QUICHE_LOG(ERROR) << "Invalid address_family " << address_family; | 
|  | return 1; | 
|  | } | 
|  | std::unique_ptr<QuicEventLoop> event_loop = | 
|  | GetDefaultEventLoop()->Create(QuicDefaultClock::Get()); | 
|  |  | 
|  | QuicUrl url(urls[0], "https"); | 
|  | if (url.host().empty() && !absl::StrContains(urls[0], "://")) { | 
|  | url = QuicUrl(absl::StrCat("https://", urls[0])); | 
|  | } | 
|  | if (url.host().empty()) { | 
|  | QUICHE_LOG(ERROR) << "Failed to parse URL \"" << urls[0] << "\""; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | MasqueTlsTcpClientHandler tls_handler(event_loop.get(), ssl_ctx->get(), url, | 
|  | disable_certificate_verification, | 
|  | address_family_for_lookup); | 
|  | if (!tls_handler.Start()) { | 
|  | return 1; | 
|  | } | 
|  | while (!tls_handler.IsDone()) { | 
|  | event_loop->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(50)); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace quic | 
|  |  | 
|  | int main(int argc, char *argv[]) { | 
|  | return quic::RunMasqueTcpClient(argc, argv); | 
|  | } |