| // Copyright (c) 2012 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 "net/dns/dns_config_service_win.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/compiler_specific.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_path_watcher.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/free_deleter.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_handle.h" |
| #include "net/base/ip_address.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/dns/dns_hosts.h" |
| #include "net/dns/dns_protocol.h" |
| #include "net/dns/serial_worker.h" |
| #include "starboard/memory.h" |
| #include "url/url_canon.h" |
| |
| namespace net { |
| |
| namespace internal { |
| |
| namespace { |
| |
| // Interval between retries to parse config. Used only until parsing succeeds. |
| const int kRetryIntervalSeconds = 5; |
| |
| // Registry key paths. |
| const wchar_t* const kTcpipPath = |
| L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"; |
| const wchar_t* const kTcpip6Path = |
| L"SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters"; |
| const wchar_t* const kDnscachePath = |
| L"SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters"; |
| const wchar_t* const kPolicyPath = |
| L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient"; |
| const wchar_t* const kPrimaryDnsSuffixPath = |
| L"SOFTWARE\\Policies\\Microsoft\\System\\DNSClient"; |
| const wchar_t* const kNRPTPath = |
| L"SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\DnsPolicyConfig"; |
| |
| enum HostsParseWinResult { |
| HOSTS_PARSE_WIN_OK = 0, |
| HOSTS_PARSE_WIN_UNREADABLE_HOSTS_FILE, |
| HOSTS_PARSE_WIN_COMPUTER_NAME_FAILED, |
| HOSTS_PARSE_WIN_IPHELPER_FAILED, |
| HOSTS_PARSE_WIN_BAD_ADDRESS, |
| HOSTS_PARSE_WIN_MAX // Bounding values for enumeration. |
| }; |
| |
| // Convenience for reading values using RegKey. |
| class RegistryReader { |
| public: |
| explicit RegistryReader(const wchar_t* key) { |
| // Ignoring the result. |key_.Valid()| will catch failures. |
| key_.Open(HKEY_LOCAL_MACHINE, key, KEY_QUERY_VALUE); |
| } |
| |
| ~RegistryReader() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); } |
| |
| bool ReadString(const wchar_t* name, |
| DnsSystemSettings::RegString* out) const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| out->set = false; |
| if (!key_.Valid()) { |
| // Assume that if the |key_| is invalid then the key is missing. |
| return true; |
| } |
| LONG result = key_.ReadValue(name, &out->value); |
| if (result == ERROR_SUCCESS) { |
| out->set = true; |
| return true; |
| } |
| return (result == ERROR_FILE_NOT_FOUND); |
| } |
| |
| bool ReadDword(const wchar_t* name, |
| DnsSystemSettings::RegDword* out) const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| out->set = false; |
| if (!key_.Valid()) { |
| // Assume that if the |key_| is invalid then the key is missing. |
| return true; |
| } |
| LONG result = key_.ReadValueDW(name, &out->value); |
| if (result == ERROR_SUCCESS) { |
| out->set = true; |
| return true; |
| } |
| return (result == ERROR_FILE_NOT_FOUND); |
| } |
| |
| private: |
| base::win::RegKey key_; |
| |
| THREAD_CHECKER(thread_checker_); |
| |
| DISALLOW_COPY_AND_ASSIGN(RegistryReader); |
| }; |
| |
| // Wrapper for GetAdaptersAddresses. Returns NULL if failed. |
| std::unique_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> ReadIpHelper( |
| ULONG flags) { |
| base::AssertBlockingAllowed(); |
| |
| std::unique_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> out; |
| ULONG len = 15000; // As recommended by MSDN for GetAdaptersAddresses. |
| UINT rv = ERROR_BUFFER_OVERFLOW; |
| // Try up to three times. |
| for (unsigned tries = 0; (tries < 3) && (rv == ERROR_BUFFER_OVERFLOW); |
| tries++) { |
| out.reset(static_cast<PIP_ADAPTER_ADDRESSES>(SbMemoryAllocate(len))); |
| memset(out.get(), 0, len); |
| rv = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, out.get(), &len); |
| } |
| if (rv != NO_ERROR) |
| out.reset(); |
| return out; |
| } |
| |
| // Converts a base::string16 domain name to ASCII, possibly using punycode. |
| // Returns true if the conversion succeeds and output is not empty. In case of |
| // failure, |domain| might become dirty. |
| bool ParseDomainASCII(base::StringPiece16 widestr, std::string* domain) { |
| DCHECK(domain); |
| if (widestr.empty()) |
| return false; |
| |
| // Check if already ASCII. |
| if (base::IsStringASCII(widestr)) { |
| domain->assign(widestr.begin(), widestr.end()); |
| return true; |
| } |
| |
| // Otherwise try to convert it from IDN to punycode. |
| const int kInitialBufferSize = 256; |
| url::RawCanonOutputT<base::char16, kInitialBufferSize> punycode; |
| if (!url::IDNToASCII(widestr.data(), widestr.length(), &punycode)) |
| return false; |
| |
| // |punycode_output| should now be ASCII; convert it to a std::string. |
| // (We could use UTF16ToASCII() instead, but that requires an extra string |
| // copy. Since ASCII is a subset of UTF8 the following is equivalent). |
| bool success = base::UTF16ToUTF8(punycode.data(), punycode.length(), domain); |
| DCHECK(success); |
| DCHECK(base::IsStringASCII(*domain)); |
| return success && !domain->empty(); |
| } |
| |
| bool ReadDevolutionSetting(const RegistryReader& reader, |
| DnsSystemSettings::DevolutionSetting* setting) { |
| return reader.ReadDword(L"UseDomainNameDevolution", &setting->enabled) && |
| reader.ReadDword(L"DomainNameDevolutionLevel", &setting->level); |
| } |
| |
| // Reads DnsSystemSettings from IpHelper and registry. |
| ConfigParseWinResult ReadSystemSettings(DnsSystemSettings* settings) { |
| base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK); |
| settings->addresses = ReadIpHelper(GAA_FLAG_SKIP_ANYCAST | |
| GAA_FLAG_SKIP_UNICAST | |
| GAA_FLAG_SKIP_MULTICAST | |
| GAA_FLAG_SKIP_FRIENDLY_NAME); |
| if (!settings->addresses.get()) |
| return CONFIG_PARSE_WIN_READ_IPHELPER; |
| |
| RegistryReader tcpip_reader(kTcpipPath); |
| RegistryReader tcpip6_reader(kTcpip6Path); |
| RegistryReader dnscache_reader(kDnscachePath); |
| RegistryReader policy_reader(kPolicyPath); |
| RegistryReader primary_dns_suffix_reader(kPrimaryDnsSuffixPath); |
| |
| if (!policy_reader.ReadString(L"SearchList", |
| &settings->policy_search_list)) { |
| return CONFIG_PARSE_WIN_READ_POLICY_SEARCHLIST; |
| } |
| |
| if (!tcpip_reader.ReadString(L"SearchList", &settings->tcpip_search_list)) |
| return CONFIG_PARSE_WIN_READ_TCPIP_SEARCHLIST; |
| |
| if (!tcpip_reader.ReadString(L"Domain", &settings->tcpip_domain)) |
| return CONFIG_PARSE_WIN_READ_DOMAIN; |
| |
| if (!ReadDevolutionSetting(policy_reader, &settings->policy_devolution)) |
| return CONFIG_PARSE_WIN_READ_POLICY_DEVOLUTION; |
| |
| if (!ReadDevolutionSetting(dnscache_reader, &settings->dnscache_devolution)) |
| return CONFIG_PARSE_WIN_READ_DNSCACHE_DEVOLUTION; |
| |
| if (!ReadDevolutionSetting(tcpip_reader, &settings->tcpip_devolution)) |
| return CONFIG_PARSE_WIN_READ_TCPIP_DEVOLUTION; |
| |
| if (!policy_reader.ReadDword(L"AppendToMultiLabelName", |
| &settings->append_to_multi_label_name)) { |
| return CONFIG_PARSE_WIN_READ_APPEND_MULTILABEL; |
| } |
| |
| if (!primary_dns_suffix_reader.ReadString(L"PrimaryDnsSuffix", |
| &settings->primary_dns_suffix)) { |
| return CONFIG_PARSE_WIN_READ_PRIMARY_SUFFIX; |
| } |
| |
| base::win::RegistryKeyIterator nrpt_rules(HKEY_LOCAL_MACHINE, kNRPTPath); |
| settings->have_name_resolution_policy = (nrpt_rules.SubkeyCount() > 0); |
| |
| return CONFIG_PARSE_WIN_OK; |
| } |
| |
| // Default address of "localhost" and local computer name can be overridden |
| // by the HOSTS file, but if it's not there, then we need to fill it in. |
| HostsParseWinResult AddLocalhostEntries(DnsHosts* hosts) { |
| IPAddress loopback_ipv4 = IPAddress::IPv4Localhost(); |
| IPAddress loopback_ipv6 = IPAddress::IPv6Localhost(); |
| |
| // This does not override any pre-existing entries from the HOSTS file. |
| hosts->insert(std::make_pair(DnsHostsKey("localhost", ADDRESS_FAMILY_IPV4), |
| loopback_ipv4)); |
| hosts->insert(std::make_pair(DnsHostsKey("localhost", ADDRESS_FAMILY_IPV6), |
| loopback_ipv6)); |
| |
| WCHAR buffer[MAX_PATH]; |
| DWORD size = MAX_PATH; |
| std::string localname; |
| if (!GetComputerNameExW(ComputerNameDnsHostname, buffer, &size) || |
| !ParseDomainASCII(buffer, &localname)) { |
| return HOSTS_PARSE_WIN_COMPUTER_NAME_FAILED; |
| } |
| localname = base::ToLowerASCII(localname); |
| |
| bool have_ipv4 = |
| hosts->count(DnsHostsKey(localname, ADDRESS_FAMILY_IPV4)) > 0; |
| bool have_ipv6 = |
| hosts->count(DnsHostsKey(localname, ADDRESS_FAMILY_IPV6)) > 0; |
| |
| if (have_ipv4 && have_ipv6) |
| return HOSTS_PARSE_WIN_OK; |
| |
| std::unique_ptr<IP_ADAPTER_ADDRESSES, base::FreeDeleter> addresses = |
| ReadIpHelper(GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER | |
| GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_FRIENDLY_NAME); |
| if (!addresses.get()) |
| return HOSTS_PARSE_WIN_IPHELPER_FAILED; |
| |
| // The order of adapters is the network binding order, so stick to the |
| // first good adapter for each family. |
| for (const IP_ADAPTER_ADDRESSES* adapter = addresses.get(); |
| adapter != NULL && (!have_ipv4 || !have_ipv6); |
| adapter = adapter->Next) { |
| if (adapter->OperStatus != IfOperStatusUp) |
| continue; |
| if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) |
| continue; |
| |
| for (const IP_ADAPTER_UNICAST_ADDRESS* address = |
| adapter->FirstUnicastAddress; |
| address != NULL; |
| address = address->Next) { |
| IPEndPoint ipe; |
| if (!ipe.FromSockAddr(address->Address.lpSockaddr, |
| address->Address.iSockaddrLength)) { |
| return HOSTS_PARSE_WIN_BAD_ADDRESS; |
| } |
| if (!have_ipv4 && (ipe.GetFamily() == ADDRESS_FAMILY_IPV4)) { |
| have_ipv4 = true; |
| (*hosts)[DnsHostsKey(localname, ADDRESS_FAMILY_IPV4)] = ipe.address(); |
| } else if (!have_ipv6 && (ipe.GetFamily() == ADDRESS_FAMILY_IPV6)) { |
| have_ipv6 = true; |
| (*hosts)[DnsHostsKey(localname, ADDRESS_FAMILY_IPV6)] = ipe.address(); |
| } |
| } |
| } |
| return HOSTS_PARSE_WIN_OK; |
| } |
| |
| // Watches a single registry key for changes. |
| class RegistryWatcher { |
| public: |
| typedef base::Callback<void(bool succeeded)> CallbackType; |
| RegistryWatcher() {} |
| |
| ~RegistryWatcher() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); } |
| |
| bool Watch(const wchar_t* key, const CallbackType& callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!callback.is_null()); |
| DCHECK(callback_.is_null()); |
| callback_ = callback; |
| if (key_.Open(HKEY_LOCAL_MACHINE, key, KEY_NOTIFY) != ERROR_SUCCESS) |
| return false; |
| |
| return key_.StartWatching(base::Bind(&RegistryWatcher::OnObjectSignaled, |
| base::Unretained(this))); |
| } |
| |
| void OnObjectSignaled() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!callback_.is_null()); |
| if (key_.StartWatching(base::Bind(&RegistryWatcher::OnObjectSignaled, |
| base::Unretained(this)))) { |
| callback_.Run(true); |
| } else { |
| key_.Close(); |
| callback_.Run(false); |
| } |
| } |
| |
| private: |
| CallbackType callback_; |
| base::win::RegKey key_; |
| |
| THREAD_CHECKER(thread_checker_); |
| |
| DISALLOW_COPY_AND_ASSIGN(RegistryWatcher); |
| }; |
| |
| // Returns true iff |address| is DNS address from IPv6 stateless discovery, |
| // i.e., matches fec0:0:0:ffff::{1,2,3}. |
| // http://tools.ietf.org/html/draft-ietf-ipngwg-dns-discovery |
| bool IsStatelessDiscoveryAddress(const IPAddress& address) { |
| if (!address.IsIPv6()) |
| return false; |
| const uint8_t kPrefix[] = {0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
| return IPAddressStartsWith(address, kPrefix) && (address.bytes().back() < 4); |
| } |
| |
| // Returns the path to the HOSTS file. |
| base::FilePath GetHostsPath() { |
| TCHAR buffer[MAX_PATH]; |
| UINT rc = GetSystemDirectory(buffer, MAX_PATH); |
| DCHECK(0 < rc && rc < MAX_PATH); |
| return base::FilePath(buffer).Append( |
| FILE_PATH_LITERAL("drivers\\etc\\hosts")); |
| } |
| |
| void ConfigureSuffixSearch(const DnsSystemSettings& settings, |
| DnsConfig* config) { |
| // SearchList takes precedence, so check it first. |
| if (settings.policy_search_list.set) { |
| std::vector<std::string> search; |
| if (ParseSearchList(settings.policy_search_list.value, &search)) { |
| config->search.swap(search); |
| return; |
| } |
| // Even if invalid, the policy disables the user-specified setting below. |
| } else if (settings.tcpip_search_list.set) { |
| std::vector<std::string> search; |
| if (ParseSearchList(settings.tcpip_search_list.value, &search)) { |
| config->search.swap(search); |
| return; |
| } |
| } |
| |
| // In absence of explicit search list, suffix search is: |
| // [primary suffix, connection-specific suffix, devolution of primary suffix]. |
| // Primary suffix can be set by policy (primary_dns_suffix) or |
| // user setting (tcpip_domain). |
| // |
| // The policy (primary_dns_suffix) can be edited via Group Policy Editor |
| // (gpedit.msc) at Local Computer Policy => Computer Configuration |
| // => Administrative Template => Network => DNS Client => Primary DNS Suffix. |
| // |
| // The user setting (tcpip_domain) can be configurred at Computer Name in |
| // System Settings |
| std::string primary_suffix; |
| if ((settings.primary_dns_suffix.set && |
| ParseDomainASCII(settings.primary_dns_suffix.value, &primary_suffix)) || |
| (settings.tcpip_domain.set && |
| ParseDomainASCII(settings.tcpip_domain.value, &primary_suffix))) { |
| // Primary suffix goes in front. |
| config->search.insert(config->search.begin(), primary_suffix); |
| } else { |
| return; // No primary suffix, hence no devolution. |
| } |
| |
| // Devolution is determined by precedence: policy > dnscache > tcpip. |
| // |enabled|: UseDomainNameDevolution and |level|: DomainNameDevolutionLevel |
| // are overridden independently. |
| DnsSystemSettings::DevolutionSetting devolution = settings.policy_devolution; |
| |
| if (!devolution.enabled.set) |
| devolution.enabled = settings.dnscache_devolution.enabled; |
| if (!devolution.enabled.set) |
| devolution.enabled = settings.tcpip_devolution.enabled; |
| if (devolution.enabled.set && (devolution.enabled.value == 0)) |
| return; // Devolution disabled. |
| |
| // By default devolution is enabled. |
| |
| if (!devolution.level.set) |
| devolution.level = settings.dnscache_devolution.level; |
| if (!devolution.level.set) |
| devolution.level = settings.tcpip_devolution.level; |
| |
| // After the recent update, Windows will try to determine a safe default |
| // value by comparing the forest root domain (FRD) to the primary suffix. |
| // See http://support.microsoft.com/kb/957579 for details. |
| // For now, if the level is not set, we disable devolution, assuming that |
| // we will fallback to the system getaddrinfo anyway. This might cause |
| // performance loss for resolutions which depend on the system default |
| // devolution setting. |
| // |
| // If the level is explicitly set below 2, devolution is disabled. |
| if (!devolution.level.set || devolution.level.value < 2) |
| return; // Devolution disabled. |
| |
| // Devolve the primary suffix. This naive logic matches the observed |
| // behavior (see also ParseSearchList). If a suffix is not valid, it will be |
| // discarded when the fully-qualified name is converted to DNS format. |
| |
| unsigned num_dots = std::count(primary_suffix.begin(), |
| primary_suffix.end(), '.'); |
| |
| for (size_t offset = 0; num_dots >= devolution.level.value; --num_dots) { |
| offset = primary_suffix.find('.', offset + 1); |
| config->search.push_back(primary_suffix.substr(offset + 1)); |
| } |
| } |
| |
| } // namespace |
| |
| DnsSystemSettings::DnsSystemSettings() |
| : policy_search_list(), |
| tcpip_search_list(), |
| tcpip_domain(), |
| primary_dns_suffix(), |
| policy_devolution(), |
| dnscache_devolution(), |
| tcpip_devolution(), |
| append_to_multi_label_name(), |
| have_name_resolution_policy(false) { |
| policy_search_list.set = false; |
| tcpip_search_list.set = false; |
| tcpip_domain.set = false; |
| primary_dns_suffix.set = false; |
| |
| policy_devolution.enabled.set = false; |
| policy_devolution.level.set = false; |
| dnscache_devolution.enabled.set = false; |
| dnscache_devolution.level.set = false; |
| tcpip_devolution.enabled.set = false; |
| tcpip_devolution.level.set = false; |
| |
| append_to_multi_label_name.set = false; |
| } |
| |
| DnsSystemSettings::~DnsSystemSettings() { |
| } |
| |
| bool ParseSearchList(const base::string16& value, |
| std::vector<std::string>* output) { |
| DCHECK(output); |
| if (value.empty()) |
| return false; |
| |
| output->clear(); |
| |
| // If the list includes an empty hostname (",," or ", ,"), it is terminated. |
| // Although nslookup and network connection property tab ignore such |
| // fragments ("a,b,,c" becomes ["a", "b", "c"]), our reference is getaddrinfo |
| // (which sees ["a", "b"]). WMI queries also return a matching search list. |
| for (const base::StringPiece16& t : base::SplitStringPiece( |
| value, L",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { |
| // Convert non-ASCII to punycode, although getaddrinfo does not properly |
| // handle such suffixes. |
| std::string parsed; |
| if (!ParseDomainASCII(t, &parsed)) |
| break; |
| output->push_back(parsed); |
| } |
| return !output->empty(); |
| } |
| |
| ConfigParseWinResult ConvertSettingsToDnsConfig( |
| const DnsSystemSettings& settings, |
| DnsConfig* config) { |
| *config = DnsConfig(); |
| |
| // Use GetAdapterAddresses to get effective DNS server order and |
| // connection-specific DNS suffix. Ignore disconnected and loopback adapters. |
| // The order of adapters is the network binding order, so stick to the |
| // first good adapter. |
| for (const IP_ADAPTER_ADDRESSES* adapter = settings.addresses.get(); |
| adapter != NULL && config->nameservers.empty(); |
| adapter = adapter->Next) { |
| if (adapter->OperStatus != IfOperStatusUp) |
| continue; |
| if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) |
| continue; |
| |
| for (const IP_ADAPTER_DNS_SERVER_ADDRESS* address = |
| adapter->FirstDnsServerAddress; |
| address != NULL; |
| address = address->Next) { |
| IPEndPoint ipe; |
| if (ipe.FromSockAddr(address->Address.lpSockaddr, |
| address->Address.iSockaddrLength)) { |
| if (IsStatelessDiscoveryAddress(ipe.address())) |
| continue; |
| // Override unset port. |
| if (!ipe.port()) |
| ipe = IPEndPoint(ipe.address(), dns_protocol::kDefaultPort); |
| config->nameservers.push_back(ipe); |
| } else { |
| return CONFIG_PARSE_WIN_BAD_ADDRESS; |
| } |
| } |
| |
| // IP_ADAPTER_ADDRESSES in Vista+ has a search list at |FirstDnsSuffix|, |
| // but it came up empty in all trials. |
| // |DnsSuffix| stores the effective connection-specific suffix, which is |
| // obtained via DHCP (regkey: Tcpip\Parameters\Interfaces\{XXX}\DhcpDomain) |
| // or specified by the user (regkey: Tcpip\Parameters\Domain). |
| std::string dns_suffix; |
| if (ParseDomainASCII(adapter->DnsSuffix, &dns_suffix)) |
| config->search.push_back(dns_suffix); |
| } |
| |
| if (config->nameservers.empty()) |
| return CONFIG_PARSE_WIN_NO_NAMESERVERS; // No point continuing. |
| |
| // Windows always tries a multi-label name "as is" before using suffixes. |
| config->ndots = 1; |
| |
| if (!settings.append_to_multi_label_name.set) { |
| config->append_to_multi_label_name = false; |
| } else { |
| config->append_to_multi_label_name = |
| (settings.append_to_multi_label_name.value != 0); |
| } |
| |
| ConfigParseWinResult result = CONFIG_PARSE_WIN_OK; |
| if (settings.have_name_resolution_policy) { |
| config->unhandled_options = true; |
| // TODO(szym): only set this to true if NRPT has DirectAccess rules. |
| config->use_local_ipv6 = true; |
| result = CONFIG_PARSE_WIN_UNHANDLED_OPTIONS; |
| } |
| |
| ConfigureSuffixSearch(settings, config); |
| return result; |
| } |
| |
| // Watches registry and HOSTS file for changes. Must live on a thread which |
| // allows IO. |
| class DnsConfigServiceWin::Watcher |
| : public NetworkChangeNotifier::IPAddressObserver { |
| public: |
| explicit Watcher(DnsConfigServiceWin* service) : service_(service) {} |
| ~Watcher() override { NetworkChangeNotifier::RemoveIPAddressObserver(this); } |
| |
| bool Watch() { |
| RegistryWatcher::CallbackType callback = |
| base::Bind(&DnsConfigServiceWin::OnConfigChanged, |
| base::Unretained(service_)); |
| |
| bool success = true; |
| |
| // The Tcpip key must be present. |
| if (!tcpip_watcher_.Watch(kTcpipPath, callback)) { |
| LOG(ERROR) << "DNS registry watch failed to start."; |
| success = false; |
| UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", |
| DNS_CONFIG_WATCH_FAILED_TO_START_CONFIG, |
| DNS_CONFIG_WATCH_MAX); |
| } |
| |
| // Watch for IPv6 nameservers. |
| tcpip6_watcher_.Watch(kTcpip6Path, callback); |
| |
| // DNS suffix search list and devolution can be configured via group |
| // policy which sets this registry key. If the key is missing, the policy |
| // does not apply, and the DNS client uses Tcpip and Dnscache settings. |
| // If a policy is installed, DnsConfigService will need to be restarted. |
| // BUG=99509 |
| |
| dnscache_watcher_.Watch(kDnscachePath, callback); |
| policy_watcher_.Watch(kPolicyPath, callback); |
| |
| if (!hosts_watcher_.Watch(GetHostsPath(), false, |
| base::Bind(&Watcher::OnHostsChanged, |
| base::Unretained(this)))) { |
| UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", |
| DNS_CONFIG_WATCH_FAILED_TO_START_HOSTS, |
| DNS_CONFIG_WATCH_MAX); |
| LOG(ERROR) << "DNS hosts watch failed to start."; |
| success = false; |
| } else { |
| // Also need to observe changes to local non-loopback IP for DnsHosts. |
| NetworkChangeNotifier::AddIPAddressObserver(this); |
| } |
| return success; |
| } |
| |
| private: |
| void OnHostsChanged(const base::FilePath& path, bool error) { |
| if (error) |
| NetworkChangeNotifier::RemoveIPAddressObserver(this); |
| service_->OnHostsChanged(!error); |
| } |
| |
| // NetworkChangeNotifier::IPAddressObserver: |
| void OnIPAddressChanged() override { |
| // Need to update non-loopback IP of local host. |
| service_->OnHostsChanged(true); |
| } |
| |
| DnsConfigServiceWin* service_; |
| |
| RegistryWatcher tcpip_watcher_; |
| RegistryWatcher tcpip6_watcher_; |
| RegistryWatcher dnscache_watcher_; |
| RegistryWatcher policy_watcher_; |
| base::FilePathWatcher hosts_watcher_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Watcher); |
| }; |
| |
| // Reads config from registry and IpHelper. All work performed in TaskScheduler. |
| class DnsConfigServiceWin::ConfigReader : public SerialWorker { |
| public: |
| explicit ConfigReader(DnsConfigServiceWin* service) |
| : service_(service), |
| success_(false) {} |
| |
| private: |
| ~ConfigReader() override {} |
| |
| void DoWork() override { |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| DnsSystemSettings settings = {}; |
| ConfigParseWinResult result = ReadSystemSettings(&settings); |
| if (result == CONFIG_PARSE_WIN_OK) |
| result = ConvertSettingsToDnsConfig(settings, &dns_config_); |
| success_ = (result == CONFIG_PARSE_WIN_OK || |
| result == CONFIG_PARSE_WIN_UNHANDLED_OPTIONS); |
| UMA_HISTOGRAM_ENUMERATION("AsyncDNS.ConfigParseWin", |
| result, CONFIG_PARSE_WIN_MAX); |
| UMA_HISTOGRAM_TIMES("AsyncDNS.ConfigParseDuration", |
| base::TimeTicks::Now() - start_time); |
| } |
| |
| void OnWorkFinished() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!IsCancelled()); |
| if (success_) { |
| service_->OnConfigRead(dns_config_); |
| } else { |
| LOG(WARNING) << "Failed to read DnsConfig."; |
| // Try again in a while in case DnsConfigWatcher missed the signal. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::Bind(&ConfigReader::WorkNow, this), |
| base::TimeDelta::FromSeconds(kRetryIntervalSeconds)); |
| } |
| } |
| |
| DnsConfigServiceWin* service_; |
| // Written in DoWork(), read in OnWorkFinished(). No locking required. |
| DnsConfig dns_config_; |
| bool success_; |
| }; |
| |
| // Reads hosts from HOSTS file and fills in localhost and local computer name if |
| // necessary. All work performed in TaskScheduler. |
| class DnsConfigServiceWin::HostsReader : public SerialWorker { |
| public: |
| explicit HostsReader(DnsConfigServiceWin* service) |
| : path_(GetHostsPath()), |
| service_(service), |
| success_(false) { |
| } |
| |
| private: |
| ~HostsReader() override {} |
| |
| void DoWork() override { |
| base::TimeTicks start_time = base::TimeTicks::Now(); |
| base::ScopedBlockingCall scoped_blocking_call( |
| base::BlockingType::MAY_BLOCK); |
| HostsParseWinResult result = HOSTS_PARSE_WIN_UNREADABLE_HOSTS_FILE; |
| if (ParseHostsFile(path_, &hosts_)) |
| result = AddLocalhostEntries(&hosts_); |
| success_ = (result == HOSTS_PARSE_WIN_OK); |
| UMA_HISTOGRAM_ENUMERATION("AsyncDNS.HostsParseWin", |
| result, HOSTS_PARSE_WIN_MAX); |
| UMA_HISTOGRAM_BOOLEAN("AsyncDNS.HostParseResult", success_); |
| UMA_HISTOGRAM_TIMES("AsyncDNS.HostsParseDuration", |
| base::TimeTicks::Now() - start_time); |
| } |
| |
| void OnWorkFinished() override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (success_) { |
| service_->OnHostsRead(hosts_); |
| } else { |
| LOG(WARNING) << "Failed to read DnsHosts."; |
| } |
| } |
| |
| const base::FilePath path_; |
| DnsConfigServiceWin* service_; |
| // Written in DoWork, read in OnWorkFinished, no locking necessary. |
| DnsHosts hosts_; |
| bool success_; |
| |
| DISALLOW_COPY_AND_ASSIGN(HostsReader); |
| }; |
| |
| DnsConfigServiceWin::DnsConfigServiceWin() |
| : config_reader_(new ConfigReader(this)), |
| hosts_reader_(new HostsReader(this)) {} |
| |
| DnsConfigServiceWin::~DnsConfigServiceWin() { |
| config_reader_->Cancel(); |
| hosts_reader_->Cancel(); |
| } |
| |
| void DnsConfigServiceWin::ReadNow() { |
| config_reader_->WorkNow(); |
| hosts_reader_->WorkNow(); |
| } |
| |
| bool DnsConfigServiceWin::StartWatching() { |
| // TODO(szym): re-start watcher if that makes sense. http://crbug.com/116139 |
| watcher_.reset(new Watcher(this)); |
| UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", DNS_CONFIG_WATCH_STARTED, |
| DNS_CONFIG_WATCH_MAX); |
| return watcher_->Watch(); |
| } |
| |
| void DnsConfigServiceWin::OnConfigChanged(bool succeeded) { |
| InvalidateConfig(); |
| config_reader_->WorkNow(); |
| if (!succeeded) { |
| LOG(ERROR) << "DNS config watch failed."; |
| set_watch_failed(true); |
| UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", |
| DNS_CONFIG_WATCH_FAILED_CONFIG, |
| DNS_CONFIG_WATCH_MAX); |
| } |
| } |
| |
| void DnsConfigServiceWin::OnHostsChanged(bool succeeded) { |
| InvalidateHosts(); |
| if (succeeded) { |
| hosts_reader_->WorkNow(); |
| } else { |
| LOG(ERROR) << "DNS hosts watch failed."; |
| set_watch_failed(true); |
| UMA_HISTOGRAM_ENUMERATION("AsyncDNS.WatchStatus", |
| DNS_CONFIG_WATCH_FAILED_HOSTS, |
| DNS_CONFIG_WATCH_MAX); |
| } |
| } |
| |
| } // namespace internal |
| |
| // static |
| std::unique_ptr<DnsConfigService> DnsConfigService::CreateSystemService() { |
| return std::unique_ptr<DnsConfigService>(new internal::DnsConfigServiceWin()); |
| } |
| |
| } // namespace net |