|  | // 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 <stdint.h> | 
|  |  | 
|  | #include <map> | 
|  | #include <memory> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #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<uint16_t> 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<uint16_t>* out_parsed) { | 
|  | DCHECK(out_parsed); | 
|  |  | 
|  | auto reader = base::BigEndianReader::FromStringPiece(param_value); | 
|  |  | 
|  | std::set<uint16_t> 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<std::string>* out_parsed) { | 
|  | DCHECK(out_parsed); | 
|  |  | 
|  | auto reader = base::BigEndianReader::FromStringPiece(param_value); | 
|  |  | 
|  | std::vector<std::string> 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 <size_t ADDRESS_SIZE> | 
|  | bool ParseIpAddresses(base::StringPiece param_value, | 
|  | std::vector<IPAddress>* out_addresses) { | 
|  | DCHECK(out_addresses); | 
|  |  | 
|  | auto reader = base::BigEndianReader::FromStringPiece(param_value); | 
|  |  | 
|  | std::vector<IPAddress> 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> 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<const HttpsRecordRdata*>(other); | 
|  | return IsEqual(https); | 
|  | } | 
|  |  | 
|  | uint16_t HttpsRecordRdata::Type() const { | 
|  | return kType; | 
|  | } | 
|  |  | 
|  | AliasFormHttpsRecordRdata* HttpsRecordRdata::AsAliasForm() { | 
|  | CHECK(IsAlias()); | 
|  | return static_cast<AliasFormHttpsRecordRdata*>(this); | 
|  | } | 
|  |  | 
|  | const AliasFormHttpsRecordRdata* HttpsRecordRdata::AsAliasForm() const { | 
|  | return const_cast<HttpsRecordRdata*>(this)->AsAliasForm(); | 
|  | } | 
|  |  | 
|  | ServiceFormHttpsRecordRdata* HttpsRecordRdata::AsServiceForm() { | 
|  | CHECK(!IsAlias()); | 
|  | return static_cast<ServiceFormHttpsRecordRdata*>(this); | 
|  | } | 
|  |  | 
|  | const ServiceFormHttpsRecordRdata* HttpsRecordRdata::AsServiceForm() const { | 
|  | return const_cast<HttpsRecordRdata*>(this)->AsServiceForm(); | 
|  | } | 
|  |  | 
|  | AliasFormHttpsRecordRdata::AliasFormHttpsRecordRdata(std::string alias_name) | 
|  | : alias_name_(std::move(alias_name)) {} | 
|  |  | 
|  | // static | 
|  | std::unique_ptr<AliasFormHttpsRecordRdata> 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<std::string> alias_name = | 
|  | dns_names_util::NetworkToDottedName(reader, true /* require_complete */); | 
|  | if (!alias_name.has_value()) | 
|  | return nullptr; | 
|  |  | 
|  | // Ignore any params. | 
|  | absl::optional<uint16_t> 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<AliasFormHttpsRecordRdata>( | 
|  | 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<uint16_t> mandatory_keys, | 
|  | std::vector<std::string> alpn_ids, | 
|  | bool default_alpn, | 
|  | absl::optional<uint16_t> port, | 
|  | std::vector<IPAddress> ipv4_hint, | 
|  | std::string ech_config, | 
|  | std::vector<IPAddress> ipv6_hint, | 
|  | std::map<uint16_t, std::string> 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> 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<std::string> 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<ServiceFormHttpsRecordRdata>( | 
|  | HttpsRecordPriority{priority}, std::move(service_name).value(), | 
|  | std::set<uint16_t>() /* mandatory_keys */, | 
|  | std::vector<std::string>() /* alpn_ids */, true /* default_alpn */, | 
|  | absl::nullopt /* port */, std::vector<IPAddress>() /* ipv4_hint */, | 
|  | std::string() /* ech_config */, | 
|  | std::vector<IPAddress>() /* ipv6_hint */, | 
|  | std::map<uint16_t, std::string>() /* 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<uint16_t> 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<std::string> 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<uint16_t> 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<const uint8_t*>(param_value.data()), | 
|  | &port_val); | 
|  | port = port_val; | 
|  | if (reader.remaining() > 0 && | 
|  | !ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) { | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | std::vector<IPAddress> ipv4_hint; | 
|  | if (param_key == dns_protocol::kHttpsServiceParamKeyIpv4Hint) { | 
|  | DCHECK(IsSupportedKey(param_key)); | 
|  | if (!ParseIpAddresses<IPAddress::kIPv4AddressSize>(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<IPAddress> ipv6_hint; | 
|  | if (param_key == dns_protocol::kHttpsServiceParamKeyIpv6Hint) { | 
|  | DCHECK(IsSupportedKey(param_key)); | 
|  | if (!ParseIpAddresses<IPAddress::kIPv6AddressSize>(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<uint16_t, std::string> unparsed_params; | 
|  | if (param_key > dns_protocol::kHttpsServiceParamKeyIpv6Hint) { | 
|  | for (;;) { | 
|  | DCHECK(!IsSupportedKey(param_key)); | 
|  | CHECK(unparsed_params | 
|  | .emplace(param_key, static_cast<std::string>(param_value)) | 
|  | .second); | 
|  | if (reader.remaining() == 0) | 
|  | break; | 
|  | if (!ReadNextServiceParam(param_key, reader, ¶m_key, ¶m_value)) | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | return std::make_unique<ServiceFormHttpsRecordRdata>( | 
|  | 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<uint16_t> 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 |