| // 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/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_h2_connection.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, certificate_file, "", | |
| "Path to the certificate chain."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG(std::string, 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."); | |
| 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: | |
| explicit MasqueTcpServer(MasqueOhttpGateway *masque_ohttp_gateway) | |
| : event_loop_(GetDefaultEventLoop()->Create(QuicDefaultClock::Get())), | |
| masque_ohttp_gateway_(masque_ohttp_gateway) {} | |
| 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 &certificate_file, | |
| const std::string &key_file, | |
| const std::string &client_root_ca_file) { | |
| ctx_.reset(SSL_CTX_new(TLS_method())); | |
| if (!SSL_CTX_use_PrivateKey_file(ctx_.get(), key_file.c_str(), | |
| SSL_FILETYPE_PEM)) { | |
| QUICHE_LOG(ERROR) << "Failed to load private key: " << key_file; | |
| return false; | |
| } | |
| if (!SSL_CTX_use_certificate_chain_file(ctx_.get(), | |
| certificate_file.c_str())) { | |
| QUICHE_LOG(ERROR) << "Failed to load cert chain: " << 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 HandleOhttpRequest(MasqueH2Connection *connection, int32_t stream_id, | |
| const std::string &encapsulated_request) { | |
| return masque_ohttp_gateway_->HandleRequest(connection, stream_id, | |
| encapsulated_request); | |
| } | |
| 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"); | |
| 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" && | |
| content_type_pair != headers.end() && | |
| content_type_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 (method_pair->second == "POST" && | |
| content_type_pair != headers.end() && | |
| content_type_pair->second == "message/ohttp-req") { | |
| if (HandleOhttpRequest(connection, stream_id, body)) { | |
| return; | |
| } else { | |
| response_headers[":status"] = "500"; | |
| response_body = "Failed to handle OHTTP 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"; | |
| } | |
| private: | |
| 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_; | |
| }; | |
| 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 certificate_file = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_certificate_file); | |
| if (certificate_file.empty()) { | |
| QUICHE_LOG(ERROR) << "--certificate_file cannot be empty"; | |
| return 1; | |
| } | |
| std::string key_file = quiche::GetQuicheCommandLineFlag(FLAGS_key_file); | |
| if (key_file.empty()) { | |
| QUICHE_LOG(ERROR) << "--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; | |
| } | |
| MasqueTcpServer server(&masque_ohttp_gateway); | |
| if (!server.SetupSslCtx(certificate_file, 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); | |
| } |