| // Copyright 2018 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/proxy_resolution/pac_library.h" |
| #include "net/base/address_list.h" |
| #include "net/base/ip_address.h" |
| #include "net/base/network_interfaces.h" |
| #include "net/dns/host_resolver_proc.h" |
| #include "net/socket/client_socket_factory.h" |
| #include "net/socket/udp_client_socket.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| enum class Mode { |
| kMyIpAddress, |
| kMyIpAddressEx, |
| }; |
| |
| // Helper used to accumulate and select the best candidate IP addresses. |
| // |
| // myIpAddress() is a broken API available to PAC scripts. |
| // It has the problematic definition of: |
| // "Returns the IP address of the host machine." |
| // |
| // This has ambiguity on what should happen for multi-homed hosts which may have |
| // multiple IP addresses to choose from. To be unambiguous we would need to |
| // know which hosts is going to be connected to, in order to use the outgoing |
| // IP for that request. |
| // |
| // However at this point that is not known, as the proxy still hasn't been |
| // decided. |
| // |
| // The strategy used here is to prioritize the IP address that would be used |
| // for connecting to the public internet by testing which interface is used for |
| // connecting to 8.8.8.8 and 2001:4860:4860::8888 (public IPs). |
| // |
| // If that fails, we will try resolving the machine's hostname, and also probing |
| // for routes in the private IP space. |
| // |
| // Link-local IP addresses are not generally returned, however may be if no |
| // other IP was found by the probes. |
| class MyIpAddressImpl { |
| public: |
| MyIpAddressImpl() = default; |
| |
| // Used for mocking the socket dependency. |
| void SetSocketFactoryForTest(ClientSocketFactory* socket_factory) { |
| override_socket_factory_ = socket_factory; |
| } |
| |
| // Used for mocking the DNS dependency. |
| void SetDNSResultForTest(const AddressList& addrs) { |
| override_dns_result_ = std::make_unique<AddressList>(addrs); |
| } |
| |
| IPAddressList Run(Mode mode) { |
| DCHECK(candidate_ips_.empty()); |
| DCHECK(link_local_ips_.empty()); |
| DCHECK(!done_); |
| |
| mode_ = mode; |
| |
| // Try several different methods to obtain IP addresses. |
| TestPublicInternetRoutes(); |
| TestResolvingHostname(); |
| TestPrivateIPRoutes(); |
| |
| return mode_ == Mode::kMyIpAddress ? GetResultForMyIpAddress() |
| : GetResultForMyIpAddressEx(); |
| } |
| |
| private: |
| // Adds |address| to the result. |
| void Add(const IPAddress& address) { |
| if (done_) |
| return; |
| |
| // Don't consider loopback addresses (ex: 127.0.0.1). These can notably be |
| // returned when probing addresses associated with the hostname. |
| if (address.IsLoopback()) |
| return; |
| |
| if (!seen_ips_.insert(address).second) |
| return; // Duplicate IP address. |
| |
| // Link-local addresses are only used as a last-resort if there are no |
| // better addresses. |
| if (address.IsLinkLocal()) { |
| link_local_ips_.push_back(address); |
| return; |
| } |
| |
| // For legacy reasons IPv4 addresses are favored over IPv6 for myIpAddress() |
| // - https://crbug.com/905126 - so this only stops the search when a IPv4 |
| // address is found. |
| if ((mode_ == Mode::kMyIpAddress) && address.IsIPv4()) |
| done_ = true; |
| |
| candidate_ips_.push_back(address); |
| } |
| |
| IPAddressList GetResultForMyIpAddress() const { |
| DCHECK_EQ(Mode::kMyIpAddress, mode_); |
| |
| if (!candidate_ips_.empty()) |
| return GetSingleResultFavoringIPv4(candidate_ips_); |
| |
| if (!link_local_ips_.empty()) |
| return GetSingleResultFavoringIPv4(link_local_ips_); |
| |
| return {}; |
| } |
| |
| IPAddressList GetResultForMyIpAddressEx() const { |
| DCHECK_EQ(Mode::kMyIpAddressEx, mode_); |
| |
| if (!candidate_ips_.empty()) |
| return candidate_ips_; |
| |
| if (!link_local_ips_.empty()) { |
| // Note that only a single link-local address is returned here, even |
| // though multiple could be returned for this API. See |
| // http://crbug.com/905366 before expanding this. |
| return GetSingleResultFavoringIPv4(link_local_ips_); |
| } |
| |
| return {}; |
| } |
| |
| // Tests what source IP address would be used for sending a UDP packet to the |
| // given destination IP. This does not hit the network and should be fast. |
| void TestRoute(const IPAddress& destination_ip) { |
| if (done_) |
| return; |
| |
| ClientSocketFactory* socket_factory = |
| override_socket_factory_ |
| ? override_socket_factory_ |
| : net::ClientSocketFactory::GetDefaultFactory(); |
| |
| auto socket = socket_factory->CreateDatagramClientSocket( |
| net::DatagramSocket::DEFAULT_BIND, nullptr, net::NetLogSource()); |
| |
| IPEndPoint destination(destination_ip, /*port=*/80); |
| |
| if (socket->Connect(destination) != OK) |
| return; |
| |
| IPEndPoint source; |
| if (socket->GetLocalAddress(&source) != OK) |
| return; |
| |
| Add(source.address()); |
| } |
| |
| void TestPublicInternetRoutes() { |
| if (done_) |
| return; |
| |
| // 8.8.8.8 and 2001:4860:4860::8888 are Google DNS. |
| TestRoute(IPAddress(8, 8, 8, 8)); |
| TestRoute(IPAddress(0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0x88, 0x88)); |
| |
| MarkAsDoneIfHaveCandidates(); |
| } |
| |
| // Marks the current search as done if candidate IPs have been found. |
| // |
| // This is used to stop exploring for IPs if any of the high-level tests find |
| // a match (i.e. either the public internet route test, or hostname test, or |
| // private route test found something). |
| // |
| // In the case of myIpAddressEx() this means it will be conservative in which |
| // IPs it returns and not enumerate the full set. See http://crbug.com/905366 |
| // before expanding that policy. |
| void MarkAsDoneIfHaveCandidates() { |
| if (!candidate_ips_.empty()) |
| done_ = true; |
| } |
| |
| void TestPrivateIPRoutes() { |
| if (done_) |
| return; |
| |
| // Representative IP from each range in RFC 1918. |
| TestRoute(IPAddress(10, 0, 0, 0)); |
| TestRoute(IPAddress(172, 16, 0, 0)); |
| TestRoute(IPAddress(192, 168, 0, 0)); |
| |
| // Representative IP for Unique Local Address (FC00::/7). |
| TestRoute(IPAddress(0xfc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); |
| |
| MarkAsDoneIfHaveCandidates(); |
| } |
| |
| void TestResolvingHostname() { |
| if (done_) |
| return; |
| |
| AddressList addrlist; |
| |
| int resolver_error; |
| |
| if (override_dns_result_) { |
| addrlist = *override_dns_result_; |
| resolver_error = addrlist.empty() ? ERR_NAME_NOT_RESOLVED : OK; |
| } else { |
| resolver_error = SystemHostResolverCall( |
| GetHostName(), AddressFamily::ADDRESS_FAMILY_UNSPECIFIED, 0, |
| &addrlist, |
| /*os_error=*/nullptr); |
| } |
| |
| if (resolver_error != OK) |
| return; |
| |
| for (const auto& e : addrlist.endpoints()) |
| Add(e.address()); |
| |
| MarkAsDoneIfHaveCandidates(); |
| } |
| |
| static IPAddressList GetSingleResultFavoringIPv4(const IPAddressList& ips) { |
| for (const auto& ip : ips) { |
| if (ip.IsIPv4()) |
| return {ip}; |
| } |
| |
| if (!ips.empty()) |
| return {ips.front()}; |
| |
| return {}; |
| } |
| |
| std::set<IPAddress> seen_ips_; |
| |
| // The preferred ordered candidate IPs so far. |
| IPAddressList candidate_ips_; |
| |
| // The link-local IP addresses seen so far (not part of |candidate_ips_|). |
| IPAddressList link_local_ips_; |
| |
| // The operation being carried out. |
| Mode mode_; |
| |
| // Whether the search for results has completed. |
| // |
| // Once "done", calling Add() will not change the final result. This is used |
| // to short-circuit early. |
| bool done_ = false; |
| |
| ClientSocketFactory* override_socket_factory_ = nullptr; |
| std::unique_ptr<AddressList> override_dns_result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MyIpAddressImpl); |
| }; |
| |
| } // namespace |
| |
| IPAddressList PacMyIpAddress() { |
| MyIpAddressImpl impl; |
| return impl.Run(Mode::kMyIpAddress); |
| } |
| |
| IPAddressList PacMyIpAddressEx() { |
| MyIpAddressImpl impl; |
| return impl.Run(Mode::kMyIpAddressEx); |
| } |
| |
| IPAddressList PacMyIpAddressForTest(ClientSocketFactory* socket_factory, |
| const AddressList& dns_result) { |
| MyIpAddressImpl impl; |
| impl.SetSocketFactoryForTest(socket_factory); |
| impl.SetDNSResultForTest(dns_result); |
| return impl.Run(Mode::kMyIpAddress); |
| } |
| |
| IPAddressList PacMyIpAddressExForTest(ClientSocketFactory* socket_factory, |
| const AddressList& dns_result) { |
| MyIpAddressImpl impl; |
| impl.SetSocketFactoryForTest(socket_factory); |
| impl.SetDNSResultForTest(dns_result); |
| return impl.Run(Mode::kMyIpAddressEx); |
| } |
| |
| } // namespace net |