| // Copyright 2019 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 file is responsible for the masque_server binary. It allows testing | |
| // our MASQUE server code by creating a MASQUE proxy that relays HTTP/3 | |
| // requests to web servers tunnelled over MASQUE connections. | |
| // e.g.: masque_server | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "quiche/quic/masque/masque_server.h" | |
| #include "quiche/quic/masque/masque_server_backend.h" | |
| #include "quiche/quic/masque/masque_utils.h" | |
| #include "quiche/quic/platform/api/quic_ip_address.h" | |
| #include "quiche/quic/platform/api/quic_logging.h" | |
| #include "quiche/quic/platform/api/quic_socket_address.h" | |
| #include "quiche/common/platform/api/quiche_command_line_flags.h" | |
| #include "quiche/common/platform/api/quiche_system_event_loop.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, cache_dir, "", | |
| "Specifies the directory used during QuicHttpResponseCache " | |
| "construction to seed the cache. Cache directory can be " | |
| "generated using `wget -p --save-headers <url>`"); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, server_authority, "", | |
| "Specifies the authority over which the server will accept MASQUE " | |
| "requests. Defaults to empty which allows all authorities."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, masque_mode, "", | |
| "Allows setting MASQUE mode, currently only valid value is \"open\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, concealed_auth, "", | |
| "Require HTTP Concealed Authentication. Pass in a list of key identifiers " | |
| "and hex-encoded public keys. " | |
| "Separated with colons and semicolons. " | |
| "For example: \"kid1:0123...f;kid2:0123...f\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| bool, concealed_auth_on_all_requests, false, | |
| "If set to true, enable concealed auth on all requests (such as GET) " | |
| "instead of just MASQUE."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, dns_server_v4, "", | |
| "IPv4 address of the DNS server to send in the DNS_ASSIGN capsule " | |
| "formatted to CONNECT-IP clients. E.g., \"8.8.8.8\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, dns_server_v6, "", | |
| "IPv6 address of the DNS server to send in the DNS_ASSIGN capsule " | |
| "formatted to CONNECT-IP clients. E.g., \"2001:4860:4860::8888\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| int32_t, dns_priority, 1, | |
| "Service priority of the DNS server (must be non-zero per specification)."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, dns_auth_domain, "", | |
| "Authentication domain name of the DNS server to send in the DNS_ASSIGN " | |
| "capsule (empty if unencrypted DNS on port 53). E.g., \"dns.google\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, dns_service_parameters, "", | |
| "Service parameters applying to the DNS nameserver. E.g., \"alpn=h2,h3\"."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, dns_internal_domains, "", | |
| "Internal domains authoritative for this DNS server (comma separated). " | |
| "An empty string or \"/root\" indicates the DNS root."); | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, dns_search_domains, "", | |
| "Search domains authoritative for this DNS server (comma separated)."); | |
| int main(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 0; | |
| } | |
| quiche::QuicheSystemEventLoop event_loop("masque_server"); | |
| quic::MasqueMode masque_mode = quic::MasqueMode::kOpen; | |
| std::string mode_string = quiche::GetQuicheCommandLineFlag(FLAGS_masque_mode); | |
| if (!mode_string.empty() && mode_string != "open") { | |
| QUIC_LOG(ERROR) << "Invalid masque_mode \"" << mode_string << "\""; | |
| return 1; | |
| } | |
| auto backend = std::make_unique<quic::MasqueServerBackend>( | |
| masque_mode, quiche::GetQuicheCommandLineFlag(FLAGS_server_authority), | |
| quiche::GetQuicheCommandLineFlag(FLAGS_cache_dir)); | |
| backend->SetConcealedAuth( | |
| quiche::GetQuicheCommandLineFlag(FLAGS_concealed_auth)); | |
| backend->SetConcealedAuthOnAllRequests( | |
| quiche::GetQuicheCommandLineFlag(FLAGS_concealed_auth_on_all_requests)); | |
| std::string dns_v4_str = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_dns_server_v4); | |
| std::string dns_v6_str = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_dns_server_v6); | |
| if (!dns_v4_str.empty() || !dns_v6_str.empty()) { | |
| quic::DnsAssignCapsule dns_capsule; | |
| quic::DnsConfiguration config; | |
| quic::DnsNameserver ns; | |
| int32_t priority = quiche::GetQuicheCommandLineFlag(FLAGS_dns_priority); | |
| if (priority <= 0 || priority > 65535) { | |
| QUIC_LOG(ERROR) << "Invalid dns_priority: " << priority << "; using 1."; | |
| ns.service_priority = 1; | |
| } else { | |
| ns.service_priority = static_cast<uint16_t>(priority); | |
| } | |
| if (!dns_v4_str.empty()) { | |
| for (absl::string_view ip_piece : absl::StrSplit(dns_v4_str, ',')) { | |
| quic::QuicIpAddress ip; | |
| if (ip.FromString(std::string(ip_piece)) && ip.IsIPv4()) { | |
| ns.ipv4_addresses.push_back(ip); | |
| } else { | |
| QUIC_LOG(ERROR) << "Invalid IPv4 address in --dns_server_v4: " | |
| << ip_piece; | |
| } | |
| } | |
| } | |
| if (!dns_v6_str.empty()) { | |
| for (absl::string_view ip_piece : absl::StrSplit(dns_v6_str, ',')) { | |
| quic::QuicIpAddress ip; | |
| if (ip.FromString(std::string(ip_piece)) && ip.IsIPv6()) { | |
| ns.ipv6_addresses.push_back(ip); | |
| } else { | |
| QUIC_LOG(ERROR) << "Invalid IPv6 address in --dns_server_v6: " | |
| << ip_piece; | |
| } | |
| } | |
| } | |
| ns.authentication_domain_name.domain_name = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_dns_auth_domain); | |
| std::string service_params_str = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_dns_service_parameters); | |
| if (!service_params_str.empty()) { | |
| std::optional<std::string> encoded_params = | |
| quic::EncodeSvcParams(service_params_str); | |
| if (!encoded_params) { | |
| QUIC_LOG(ERROR) | |
| << "Invalid SVCB presentation format in --dns_service_parameters: " | |
| << service_params_str; | |
| } else { | |
| ns.service_parameters = *std::move(encoded_params); | |
| } | |
| } | |
| config.nameservers.push_back(std::move(ns)); | |
| std::string internal_doms = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_dns_internal_domains); | |
| if (!internal_doms.empty()) { | |
| for (absl::string_view dom : absl::StrSplit(internal_doms, ',')) { | |
| if (dom == "/root") { | |
| config.internal_domains.push_back(quic::DnsDomain{""}); | |
| } else { | |
| config.internal_domains.push_back(quic::DnsDomain{std::string(dom)}); | |
| } | |
| } | |
| } else { | |
| config.internal_domains.push_back(quic::DnsDomain{""}); | |
| } | |
| std::string search_doms = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_dns_search_domains); | |
| if (!search_doms.empty()) { | |
| for (absl::string_view dom : absl::StrSplit(search_doms, ',')) { | |
| if (!dom.empty()) { | |
| config.search_domains.push_back(quic::DnsDomain{std::string(dom)}); | |
| } | |
| } | |
| } | |
| dns_capsule.dns_configurations.push_back(std::move(config)); | |
| backend->SetDnsAssignCapsule(dns_capsule); | |
| QUIC_LOG(INFO) << "Configured " << dns_capsule.ToString() | |
| << " to send to CONNECT-IP clients."; | |
| } | |
| auto server = | |
| std::make_unique<quic::MasqueServer>(masque_mode, backend.get()); | |
| if (!server->CreateUDPSocketAndListen(quic::QuicSocketAddress( | |
| quic::QuicIpAddress::Any6(), | |
| quiche::GetQuicheCommandLineFlag(FLAGS_port)))) { | |
| return 1; | |
| } | |
| QUIC_LOG(INFO) << "Started " << masque_mode << " MASQUE server"; | |
| server->HandleEventsForever(); | |
| return 0; | |
| } |