| // Copyright 2012 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/base/address_tracker_linux.h" |
| |
| #include <errno.h> |
| #include <linux/if.h> |
| #include <stdint.h> |
| #include <sys/ioctl.h> |
| #include <vector> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/dcheck_is_on.h" |
| #include "base/files/scoped_file.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/page_size.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/sequence_checker.h" |
| #include "base/task/current_thread.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "net/base/network_interfaces_linux.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "base/android/build_info.h" |
| #endif |
| |
| namespace net::internal { |
| |
| namespace { |
| |
| // Some kernel functions such as wireless_send_event and rtnetlink_ifinfo_prep |
| // may send spurious messages over rtnetlink. RTM_NEWLINK messages where |
| // ifi_change == 0 and rta_type == IFLA_WIRELESS should be ignored. |
| bool IgnoreWirelessChange(const struct ifinfomsg* msg, int length) { |
| for (const struct rtattr* attr = IFLA_RTA(msg); RTA_OK(attr, length); |
| attr = RTA_NEXT(attr, length)) { |
| if (attr->rta_type == IFLA_WIRELESS && msg->ifi_change == 0) |
| return true; |
| } |
| return false; |
| } |
| |
| // Retrieves address from NETLINK address message. |
| // Sets |really_deprecated| for IPv6 addresses with preferred lifetimes of 0. |
| // Precondition: |header| must already be validated with NLMSG_OK. |
| bool GetAddress(const struct nlmsghdr* header, |
| int header_length, |
| IPAddress* out, |
| bool* really_deprecated) { |
| if (really_deprecated) |
| *really_deprecated = false; |
| |
| // Extract the message and update |header_length| to be the number of |
| // remaining bytes. |
| const struct ifaddrmsg* msg = |
| reinterpret_cast<const struct ifaddrmsg*>(NLMSG_DATA(header)); |
| header_length -= NLMSG_HDRLEN; |
| |
| size_t address_length = 0; |
| switch (msg->ifa_family) { |
| case AF_INET: |
| address_length = IPAddress::kIPv4AddressSize; |
| break; |
| case AF_INET6: |
| address_length = IPAddress::kIPv6AddressSize; |
| break; |
| default: |
| // Unknown family. |
| return false; |
| } |
| // Use IFA_ADDRESS unless IFA_LOCAL is present. This behavior here is based on |
| // getaddrinfo in glibc (check_pf.c). Judging from kernel implementation of |
| // NETLINK, IPv4 addresses have only the IFA_ADDRESS attribute, while IPv6 |
| // have the IFA_LOCAL attribute. |
| uint8_t* address = nullptr; |
| uint8_t* local = nullptr; |
| int length = IFA_PAYLOAD(header); |
| if (length > header_length) { |
| LOG(ERROR) << "ifaddrmsg length exceeds bounds"; |
| return false; |
| } |
| for (const struct rtattr* attr = |
| reinterpret_cast<const struct rtattr*>(IFA_RTA(msg)); |
| RTA_OK(attr, length); attr = RTA_NEXT(attr, length)) { |
| switch (attr->rta_type) { |
| case IFA_ADDRESS: |
| if (RTA_PAYLOAD(attr) < address_length) { |
| LOG(ERROR) << "attr does not have enough bytes to read an address"; |
| return false; |
| } |
| address = reinterpret_cast<uint8_t*>(RTA_DATA(attr)); |
| break; |
| case IFA_LOCAL: |
| if (RTA_PAYLOAD(attr) < address_length) { |
| LOG(ERROR) << "attr does not have enough bytes to read an address"; |
| return false; |
| } |
| local = reinterpret_cast<uint8_t*>(RTA_DATA(attr)); |
| break; |
| case IFA_CACHEINFO: { |
| if (RTA_PAYLOAD(attr) < sizeof(struct ifa_cacheinfo)) { |
| LOG(ERROR) |
| << "attr does not have enough bytes to read an ifa_cacheinfo"; |
| return false; |
| } |
| const struct ifa_cacheinfo* cache_info = |
| reinterpret_cast<const struct ifa_cacheinfo*>(RTA_DATA(attr)); |
| if (really_deprecated) |
| *really_deprecated = (cache_info->ifa_prefered == 0); |
| } break; |
| default: |
| break; |
| } |
| } |
| if (local) |
| address = local; |
| if (!address) |
| return false; |
| *out = IPAddress(address, address_length); |
| return true; |
| } |
| |
| // SafelyCastNetlinkMsgData<T> performs a bounds check before casting |header|'s |
| // data to a |T*|. When the bounds check fails, returns nullptr. |
| template <typename T> |
| T* SafelyCastNetlinkMsgData(const struct nlmsghdr* header, int length) { |
| DCHECK(NLMSG_OK(header, static_cast<__u32>(length))); |
| if (length <= 0 || static_cast<size_t>(length) < NLMSG_HDRLEN + sizeof(T)) |
| return nullptr; |
| return reinterpret_cast<const T*>(NLMSG_DATA(header)); |
| } |
| |
| } // namespace |
| |
| // static |
| char* AddressTrackerLinux::GetInterfaceName(int interface_index, char* buf) { |
| memset(buf, 0, IFNAMSIZ); |
| base::ScopedFD ioctl_socket = GetSocketForIoctl(); |
| if (!ioctl_socket.is_valid()) |
| return buf; |
| |
| struct ifreq ifr = {}; |
| ifr.ifr_ifindex = interface_index; |
| |
| if (ioctl(ioctl_socket.get(), SIOCGIFNAME, &ifr) == 0) |
| strncpy(buf, ifr.ifr_name, IFNAMSIZ - 1); |
| return buf; |
| } |
| |
| AddressTrackerLinux::AddressTrackerLinux() |
| : get_interface_name_(GetInterfaceName), |
| address_callback_(base::DoNothing()), |
| link_callback_(base::DoNothing()), |
| tunnel_callback_(base::DoNothing()), |
| ignored_interfaces_(), |
| connection_type_initialized_cv_(&connection_type_lock_), |
| tracking_(false) {} |
| |
| AddressTrackerLinux::AddressTrackerLinux( |
| const base::RepeatingClosure& address_callback, |
| const base::RepeatingClosure& link_callback, |
| const base::RepeatingClosure& tunnel_callback, |
| const std::unordered_set<std::string>& ignored_interfaces, |
| scoped_refptr<base::SequencedTaskRunner> blocking_thread_runner) |
| : get_interface_name_(GetInterfaceName), |
| address_callback_(address_callback), |
| link_callback_(link_callback), |
| tunnel_callback_(tunnel_callback), |
| ignored_interfaces_(ignored_interfaces), |
| connection_type_initialized_cv_(&connection_type_lock_), |
| tracking_(true), |
| sequenced_task_runner_(std::move(blocking_thread_runner)) { |
| DCHECK(!address_callback.is_null()); |
| DCHECK(!link_callback.is_null()); |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| AddressTrackerLinux::~AddressTrackerLinux() = default; |
| |
| void AddressTrackerLinux::InitWithFdForTesting(base::ScopedFD fd) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| netlink_fd_ = std::move(fd); |
| DumpInitialAddressesAndWatch(); |
| } |
| |
| void AddressTrackerLinux::Init() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| #if BUILDFLAG(IS_ANDROID) |
| // RTM_GETLINK stopped working in Android 11 (see |
| // https://developer.android.com/preview/privacy/mac-address), |
| // so AddressTrackerLinux should not be used in later versions |
| // of Android. Chromium code doesn't need it past Android P. |
| DCHECK_LT(base::android::BuildInfo::GetInstance()->sdk_int(), |
| base::android::SDK_VERSION_P); |
| #endif |
| netlink_fd_.reset(socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); |
| if (!netlink_fd_.is_valid()) { |
| PLOG(ERROR) << "Could not create NETLINK socket"; |
| AbortAndForceOnline(); |
| return; |
| } |
| |
| int rv; |
| |
| if (tracking_) { |
| // Request notifications. |
| struct sockaddr_nl addr = {}; |
| addr.nl_family = AF_NETLINK; |
| addr.nl_pid = 0; // Let the kernel select a unique value. |
| // TODO(szym): Track RTMGRP_LINK as well for ifi_type, |
| // http://crbug.com/113993 |
| addr.nl_groups = |
| RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY | RTMGRP_LINK; |
| rv = bind(netlink_fd_.get(), reinterpret_cast<struct sockaddr*>(&addr), |
| sizeof(addr)); |
| if (rv < 0) { |
| PLOG(ERROR) << "Could not bind NETLINK socket"; |
| AbortAndForceOnline(); |
| return; |
| } |
| } |
| |
| DumpInitialAddressesAndWatch(); |
| } |
| |
| bool AddressTrackerLinux::DidTrackingInitSucceedForTesting() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(tracking_); |
| return watcher_ != nullptr; |
| } |
| |
| void AddressTrackerLinux::AbortAndForceOnline() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| watcher_.reset(); |
| netlink_fd_.reset(); |
| AddressTrackerAutoLock lock(*this, connection_type_lock_); |
| current_connection_type_ = NetworkChangeNotifier::CONNECTION_UNKNOWN; |
| connection_type_initialized_ = true; |
| connection_type_initialized_cv_.Broadcast(); |
| } |
| |
| AddressTrackerLinux::AddressMap AddressTrackerLinux::GetAddressMap() const { |
| AddressTrackerAutoLock lock(*this, address_map_lock_); |
| return address_map_; |
| } |
| |
| std::unordered_set<int> AddressTrackerLinux::GetOnlineLinks() const { |
| AddressTrackerAutoLock lock(*this, online_links_lock_); |
| return online_links_; |
| } |
| |
| AddressTrackerLinux* AddressTrackerLinux::GetAddressTrackerLinux() { |
| return this; |
| } |
| |
| std::pair<AddressTrackerLinux::AddressMap, std::unordered_set<int>> |
| AddressTrackerLinux::GetInitialDataAndStartRecordingDiffs() { |
| DCHECK(tracking_); |
| AddressTrackerAutoLock lock_address_map(*this, address_map_lock_); |
| AddressTrackerAutoLock lock_online_links(*this, online_links_lock_); |
| address_map_diff_ = AddressMapDiff(); |
| online_links_diff_ = OnlineLinksDiff(); |
| return {address_map_, online_links_}; |
| } |
| |
| void AddressTrackerLinux::SetDiffCallback(DiffCallback diff_callback) { |
| DCHECK(tracking_); |
| DCHECK(sequenced_task_runner_); |
| |
| if (!sequenced_task_runner_->RunsTasksInCurrentSequence()) { |
| sequenced_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&AddressTrackerLinux::SetDiffCallback, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(diff_callback))); |
| return; |
| } |
| |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| #if DCHECK_IS_ON() |
| { |
| // GetInitialDataAndStartRecordingDiffs() must be called before |
| // SetDiffCallback(). |
| AddressTrackerAutoLock lock_address_map(*this, address_map_lock_); |
| AddressTrackerAutoLock lock_online_links(*this, online_links_lock_); |
| DCHECK(address_map_diff_.has_value()); |
| DCHECK(online_links_diff_.has_value()); |
| } |
| #endif // DCHECK_IS_ON() |
| diff_callback_ = std::move(diff_callback); |
| RunDiffCallback(); |
| } |
| |
| bool AddressTrackerLinux::IsInterfaceIgnored(int interface_index) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (ignored_interfaces_.empty()) |
| return false; |
| |
| char buf[IFNAMSIZ] = {0}; |
| const char* interface_name = get_interface_name_(interface_index, buf); |
| return ignored_interfaces_.find(interface_name) != ignored_interfaces_.end(); |
| } |
| |
| NetworkChangeNotifier::ConnectionType |
| AddressTrackerLinux::GetCurrentConnectionType() { |
| // http://crbug.com/125097 |
| base::ScopedAllowBaseSyncPrimitivesOutsideBlockingScope allow_wait; |
| AddressTrackerAutoLock lock(*this, connection_type_lock_); |
| // Make sure the initial connection type is set before returning. |
| threads_waiting_for_connection_type_initialization_++; |
| while (!connection_type_initialized_) { |
| connection_type_initialized_cv_.Wait(); |
| } |
| threads_waiting_for_connection_type_initialization_--; |
| return current_connection_type_; |
| } |
| |
| void AddressTrackerLinux::DumpInitialAddressesAndWatch() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Request dump of addresses. |
| struct sockaddr_nl peer = {}; |
| peer.nl_family = AF_NETLINK; |
| |
| struct { |
| struct nlmsghdr header; |
| struct rtgenmsg msg; |
| } request = {}; |
| |
| request.header.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg)); |
| request.header.nlmsg_type = RTM_GETADDR; |
| request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; |
| request.header.nlmsg_pid = 0; // This field is opaque to netlink. |
| request.msg.rtgen_family = AF_UNSPEC; |
| |
| int rv = HANDLE_EINTR( |
| sendto(netlink_fd_.get(), &request, request.header.nlmsg_len, 0, |
| reinterpret_cast<struct sockaddr*>(&peer), sizeof(peer))); |
| if (rv < 0) { |
| PLOG(ERROR) << "Could not send NETLINK request"; |
| AbortAndForceOnline(); |
| return; |
| } |
| |
| // Consume pending message to populate the AddressMap, but don't notify. |
| // Sending another request without first reading responses results in EBUSY. |
| bool address_changed; |
| bool link_changed; |
| bool tunnel_changed; |
| ReadMessages(&address_changed, &link_changed, &tunnel_changed); |
| |
| // Request dump of link state |
| request.header.nlmsg_type = RTM_GETLINK; |
| |
| rv = HANDLE_EINTR( |
| sendto(netlink_fd_.get(), &request, request.header.nlmsg_len, 0, |
| reinterpret_cast<struct sockaddr*>(&peer), sizeof(peer))); |
| if (rv < 0) { |
| PLOG(ERROR) << "Could not send NETLINK request"; |
| AbortAndForceOnline(); |
| return; |
| } |
| |
| // Consume pending message to populate links_online_, but don't notify. |
| ReadMessages(&address_changed, &link_changed, &tunnel_changed); |
| { |
| AddressTrackerAutoLock lock(*this, connection_type_lock_); |
| connection_type_initialized_ = true; |
| connection_type_initialized_cv_.Broadcast(); |
| } |
| |
| if (tracking_) { |
| DCHECK(!sequenced_task_runner_ || |
| sequenced_task_runner_->RunsTasksInCurrentSequence()); |
| |
| watcher_ = base::FileDescriptorWatcher::WatchReadable( |
| netlink_fd_.get(), |
| base::BindRepeating(&AddressTrackerLinux::OnFileCanReadWithoutBlocking, |
| base::Unretained(this))); |
| } |
| } |
| |
| void AddressTrackerLinux::ReadMessages(bool* address_changed, |
| bool* link_changed, |
| bool* tunnel_changed) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| *address_changed = false; |
| *link_changed = false; |
| *tunnel_changed = false; |
| bool first_loop = true; |
| |
| // Varying sources have different opinions regarding the buffer size needed |
| // for netlink messages to avoid truncation: |
| // - The official documentation on netlink says messages are generally 8kb |
| // or the system page size, whichever is *larger*: |
| // https://www.kernel.org/doc/html/v6.2/userspace-api/netlink/intro.html#buffer-sizing |
| // - The kernel headers would imply that messages are generally the system |
| // page size or 8kb, whichever is *smaller*: |
| // https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/include/linux/netlink.h?h=v6.2.2#n226 |
| // (libmnl follows this.) |
| // - The netlink(7) man page's example always uses a fixed size 8kb buffer: |
| // https://man7.org/linux/man-pages/man7/netlink.7.html |
| // Here, we follow the guidelines in the documentation, for two primary |
| // reasons: |
| // - Erring on the side of a larger size is the safer way to go to avoid |
| // MSG_TRUNC. |
| // - Since this is heap-allocated anyway, there's no risk to the stack by |
| // using the larger size. |
| |
| constexpr size_t kMinNetlinkBufferSize = 8 * 1024; |
| std::vector<char> buffer( |
| std::max(base::GetPageSize(), kMinNetlinkBufferSize)); |
| |
| { |
| absl::optional<base::ScopedBlockingCall> blocking_call; |
| if (tracking_) { |
| // If the loop below takes a long time to run, a new thread should added |
| // to the current thread pool to ensure forward progress of all tasks. |
| blocking_call.emplace(FROM_HERE, base::BlockingType::MAY_BLOCK); |
| } |
| |
| for (;;) { |
| int rv = |
| HANDLE_EINTR(recv(netlink_fd_.get(), buffer.data(), buffer.size(), |
| // Block the first time through loop. |
| first_loop ? 0 : MSG_DONTWAIT)); |
| first_loop = false; |
| if (rv == 0) { |
| LOG(ERROR) << "Unexpected shutdown of NETLINK socket."; |
| return; |
| } |
| if (rv < 0) { |
| if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) |
| break; |
| PLOG(ERROR) << "Failed to recv from netlink socket"; |
| return; |
| } |
| HandleMessage(buffer.data(), rv, address_changed, link_changed, |
| tunnel_changed); |
| } |
| } |
| if (*link_changed || *address_changed) |
| UpdateCurrentConnectionType(); |
| } |
| |
| void AddressTrackerLinux::HandleMessage(const char* buffer, |
| int length, |
| bool* address_changed, |
| bool* link_changed, |
| bool* tunnel_changed) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(buffer); |
| // Note that NLMSG_NEXT decrements |length| to reflect the number of bytes |
| // remaining in |buffer|. |
| for (const struct nlmsghdr* header = |
| reinterpret_cast<const struct nlmsghdr*>(buffer); |
| length >= 0 && NLMSG_OK(header, static_cast<__u32>(length)); |
| header = NLMSG_NEXT(header, length)) { |
| // The |header| pointer should never precede |buffer|. |
| DCHECK_LE(buffer, reinterpret_cast<const char*>(header)); |
| switch (header->nlmsg_type) { |
| case NLMSG_DONE: |
| return; |
| case NLMSG_ERROR: { |
| const struct nlmsgerr* msg = |
| SafelyCastNetlinkMsgData<const struct nlmsgerr>(header, length); |
| if (msg == nullptr) |
| return; |
| LOG(ERROR) << "Unexpected netlink error " << msg->error << "."; |
| } return; |
| case RTM_NEWADDR: { |
| IPAddress address; |
| bool really_deprecated; |
| const struct ifaddrmsg* msg = |
| SafelyCastNetlinkMsgData<const struct ifaddrmsg>(header, length); |
| if (msg == nullptr) |
| return; |
| if (IsInterfaceIgnored(msg->ifa_index)) |
| break; |
| if (GetAddress(header, length, &address, &really_deprecated)) { |
| struct ifaddrmsg msg_copy = *msg; |
| AddressTrackerAutoLock lock(*this, address_map_lock_); |
| // Routers may frequently (every few seconds) output the IPv6 ULA |
| // prefix which can cause the linux kernel to frequently output two |
| // back-to-back messages, one without the deprecated flag and one with |
| // the deprecated flag but both with preferred lifetimes of 0. Avoid |
| // interpreting this as an actual change by canonicalizing the two |
| // messages by setting the deprecated flag based on the preferred |
| // lifetime also. http://crbug.com/268042 |
| if (really_deprecated) |
| msg_copy.ifa_flags |= IFA_F_DEPRECATED; |
| // Only indicate change if the address is new or ifaddrmsg info has |
| // changed. |
| auto it = address_map_.find(address); |
| if (it == address_map_.end()) { |
| address_map_.insert(it, std::make_pair(address, msg_copy)); |
| *address_changed = true; |
| } else if (memcmp(&it->second, &msg_copy, sizeof(msg_copy))) { |
| it->second = msg_copy; |
| *address_changed = true; |
| } |
| if (*address_changed && address_map_diff_.has_value()) { |
| (*address_map_diff_)[address] = msg_copy; |
| } |
| } |
| } break; |
| case RTM_DELADDR: { |
| IPAddress address; |
| const struct ifaddrmsg* msg = |
| SafelyCastNetlinkMsgData<const struct ifaddrmsg>(header, length); |
| if (msg == nullptr) |
| return; |
| if (IsInterfaceIgnored(msg->ifa_index)) |
| break; |
| if (GetAddress(header, length, &address, nullptr)) { |
| AddressTrackerAutoLock lock(*this, address_map_lock_); |
| if (address_map_.erase(address)) { |
| *address_changed = true; |
| if (address_map_diff_.has_value()) { |
| (*address_map_diff_)[address] = absl::nullopt; |
| } |
| } |
| } |
| } break; |
| case RTM_NEWLINK: { |
| const struct ifinfomsg* msg = |
| SafelyCastNetlinkMsgData<const struct ifinfomsg>(header, length); |
| if (msg == nullptr) |
| return; |
| if (IsInterfaceIgnored(msg->ifi_index)) |
| break; |
| if (IgnoreWirelessChange(msg, IFLA_PAYLOAD(header))) { |
| VLOG(2) << "Ignoring RTM_NEWLINK message"; |
| break; |
| } |
| if (!(msg->ifi_flags & IFF_LOOPBACK) && (msg->ifi_flags & IFF_UP) && |
| (msg->ifi_flags & IFF_LOWER_UP) && (msg->ifi_flags & IFF_RUNNING)) { |
| AddressTrackerAutoLock lock(*this, online_links_lock_); |
| if (online_links_.insert(msg->ifi_index).second) { |
| *link_changed = true; |
| if (online_links_diff_.has_value()) { |
| (*online_links_diff_)[msg->ifi_index] = true; |
| } |
| if (IsTunnelInterface(msg->ifi_index)) |
| *tunnel_changed = true; |
| } |
| } else { |
| AddressTrackerAutoLock lock(*this, online_links_lock_); |
| if (online_links_.erase(msg->ifi_index)) { |
| *link_changed = true; |
| if (online_links_diff_.has_value()) { |
| (*online_links_diff_)[msg->ifi_index] = false; |
| } |
| if (IsTunnelInterface(msg->ifi_index)) |
| *tunnel_changed = true; |
| } |
| } |
| } break; |
| case RTM_DELLINK: { |
| const struct ifinfomsg* msg = |
| SafelyCastNetlinkMsgData<const struct ifinfomsg>(header, length); |
| if (msg == nullptr) |
| return; |
| if (IsInterfaceIgnored(msg->ifi_index)) |
| break; |
| AddressTrackerAutoLock lock(*this, online_links_lock_); |
| if (online_links_.erase(msg->ifi_index)) { |
| *link_changed = true; |
| if (online_links_diff_.has_value()) { |
| (*online_links_diff_)[msg->ifi_index] = false; |
| } |
| if (IsTunnelInterface(msg->ifi_index)) |
| *tunnel_changed = true; |
| } |
| } break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| void AddressTrackerLinux::OnFileCanReadWithoutBlocking() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| bool address_changed; |
| bool link_changed; |
| bool tunnel_changed; |
| ReadMessages(&address_changed, &link_changed, &tunnel_changed); |
| if (diff_callback_) { |
| RunDiffCallback(); |
| } |
| if (address_changed) { |
| address_callback_.Run(); |
| } |
| if (link_changed) { |
| link_callback_.Run(); |
| } |
| if (tunnel_changed) { |
| tunnel_callback_.Run(); |
| } |
| } |
| |
| bool AddressTrackerLinux::IsTunnelInterface(int interface_index) const { |
| char buf[IFNAMSIZ] = {0}; |
| return IsTunnelInterfaceName(get_interface_name_(interface_index, buf)); |
| } |
| |
| // static |
| bool AddressTrackerLinux::IsTunnelInterfaceName(const char* name) { |
| // Linux kernel drivers/net/tun.c uses "tun" name prefix. |
| return strncmp(name, "tun", 3) == 0; |
| } |
| |
| void AddressTrackerLinux::UpdateCurrentConnectionType() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| AddressTrackerLinux::AddressMap address_map = GetAddressMap(); |
| std::unordered_set<int> online_links = GetOnlineLinks(); |
| |
| // Strip out tunnel interfaces from online_links |
| for (auto it = online_links.cbegin(); it != online_links.cend();) { |
| if (IsTunnelInterface(*it)) { |
| it = online_links.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| |
| NetworkInterfaceList networks; |
| NetworkChangeNotifier::ConnectionType type = |
| NetworkChangeNotifier::CONNECTION_NONE; |
| if (GetNetworkListImpl(&networks, 0, online_links, address_map, |
| get_interface_name_)) { |
| type = NetworkChangeNotifier::ConnectionTypeFromInterfaceList(networks); |
| } else { |
| type = online_links.empty() ? NetworkChangeNotifier::CONNECTION_NONE |
| : NetworkChangeNotifier::CONNECTION_UNKNOWN; |
| } |
| |
| AddressTrackerAutoLock lock(*this, connection_type_lock_); |
| current_connection_type_ = type; |
| } |
| |
| void AddressTrackerLinux::RunDiffCallback() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(tracking_); |
| DCHECK(address_map_diff_.has_value()); |
| DCHECK(online_links_diff_.has_value()); |
| // It's fine to access `address_map_diff_` and `online_links_diff_` without |
| // any locking here, as the only time they are ever accessed on another thread |
| // is in GetInitialDataAndStartRecordingDiffs(). But |
| // GetInitialDataAndStartRecordingDiffs() must be called before |
| // SetDiffCallback(), which must be called before RunDiffCallback(), so this |
| // function cannot overlap with any modifications on another thread. |
| |
| // There should be a diff or the DiffCallback shouldn't be run. |
| if (address_map_diff_->empty() && online_links_diff_->empty()) { |
| return; |
| } |
| diff_callback_.Run(address_map_diff_.value(), online_links_diff_.value()); |
| address_map_diff_->clear(); |
| online_links_diff_->clear(); |
| } |
| |
| int AddressTrackerLinux::GetThreadsWaitingForConnectionTypeInitForTesting() { |
| AddressTrackerAutoLock lock(*this, connection_type_lock_); |
| return threads_waiting_for_connection_type_initialization_; |
| } |
| |
| AddressTrackerLinux::AddressTrackerAutoLock::AddressTrackerAutoLock( |
| const AddressTrackerLinux& tracker, |
| base::Lock& lock) |
| : tracker_(tracker), lock_(lock) { |
| if (tracker_->tracking_) { |
| lock_->Acquire(); |
| } else { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(tracker_->sequence_checker_); |
| } |
| } |
| |
| AddressTrackerLinux::AddressTrackerAutoLock::~AddressTrackerAutoLock() { |
| if (tracker_->tracking_) { |
| lock_->AssertAcquired(); |
| lock_->Release(); |
| } else { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(tracker_->sequence_checker_); |
| } |
| } |
| |
| } // namespace net::internal |