Add NetworkInfo class to maintain network interface details.

PiperOrigin-RevId: 817210384
This commit is contained in:
Francis Tsui
2025-10-09 09:06:56 -07:00
committed by Copybara-Service
parent 382f607d3d
commit 7145975cd4
4 changed files with 344 additions and 0 deletions
@@ -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",
],
)
@@ -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 <windows.h>
#include <winsock2.h>
#include <iphlpapi.h>
#include <combaseapi.h>
// clang-format on
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
#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<IP_ADAPTER_ADDRESSES*>(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<InterfaceInfo> 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::InterfaceInfo> 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<IP_INTERFACE_INFO*>(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
@@ -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 <winsock2.h>
// clang-format on
#include <cstdint>
#include <vector>
#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<sockaddr_storage> ipv4_addresses;
std::vector<sockaddr_storage> ipv6_addresses;
};
// Refreshes the network interfaces information keep by this class.
// Returns true on success.
bool Refresh();
// Returns the network interfaces information.
std::vector<InterfaceInfo> 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<InterfaceInfo> interfaces_ ABSL_GUARDED_BY(mutex_);
};
} // namespace nearby::windows
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_NETWORK_INFO_H_
@@ -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