| // 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. | |
| #include "quiche/quic/masque/masque_utils.h" | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <optional> | |
| #include <ostream> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/container/btree_map.h" | |
| #include "absl/strings/escaping.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/numbers.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/str_join.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "quiche/quic/core/http/quic_spdy_stream.h" | |
| #include "quiche/quic/core/quic_data_reader.h" | |
| #include "quiche/quic/core/quic_data_writer.h" | |
| #include "quiche/quic/core/quic_versions.h" | |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" | |
| #include "quiche/quic/platform/api/quic_ip_address.h" | |
| #include "quiche/quic/platform/api/quic_logging.h" | |
| #include "quiche/common/capsule.h" | |
| #include "quiche/common/platform/api/quiche_command_line_flags.h" | |
| #include "quiche/common/platform/api/quiche_logging.h" | |
| #if defined(__linux__) | |
| #include <fcntl.h> | |
| #include <linux/if.h> | |
| #include <linux/if_tun.h> | |
| #include <linux/sockios.h> | |
| #include <sys/ioctl.h> | |
| #endif // defined(__linux__) | |
| #include "absl/cleanup/cleanup.h" | |
| DEFINE_QUICHE_COMMAND_LINE_FLAG( | |
| std::string, tap_bridge_interface, "", | |
| "Bridge tap interfaces created by CONNECT-ETHERNET mode to be bridged to " | |
| "the specified interface, if any."); | |
| namespace quic { | |
| ParsedQuicVersionVector MasqueSupportedVersions() { | |
| ParsedQuicVersionVector versions; | |
| for (const ParsedQuicVersion& version : AllSupportedVersions()) { | |
| // Use all versions that support IETF QUIC except QUICv2. | |
| if (version.IsIetfQuic() && !version.AlpnDeferToRFCv1()) { | |
| QuicEnableVersion(version); | |
| versions.push_back(version); | |
| } | |
| } | |
| QUICHE_CHECK(!versions.empty()); | |
| return versions; | |
| } | |
| std::string MasqueModeToString(MasqueMode masque_mode) { | |
| switch (masque_mode) { | |
| case MasqueMode::kInvalid: | |
| return "Invalid"; | |
| case MasqueMode::kOpen: | |
| return "Open"; | |
| case MasqueMode::kConnectIp: | |
| return "CONNECT-IP"; | |
| case MasqueMode::kConnectEthernet: | |
| return "CONNECT-ETHERNET"; | |
| case MasqueMode::kConnectUdpBind: | |
| return "CONNECT-UDP-BIND"; | |
| } | |
| return absl::StrCat("Unknown(", static_cast<int>(masque_mode), ")"); | |
| } | |
| std::ostream& operator<<(std::ostream& os, const MasqueMode& masque_mode) { | |
| os << MasqueModeToString(masque_mode); | |
| return os; | |
| } | |
| #if defined(__linux__) | |
| int CreateTunInterface(const QuicIpAddress& client_address, bool server) { | |
| if (!client_address.IsIPv4()) { | |
| QUIC_LOG(ERROR) << "CreateTunInterface currently only supports IPv4"; | |
| return -1; | |
| } | |
| // TODO(b/281517862): add test to validate O_NONBLOCK | |
| int tun_fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK); | |
| if (tun_fd < 0) { | |
| QUIC_PLOG(ERROR) << "Failed to open clone device"; | |
| return -1; | |
| } | |
| absl::Cleanup tun_fd_closer = [tun_fd] { close(tun_fd); }; | |
| struct ifreq ifr = {}; | |
| ifr.ifr_flags = IFF_TUN | IFF_NO_PI; | |
| // If we want to pick a specific device name, we can set it via | |
| // ifr.ifr_name. Otherwise, the kernel will pick the next available tunX | |
| // name. | |
| int err = ioctl(tun_fd, TUNSETIFF, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "TUNSETIFF failed"; | |
| return -1; | |
| } | |
| int ip_fd = socket(AF_INET, SOCK_DGRAM, 0); | |
| if (ip_fd < 0) { | |
| QUIC_PLOG(ERROR) << "Failed to open IP configuration socket"; | |
| return -1; | |
| } | |
| absl::Cleanup ip_fd_closer = [ip_fd] { close(ip_fd); }; | |
| struct sockaddr_in addr = {}; | |
| addr.sin_family = AF_INET; | |
| // Local address, unused but needs to be set. We use the same address as the | |
| // client address, but with last byte set to 1. | |
| addr.sin_addr = client_address.GetIPv4(); | |
| if (server) { | |
| addr.sin_addr.s_addr &= htonl(0xffffff00); | |
| addr.sin_addr.s_addr |= htonl(0x00000001); | |
| } | |
| memcpy(&ifr.ifr_addr, &addr, sizeof(addr)); | |
| err = ioctl(ip_fd, SIOCSIFADDR, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCSIFADDR failed"; | |
| return -1; | |
| } | |
| // Peer address, needs to match source IP address of sent packets. | |
| addr.sin_addr = client_address.GetIPv4(); | |
| if (!server) { | |
| addr.sin_addr.s_addr &= htonl(0xffffff00); | |
| addr.sin_addr.s_addr |= htonl(0x00000001); | |
| } | |
| memcpy(&ifr.ifr_addr, &addr, sizeof(addr)); | |
| err = ioctl(ip_fd, SIOCSIFDSTADDR, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCSIFDSTADDR failed"; | |
| return -1; | |
| } | |
| if (!server) { | |
| // Set MTU, to 1280 for now which should always fit (fingers crossed) | |
| ifr.ifr_mtu = 1280; | |
| err = ioctl(ip_fd, SIOCSIFMTU, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCSIFMTU failed"; | |
| return -1; | |
| } | |
| } | |
| err = ioctl(ip_fd, SIOCGIFFLAGS, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCGIFFLAGS failed"; | |
| return -1; | |
| } | |
| ifr.ifr_flags |= (IFF_UP | IFF_RUNNING); | |
| err = ioctl(ip_fd, SIOCSIFFLAGS, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCSIFFLAGS failed"; | |
| return -1; | |
| } | |
| close(ip_fd); | |
| QUIC_DLOG(INFO) << "Successfully created TUN interface " << ifr.ifr_name | |
| << " with fd " << tun_fd; | |
| std::move(tun_fd_closer).Cancel(); | |
| return tun_fd; | |
| } | |
| #else | |
| int CreateTunInterface(const QuicIpAddress& /*client_address*/, | |
| bool /*server*/) { | |
| // Unsupported. | |
| return -1; | |
| } | |
| #endif // defined(__linux__) | |
| #if defined(__linux__) | |
| int CreateTapInterface() { | |
| int tap_fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK); | |
| if (tap_fd < 0) { | |
| QUIC_PLOG(ERROR) << "Failed to open clone device"; | |
| return -1; | |
| } | |
| absl::Cleanup tap_fd_closer = [tap_fd] { close(tap_fd); }; | |
| struct ifreq ifr = {}; | |
| ifr.ifr_flags = IFF_TAP | IFF_NO_PI; | |
| // If we want to pick a specific device name, we can set it via | |
| // ifr.ifr_name. Otherwise, the kernel will pick the next available tapX | |
| // name. | |
| int err = ioctl(tap_fd, TUNSETIFF, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "TUNSETIFF failed"; | |
| return -1; | |
| } | |
| QUIC_DLOG(INFO) << "Successfully created TAP interface " << ifr.ifr_name | |
| << " with fd " << tap_fd; | |
| int sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0); | |
| if (sock_fd < 0) { | |
| QUIC_PLOG(ERROR) << "Error opening configuration socket"; | |
| return -1; | |
| } | |
| absl::Cleanup sock_fd_closer = [sock_fd] { close(sock_fd); }; | |
| err = ioctl(sock_fd, SIOCGIFINDEX, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCGIFINDEX failed"; | |
| } | |
| int tap_ifindex = ifr.ifr_ifindex; | |
| ifr.ifr_mtu = 1280; | |
| err = ioctl(sock_fd, SIOCSIFMTU, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCSIFMTU failed"; | |
| return -1; | |
| } | |
| err = ioctl(sock_fd, SIOCGIFFLAGS, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCGIFFLAGS failed"; | |
| return -1; | |
| } | |
| ifr.ifr_flags |= (IFF_UP | IFF_RUNNING); | |
| err = ioctl(sock_fd, SIOCSIFFLAGS, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCSIFFLAGS failed"; | |
| return -1; | |
| } | |
| const std::string tap_bridge_interface = | |
| quiche::GetQuicheCommandLineFlag(FLAGS_tap_bridge_interface); | |
| if (!tap_bridge_interface.empty()) { | |
| if (tap_bridge_interface.size() >= IFNAMSIZ) { | |
| QUIC_LOG(ERROR) << "tap bridge interface size too long: " | |
| << tap_bridge_interface.size(); | |
| return -1; | |
| } | |
| strncpy(ifr.ifr_name, tap_bridge_interface.c_str(), IFNAMSIZ); | |
| ifr.ifr_ifindex = tap_ifindex; | |
| err = ioctl(sock_fd, SIOCBRADDIF, &ifr); | |
| if (err < 0) { | |
| QUIC_PLOG(ERROR) << "SIOCBRADDIF failed"; | |
| return -1; | |
| } | |
| } | |
| std::move(tap_fd_closer).Cancel(); | |
| return tap_fd; | |
| } | |
| #else | |
| int CreateTapInterface() { | |
| // Unsupported. | |
| return -1; | |
| } | |
| #endif // defined(__linux__) | |
| std::string ComputeConcealedAuthContext(uint16_t signature_scheme, | |
| absl::string_view key_id, | |
| absl::string_view public_key, | |
| absl::string_view scheme, | |
| absl::string_view host, uint16_t port, | |
| absl::string_view realm) { | |
| QUIC_DVLOG(2) << "ComputeConcealedAuthContext: key_id=\"" << key_id | |
| << "\" public_key=" << absl::WebSafeBase64Escape(public_key) | |
| << " scheme=\"" << scheme << "\" host=\"" << host | |
| << "\" port=" << port << " realm=\"" << realm << "\""; | |
| std::string key_exporter_context; | |
| key_exporter_context.resize( | |
| sizeof(signature_scheme) + QuicDataWriter::GetVarInt62Len(key_id.size()) + | |
| key_id.size() + QuicDataWriter::GetVarInt62Len(public_key.size()) + | |
| public_key.size() + QuicDataWriter::GetVarInt62Len(scheme.size()) + | |
| scheme.size() + QuicDataWriter::GetVarInt62Len(host.size()) + | |
| host.size() + sizeof(port) + | |
| QuicDataWriter::GetVarInt62Len(realm.size()) + realm.size()); | |
| QuicDataWriter writer(key_exporter_context.size(), | |
| key_exporter_context.data()); | |
| if (!writer.WriteUInt16(signature_scheme) || | |
| !writer.WriteStringPieceVarInt62(key_id) || | |
| !writer.WriteStringPieceVarInt62(public_key) || | |
| !writer.WriteStringPieceVarInt62(scheme) || | |
| !writer.WriteStringPieceVarInt62(host) || !writer.WriteUInt16(port) || | |
| !writer.WriteStringPieceVarInt62(realm) || writer.remaining() != 0) { | |
| QUIC_LOG(FATAL) << "ComputeConcealedAuthContext failed"; | |
| } | |
| return key_exporter_context; | |
| } | |
| std::string ConcealedAuthDataCoveredBySignature( | |
| absl::string_view signature_input) { | |
| return absl::StrCat(std::string(64, 0x20), "HTTP Concealed Authentication", | |
| std::string(1, 0x00), signature_input); | |
| } | |
| namespace { | |
| bool ParseDnsDomain(QuicDataReader& reader, DnsDomain* domain) { | |
| if (domain == nullptr) { | |
| return false; | |
| } | |
| absl::string_view piece; | |
| if (!reader.ReadStringPieceVarInt62(&piece)) { | |
| return false; | |
| } | |
| domain->domain_name = std::string(piece); | |
| return true; | |
| } | |
| size_t ComputeDnsDomainLength(const DnsDomain& domain) { | |
| return QuicDataWriter::GetVarInt62Len(domain.domain_name.size()) + | |
| domain.domain_name.size(); | |
| } | |
| size_t ComputeDnsNameserverLength(const DnsNameserver& ns) { | |
| size_t total = sizeof(ns.service_priority); | |
| total += QuicDataWriter::GetVarInt62Len(ns.ipv4_addresses.size()); | |
| total += ns.ipv4_addresses.size() * QuicIpAddress::kIPv4AddressSize; | |
| total += QuicDataWriter::GetVarInt62Len(ns.ipv6_addresses.size()); | |
| total += ns.ipv6_addresses.size() * QuicIpAddress::kIPv6AddressSize; | |
| total += ComputeDnsDomainLength(ns.authentication_domain_name); | |
| total += QuicDataWriter::GetVarInt62Len(ns.service_parameters.size()); | |
| total += ns.service_parameters.size(); | |
| return total; | |
| } | |
| size_t ComputeDnsConfigurationLength(const DnsConfiguration& config) { | |
| size_t total = QuicDataWriter::GetVarInt62Len(config.nameservers.size()); | |
| for (const DnsNameserver& ns : config.nameservers) { | |
| total += ComputeDnsNameserverLength(ns); | |
| } | |
| total += QuicDataWriter::GetVarInt62Len(config.internal_domains.size()); | |
| for (const DnsDomain& domain : config.internal_domains) { | |
| total += ComputeDnsDomainLength(domain); | |
| } | |
| total += QuicDataWriter::GetVarInt62Len(config.search_domains.size()); | |
| for (const DnsDomain& domain : config.search_domains) { | |
| total += ComputeDnsDomainLength(domain); | |
| } | |
| return total; | |
| } | |
| } // namespace | |
| bool ParseDnsAssignCapsulePayload(absl::string_view payload, | |
| DnsAssignCapsule* capsule) { | |
| if (capsule == nullptr) { | |
| return false; | |
| } | |
| capsule->dns_configurations.clear(); | |
| QuicDataReader reader(payload); | |
| while (!reader.IsDoneReading()) { | |
| DnsConfiguration config; | |
| uint64_t nameserver_count = 0; | |
| if (!reader.ReadVarInt62(&nameserver_count)) { | |
| return false; | |
| } | |
| for (uint64_t i = 0; i < nameserver_count; ++i) { | |
| DnsNameserver ns; | |
| if (!reader.ReadUInt16(&ns.service_priority)) { | |
| return false; | |
| } | |
| if (ns.service_priority == 0) { | |
| return false; | |
| } | |
| uint64_t ipv4_count = 0; | |
| if (!reader.ReadVarInt62(&ipv4_count)) { | |
| return false; | |
| } | |
| for (uint64_t j = 0; j < ipv4_count; ++j) { | |
| absl::string_view ip_bytes; | |
| if (!reader.ReadStringPiece(&ip_bytes, | |
| QuicIpAddress::kIPv4AddressSize)) { | |
| return false; | |
| } | |
| QuicIpAddress ip; | |
| if (!ip.FromPackedString(ip_bytes.data(), | |
| QuicIpAddress::kIPv4AddressSize)) { | |
| return false; | |
| } | |
| ns.ipv4_addresses.push_back(ip); | |
| } | |
| uint64_t ipv6_count = 0; | |
| if (!reader.ReadVarInt62(&ipv6_count)) { | |
| return false; | |
| } | |
| for (uint64_t j = 0; j < ipv6_count; ++j) { | |
| absl::string_view ip_bytes; | |
| if (!reader.ReadStringPiece(&ip_bytes, | |
| QuicIpAddress::kIPv6AddressSize)) { | |
| return false; | |
| } | |
| QuicIpAddress ip; | |
| if (!ip.FromPackedString(ip_bytes.data(), | |
| QuicIpAddress::kIPv6AddressSize)) { | |
| return false; | |
| } | |
| ns.ipv6_addresses.push_back(ip); | |
| } | |
| if (!ParseDnsDomain(reader, &ns.authentication_domain_name)) { | |
| return false; | |
| } | |
| absl::string_view sp_piece; | |
| if (!reader.ReadStringPieceVarInt62(&sp_piece)) { | |
| return false; | |
| } | |
| if (!sp_piece.empty() && !DecodeSvcParams(sp_piece).has_value()) { | |
| return false; | |
| } | |
| ns.service_parameters = std::string(sp_piece); | |
| config.nameservers.push_back(std::move(ns)); | |
| } | |
| uint64_t internal_domain_count = 0; | |
| if (!reader.ReadVarInt62(&internal_domain_count)) { | |
| return false; | |
| } | |
| for (uint64_t i = 0; i < internal_domain_count; ++i) { | |
| DnsDomain domain; | |
| if (!ParseDnsDomain(reader, &domain)) { | |
| return false; | |
| } | |
| config.internal_domains.push_back(std::move(domain)); | |
| } | |
| uint64_t search_domain_count = 0; | |
| if (!reader.ReadVarInt62(&search_domain_count)) { | |
| return false; | |
| } | |
| for (uint64_t i = 0; i < search_domain_count; ++i) { | |
| DnsDomain domain; | |
| if (!ParseDnsDomain(reader, &domain)) { | |
| return false; | |
| } | |
| config.search_domains.push_back(std::move(domain)); | |
| } | |
| capsule->dns_configurations.push_back(std::move(config)); | |
| } | |
| return true; | |
| } | |
| bool ParsePref64CapsulePayload(absl::string_view payload, | |
| Pref64Capsule* capsule) { | |
| if (capsule == nullptr) { | |
| return false; | |
| } | |
| capsule->nat64_prefixes.clear(); | |
| if (payload.size() % 13 != 0) { | |
| return false; | |
| } | |
| QuicDataReader reader(payload); | |
| while (!reader.IsDoneReading()) { | |
| Pref64Prefix prefix; | |
| if (!reader.ReadUInt8(&prefix.prefix_length)) { | |
| return false; | |
| } | |
| if (prefix.prefix_length != 32 && prefix.prefix_length != 40 && | |
| prefix.prefix_length != 48 && prefix.prefix_length != 56 && | |
| prefix.prefix_length != 64 && prefix.prefix_length != 96) { | |
| return false; | |
| } | |
| absl::string_view prefix_bytes; | |
| if (!reader.ReadStringPiece(&prefix_bytes, 12)) { | |
| return false; | |
| } | |
| char full_ipv6_bytes[16] = {}; | |
| memcpy(full_ipv6_bytes, prefix_bytes.data(), 12); | |
| if (!prefix.prefix.FromPackedString(full_ipv6_bytes, 16)) { | |
| return false; | |
| } | |
| capsule->nat64_prefixes.push_back(std::move(prefix)); | |
| } | |
| return true; | |
| } | |
| std::string SerializeDnsAssignCapsulePayload(const DnsAssignCapsule& capsule) { | |
| size_t total_len = 0; | |
| for (const DnsConfiguration& config : capsule.dns_configurations) { | |
| total_len += ComputeDnsConfigurationLength(config); | |
| } | |
| std::string buffer(total_len, '\0'); | |
| QuicDataWriter writer(buffer.size(), buffer.data()); | |
| for (const DnsConfiguration& config : capsule.dns_configurations) { | |
| if (!writer.WriteVarInt62(config.nameservers.size())) { | |
| QUIC_BUG(dns_assign_serial_error) << "Failed writing nameservers count"; | |
| return ""; | |
| } | |
| for (const DnsNameserver& ns : config.nameservers) { | |
| if (!writer.WriteUInt16(ns.service_priority) || | |
| !writer.WriteVarInt62(ns.ipv4_addresses.size())) { | |
| QUIC_BUG(dns_assign_serial_error) | |
| << "Failed writing priority or ipv4 count"; | |
| return ""; | |
| } | |
| for (const QuicIpAddress& ip : ns.ipv4_addresses) { | |
| if (!ip.IsIPv4()) { | |
| QUIC_BUG(dns_assign_serial_error) << "Not an IPv4 address"; | |
| return ""; | |
| } | |
| std::string packed = ip.ToPackedString(); | |
| if (packed.size() != QuicIpAddress::kIPv4AddressSize || | |
| !writer.WriteStringPiece(packed)) { | |
| QUIC_BUG(dns_assign_serial_error) << "Failed writing ipv4"; | |
| return ""; | |
| } | |
| } | |
| if (!writer.WriteVarInt62(ns.ipv6_addresses.size())) { | |
| QUIC_BUG(dns_assign_serial_error) << "Failed writing ipv6 count"; | |
| return ""; | |
| } | |
| for (const QuicIpAddress& ip : ns.ipv6_addresses) { | |
| if (!ip.IsIPv6()) { | |
| QUIC_BUG(dns_assign_serial_error) << "Not an IPv6 address"; | |
| return ""; | |
| } | |
| std::string packed = ip.ToPackedString(); | |
| if (packed.size() != QuicIpAddress::kIPv6AddressSize || | |
| !writer.WriteStringPiece(packed)) { | |
| QUIC_BUG(dns_assign_serial_error) << "Failed writing ipv6"; | |
| return ""; | |
| } | |
| } | |
| if (!ns.service_parameters.empty() && | |
| !DecodeSvcParams(ns.service_parameters).has_value()) { | |
| QUIC_BUG(dns_assign_serial_error) | |
| << "Invalid service parameters wire format"; | |
| return ""; | |
| } | |
| if (!writer.WriteStringPieceVarInt62( | |
| ns.authentication_domain_name.domain_name) || | |
| !writer.WriteStringPieceVarInt62(ns.service_parameters)) { | |
| QUIC_BUG(dns_assign_serial_error) | |
| << "Failed writing domain name / service params"; | |
| return ""; | |
| } | |
| } | |
| if (!writer.WriteVarInt62(config.internal_domains.size())) { | |
| QUIC_BUG(dns_assign_serial_error) | |
| << "Failed writing internal domains count"; | |
| return ""; | |
| } | |
| for (const DnsDomain& domain : config.internal_domains) { | |
| if (!writer.WriteStringPieceVarInt62(domain.domain_name)) { | |
| QUIC_BUG(dns_assign_serial_error) << "Failed writing internal domain"; | |
| return ""; | |
| } | |
| } | |
| if (!writer.WriteVarInt62(config.search_domains.size())) { | |
| QUIC_BUG(dns_assign_serial_error) | |
| << "Failed writing search domains count"; | |
| return ""; | |
| } | |
| for (const DnsDomain& domain : config.search_domains) { | |
| if (!writer.WriteStringPieceVarInt62(domain.domain_name)) { | |
| QUIC_BUG(dns_assign_serial_error) << "Failed writing search domain"; | |
| return ""; | |
| } | |
| } | |
| } | |
| if (writer.remaining() != 0) { | |
| QUIC_BUG(dns_assign_serial_error) | |
| << "Remaining length after serialization: " << writer.remaining(); | |
| return ""; | |
| } | |
| return buffer; | |
| } | |
| std::string SerializePref64CapsulePayload(const Pref64Capsule& capsule) { | |
| size_t total_len = capsule.nat64_prefixes.size() * 13; | |
| std::string buffer(total_len, '\0'); | |
| QuicDataWriter writer(buffer.size(), buffer.data()); | |
| for (const Pref64Prefix& prefix : capsule.nat64_prefixes) { | |
| if (!writer.WriteUInt8(prefix.prefix_length) || !prefix.prefix.IsIPv6()) { | |
| QUIC_BUG(pref64_serial_error) | |
| << "Failed writing prefix length / not ipv6"; | |
| return ""; | |
| } | |
| std::string packed = prefix.prefix.ToPackedString(); | |
| if (packed.size() != 16 || | |
| !writer.WriteStringPiece(absl::string_view(packed.data(), 12))) { | |
| QUIC_BUG(pref64_serial_error) << "Failed writing prefix data"; | |
| return ""; | |
| } | |
| } | |
| if (writer.remaining() != 0) { | |
| QUIC_BUG(pref64_serial_error) | |
| << "Remaining length after serialization: " << writer.remaining(); | |
| return ""; | |
| } | |
| return buffer; | |
| } | |
| namespace { | |
| constexpr uint16_t kSvcParamKeyAlpn = 1; | |
| constexpr uint16_t kSvcParamKeyNoDefaultAlpn = 2; | |
| constexpr uint16_t kSvcParamKeyPort = 3; | |
| constexpr uint16_t kSvcParamKeyIpv4Hint = 4; | |
| constexpr uint16_t kSvcParamKeyIpv6Hint = 6; | |
| constexpr uint16_t kSvcParamKeyDohPath = 7; | |
| constexpr uint16_t kSvcParamKeyOhttp = 8; | |
| } // namespace | |
| std::optional<std::string> EncodeSvcParams(absl::string_view presentation) { | |
| absl::btree_map<uint16_t, std::string> params; | |
| for (absl::string_view token : absl::StrSplit( | |
| presentation, absl::ByAnyChar(" \t\r\n"), absl::SkipWhitespace())) { | |
| size_t equals_pos = token.find('='); | |
| absl::string_view key_str = token.substr(0, equals_pos); | |
| absl::string_view value_str = (equals_pos == absl::string_view::npos) | |
| ? "" | |
| : token.substr(equals_pos + 1); | |
| uint16_t key = 0; | |
| std::string wire_val; | |
| if (key_str == "alpn") { | |
| key = kSvcParamKeyAlpn; | |
| for (absl::string_view alpn : absl::StrSplit(value_str, ',')) { | |
| if (alpn.empty() || alpn.size() > 255) { | |
| return std::nullopt; | |
| } | |
| wire_val.push_back(static_cast<char>(alpn.size())); | |
| wire_val.append(alpn.data(), alpn.size()); | |
| } | |
| if (wire_val.empty()) { | |
| return std::nullopt; | |
| } | |
| } else if (key_str == "no-default-alpn") { | |
| key = kSvcParamKeyNoDefaultAlpn; | |
| if (!value_str.empty()) { | |
| return std::nullopt; | |
| } | |
| } else if (key_str == "port") { | |
| key = kSvcParamKeyPort; | |
| uint16_t port = 0; | |
| if (!absl::SimpleAtoi(value_str, &port)) { | |
| return std::nullopt; | |
| } | |
| char buf[2]; | |
| QuicDataWriter writer(2, buf); | |
| writer.WriteUInt16(port); | |
| wire_val = std::string(buf, 2); | |
| } else if (key_str == "ipv4hint") { | |
| key = kSvcParamKeyIpv4Hint; | |
| for (absl::string_view ip_str : absl::StrSplit(value_str, ',')) { | |
| QuicIpAddress ip; | |
| if (!ip.FromString(std::string(ip_str)) || !ip.IsIPv4()) { | |
| return std::nullopt; | |
| } | |
| wire_val.append(ip.ToPackedString()); | |
| } | |
| if (wire_val.empty()) { | |
| return std::nullopt; | |
| } | |
| } else if (key_str == "ipv6hint") { | |
| key = kSvcParamKeyIpv6Hint; | |
| for (absl::string_view ip_str : absl::StrSplit(value_str, ',')) { | |
| QuicIpAddress ip; | |
| if (!ip.FromString(std::string(ip_str)) || !ip.IsIPv6()) { | |
| return std::nullopt; | |
| } | |
| wire_val.append(ip.ToPackedString()); | |
| } | |
| if (wire_val.empty()) { | |
| return std::nullopt; | |
| } | |
| } else if (key_str == "dohpath") { | |
| key = kSvcParamKeyDohPath; | |
| if (value_str.empty()) { | |
| return std::nullopt; | |
| } | |
| wire_val = std::string(value_str); | |
| } else if (key_str == "ohttp") { | |
| key = kSvcParamKeyOhttp; | |
| if (!value_str.empty()) { | |
| return std::nullopt; | |
| } | |
| } else if (absl::StartsWith(key_str, "key")) { | |
| uint32_t key_num; | |
| if (!absl::SimpleAtoi(key_str.substr(3), &key_num) || key_num > 65535) { | |
| return std::nullopt; | |
| } | |
| key = static_cast<uint16_t>(key_num); | |
| wire_val = std::string(value_str); | |
| } else { | |
| return std::nullopt; // Unknown key string | |
| } | |
| if (!params.try_emplace(key, std::move(wire_val)).second) { | |
| return std::nullopt; // Duplicate key not allowed per Section 2.2 of RFC | |
| // 9460. | |
| } | |
| } | |
| size_t total_len = 0; | |
| for (const auto& [key, val] : params) { | |
| if (val.size() > 65535) { | |
| return std::nullopt; | |
| } | |
| total_len += 4 + val.size(); // 2 bytes key + 2 bytes len + value bytes | |
| } | |
| std::string wire(total_len, '\0'); | |
| QuicDataWriter writer(wire.size(), wire.data()); | |
| for (const auto& [key, val] : params) { | |
| if (!writer.WriteUInt16(key) || | |
| !writer.WriteUInt16(static_cast<uint16_t>(val.size())) || | |
| !writer.WriteStringPiece(val)) { | |
| return std::nullopt; | |
| } | |
| } | |
| return wire; | |
| } | |
| std::optional<std::string> DecodeSvcParams(absl::string_view wire_format) { | |
| if (wire_format.empty()) { | |
| return ""; | |
| } | |
| QuicDataReader reader(wire_format); | |
| std::vector<std::string> param_strings; | |
| uint16_t last_key = 0; | |
| bool first = true; | |
| while (!reader.IsDoneReading()) { | |
| uint16_t key = 0; | |
| uint16_t len = 0; | |
| if (!reader.ReadUInt16(&key) || !reader.ReadUInt16(&len)) { | |
| return std::nullopt; | |
| } | |
| absl::string_view val; | |
| if (!reader.ReadStringPiece(&val, len)) { | |
| return std::nullopt; | |
| } | |
| if (!first && key <= last_key) { | |
| return std::nullopt; // Keys must be strictly increasing per Section 2.2 | |
| // of RFC 9460. | |
| } | |
| first = false; | |
| last_key = key; | |
| if (key == kSvcParamKeyAlpn) { | |
| std::vector<absl::string_view> alpns; | |
| QuicDataReader alpn_reader(val); | |
| while (!alpn_reader.IsDoneReading()) { | |
| uint8_t alpn_len = 0; | |
| absl::string_view alpn; | |
| if (!alpn_reader.ReadUInt8(&alpn_len) || alpn_len == 0 || | |
| !alpn_reader.ReadStringPiece(&alpn, alpn_len)) { | |
| return std::nullopt; | |
| } | |
| alpns.push_back(alpn); | |
| } | |
| if (alpns.empty()) { | |
| return std::nullopt; | |
| } | |
| param_strings.push_back(absl::StrCat("alpn=", absl::StrJoin(alpns, ","))); | |
| } else if (key == kSvcParamKeyNoDefaultAlpn) { | |
| if (!val.empty()) { | |
| return std::nullopt; | |
| } | |
| param_strings.push_back("no-default-alpn"); | |
| } else if (key == kSvcParamKeyPort) { | |
| if (val.size() != 2) { | |
| return std::nullopt; | |
| } | |
| QuicDataReader port_reader(val); | |
| uint16_t port = 0; | |
| if (!port_reader.ReadUInt16(&port)) { | |
| return std::nullopt; | |
| } | |
| param_strings.push_back(absl::StrCat("port=", port)); | |
| } else if (key == kSvcParamKeyIpv4Hint) { | |
| if (val.empty() || val.size() % QuicIpAddress::kIPv4AddressSize != 0) { | |
| return std::nullopt; | |
| } | |
| std::vector<std::string> ips; | |
| QuicDataReader ip_reader(val); | |
| while (!ip_reader.IsDoneReading()) { | |
| absl::string_view ip_bytes; | |
| if (!ip_reader.ReadStringPiece(&ip_bytes, | |
| QuicIpAddress::kIPv4AddressSize)) { | |
| return std::nullopt; | |
| } | |
| QuicIpAddress ip; | |
| if (!ip.FromPackedString(ip_bytes.data(), | |
| QuicIpAddress::kIPv4AddressSize)) { | |
| return std::nullopt; | |
| } | |
| ips.push_back(ip.ToString()); | |
| } | |
| param_strings.push_back( | |
| absl::StrCat("ipv4hint=", absl::StrJoin(ips, ","))); | |
| } else if (key == kSvcParamKeyIpv6Hint) { | |
| if (val.empty() || val.size() % QuicIpAddress::kIPv6AddressSize != 0) { | |
| return std::nullopt; | |
| } | |
| std::vector<std::string> ips; | |
| QuicDataReader ip_reader(val); | |
| while (!ip_reader.IsDoneReading()) { | |
| absl::string_view ip_bytes; | |
| if (!ip_reader.ReadStringPiece(&ip_bytes, | |
| QuicIpAddress::kIPv6AddressSize)) { | |
| return std::nullopt; | |
| } | |
| QuicIpAddress ip; | |
| if (!ip.FromPackedString(ip_bytes.data(), | |
| QuicIpAddress::kIPv6AddressSize)) { | |
| return std::nullopt; | |
| } | |
| ips.push_back(ip.ToString()); | |
| } | |
| param_strings.push_back( | |
| absl::StrCat("ipv6hint=", absl::StrJoin(ips, ","))); | |
| } else if (key == kSvcParamKeyDohPath) { | |
| if (val.empty()) { | |
| return std::nullopt; | |
| } | |
| param_strings.push_back(absl::StrCat("dohpath=", val)); | |
| } else if (key == kSvcParamKeyOhttp) { | |
| if (!val.empty()) { | |
| return std::nullopt; | |
| } | |
| param_strings.push_back("ohttp"); | |
| } else { | |
| param_strings.push_back(absl::StrCat("key", key, "=", val)); | |
| } | |
| } | |
| return absl::StrJoin(param_strings, " "); | |
| } | |
| std::string DnsNameserver::ToString() const { | |
| std::string rv = absl::StrCat("(priority:", service_priority); | |
| for (const QuicIpAddress& ip : ipv4_addresses) { | |
| absl::StrAppend(&rv, ",ipv4:", ip.ToString()); | |
| } | |
| for (const QuicIpAddress& ip : ipv6_addresses) { | |
| absl::StrAppend(&rv, ",ipv6:", ip.ToString()); | |
| } | |
| if (!authentication_domain_name.domain_name.empty()) { | |
| absl::StrAppend(&rv, | |
| ",auth_domain:", authentication_domain_name.domain_name); | |
| } | |
| if (!service_parameters.empty()) { | |
| std::optional<std::string> decoded = DecodeSvcParams(service_parameters); | |
| absl::StrAppend(&rv, ",service_params:", | |
| decoded.has_value() ? absl::CEscape(*decoded) | |
| : absl::CEscape(service_parameters)); | |
| } | |
| absl::StrAppend(&rv, ")"); | |
| return rv; | |
| } | |
| std::string DnsConfiguration::ToString() const { | |
| std::string rv = "{nameservers:["; | |
| for (const DnsNameserver& ns : nameservers) { | |
| absl::StrAppend(&rv, ns.ToString()); | |
| } | |
| absl::StrAppend(&rv, "],internal_domains:["); | |
| bool first = true; | |
| for (const DnsDomain& dom : internal_domains) { | |
| if (!first) { | |
| absl::StrAppend(&rv, ","); | |
| } | |
| first = false; | |
| absl::StrAppend(&rv, dom.domain_name.empty() ? "/root" : dom.domain_name); | |
| } | |
| absl::StrAppend(&rv, "],search_domains:["); | |
| first = true; | |
| for (const DnsDomain& dom : search_domains) { | |
| if (!first) { | |
| absl::StrAppend(&rv, ","); | |
| } | |
| first = false; | |
| absl::StrAppend(&rv, dom.domain_name); | |
| } | |
| absl::StrAppend(&rv, "]}"); | |
| return rv; | |
| } | |
| std::string DnsAssignCapsule::ToString() const { | |
| std::string rv = "DNS_ASSIGN["; | |
| for (const DnsConfiguration& config : dns_configurations) { | |
| absl::StrAppend(&rv, config.ToString()); | |
| } | |
| absl::StrAppend(&rv, "]"); | |
| return rv; | |
| } | |
| std::string Pref64Prefix::ToString() const { | |
| return absl::StrCat("(", prefix.ToString(), "/", | |
| static_cast<int>(prefix_length), ")"); | |
| } | |
| std::string Pref64Capsule::ToString() const { | |
| std::string rv = "PREF64["; | |
| for (const Pref64Prefix& prefix : nat64_prefixes) { | |
| absl::StrAppend(&rv, prefix.ToString()); | |
| } | |
| absl::StrAppend(&rv, "]"); | |
| return rv; | |
| } | |
| void WriteDnsAssignCapsule(QuicSpdyStream* stream, | |
| const DnsAssignCapsule& capsule, bool fin) { | |
| QUICHE_CHECK(stream != nullptr); | |
| std::string payload = SerializeDnsAssignCapsulePayload(capsule); | |
| stream->WriteCapsule(quiche::Capsule::Unknown(kDnsAssignCapsuleType, payload), | |
| fin); | |
| } | |
| void WritePref64Capsule(QuicSpdyStream* stream, const Pref64Capsule& capsule, | |
| bool fin) { | |
| QUICHE_CHECK(stream != nullptr); | |
| std::string payload = SerializePref64CapsulePayload(capsule); | |
| stream->WriteCapsule(quiche::Capsule::Unknown(kPref64CapsuleType, payload), | |
| fin); | |
| } | |
| } // namespace quic |