// Copyright 2020 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/dns/https_record_rdata.h" #include #include #include #include #include #include #include #include "base/big_endian.h" #include "base/check.h" #include "base/containers/contains.h" #include "base/dcheck_is_on.h" #include "base/immediate_crash.h" #include "base/memory/ptr_util.h" #include "base/strings/string_piece.h" #include "net/base/ip_address.h" #include "net/dns/dns_names_util.h" #include "net/dns/public/dns_protocol.h" #include "third_party/abseil-cpp/absl/types/optional.h" namespace net { namespace { bool ReadNextServiceParam(absl::optional last_key, base::BigEndianReader& reader, uint16_t* out_param_key, base::StringPiece* out_param_value) { DCHECK(out_param_key); DCHECK(out_param_value); uint16_t key; if (!reader.ReadU16(&key)) return false; if (last_key.has_value() && last_key.value() >= key) return false; base::StringPiece value; if (!reader.ReadU16LengthPrefixed(&value)) return false; *out_param_key = key; *out_param_value = value; return true; } bool ParseMandatoryKeys(base::StringPiece param_value, std::set* out_parsed) { DCHECK(out_parsed); auto reader = base::BigEndianReader::FromStringPiece(param_value); std::set mandatory_keys; // Do/while to require at least one key. do { uint16_t key; if (!reader.ReadU16(&key)) return false; // Mandatory key itself is disallowed from its list. if (key == dns_protocol::kHttpsServiceParamKeyMandatory) return false; // Keys required to be listed in ascending order. if (!mandatory_keys.empty() && key <= *mandatory_keys.rbegin()) return false; CHECK(mandatory_keys.insert(key).second); } while (reader.remaining() > 0); *out_parsed = std::move(mandatory_keys); return true; } bool ParseAlpnIds(base::StringPiece param_value, std::vector* out_parsed) { DCHECK(out_parsed); auto reader = base::BigEndianReader::FromStringPiece(param_value); std::vector alpn_ids; // Do/while to require at least one ID. do { base::StringPiece alpn_id; if (!reader.ReadU8LengthPrefixed(&alpn_id)) return false; if (alpn_id.size() < 1) return false; DCHECK_LE(alpn_id.size(), 255u); alpn_ids.emplace_back(alpn_id.data(), alpn_id.size()); } while (reader.remaining() > 0); *out_parsed = std::move(alpn_ids); return true; } template bool ParseIpAddresses(base::StringPiece param_value, std::vector* out_addresses) { DCHECK(out_addresses); auto reader = base::BigEndianReader::FromStringPiece(param_value); std::vector addresses; uint8_t addr_bytes[ADDRESS_SIZE]; do { if (!reader.ReadBytes(addr_bytes, ADDRESS_SIZE)) return false; addresses.emplace_back(addr_bytes); DCHECK(addresses.back().IsValid()); } while (reader.remaining() > 0); *out_addresses = std::move(addresses); return true; } } // namespace // static std::unique_ptr HttpsRecordRdata::Parse( base::StringPiece data) { if (!HasValidSize(data, kType)) return nullptr; auto reader = base::BigEndianReader::FromStringPiece(data); uint16_t priority; CHECK(reader.ReadU16(&priority)); if (priority == 0) { return AliasFormHttpsRecordRdata::Parse(data); } return ServiceFormHttpsRecordRdata::Parse(data); } HttpsRecordRdata::~HttpsRecordRdata() = default; bool HttpsRecordRdata::IsEqual(const RecordRdata* other) const { DCHECK(other); if (other->Type() != kType) return false; const HttpsRecordRdata* https = static_cast(other); return IsEqual(https); } uint16_t HttpsRecordRdata::Type() const { return kType; } AliasFormHttpsRecordRdata* HttpsRecordRdata::AsAliasForm() { CHECK(IsAlias()); return static_cast(this); } const AliasFormHttpsRecordRdata* HttpsRecordRdata::AsAliasForm() const { return const_cast(this)->AsAliasForm(); } ServiceFormHttpsRecordRdata* HttpsRecordRdata::AsServiceForm() { CHECK(!IsAlias()); return static_cast(this); } const ServiceFormHttpsRecordRdata* HttpsRecordRdata::AsServiceForm() const { return const_cast(this)->AsServiceForm(); } AliasFormHttpsRecordRdata::AliasFormHttpsRecordRdata(std::string alias_name) : alias_name_(std::move(alias_name)) {} // static std::unique_ptr AliasFormHttpsRecordRdata::Parse( base::StringPiece data) { auto reader = base::BigEndianReader::FromStringPiece(data); uint16_t priority; if (!reader.ReadU16(&priority)) return nullptr; if (priority != 0) return nullptr; absl::optional alias_name = dns_names_util::NetworkToDottedName(reader, true /* require_complete */); if (!alias_name.has_value()) return nullptr; // Ignore any params. absl::optional last_param_key; while (reader.remaining() > 0) { uint16_t param_key; base::StringPiece param_value; if (!ReadNextServiceParam(last_param_key, reader, ¶m_key, ¶m_value)) return nullptr; last_param_key = param_key; } return std::make_unique( std::move(alias_name).value()); } bool AliasFormHttpsRecordRdata::IsEqual(const HttpsRecordRdata* other) const { DCHECK(other); if (!other->IsAlias()) return false; const AliasFormHttpsRecordRdata* alias = other->AsAliasForm(); return alias_name_ == alias->alias_name_; } bool AliasFormHttpsRecordRdata::IsAlias() const { return true; } // static constexpr uint16_t ServiceFormHttpsRecordRdata::kSupportedKeys[]; ServiceFormHttpsRecordRdata::ServiceFormHttpsRecordRdata( HttpsRecordPriority priority, std::string service_name, std::set mandatory_keys, std::vector alpn_ids, bool default_alpn, absl::optional port, std::vector ipv4_hint, std::string ech_config, std::vector ipv6_hint, std::map unparsed_params) : priority_(priority), service_name_(std::move(service_name)), mandatory_keys_(std::move(mandatory_keys)), alpn_ids_(std::move(alpn_ids)), default_alpn_(default_alpn), port_(port), ipv4_hint_(std::move(ipv4_hint)), ech_config_(std::move(ech_config)), ipv6_hint_(std::move(ipv6_hint)), unparsed_params_(std::move(unparsed_params)) { DCHECK_NE(priority_, 0); DCHECK(mandatory_keys_.find(dns_protocol::kHttpsServiceParamKeyMandatory) == mandatory_keys_.end()); #if DCHECK_IS_ON() for (const IPAddress& address : ipv4_hint_) { DCHECK(address.IsIPv4()); } for (const IPAddress& address : ipv6_hint_) { DCHECK(address.IsIPv6()); } for (const auto& unparsed_param : unparsed_params_) { DCHECK(!IsSupportedKey(unparsed_param.first)); } #endif // DCHECK_IS_ON() } ServiceFormHttpsRecordRdata::~ServiceFormHttpsRecordRdata() = default; bool ServiceFormHttpsRecordRdata::IsEqual(const HttpsRecordRdata* other) const { DCHECK(other); if (other->IsAlias()) return false; const ServiceFormHttpsRecordRdata* service = other->AsServiceForm(); return priority_ == service->priority_ && service_name_ == service->service_name_ && mandatory_keys_ == service->mandatory_keys_ && alpn_ids_ == service->alpn_ids_ && default_alpn_ == service->default_alpn_ && port_ == service->port_ && ipv4_hint_ == service->ipv4_hint_ && ech_config_ == service->ech_config_ && ipv6_hint_ == service->ipv6_hint_; } bool ServiceFormHttpsRecordRdata::IsAlias() const { return false; } // static std::unique_ptr ServiceFormHttpsRecordRdata::Parse( base::StringPiece data) { auto reader = base::BigEndianReader::FromStringPiece(data); uint16_t priority; if (!reader.ReadU16(&priority)) return nullptr; if (priority == 0) return nullptr; absl::optional service_name = dns_names_util::NetworkToDottedName(reader, true /* require_complete */); if (!service_name.has_value()) return nullptr; if (reader.remaining() == 0) { return std::make_unique( HttpsRecordPriority{priority}, std::move(service_name).value(), std::set() /* mandatory_keys */, std::vector() /* alpn_ids */, true /* default_alpn */, absl::nullopt /* port */, std::vector() /* ipv4_hint */, std::string() /* ech_config */, std::vector() /* ipv6_hint */, std::map() /* unparsed_params */); } uint16_t param_key = 0; base::StringPiece param_value; if (!ReadNextServiceParam(absl::nullopt /* last_key */, reader, ¶m_key, ¶m_value)) return nullptr; // Assume keys less than Mandatory are not possible. DCHECK_GE(param_key, dns_protocol::kHttpsServiceParamKeyMandatory); std::set mandatory_keys; if (param_key == dns_protocol::kHttpsServiceParamKeyMandatory) { DCHECK(IsSupportedKey(param_key)); if (!ParseMandatoryKeys(param_value, &mandatory_keys)) return nullptr; if (reader.remaining() > 0 && !ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) { return nullptr; } } std::vector alpn_ids; if (param_key == dns_protocol::kHttpsServiceParamKeyAlpn) { DCHECK(IsSupportedKey(param_key)); if (!ParseAlpnIds(param_value, &alpn_ids)) return nullptr; if (reader.remaining() > 0 && !ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) { return nullptr; } } bool default_alpn = true; if (param_key == dns_protocol::kHttpsServiceParamKeyNoDefaultAlpn) { DCHECK(IsSupportedKey(param_key)); if (!param_value.empty()) return nullptr; default_alpn = false; if (reader.remaining() > 0 && !ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) { return nullptr; } } absl::optional port; if (param_key == dns_protocol::kHttpsServiceParamKeyPort) { DCHECK(IsSupportedKey(param_key)); if (param_value.size() != 2) return nullptr; uint16_t port_val; base::ReadBigEndian(reinterpret_cast(param_value.data()), &port_val); port = port_val; if (reader.remaining() > 0 && !ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) { return nullptr; } } std::vector ipv4_hint; if (param_key == dns_protocol::kHttpsServiceParamKeyIpv4Hint) { DCHECK(IsSupportedKey(param_key)); if (!ParseIpAddresses(param_value, &ipv4_hint)) return nullptr; if (reader.remaining() > 0 && !ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) { return nullptr; } } std::string ech_config; if (param_key == dns_protocol::kHttpsServiceParamKeyEchConfig) { DCHECK(IsSupportedKey(param_key)); ech_config = std::string(param_value.data(), param_value.size()); if (reader.remaining() > 0 && !ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) { return nullptr; } } std::vector ipv6_hint; if (param_key == dns_protocol::kHttpsServiceParamKeyIpv6Hint) { DCHECK(IsSupportedKey(param_key)); if (!ParseIpAddresses(param_value, &ipv6_hint)) return nullptr; if (reader.remaining() > 0 && !ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) { return nullptr; } } // Note that if parsing has already reached the end of the rdata, `param_key` // is still set for whatever param was read last. std::map unparsed_params; if (param_key > dns_protocol::kHttpsServiceParamKeyIpv6Hint) { for (;;) { DCHECK(!IsSupportedKey(param_key)); CHECK(unparsed_params .emplace(param_key, static_cast(param_value)) .second); if (reader.remaining() == 0) break; if (!ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) return nullptr; } } return std::make_unique( HttpsRecordPriority{priority}, std::move(service_name).value(), std::move(mandatory_keys), std::move(alpn_ids), default_alpn, port, std::move(ipv4_hint), std::move(ech_config), std::move(ipv6_hint), std::move(unparsed_params)); } bool ServiceFormHttpsRecordRdata::IsCompatible() const { std::set supported_keys(std::begin(kSupportedKeys), std::end(kSupportedKeys)); for (uint16_t mandatory_key : mandatory_keys_) { DCHECK_NE(mandatory_key, dns_protocol::kHttpsServiceParamKeyMandatory); if (supported_keys.find(mandatory_key) == supported_keys.end()) return false; } #if DCHECK_IS_ON() for (const auto& unparsed_param : unparsed_params_) { DCHECK(mandatory_keys_.find(unparsed_param.first) == mandatory_keys_.end()); } #endif // DCHECK_IS_ON() return true; } // static bool ServiceFormHttpsRecordRdata::IsSupportedKey(uint16_t key) { #if DCHECK_IS_ON() return base::Contains(kSupportedKeys, key); #else // Only intended for DCHECKs. base::ImmediateCrash(); #endif // DCHECK_IS_ON() } } // namespace net