blob: d28560af3140dcfa9a7d7e1c98ba3992d13f2cbe [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/cleanup/cleanup.h"
#include "absl/container/flat_hash_map.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.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/common/quiche_status_utils.h"
#include "quiche/common/quiche_text_utils.h"
#include "quiche/oblivious_http/buffers/oblivious_http_request.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(
bool, pq, false, "Enable OHTTP Post Quantum support via X-Wing.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, pq_key, "",
"Hex-encoded bytes of the X-Wing OHTTP HPKE private key.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(bool, no_classic, false,
"Disable X25519 for OHTTP.");
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, gateway_path, "",
"Enables and configures an OHTTP gateway. Sets the path at which the "
"gateway will respond to both key requests and encapsulated requests. "
"Example: \"/.well-known/ohttp-gateway\".");
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, relay, "",
"Enables and configures an OHTTP relay. The format is a list of (path, "
"URL) pairs where each local path is relayed to the corresponding URL, "
"formatted as \"path1>url1|path2>url2\". For example: "
"\"/foo>https://foo.example:8443/|/bar>https://example.com/bar\".");
DEFINE_QUICHE_COMMAND_LINE_FLAG(
std::string, key_proxy, "",
"Enables and configures proxying of OHTTP key requests. The format is a "
"list of (path, URL) pairs where each local path is proxied to the "
"corresponding "
"URL, formatted as \"path1>url1|path2>url2\". For example: "
"\"/foo>https://foo.example:8443/|/bar>https://example.com/bar\".");
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 {
constexpr absl::string_view kInfo = "TS";
absl::string_view RemoveParameters(absl::string_view value) {
std::vector<absl::string_view> split =
absl::StrSplit(value, absl::MaxSplits(';', 1));
absl::string_view without_params = split[0];
quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(&without_params);
return without_params;
}
bool ListHeaderContainsValue(absl::string_view header,
absl::string_view value) {
std::vector<absl::string_view> header_split = absl::StrSplit(header, ',');
for (absl::string_view header_value : header_split) {
if (RemoveParameters(header_value) == value) {
return true;
}
}
return false;
}
class MasqueOhttpGateway {
public:
class Visitor {
public:
virtual ~Visitor() = default;
virtual void SavePendingGatewayRequest(
MasqueH2Connection* connection, int32_t stream_id,
MasqueConnectionPool::RequestId request_id,
ObliviousHttpRequest::Context&& ohttp_context,
ObliviousHttpGateway&& ohttp_gateway) = 0;
};
static absl::StatusOr<std::unique_ptr<MasqueOhttpGateway>> Create() {
auto ohttp_gateway = absl::WrapUnique(new MasqueOhttpGateway());
if (quiche::GetQuicheCommandLineFlag(FLAGS_pq)) {
QUICHE_RETURN_IF_ERROR(ohttp_gateway->AddKeyConfig(
/*key_id=*/0x02, EVP_HPKE_XWING,
quiche::GetQuicheCommandLineFlag(FLAGS_pq_key)));
}
if (!quiche::GetQuicheCommandLineFlag(FLAGS_no_classic)) {
QUICHE_RETURN_IF_ERROR(ohttp_gateway->AddKeyConfig(
/*key_id=*/0x01, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
quiche::GetQuicheCommandLineFlag(FLAGS_ohttp_key)));
}
return ohttp_gateway;
}
absl::StatusOr<std::string> GetPrivateKey(uint8_t key_id) {
auto it = hpke_private_keys_.find(key_id);
if (it == hpke_private_keys_.end()) {
return absl::NotFoundError("OHTTP key not found");
}
return it->second;
}
absl::Status HandleRequest(MasqueConnectionPool* pool,
MasqueH2Connection* connection, int32_t stream_id,
const std::string& encapsulated_request) {
if (!ohttp_key_configs_) {
return absl::InternalError("Not ready to handle OHTTP request");
}
QUICHE_ASSIGN_OR_RETURN(
ObliviousHttpHeaderKeyConfig ohttp_header_key_config,
ohttp_key_configs_->GetConfigForPayload(encapsulated_request));
QUICHE_ASSIGN_OR_RETURN(std::string private_key,
GetPrivateKey(ohttp_header_key_config.GetKeyId()));
QUICHE_ASSIGN_OR_RETURN(
ObliviousHttpGateway ohttp_gateway,
ObliviousHttpGateway::Create(std::move(private_key),
ohttp_header_key_config));
QUICHE_ASSIGN_OR_RETURN(
ObliviousHttpRequest decrypted_request,
ohttp_gateway.DecryptObliviousHttpRequest(encapsulated_request));
QUICHE_ASSIGN_OR_RETURN(
BinaryHttpRequest binary_request,
BinaryHttpRequest::Create(decrypted_request.GetPlaintextData()));
const BinaryHttpRequest::ControlData& control_data =
binary_request.control_data();
MasqueConnectionPool::Message request;
request.headers[":method"] = control_data.method;
request.headers[":scheme"] = control_data.scheme;
request.headers[":authority"] = control_data.authority;
request.headers[":path"] = control_data.path;
request.body = binary_request.body();
QUICHE_ASSIGN_OR_RETURN(MasqueConnectionPool::RequestId request_id,
pool->SendRequest(request));
QUICHE_LOG(INFO) << "Sent decapsulated request";
visitor_->SavePendingGatewayRequest(
connection, stream_id, request_id,
std::move(decrypted_request).ReleaseContext(),
std::move(ohttp_gateway));
return absl::OkStatus();
}
absl::StatusOr<MasqueConnectionPool::Message> HandleResponse(
MasqueConnectionPool::Message&& response,
ObliviousHttpRequest::Context&& ohttp_context,
ObliviousHttpGateway&& ohttp_gateway) {
auto status_pair = response.headers.find(":status");
if (status_pair == response.headers.end()) {
return absl::InternalError("Response is missing status code");
}
int status_code;
if (!absl::SimpleAtoi(status_pair->second, &status_code)) {
return absl::InternalError(
absl::StrCat("Failed to parse status code: ", status_pair->second));
}
BinaryHttpResponse binary_response(status_code);
for (const auto& [key, value] : response.headers) {
if (key != ":status") {
binary_response.AddHeaderField({std::string(key), std::string(value)});
}
}
binary_response.swap_body(response.body);
QUICHE_ASSIGN_OR_RETURN(std::string encoded_response,
binary_response.Serialize());
QUICHE_ASSIGN_OR_RETURN(ObliviousHttpResponse ohttp_response,
ohttp_gateway.CreateObliviousHttpResponse(
std::move(encoded_response), ohttp_context));
MasqueConnectionPool::Message outer_response;
outer_response.headers[":status"] = "200";
outer_response.headers["content-type"] = "message/ohttp-res";
outer_response.body = ohttp_response.EncapsulateAndSerialize();
return outer_response;
}
std::string concatenated_keys() const { return concatenated_keys_; }
void set_visitor(Visitor* visitor) { visitor_ = visitor; }
private:
MasqueOhttpGateway() = default;
absl::Status AddKeyConfig(
uint8_t key_id, uint16_t kem_id,
absl::string_view force_hpke_private_key_hex = absl::string_view()) {
QUICHE_ASSIGN_OR_RETURN(const EVP_HPKE_KEM* kem,
quiche::CheckKemId(kem_id));
bssl::UniquePtr<EVP_HPKE_KEY> hpke_key;
hpke_key.reset(EVP_HPKE_KEY_new());
std::string hpke_private_key;
if (!force_hpke_private_key_hex.empty()) {
if (!absl::HexStringToBytes(force_hpke_private_key_hex,
&hpke_private_key)) {
return absl::InvalidArgumentError(
"OHTTP key is not a valid hex string");
}
if (EVP_HPKE_KEY_init(
hpke_key.get(), kem,
reinterpret_cast<const uint8_t*>(hpke_private_key.data()),
hpke_private_key.size()) != 1) {
return absl::InternalError("Failed to ingest HPKE key");
}
} else {
if (EVP_HPKE_KEY_generate(hpke_key.get(), kem) != 1) {
return absl::InternalError("Failed to generate new HPKE key");
}
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()) {
return absl::InternalError("Failed to extract new HPKE private key");
}
QUICHE_LOG(INFO) << "Generated new HPKE private key: "
<< absl::BytesToHexString(hpke_private_key);
}
hpke_private_keys_.emplace(key_id, std::move(hpke_private_key));
size_t public_key_len = EVP_HPKE_KEM_public_key_len(kem);
std::string hpke_public_key(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()) {
return absl::InternalError("Failed to extract HPKE public key");
}
ObliviousHttpKeyConfigs::OhttpKeyConfig config = {
/*key_id=*/key_id,
kem_id,
hpke_public_key,
{{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM}}};
configs_.push_back(config);
QUICHE_ASSIGN_OR_RETURN(ObliviousHttpKeyConfigs ohttp_key_configs,
ObliviousHttpKeyConfigs::Create(configs_));
ohttp_key_configs_.emplace(std::move(ohttp_key_configs));
QUICHE_LOG(INFO) << "Using OHTTP key configs: " << std::endl
<< ohttp_key_configs_->DebugString();
QUICHE_ASSIGN_OR_RETURN(concatenated_keys_,
ohttp_key_configs_->GenerateConcatenatedKeys());
return absl::OkStatus();
}
Visitor* visitor_ = nullptr;
std::vector<ObliviousHttpKeyConfigs::OhttpKeyConfig> configs_;
absl::flat_hash_map<uint8_t, std::string> hpke_private_keys_;
std::optional<ObliviousHttpKeyConfigs> ohttp_key_configs_;
std::string concatenated_keys_;
};
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, kInfo) {
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) {
auto cleanup = absl::MakeCleanup([this, event_loop, fd]() {
if (!event_loop->SupportsEdgeTriggered() && !connection_.aborted()) {
if (!event_loop->RearmSocket(
fd, kSocketEventReadable | kSocketEventWritable)) {
QUICHE_LOG(FATAL) << "Failed to re-arm socket " << fd;
}
}
});
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 MasqueOhttpGateway::Visitor {
public:
using RequestId = MasqueConnectionPool::RequestId;
using Message = MasqueConnectionPool::Message;
explicit MasqueTcpServer(SSL_CTX* client_ssl_ctx,
bool disable_certificate_verification,
const MasqueConnectionPool::DnsConfig& dns_config)
: event_loop_(GetDefaultEventLoop()->Create(QuicDefaultClock::Get())),
connection_pool_(event_loop_.get(), client_ssl_ctx,
disable_certificate_verification, dns_config, this,
kInfo) {}
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);
SSL_CTX_set_mode(ctx_.get(), SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
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();
if (!event_loop->SupportsEdgeTriggered()) {
if (!event_loop->RearmSocket(server_socket_, kSocketEventReadable)) {
QUICHE_LOG(FATAL) << "Failed to re-arm socket " << server_socket_;
}
}
}
// From MasqueH2Connection::Visitor.
void OnConnectionReady(MasqueH2Connection* /*connection*/) override {}
void OnConnectionFinished(MasqueH2Connection* connection,
absl::Status /*error*/) override {
connections_.erase(
std::remove_if(connections_.begin(), connections_.end(),
[connection](const auto& socket_connection) {
return socket_connection->connection() == connection;
}),
connections_.end());
}
absl::Status HandleOhttpGatewayRequest(
MasqueH2Connection* connection, int32_t stream_id,
const std::string& encapsulated_request) {
return masque_ohttp_gateway_->HandleRequest(
&connection_pool_, connection, stream_id, encapsulated_request);
}
absl::Status HandleOhttpRelayRequest(MasqueH2Connection* connection,
int32_t stream_id,
const std::string& encapsulated_request,
const QuicUrl& relay_gateway_url) {
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.PathParamsQuery();
request.headers["content-type"] = "message/ohttp-req";
request.body = encapsulated_request;
QUICHE_ASSIGN_OR_RETURN(RequestId request_id,
connection_pool_.SendRequest(request));
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();
}
absl::Status HandleOhttpKeyProxyRequest(MasqueH2Connection* connection,
int32_t stream_id,
const QuicUrl& key_proxy_url) {
Message request;
request.headers[":method"] = "GET";
request.headers[":scheme"] = key_proxy_url.scheme();
request.headers[":authority"] = key_proxy_url.HostPort();
request.headers[":path"] = key_proxy_url.PathParamsQuery();
request.headers["accept"] = "application/ohttp-keys";
QUICHE_ASSIGN_OR_RETURN(RequestId request_id,
connection_pool_.SendRequest(request));
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");
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";
connection->SendResponse(stream_id, response_headers, response_body);
return;
}
std::vector<absl::string_view> path_parts =
absl::StrSplit(path_pair->second, absl::MaxSplits('?', 1));
absl::string_view path = path_parts[0];
absl::string_view content_type;
auto content_type_pair = headers.find("content-type");
if (content_type_pair != headers.end()) {
content_type = RemoveParameters(content_type_pair->second);
}
auto accept_pair = headers.find("accept");
if (!gateway_path_.empty() && path == gateway_path_ &&
masque_ohttp_gateway_ && method_pair->second == "GET" &&
(accept_pair == headers.end() ||
ListHeaderContainsValue(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 (auto key_proxy_pair = key_proxy_urls_.find(path);
key_proxy_pair != key_proxy_urls_.end() &&
method_pair->second == "GET" &&
(accept_pair == headers.end() ||
ListHeaderContainsValue(accept_pair->second,
"application/ohttp-keys")) &&
body.empty()) {
absl::Status status = HandleOhttpKeyProxyRequest(connection, stream_id,
key_proxy_pair->second);
if (status.ok()) {
return;
} else {
QUICHE_LOG(ERROR) << "Failed to handle OHTTP key proxy request for "
<< path << ": " << status;
response_headers[":status"] = "500";
response_body = status.message();
}
} else if (auto relay_pair = relay_gateway_urls_.find(path);
relay_pair != relay_gateway_urls_.end() &&
method_pair->second == "POST" &&
content_type == "message/ohttp-req") {
absl::Status status = HandleOhttpRelayRequest(connection, stream_id, body,
relay_pair->second);
if (status.ok()) {
return;
} else {
QUICHE_LOG(ERROR) << "Failed to handle OHTTP relay request for " << path
<< ": " << status;
response_headers[":status"] = "500";
response_body = status.message();
}
} else if (!gateway_path_.empty() && path == gateway_path_ &&
method_pair->second == "POST" &&
content_type == "message/ohttp-req") {
absl::Status status =
HandleOhttpGatewayRequest(connection, stream_id, body);
if (status.ok()) {
return;
} else {
response_headers[":status"] = "500";
QUICHE_LOG(ERROR) << "Failed to handle OHTTP gateway request: "
<< status;
response_body = status.message();
}
} else if (method_pair->second == "GET" && path == "/") {
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*/, bool /*end_stream*/) override {
QUICHE_LOG(FATAL) << "Server cannot receive responses";
}
void OnStreamFailure(MasqueH2Connection* /*connection*/, int32_t stream_id,
absl::Status error) override {
QUICHE_LOG(ERROR) << "Stream " << stream_id << " failed: " << error;
}
void OnDataForStream(MasqueH2Connection* /*connection*/,
int32_t /*stream_id*/, absl::string_view /*data*/,
bool /*end_stream*/) override {
QUICHE_LOG(FATAL) << "MasqueTcpServer does not request streamed responses";
}
// From MasqueConnectionPool::Visitor.
void OnPoolResponse(MasqueConnectionPool* /*pool*/, RequestId request_id,
absl::StatusOr<Message>&& response,
bool end_stream) override {
if (!end_stream) {
QUICHE_LOG(FATAL)
<< "MasqueTcpServer does not request streamed responses";
}
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()) {
if (pending_request.ohttp_context && pending_request.ohttp_gateway) {
absl::StatusOr<MasqueConnectionPool::Message> gateway_response =
masque_ohttp_gateway_->HandleResponse(
std::move(*response), std::move(*pending_request.ohttp_context),
std::move(*pending_request.ohttp_gateway));
if (!gateway_response.ok()) {
response_headers[":status"] = "500";
response_body = absl::StrCat("Failed to handle gateway response: ",
gateway_response.status().message());
QUICHE_LOG(ERROR) << response_body;
} else {
response_headers = std::move(gateway_response->headers);
response_body = std::move(gateway_response->body);
QUICHE_LOG(INFO) << "Sending OHTTP response";
}
} else {
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 =
absl::StrCat("Relayed request failed: ", response.status().message());
}
pending_request.connection->SendResponse(pending_request.stream_id,
response_headers, response_body);
pending_request.connection->AttemptToSend();
}
void OnPoolData(MasqueConnectionPool* /*pool*/, RequestId /*request_id*/,
absl::string_view /*data*/, bool /*end_stream*/) override {
QUICHE_LOG(FATAL) << "Server received unexpected pool data";
}
bool SetupGateway(const std::string& gateway_path,
MasqueOhttpGateway* gateway) {
if (gateway_path.empty() != (gateway == nullptr)) {
QUICHE_LOG(ERROR) << "Invalid gateway configuration";
return false;
}
gateway_path_ = gateway_path;
masque_ohttp_gateway_ = gateway;
if (masque_ohttp_gateway_) {
masque_ohttp_gateway_->set_visitor(this);
}
return true;
}
bool SetupRelay(const std::string& relay) {
if (relay.empty()) {
return true;
}
std::vector<absl::string_view> relay_split = absl::StrSplit(relay, '|');
for (absl::string_view relay_param : relay_split) {
std::vector<absl::string_view> relay_param_split =
absl::StrSplit(relay_param, '>');
if (relay_param_split.size() != 2) {
QUICHE_LOG(ERROR) << "Invalid relay parameter: \"" << relay_param
<< "\". It should be in the format of \"path>url\"";
return false;
}
absl::string_view path = relay_param_split[0];
absl::string_view gateway_url = relay_param_split[1];
auto [it, inserted] = relay_gateway_urls_.insert(
{std::string(path), QuicUrl(gateway_url, "https")});
if (!inserted) {
QUICHE_LOG(ERROR) << "Duplicate relay path: \"" << path << "\"";
return false;
}
}
return true;
}
bool SetupKeyProxy(const std::string& key_proxy) {
if (key_proxy.empty()) {
return true;
}
std::vector<absl::string_view> key_proxy_split =
absl::StrSplit(key_proxy, '|');
for (absl::string_view key_proxy_param : key_proxy_split) {
std::vector<absl::string_view> key_proxy_param_split =
absl::StrSplit(key_proxy_param, '>');
if (key_proxy_param_split.size() != 2) {
QUICHE_LOG(ERROR) << "Invalid key proxy parameter: \""
<< key_proxy_param
<< "\". It should be in the format of \"path>url\"";
return false;
}
absl::string_view path = key_proxy_param_split[0];
absl::string_view key_proxy_url = key_proxy_param_split[1];
auto [it, inserted] = key_proxy_urls_.insert(
{std::string(path), QuicUrl(key_proxy_url, "https")});
if (!inserted) {
QUICHE_LOG(ERROR) << "Duplicate relay path: \"" << path << "\"";
return false;
}
QUICHE_LOG(INFO) << "Added key proxy for " << path << ": "
<< key_proxy_url;
}
return true;
}
void SavePendingGatewayRequest(
MasqueH2Connection* connection, int32_t stream_id,
MasqueConnectionPool::RequestId request_id,
ObliviousHttpRequest::Context&& ohttp_context,
ObliviousHttpGateway&& ohttp_gateway) override {
PendingRequest pending_request;
pending_request.connection = connection;
pending_request.stream_id = stream_id;
pending_request.ohttp_context = std::move(ohttp_context);
pending_request.ohttp_gateway = std::move(ohttp_gateway);
pending_requests_.insert({request_id, std::move(pending_request)});
}
private:
struct PendingRequest {
MasqueH2Connection* connection = nullptr; // Not owned.
int32_t stream_id = -1;
std::optional<ObliviousHttpRequest::Context> ohttp_context;
std::optional<ObliviousHttpGateway> ohttp_gateway;
};
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_;
std::string gateway_path_;
MasqueOhttpGateway* masque_ohttp_gateway_; // Unowned.
SocketFd server_socket_ = kInvalidSocketFd;
std::vector<std::unique_ptr<MasqueH2SocketConnection>> connections_;
// Maps from local paths to remote gateway URLs.
absl::flat_hash_map<std::string, QuicUrl> relay_gateway_urls_;
// Maps from local paths to remote key fetch URLs.
absl::flat_hash_map<std::string, QuicUrl> key_proxy_urls_;
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");
std::unique_ptr<MasqueOhttpGateway> masque_ohttp_gateway;
std::string gateway_path =
quiche::GetQuicheCommandLineFlag(FLAGS_gateway_path);
if (!gateway_path.empty()) {
auto masque_ohttp_gateway_or = MasqueOhttpGateway::Create();
if (!masque_ohttp_gateway_or.ok()) {
QUICHE_LOG(ERROR) << "Failed to create OHTTP gateway: "
<< masque_ohttp_gateway_or.status().message();
return 1;
}
masque_ohttp_gateway = std::move(*masque_ohttp_gateway_or);
}
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;
}
MasqueConnectionPool::DnsConfig dns_config;
absl::Status address_family_status = dns_config.SetAddressFamily(
quiche::GetQuicheCommandLineFlag(FLAGS_address_family));
if (!address_family_status.ok()) {
QUICHE_LOG(ERROR) << address_family_status;
return 1;
}
MasqueTcpServer server(client_ssl_ctx->get(),
disable_certificate_verification, dns_config);
if (!server.SetupGateway(gateway_path, masque_ohttp_gateway.get())) {
QUICHE_LOG(ERROR) << "Invalid gateway configuration";
return 1;
}
if (!server.SetupRelay(quiche::GetQuicheCommandLineFlag(FLAGS_relay))) {
QUICHE_LOG(ERROR) << "Invalid --relay input";
return 1;
}
if (!server.SetupKeyProxy(
quiche::GetQuicheCommandLineFlag(FLAGS_key_proxy))) {
QUICHE_LOG(ERROR) << "Invalid --key_proxy input";
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);
}