blob: 8f865528254bb4317a35694cbd48a2a53b468b38 [file]
// 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