blob: 4b51846691f377de154018eae924717b24aa57b9 [file]
// 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 create an HTTP/2 server with TLS and
// TCP. 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 <netinet/in.h>
#include <sys/socket.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "openssl/base.h"
#include "openssl/bio.h"
#include "openssl/hpke.h"
#include "openssl/ssl.h"
#include "openssl/x509.h"
#include "quiche/quic/core/io/quic_default_event_loop.h"
#include "quiche/quic/core/io/quic_event_loop.h"
#include "quiche/quic/core/io/socket.h"
#include "quiche/quic/core/quic_default_clock.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/masque/masque_connection_pool.h"
#include "quiche/quic/masque/masque_h2_connection.h"
#include "quiche/quic/tools/quic_url.h"
#include "quiche/binary_http/binary_http_message.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_ip_address.h"
#include "quiche/common/quiche_ip_address_family.h"
#include "quiche/common/quiche_socket_address.h"
#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
#include "quiche/oblivious_http/oblivious_http_gateway.h"
DEFINE_QUICHE_COMMAND_LINE_FLAG(int32_t, port, 9661,
"The port the MASQUE server will listen on.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, server_certificate_file, "",
"Path to the certificate chain.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, server_key_file, "",
"Path to the pkcs8 private key.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, client_root_ca_file, "",
"Path to the PEM file containing root CAs.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, ohttp_key, "",
"Hex-encoded bytes of the OHTTP HPKE private key.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, relay_path, "",
"Path of the relay server to accept requests on.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, relay_gateway_url, "",
"URL of the gateway that this relay will forward requests to.");
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.");
using quiche::BinaryHttpRequest;
using quiche::BinaryHttpResponse;
using quiche::ObliviousHttpGateway;
using quiche::ObliviousHttpHeaderKeyConfig;
using quiche::ObliviousHttpKeyConfigs;
using quiche::ObliviousHttpRequest;
using quiche::ObliviousHttpResponse;
namespace quic {
namespace {
class MasqueOhttpGateway {
public:
MasqueOhttpGateway() {}
bool Setup(const std::string& ohttp_key) {
hpke_key_.reset(EVP_HPKE_KEY_new());
if (!ohttp_key.empty()) {
if (!absl::HexStringToBytes(ohttp_key, &hpke_private_key_)) {
QUICHE_LOG(ERROR) << "OHTTP key is not a valid hex string";
return false;
}
if (EVP_HPKE_KEY_init(
hpke_key_.get(), kem_,
reinterpret_cast<const uint8_t*>(hpke_private_key_.data()),
hpke_private_key_.size()) != 1) {
QUICHE_LOG(ERROR) << "Failed to ingest HPKE key";
return false;
}
} else {
if (EVP_HPKE_KEY_generate(hpke_key_.get(), kem_) != 1) {
QUICHE_LOG(ERROR) << "Failed to generate new HPKE key";
return false;
}
size_t private_key_len = EVP_HPKE_KEM_private_key_len(kem_);
hpke_private_key_ = std::string(private_key_len, '0');
if (EVP_HPKE_KEY_private_key(
hpke_key_.get(),
reinterpret_cast<uint8_t*>(hpke_private_key_.data()),
&private_key_len, private_key_len) != 1 ||
private_key_len != hpke_private_key_.size()) {
QUICHE_LOG(ERROR) << "Failed to extract new HPKE private key";
return false;
}
QUICHE_LOG(INFO) << "Generated new HPKE private key: "
<< absl::BytesToHexString(hpke_private_key_);
}
size_t public_key_len = EVP_HPKE_KEM_public_key_len(kem_);
hpke_public_key_ = std::string(public_key_len, '0');
if (EVP_HPKE_KEY_public_key(
hpke_key_.get(),
reinterpret_cast<uint8_t*>(hpke_public_key_.data()),
&public_key_len, public_key_len) != 1 ||
public_key_len != hpke_public_key_.size()) {
QUICHE_LOG(ERROR) << "Failed to extract new HPKE public key";
return false;
}
static constexpr uint8_t kOhttpKeyId = 0x01;
static constexpr uint16_t kOhttpKemId = EVP_HPKE_DHKEM_X25519_HKDF_SHA256;
static constexpr uint16_t kOhttpKdfId = EVP_HPKE_HKDF_SHA256;
static constexpr uint16_t kOhttpAeadId = EVP_HPKE_AES_128_GCM;
absl::StatusOr<ObliviousHttpHeaderKeyConfig> ohttp_header_key_config =
ObliviousHttpHeaderKeyConfig::Create(kOhttpKeyId, kOhttpKemId,
kOhttpKdfId, kOhttpAeadId);
if (!ohttp_header_key_config.ok()) {
QUICHE_LOG(ERROR) << "Failed to create OHTTP header key config: "
<< ohttp_header_key_config.status();
return false;
}
QUICHE_LOG(INFO) << "Using OHTTP header key config: "
<< ohttp_header_key_config->DebugString();
absl::StatusOr<ObliviousHttpKeyConfigs> ohttp_key_configs =
ObliviousHttpKeyConfigs::Create(*ohttp_header_key_config,
hpke_public_key_);
if (!ohttp_key_configs.ok()) {
QUICHE_LOG(ERROR) << "Failed to create OHTTP key configs: "
<< ohttp_key_configs.status();
return false;
}
QUICHE_LOG(INFO) << "Using OHTTP key configs: " << std::endl
<< ohttp_key_configs->DebugString();
absl::StatusOr<std::string> concatenated_keys =
ohttp_key_configs->GenerateConcatenatedKeys();
if (!concatenated_keys.ok()) {
QUICHE_LOG(ERROR) << "Failed to generate concatenated keys: "
<< concatenated_keys.status();
return false;
}
concatenated_keys_ = *concatenated_keys;
absl::StatusOr<ObliviousHttpGateway> ohttp_gateway =
ObliviousHttpGateway::Create(hpke_private_key_,
*ohttp_header_key_config);
if (!ohttp_gateway.ok()) {
QUICHE_LOG(ERROR) << "Failed to create OHTTP gateway: "
<< ohttp_gateway.status();
return false;
}
ohttp_gateway_.emplace(std::move(*ohttp_gateway));
return true;
}
bool HandleRequest(MasqueH2Connection* connection, int32_t stream_id,
const std::string& encapsulated_request) {
if (!ohttp_gateway_.has_value()) {
QUICHE_LOG(ERROR) << "Not ready to handle OHTTP request";
return false;
}
absl::StatusOr<ObliviousHttpRequest> decrypted_request =
ohttp_gateway_->DecryptObliviousHttpRequest(encapsulated_request);
if (!decrypted_request.ok()) {
QUICHE_LOG(ERROR) << "Failed to decrypt OHTTP request: "
<< decrypted_request.status();
return false;
}
absl::StatusOr<BinaryHttpRequest> binary_request =
BinaryHttpRequest::Create(decrypted_request->GetPlaintextData());
if (!binary_request.ok()) {
QUICHE_LOG(ERROR) << "Failed to parse binary request: "
<< binary_request.status();
return false;
}
const BinaryHttpRequest::ControlData& control_data =
binary_request->control_data();
// TODO(dschinazi): Send the decapsulated request to the authority instead
// of replying with a fake local response.
absl::string_view request_body = binary_request->body();
std::string response_body = absl::StrCat(
"OHTTP Response! Request method: ", control_data.method,
" scheme: ", control_data.scheme, " path: ", control_data.path,
" authority: ", control_data.authority, " body: \"", request_body,
"\"");
BinaryHttpResponse binary_response(/*status_code=*/200);
binary_response.swap_body(response_body);
absl::StatusOr<std::string> encoded_response = binary_response.Serialize();
if (!encoded_response.ok()) {
QUICHE_LOG(ERROR) << "Failed to encode response: "
<< encoded_response.status();
return false;
}
auto context = std::move(*decrypted_request).ReleaseContext();
absl::StatusOr<ObliviousHttpResponse> ohttp_response =
ohttp_gateway_->CreateObliviousHttpResponse(*encoded_response, context);
if (!ohttp_response.ok()) {
QUICHE_LOG(ERROR) << "Failed to create OHTTP response: "
<< ohttp_response.status();
return false;
}
std::string encapsulated_response =
ohttp_response->EncapsulateAndSerialize();
QUICHE_LOG(INFO) << "Sending OHTTP response";
quiche::HttpHeaderBlock response_headers;
response_headers[":status"] = "200";
response_headers["content-type"] = "message/ohttp-res";
connection->SendResponse(stream_id, response_headers,
encapsulated_response);
return true;
}
std::string concatenated_keys() const { return concatenated_keys_; }
private:
std::string hpke_private_key_;
std::string hpke_public_key_;
const EVP_HPKE_KEM* kem_ = EVP_hpke_x25519_hkdf_sha256();
bssl::UniquePtr<EVP_HPKE_KEY> hpke_key_;
std::string concatenated_keys_;
std::optional<ObliviousHttpGateway> ohttp_gateway_;
};
static int SelectAlpnCallback(SSL* /*ssl*/, const uint8_t** out,
uint8_t* out_len, const uint8_t* in,
unsigned in_len, void* /*arg*/) {
unsigned i = 0;
while (i < in_len) {
uint8_t alpn_length = in[i];
if (i + alpn_length > in_len) {
// Client sent a malformed ALPN extension.
break;
}
if (alpn_length == 2 && in[i + 1] == 'h' && in[i + 2] == '2') {
// Found "h2".
*out = in + i + 1;
*out_len = alpn_length;
return SSL_TLSEXT_ERR_OK;
}
i += alpn_length + 1;
}
*out = nullptr;
*out_len = 0;
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
class MasqueH2SocketConnection : public QuicSocketEventListener {
public:
explicit MasqueH2SocketConnection(SocketFd connected_socket,
QuicEventLoop* event_loop, SSL_CTX* ctx,
bool is_server,
MasqueH2Connection::Visitor* visitor)
: socket_(connected_socket),
event_loop_(event_loop),
connection_(CreateSsl(ctx), is_server, visitor) {
if (!event_loop_->RegisterSocket(socket_, kSocketEventReadable, this)) {
QUICHE_LOG(FATAL)
<< "Failed to register connection socket with the event loop";
}
}
~MasqueH2SocketConnection() {
if (socket_ != kInvalidSocketFd) {
if (!event_loop_->UnregisterSocket(socket_)) {
QUICHE_LOG(ERROR) << "Failed to unregister socket";
}
close(socket_);
socket_ = kInvalidSocketFd;
}
}
bool Start() {
connection_.OnTransportReadable();
return !connection_.aborted();
}
// From QuicSocketEventListener.
void OnSocketEvent(QuicEventLoop* /*event_loop*/, SocketFd fd,
QuicSocketEventMask events) {
if (fd != socket_ || ((events & kSocketEventReadable) == 0)) {
return;
}
connection_.OnTransportReadable();
}
MasqueH2Connection* connection() { return &connection_; }
private:
SSL* CreateSsl(SSL_CTX* ctx) {
ssl_.reset(SSL_new(ctx));
SSL_set_accept_state(ssl_.get());
BIO* bio = BIO_new_socket(socket_, BIO_CLOSE);
SSL_set_bio(ssl_.get(), bio, bio);
// `SSL_set_bio` causes `ssl_` to take ownership of `bio`.
return ssl_.get();
}
SocketFd socket_;
bssl::UniquePtr<SSL> ssl_;
QuicEventLoop* event_loop_; // Unowned.
MasqueH2Connection connection_;
};
class MasqueTcpServer : public QuicSocketEventListener,
public MasqueH2Connection::Visitor,
public MasqueConnectionPool::Visitor {
public:
using RequestId = MasqueConnectionPool::RequestId;
using Message = MasqueConnectionPool::Message;
explicit MasqueTcpServer(MasqueOhttpGateway* masque_ohttp_gateway,
SSL_CTX* client_ssl_ctx,
bool disable_certificate_verification,
int address_family_for_lookup)
: event_loop_(GetDefaultEventLoop()->Create(QuicDefaultClock::Get())),
masque_ohttp_gateway_(masque_ohttp_gateway),
connection_pool_(event_loop_.get(), client_ssl_ctx,
disable_certificate_verification,
address_family_for_lookup, this) {}
MasqueTcpServer(const MasqueTcpServer&) = delete;
MasqueTcpServer(MasqueTcpServer&&) = delete;
MasqueTcpServer& operator=(const MasqueTcpServer&) = delete;
MasqueTcpServer& operator=(MasqueTcpServer&&) = delete;
~MasqueTcpServer() {
if (server_socket_ != kInvalidSocketFd) {
if (!event_loop_->UnregisterSocket(server_socket_)) {
QUICHE_LOG(ERROR) << "Failed to unregister socket";
}
close(server_socket_);
server_socket_ = kInvalidSocketFd;
}
}
bool SetupSslCtx(const std::string& server_certificate_file,
const std::string& server_key_file,
const std::string& client_root_ca_file) {
ctx_.reset(SSL_CTX_new(TLS_method()));
if (!SSL_CTX_use_PrivateKey_file(ctx_.get(), server_key_file.c_str(),
SSL_FILETYPE_PEM)) {
QUICHE_LOG(ERROR) << "Failed to load private key: " << server_key_file;
return false;
}
if (!SSL_CTX_use_certificate_chain_file(ctx_.get(),
server_certificate_file.c_str())) {
QUICHE_LOG(ERROR) << "Failed to load cert chain: "
<< server_certificate_file;
return false;
}
if (!client_root_ca_file.empty()) {
X509_STORE* store = SSL_CTX_get_cert_store(ctx_.get());
if (store == nullptr) {
QUICHE_LOG(ERROR) << "Failed to get certificate store";
return false;
}
if (X509_STORE_load_locations(store, client_root_ca_file.c_str(),
/*dir=*/nullptr) != 1) {
QUICHE_LOG(ERROR) << "Failed to load client root CA file: "
<< client_root_ca_file;
return false;
}
SSL_CTX_set_verify(ctx_.get(),
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
/*callback=*/nullptr);
}
SSL_CTX_set_alpn_select_cb(ctx_.get(), &SelectAlpnCallback, this);
SSL_CTX_set_min_proto_version(ctx_.get(), TLS1_2_VERSION);
SSL_CTX_set_max_proto_version(ctx_.get(), TLS1_3_VERSION);
return true;
}
bool SetupSocket(uint16_t server_port) {
if (server_socket_ != kInvalidSocketFd) {
QUICHE_LOG(ERROR) << "Socket already set up";
return false;
}
absl::StatusOr<SocketFd> create_result = socket_api::CreateSocket(
quiche::IpAddressFamily::IP_V6, socket_api::SocketProtocol::kTcp,
/*blocking=*/false);
if (!create_result.ok() || create_result.value() == kInvalidSocketFd) {
QUICHE_LOG(ERROR) << "Failed to create socket: "
<< create_result.status();
return false;
}
server_socket_ = create_result.value();
const int enable = 1;
if (setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR,
(const char*)&enable, sizeof(enable)) < 0) {
QUICHE_LOG(ERROR) << "Failed to set SO_REUSEADDR on socket";
return false;
}
absl::Status bind_result = socket_api::Bind(
server_socket_, quiche::QuicheSocketAddress(
quiche::QuicheIpAddress::Any6(), server_port));
if (!bind_result.ok()) {
QUICHE_LOG(ERROR) << "Failed to bind socket: " << bind_result;
return false;
}
absl::Status listen_result = socket_api::Listen(server_socket_, SOMAXCONN);
if (!listen_result.ok()) {
QUICHE_LOG(ERROR) << "Failed to listen on socket: " << listen_result;
return false;
}
if (!event_loop_->RegisterSocket(server_socket_, kSocketEventReadable,
this)) {
QUICHE_LOG(ERROR) << "Failed to register socket with the event loop";
return false;
}
QUICHE_LOG(INFO) << "Started listening on port " << server_port;
return true;
}
void Run() {
while (true) {
event_loop_->RunEventLoopOnce(QuicTime::Delta::FromMilliseconds(50));
}
}
void OnSocketEvent(QuicEventLoop* /*event_loop*/, SocketFd fd,
QuicSocketEventMask events) override {
if (fd != server_socket_ || ((events & kSocketEventReadable) == 0)) {
return;
}
AcceptConnection();
}
// From MasqueH2Connection::Visitor.
void OnConnectionReady(MasqueH2Connection* /*connection*/) override {}
void OnConnectionFinished(MasqueH2Connection* connection) override {
connections_.erase(
std::remove_if(connections_.begin(), connections_.end(),
[connection](const auto& socket_connection) {
return socket_connection->connection() == connection;
}),
connections_.end());
}
bool HandleOhttpGatewayRequest(MasqueH2Connection* connection,
int32_t stream_id,
const std::string& encapsulated_request) {
return masque_ohttp_gateway_->HandleRequest(connection, stream_id,
encapsulated_request);
}
absl::Status HandleOhttpRelayRequest(
MasqueH2Connection* connection, int32_t stream_id,
const std::string& encapsulated_request) {
Message request;
request.headers[":method"] = "POST";
request.headers[":scheme"] = relay_gateway_url_.scheme();
request.headers[":authority"] = relay_gateway_url_.HostPort();
request.headers[":path"] = relay_gateway_url_.path();
request.headers["content-type"] = "message/ohttp-req";
request.body = encapsulated_request;
absl::StatusOr<RequestId> request_id =
connection_pool_.SendRequest(request);
if (!request_id.ok()) {
QUICHE_LOG(ERROR) << "Failed to send relayed request: "
<< request_id.status();
return request_id.status();
}
QUICHE_LOG(INFO) << "Sent relayed request";
PendingRequest pending_request;
pending_request.connection = connection;
pending_request.stream_id = stream_id;
pending_requests_.insert({*request_id, std::move(pending_request)});
return absl::OkStatus();
}
void OnRequest(MasqueH2Connection* connection, int32_t stream_id,
const quiche::HttpHeaderBlock& headers,
const std::string& body) override {
quiche::HttpHeaderBlock response_headers;
std::string response_body;
auto path_pair = headers.find(":path");
auto method_pair = headers.find(":method");
auto content_type_pair = headers.find("content-type");
auto accept_pair = headers.find("accept");
if (path_pair == headers.end() || method_pair == headers.end()) {
// This should never happen because the h2 adapter should have rejected
// the request, but handle it gracefully just in case.
response_headers[":status"] = "400";
response_body = "Request missing pseudo-headers";
} else if (method_pair->second == "GET" && accept_pair != headers.end() &&
accept_pair->second == "application/ohttp-keys") {
response_headers[":status"] = "200";
response_headers["content-type"] = "application/ohttp-keys";
response_body = masque_ohttp_gateway_->concatenated_keys();
} else if (!relay_path_.empty() && path_pair->second == relay_path_ &&
method_pair->second == "POST" &&
content_type_pair != headers.end() &&
content_type_pair->second == "message/ohttp-req") {
if (HandleOhttpRelayRequest(connection, stream_id, body).ok()) {
return;
} else {
response_headers[":status"] = "500";
response_body = "Failed to handle OHTTP relay request";
}
} else if (method_pair->second == "POST" &&
content_type_pair != headers.end() &&
content_type_pair->second == "message/ohttp-req") {
if (HandleOhttpGatewayRequest(connection, stream_id, body)) {
return;
} else {
response_headers[":status"] = "500";
response_body = "Failed to handle OHTTP gateway request";
}
} else if (method_pair->second == "GET" && path_pair->second == "/") {
response_headers[":status"] = "200";
response_body = "<h1>This is a response body</h1>";
} else {
response_headers[":status"] = "404";
response_body = "Path not found";
}
connection->SendResponse(stream_id, response_headers, response_body);
}
void OnResponse(MasqueH2Connection* /*connection*/, int32_t /*stream_id*/,
const quiche::HttpHeaderBlock& /*headers*/,
const std::string& /*body*/) override {
QUICHE_LOG(FATAL) << "Server cannot receive responses";
}
// From MasqueConnectionPool::Visitor.
void OnPoolResponse(MasqueConnectionPool* /*pool*/, RequestId request_id,
absl::StatusOr<Message>&& response) override {
auto it = pending_requests_.find(request_id);
if (it == pending_requests_.end()) {
QUICHE_LOG(ERROR) << "Received unexpected response for unknown request "
<< request_id;
return;
}
PendingRequest pending_request = std::move(it->second);
pending_requests_.erase(it);
quiche::HttpHeaderBlock response_headers;
std::string response_body;
if (response.ok()) {
QUICHE_LOG(INFO) << "Forwarding relayed response to stream ID "
<< pending_request.stream_id;
response_headers = std::move(response->headers);
response_body = std::move(response->body);
} else {
QUICHE_LOG(ERROR) << "Received relayed error response: "
<< response.status();
response_headers[":status"] = "500";
response_body = "Relayed request failed";
}
pending_request.connection->SendResponse(pending_request.stream_id,
response_headers, response_body);
pending_request.connection->AttemptToSend();
}
bool SetupRelay(const std::string& relay_path,
const std::string& relay_gateway_url) {
if (relay_path.empty() != relay_gateway_url.empty()) {
return false;
}
relay_path_ = relay_path;
relay_gateway_url_ = QuicUrl(relay_gateway_url, "https");
return true;
}
private:
struct PendingRequest {
MasqueH2Connection* connection = nullptr; // Not owned.
int32_t stream_id = -1;
};
void AcceptConnection() {
absl::StatusOr<socket_api::AcceptResult> accept_result =
socket_api::Accept(server_socket_, /*blocking=*/false);
if (!accept_result.ok()) {
QUICHE_LOG(ERROR) << "Failed to accept connection: "
<< accept_result.status();
return;
}
QUICHE_LOG(INFO) << "Accepted TCP connection from "
<< accept_result.value().peer_address;
// `connection` takes ownership of the socket.
auto connection = std::make_unique<MasqueH2SocketConnection>(
accept_result.value().fd, event_loop_.get(), ctx_.get(),
/*is_server=*/true, this);
if (!connection->Start()) {
QUICHE_LOG(ERROR) << "Failed to start connection handler from "
<< accept_result.value().peer_address;
return;
}
QUICHE_LOG(INFO) << "Started connection from "
<< accept_result.value().peer_address;
connections_.push_back(std::move(connection));
}
std::unique_ptr<QuicEventLoop> event_loop_;
bssl::UniquePtr<SSL_CTX> ctx_;
MasqueOhttpGateway* masque_ohttp_gateway_; // Unowned.
SocketFd server_socket_ = kInvalidSocketFd;
std::vector<std::unique_ptr<MasqueH2SocketConnection>> connections_;
std::string relay_path_;
QuicUrl relay_gateway_url_;
MasqueConnectionPool connection_pool_;
absl::flat_hash_map<RequestId, PendingRequest> pending_requests_;
};
int RunMasqueTcpServer(int argc, char* argv[]) {
const char* usage = "Usage: masque_server [options]";
std::vector<std::string> non_option_args =
quiche::QuicheParseCommandLineFlags(usage, argc, argv);
if (!non_option_args.empty()) {
quiche::QuichePrintCommandLineFlagHelp(usage);
return 1;
}
std::string server_certificate_file =
quiche::GetQuicheCommandLineFlag(FLAGS_server_certificate_file);
if (server_certificate_file.empty()) {
QUICHE_LOG(ERROR) << "--server_certificate_file cannot be empty";
return 1;
}
std::string server_key_file =
quiche::GetQuicheCommandLineFlag(FLAGS_server_key_file);
if (server_key_file.empty()) {
QUICHE_LOG(ERROR) << "--server_key_file cannot be empty";
return 1;
}
std::string client_root_ca_file =
quiche::GetQuicheCommandLineFlag(FLAGS_client_root_ca_file);
quiche::QuicheSystemEventLoop system_event_loop("masque_tcp_server");
MasqueOhttpGateway masque_ohttp_gateway;
if (!masque_ohttp_gateway.Setup(
quiche::GetQuicheCommandLineFlag(FLAGS_ohttp_key))) {
QUICHE_LOG(ERROR) << "Failed to setup OHTTP";
return 1;
}
const bool disable_certificate_verification =
quiche::GetQuicheCommandLineFlag(FLAGS_disable_certificate_verification);
const std::string client_cert_file =
quiche::GetQuicheCommandLineFlag(FLAGS_client_cert_file);
const std::string client_cert_key_file =
quiche::GetQuicheCommandLineFlag(FLAGS_client_cert_key_file);
absl::StatusOr<bssl::UniquePtr<SSL_CTX>> client_ssl_ctx =
MasqueConnectionPool::CreateSslCtx(client_cert_file,
client_cert_key_file);
if (!client_ssl_ctx.ok()) {
QUICHE_LOG(ERROR) << "Failed to create client SSL context: "
<< client_ssl_ctx.status();
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;
}
MasqueTcpServer server(&masque_ohttp_gateway, client_ssl_ctx->get(),
disable_certificate_verification,
address_family_for_lookup);
if (!server.SetupRelay(
quiche::GetQuicheCommandLineFlag(FLAGS_relay_path),
quiche::GetQuicheCommandLineFlag(FLAGS_relay_gateway_url))) {
QUICHE_LOG(ERROR) << "Both --relay_path and --relay_gateway_url must be "
"set, or neither can be set";
return 1;
}
if (!server.SetupSslCtx(server_certificate_file, server_key_file,
client_root_ca_file)) {
QUICHE_LOG(ERROR) << "Failed to setup SSL context";
return 1;
}
if (!server.SetupSocket(quiche::GetQuicheCommandLineFlag(FLAGS_port))) {
QUICHE_LOG(ERROR) << "Failed to setup socket";
return 1;
}
server.Run();
return 0;
}
} // namespace
} // namespace quic
int main(int argc, char* argv[]) {
return quic::RunMasqueTcpServer(argc, argv);
}