From 7145975cd40480ec74302dbcdab121734f7bb4f6 Mon Sep 17 00:00:00 2001 From: Francis Tsui Date: Thu, 9 Oct 2025 09:01:38 -0700 Subject: [PATCH] Add NetworkInfo class to maintain network interface details. PiperOrigin-RevId: 817210384 --- .../platform/implementation/windows/BUILD | 39 ++++ .../implementation/windows/network_info.cc | 198 ++++++++++++++++++ .../implementation/windows/network_info.h | 66 ++++++ .../windows/network_info_test.cc | 41 ++++ 4 files changed, 344 insertions(+) create mode 100644 internal/platform/implementation/windows/network_info.cc create mode 100644 internal/platform/implementation/windows/network_info.h create mode 100644 internal/platform/implementation/windows/network_info_test.cc diff --git a/internal/platform/implementation/windows/BUILD b/internal/platform/implementation/windows/BUILD index 050148a6..9d3802ef 100644 --- a/internal/platform/implementation/windows/BUILD +++ b/internal/platform/implementation/windows/BUILD @@ -196,6 +196,31 @@ cc_library( ], ) +cc_library( + name = "network_info", + srcs = [ + "network_info.cc", + ], + hdrs = [ + "network_info.h", + ], + compatible_with = ["//buildenv/target:non_prod"], + linkopts = [ + "iphlpapi.lib", + "ole32.lib", + ], + tags = ["windows"], + visibility = [ + "//:__subpackages__", + "//location/nearby:__subpackages__", + ], + deps = [ + ":string_utils", + "//internal/platform:logging", + "@com_google_absl//absl/synchronization", + ], +) + cc_library( name = "windows", srcs = [ @@ -423,3 +448,17 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "network_info_test", + size = "small", + timeout = "short", + srcs = [ + "network_info_test.cc", + ], + deps = [ + ":network_info", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/internal/platform/implementation/windows/network_info.cc b/internal/platform/implementation/windows/network_info.cc new file mode 100644 index 00000000..7f630946 --- /dev/null +++ b/internal/platform/implementation/windows/network_info.cc @@ -0,0 +1,198 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/windows/network_info.h" + +// clang-format off +#include +#include +#include +#include +// clang-format on + +#include +#include +#include +#include +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/windows/string_utils.h" +#include "internal/platform/logging.h" + +namespace nearby::windows { +namespace { + +void AddIpUnicastAddresses(IP_ADAPTER_UNICAST_ADDRESS* unicast_addresses, + NetworkInfo::InterfaceInfo& net_interface) { + while (unicast_addresses != nullptr) { + sockaddr* address = unicast_addresses->Address.lpSockaddr; + if (address == nullptr) { + unicast_addresses = unicast_addresses->Next; + continue; + } + sockaddr_storage storage; + std::memcpy(&storage, address, unicast_addresses->Address.iSockaddrLength); + if (address->sa_family == AF_INET) { + net_interface.ipv4_addresses.push_back(storage); + } else if (address->sa_family == AF_INET6) { + net_interface.ipv6_addresses.push_back(storage); + } + unicast_addresses = unicast_addresses->Next; + } + LOG(INFO) << "Added to interface: " << net_interface.index << ", " + << net_interface.ipv4_addresses.size() + << " v4 addresses, " + << net_interface.ipv6_addresses.size() << " v6 addresses."; +} + +std::string GuidToString(GUID guid) { + std::wstring guid_str; + guid_str.resize(39); + int guid_size = StringFromGUID2(guid, guid_str.data(), guid_str.size()); + if (guid_size != 0) { + guid_str.resize(guid_size - 1); + return string_utils::WideStringToString(guid_str); + } + return ""; +} + +} // namespace + +bool NetworkInfo::Refresh() { + static constexpr int kDefaultBufferSize = 15 * 1024; // default to 15K buffer + static constexpr int kMaxBufferSize = + 45 * 1024; // Try to increase buffer 2 times. + static constexpr ULONG kDefaultFlags = + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | + GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME; + ULONG buffer_size = 0; + // A string to own the memory for IP_ADAPTER_ADDRESSES. + std::string address_buffer; + ULONG error_code = ERROR_NO_DATA; + IP_ADAPTER_ADDRESSES* addresses = nullptr; + do { + buffer_size += kDefaultBufferSize; + address_buffer.reserve(buffer_size); + addresses = reinterpret_cast(address_buffer.data()); + error_code = + ::GetAdaptersAddresses(AF_UNSPEC, kDefaultFlags, /*reserved=*/nullptr, + addresses, &buffer_size); + } while (error_code == ERROR_BUFFER_OVERFLOW && + buffer_size <= kMaxBufferSize); + if (error_code != ERROR_NO_DATA && error_code != NO_ERROR) { + LOG(ERROR) << "Cannot get adapter addresses. Error code: " << error_code; + return false; + } + if (error_code == ERROR_NO_DATA) { + LOG(INFO) << "No network interfaces found."; + absl::MutexLock lock(mutex_); + interfaces_.clear(); + return true; + } + std::vector result; + IP_ADAPTER_ADDRESSES* next_address = addresses; + while (next_address != nullptr) { + if (next_address->OperStatus != IfOperStatusUp || + next_address->IfType == IF_TYPE_SOFTWARE_LOOPBACK) { + // Skip down and loopback interfaces. + next_address = next_address->Next; + continue; + } + auto it = result.insert(result.end(), InterfaceInfo{ + .index = next_address->IfIndex, + .guid = next_address->NetworkGuid, + }); + if (next_address->IfType == IF_TYPE_ETHERNET_CSMACD) { + it->type = InterfaceType::kEthernet; + VLOG(1) << "Found ethernet interface: " << next_address->AdapterName + << " index: " << next_address->IfIndex; + } else if (next_address->IfType == IF_TYPE_IEEE80211) { + it->type = InterfaceType::kWifi; + VLOG(1) << "Found wifi interface: " << next_address->AdapterName + << " index: " << next_address->IfIndex; + } else { + it->type = InterfaceType::kOther; + VLOG(1) << "Found other interface: " << next_address->AdapterName + << " index: " << next_address->IfIndex; + } + AddIpUnicastAddresses(next_address->FirstUnicastAddress, *it); + next_address = next_address->Next; + } + LOG(INFO) << "Found " << result.size() << " up interfaces."; + { + absl::MutexLock lock(mutex_); + interfaces_ = std::move(result); + } + return true; +} + +std::vector NetworkInfo::GetInterfaces() const { + absl::MutexLock lock(mutex_); + return interfaces_; +} + +bool NetworkInfo::RenewIpv4Address(GUID interface_guid) const { + uint64_t index = 0; + { + absl::MutexLock lock(mutex_); + auto it = std::find_if(interfaces_.begin(), interfaces_.end(), + [&interface_guid](const InterfaceInfo& intf) { + return intf.guid == interface_guid; + }); + if (it == interfaces_.end()) { + LOG(ERROR) << "Interface not found: " << GuidToString(interface_guid); + return false; + } + index = it->index; + } + ULONG interface_info_size = 0; + DWORD error = ::GetInterfaceInfo(nullptr, &interface_info_size); + if (error == ERROR_NO_DATA || error == NO_ERROR) { + LOG(INFO) << "No interface info found."; + return true; + } + if (error != ERROR_INSUFFICIENT_BUFFER) { + LOG(ERROR) << "GetInterfaceInfo failed: " << error; + return false; + } + std::string interface_info_data; + interface_info_data.resize(interface_info_size); + IP_INTERFACE_INFO* interface_info = + reinterpret_cast(interface_info_data.data()); + error = ::GetInterfaceInfo(interface_info, &interface_info_size); + if (error != NO_ERROR && error != ERROR_NO_DATA) { + LOG(ERROR) << "GetInterfaceInfo failed: " << error; + return false; + } + error = NO_ERROR; + VLOG(1) << "Got " << interface_info->NumAdapters << " IPV4 adapters"; + for (int i = 0; i < interface_info->NumAdapters; ++i) { + if (interface_info->Adapter[i].Index != index) { + continue; + } + LOG(INFO) << "Renewing IPV4 address for adapter: " << index; + error = ::IpRenewAddress(&interface_info->Adapter[i]); + VLOG(1) << "Renewed IPV4 address for adapter: " + << interface_info->Adapter[i].Index; + } + if (error != NO_ERROR) { + LOG(ERROR) << "IpRenewAddress failed: " << error; + return false; + } + return true; +} + +} // namespace nearby::windows diff --git a/internal/platform/implementation/windows/network_info.h b/internal/platform/implementation/windows/network_info.h new file mode 100644 index 00000000..40caf9e8 --- /dev/null +++ b/internal/platform/implementation/windows/network_info.h @@ -0,0 +1,66 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NETWORK_INFO_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NETWORK_INFO_H_ + +// clang-format off +#include +// clang-format on + +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" + +namespace nearby::windows { + +// The type of the network interface. +enum InterfaceType { + kEthernet, + kWifi, + kOther, +}; + +// Class to track network interfaces details. These include the interface type, +// interface index, GUID and IP addresses. +// This class is thread-safe. +class NetworkInfo { + public: + struct InterfaceInfo { + uint64_t index; + InterfaceType type; + GUID guid; + std::vector ipv4_addresses; + std::vector ipv6_addresses; + }; + + // Refreshes the network interfaces information keep by this class. + // Returns true on success. + bool Refresh(); + // Returns the network interfaces information. + std::vector GetInterfaces() const; + // Renews the IPv4 address for the given interface. + // Returns true on success. + bool RenewIpv4Address(GUID interface_guid) const; + + private: + mutable absl::Mutex mutex_; + std::vector interfaces_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace nearby::windows + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NETWORK_INFO_H_ diff --git a/internal/platform/implementation/windows/network_info_test.cc b/internal/platform/implementation/windows/network_info_test.cc new file mode 100644 index 00000000..15bf793a --- /dev/null +++ b/internal/platform/implementation/windows/network_info_test.cc @@ -0,0 +1,41 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/windows/network_info.h" + +#include "gtest/gtest.h" +#include "internal/platform/logging.h" + +namespace nearby::windows { +namespace { + +TEST(NetworkInfoTest, Refresh) { + NetworkInfo network_info; + EXPECT_TRUE(network_info.Refresh()); + EXPECT_FALSE(network_info.GetInterfaces().empty()); +} + +TEST(NetworkInfoTest, RenewIpv4Address) { + NetworkInfo network_info; + EXPECT_TRUE(network_info.Refresh()); + for (const auto& net_interface : network_info.GetInterfaces()) { + if (net_interface.ipv4_addresses.empty()) { + LOG(INFO) << "Ipv6 only interface"; + } + EXPECT_TRUE(network_info.RenewIpv4Address(net_interface.guid)); + } +} + +} // namespace +} // namespace nearby::windows