blob: 351a0f91202f116cfad649db7e7e6f8cd91a38ec [file] [edit]
// Copyright 2026 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 <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "quiche/quic/platform/api/quic_expect_bug.h"
#include "quiche/quic/platform/api/quic_ip_address.h"
#include "quiche/quic/platform/api/quic_test.h"
namespace quic {
namespace test {
namespace {
using ::testing::_;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Optional;
TEST(MasqueUtilsTest, DnsAssignCapsuleRoundTrip) {
DnsAssignCapsule original;
DnsConfiguration config1;
DnsNameserver ns1;
ns1.service_priority = 10;
QuicIpAddress ipv4_1;
ASSERT_TRUE(ipv4_1.FromString("192.0.2.33"));
ns1.ipv4_addresses.push_back(ipv4_1);
QuicIpAddress ipv6_1;
ASSERT_TRUE(ipv6_1.FromString("2001:db8::1"));
ns1.ipv6_addresses.push_back(ipv6_1);
ns1.authentication_domain_name.domain_name = "dns.corp.example";
std::optional<std::string> encoded_params =
EncodeSvcParams("alpn=h2,h3\ndohpath=/dns-query{?dns}");
ASSERT_THAT(encoded_params, Optional(_));
ns1.service_parameters = *std::move(encoded_params);
config1.nameservers.push_back(ns1);
DnsDomain int_dom1;
int_dom1.domain_name = "internal.corp.example";
config1.internal_domains.push_back(int_dom1);
DnsDomain search_dom1;
search_dom1.domain_name = "corp.example";
config1.search_domains.push_back(search_dom1);
original.dns_configurations.push_back(config1);
DnsConfiguration config2;
config2.internal_domains.push_back(DnsDomain{""}); // Root domain
original.dns_configurations.push_back(config2);
std::string serialized = SerializeDnsAssignCapsulePayload(original);
ASSERT_FALSE(serialized.empty());
DnsAssignCapsule parsed;
ASSERT_TRUE(ParseDnsAssignCapsulePayload(serialized, &parsed));
EXPECT_EQ(parsed, original);
EXPECT_EQ(
parsed.ToString(),
"DNS_ASSIGN[{nameservers:[(priority:10,ipv4:192.0.2.33,ipv6:2001:db8::1,"
"auth_domain:dns.corp.example,service_params:alpn=h2,h3 dohpath=/dns-"
"query{?dns})],internal_domains:[internal.corp.example],search_domains:["
"corp.example]}{nameservers:[],internal_domains:[/root],search_domains:["
"]}]");
}
TEST(MasqueUtilsTest, DnsAssignCapsuleZeroPriorityIsMalformed) {
DnsAssignCapsule original;
DnsConfiguration config;
DnsNameserver ns;
ns.service_priority = 0; // Priority 0 is forbidden by spec.
QuicIpAddress ipv4;
ASSERT_TRUE(ipv4.FromString("192.0.2.1"));
ns.ipv4_addresses.push_back(ipv4);
config.nameservers.push_back(ns);
original.dns_configurations.push_back(config);
std::string serialized = SerializeDnsAssignCapsulePayload(original);
ASSERT_FALSE(serialized.empty());
DnsAssignCapsule parsed;
EXPECT_FALSE(ParseDnsAssignCapsulePayload(serialized, &parsed));
}
TEST(MasqueUtilsTest, DnsAssignCapsuleTrailingJunkIsMalformed) {
DnsAssignCapsule original;
DnsConfiguration config;
original.dns_configurations.push_back(config);
std::string serialized = SerializeDnsAssignCapsulePayload(original);
serialized += "junk";
DnsAssignCapsule parsed;
EXPECT_FALSE(ParseDnsAssignCapsulePayload(serialized, &parsed));
}
TEST(MasqueUtilsTest, Pref64CapsuleRoundTrip) {
Pref64Capsule original;
Pref64Prefix prefix1;
prefix1.prefix_length = 96;
ASSERT_TRUE(prefix1.prefix.FromString("64:ff9b::"));
original.nat64_prefixes.push_back(prefix1);
Pref64Prefix prefix2;
prefix2.prefix_length = 64;
ASSERT_TRUE(prefix2.prefix.FromString("2001:db8:64::"));
original.nat64_prefixes.push_back(prefix2);
std::string serialized = SerializePref64CapsulePayload(original);
ASSERT_EQ(serialized.size(), 26u); // 2 prefixes * 13 bytes each.
Pref64Capsule parsed;
ASSERT_TRUE(ParsePref64CapsulePayload(serialized, &parsed));
EXPECT_EQ(parsed, original);
EXPECT_EQ(parsed.ToString(), "PREF64[(64:ff9b::/96)(2001:db8:64::/64)]");
}
TEST(MasqueUtilsTest, Pref64CapsuleInvalidLengthIsMalformed) {
Pref64Capsule original;
Pref64Prefix prefix;
prefix.prefix_length = 96;
ASSERT_TRUE(prefix.prefix.FromString("64:ff9b::"));
original.nat64_prefixes.push_back(prefix);
std::string serialized = SerializePref64CapsulePayload(original);
// Remove one byte so length is not a multiple of 13.
serialized.pop_back();
Pref64Capsule parsed;
EXPECT_FALSE(ParsePref64CapsulePayload(serialized, &parsed));
}
TEST(MasqueUtilsTest, Pref64CapsuleInvalidPrefixLengthIsMalformed) {
Pref64Capsule original;
Pref64Prefix prefix;
prefix.prefix_length = 60; // 60 is not in {32, 40, 48, 56, 64, 96}.
ASSERT_TRUE(prefix.prefix.FromString("64:ff9b::"));
original.nat64_prefixes.push_back(prefix);
std::string serialized = SerializePref64CapsulePayload(original);
ASSERT_EQ(serialized.size(), 13u);
Pref64Capsule parsed;
EXPECT_FALSE(ParsePref64CapsulePayload(serialized, &parsed));
}
TEST(MasqueUtilsTest, SvcParamsRoundTrip) {
EXPECT_THAT(EncodeSvcParams(""), Optional(Eq("")));
EXPECT_THAT(DecodeSvcParams(""), Optional(Eq("")));
std::optional<std::string> encoded1 =
EncodeSvcParams("alpn=h2,h3 dohpath=/dns-query{?dns}");
ASSERT_THAT(encoded1, Optional(Not(IsEmpty())));
EXPECT_THAT(DecodeSvcParams(*encoded1),
Optional(Eq("alpn=h2,h3 dohpath=/dns-query{?dns}")));
// Test out-of-order presentation strings are sorted by SvcParamKey in wire
// format.
std::optional<std::string> encoded2 =
EncodeSvcParams("dohpath=/dns-query{?dns} alpn=h2,h3");
ASSERT_THAT(encoded2, Optional(Not(IsEmpty())));
EXPECT_EQ(*encoded1, *encoded2);
// Test port, no-default-alpn, and ip hints.
std::optional<std::string> encoded3 = EncodeSvcParams(
"port=8080 no-default-alpn ipv4hint=192.0.2.1,10.0.0.1 "
"ipv6hint=2001:db8::1");
ASSERT_THAT(encoded3, Optional(Not(IsEmpty())));
EXPECT_THAT(
DecodeSvcParams(*encoded3),
Optional(Eq("no-default-alpn port=8080 ipv4hint=192.0.2.1,10.0.0.1 "
"ipv6hint=2001:db8::1")));
// Test malformed presentation format.
EXPECT_EQ(EncodeSvcParams("unknown_key_format"), std::nullopt);
EXPECT_EQ(EncodeSvcParams("port=not_an_int"), std::nullopt);
EXPECT_EQ(EncodeSvcParams("ipv4hint=not_an_ip"), std::nullopt);
EXPECT_EQ(EncodeSvcParams("alpn=h2 alpn=h3"), std::nullopt); // Duplicate key
// Test malformed wire format (out-of-order keys).
char bad_wire_bytes[] = {0x00, 0x07, 0x00, 0x01, 'x', 0x00,
0x01, 0x00, 0x02, 'h', '2'};
EXPECT_EQ(
DecodeSvcParams(std::string(bad_wire_bytes, sizeof(bad_wire_bytes))),
std::nullopt);
}
TEST(MasqueUtilsTest, DnsAssignCapsuleMalformedSvcParams) {
DnsAssignCapsule original;
DnsConfiguration config;
DnsNameserver ns;
ns.service_priority = 10;
QuicIpAddress ipv4;
ASSERT_TRUE(ipv4.FromString("192.0.2.1"));
ns.ipv4_addresses.push_back(ipv4);
char bad_wire_bytes[] = {0x00, 0x07, 0x00, 0x01, 'x', 0x00,
0x01, 0x00, 0x02, 'h', '2'};
ns.service_parameters = std::string(bad_wire_bytes, sizeof(bad_wire_bytes));
config.nameservers.push_back(ns);
original.dns_configurations.push_back(config);
std::string serialized;
EXPECT_QUIC_BUG(serialized = SerializeDnsAssignCapsulePayload(original),
"Invalid service parameters wire format");
EXPECT_TRUE(serialized.empty());
}
TEST(MasqueUtilsTest, NullPointerParsing) {
EXPECT_FALSE(ParseDnsAssignCapsulePayload("any", nullptr));
EXPECT_FALSE(ParsePref64CapsulePayload("any", nullptr));
}
} // namespace
} // namespace test
} // namespace quic