| // 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); |
| } |