// Copyright 2023 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/loopback_only.h"

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h"
#include "net/base/network_change_notifier.h"
#include "net/base/network_interfaces.h"
#include "net/base/sys_addrinfo.h"

#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#include <net/if.h>
#if BUILDFLAG(IS_ANDROID)
#include "net/android/network_library.h"
#else  // BUILDFLAG(IS_ANDROID)
#include <ifaddrs.h>
#endif  // BUILDFLAG(IS_ANDROID)
#endif  // BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)

#if BUILDFLAG(IS_LINUX)
#include <linux/rtnetlink.h>
#include "net/base/address_map_linux.h"
#include "net/base/address_tracker_linux.h"
#include "net/base/network_interfaces_linux.h"
#endif

namespace net {

namespace {

#if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID)) || BUILDFLAG(IS_FUCHSIA)
bool HaveOnlyLoopbackAddressesUsingGetifaddrs() {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);
  struct ifaddrs* interface_addr = nullptr;
  int rv = getifaddrs(&interface_addr);
  if (rv != 0) {
    DVPLOG(1) << "getifaddrs() failed";
    return false;
  }

  bool result = true;
  for (struct ifaddrs* interface = interface_addr; interface != nullptr;
       interface = interface->ifa_next) {
    if (!(IFF_UP & interface->ifa_flags)) {
      continue;
    }
    if (IFF_LOOPBACK & interface->ifa_flags) {
      continue;
    }
    const struct sockaddr* addr = interface->ifa_addr;
    if (!addr) {
      continue;
    }
    if (addr->sa_family == AF_INET6) {
      // Safe cast since this is AF_INET6.
      const struct sockaddr_in6* addr_in6 =
          reinterpret_cast<const struct sockaddr_in6*>(addr);
      const struct in6_addr* sin6_addr = &addr_in6->sin6_addr;
      if (IN6_IS_ADDR_LOOPBACK(sin6_addr) || IN6_IS_ADDR_LINKLOCAL(sin6_addr)) {
        continue;
      }
    }
    if (addr->sa_family != AF_INET6 && addr->sa_family != AF_INET) {
      continue;
    }

    result = false;
    break;
  }
  freeifaddrs(interface_addr);
  return result;
}
#endif  // (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID)) ||
        // BUILDFLAG(IS_FUCHSIA)

// This implementation will always be posted to a thread pool.
bool HaveOnlyLoopbackAddressesSlow() {
#if BUILDFLAG(IS_WIN)
  // TODO(wtc): implement with the GetAdaptersAddresses function.
  NOTIMPLEMENTED();
  return false;
#elif BUILDFLAG(IS_ANDROID)
  return android::HaveOnlyLoopbackAddresses();
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
  return HaveOnlyLoopbackAddressesUsingGetifaddrs();
#endif  // defined(various platforms)
}

#if BUILDFLAG(IS_LINUX)
// This implementation can run on the main thread as it will not block.
bool HaveOnlyLoopbackAddressesFast(AddressMapOwnerLinux* address_map_owner) {
  // The AddressMapOwnerLinux has already cached all the information necessary
  // to determine if only loopback addresses exist.
  AddressMapOwnerLinux::AddressMap address_map =
      address_map_owner->GetAddressMap();
  std::unordered_set<int> online_links = address_map_owner->GetOnlineLinks();
  for (const auto& [address, ifaddrmsg] : address_map) {
    // If there is an online link that isn't loopback or IPv6 link-local, return
    // false.
    // `online_links` shouldn't ever contain a loopback address, but keep the
    // check as it is clearer and harmless.
    //
    // NOTE(2023-05-26): `online_links` only contains links with *both*
    // IFF_LOWER_UP and IFF_UP, which is stricter than the
    // HaveOnlyLoopbackAddressesUsingGetifaddrs() check above. LOWER_UP means
    // the physical link layer is up and IFF_UP means the interface is
    // administratively up. This new behavior might even be desirable, but if
    // this causes issues it will need to be reverted.
    if (online_links.contains(ifaddrmsg.ifa_index) && !address.IsLoopback() &&
        !(address.IsIPv6() && address.IsLinkLocal())) {
      return false;
    }
  }

  return true;
}
#endif  // BUILDFLAG(IS_LINUX)

}  // namespace

void RunHaveOnlyLoopbackAddressesJob(
    base::OnceCallback<void(bool)> finished_cb) {
#if BUILDFLAG(IS_LINUX)
  // On Linux, this check can be fast if it accesses only network information
  // that's cached by NetworkChangeNotifier, so there's no need to post this
  // task to a thread pool. If HaveOnlyLoopbackAddressesFast() *is* posted to a
  // different thread, it can cause a TSAN error when also setting a mock
  // NetworkChangeNotifier in tests. So it's important to not run off the main
  // thread if using cached, global information.
  AddressMapOwnerLinux* address_map_owner =
      NetworkChangeNotifier::GetAddressMapOwner();
  if (address_map_owner) {
    // Post `finished_cb` to avoid the bug-prone sometimes-synchronous behavior,
    // which is only useful in latency-sensitive situations.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(finished_cb),
                       HaveOnlyLoopbackAddressesFast(address_map_owner)));
    return;
  }
#endif  // BUILDFLAG(IS_LINUX)

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(&HaveOnlyLoopbackAddressesSlow), std::move(finished_cb));
}

}  // namespace net
