Read IPv6 address from mDNS record.

PiperOrigin-RevId: 822743670
This commit is contained in:
Francis Tsui
2025-10-22 14:35:56 -07:00
committed by Copybara-Service
parent 7e8cbfc140
commit bd21843464
9 changed files with 242 additions and 116 deletions
-2
View File
@@ -70,7 +70,6 @@ cc_library(
"//connections:partners",
],
deps = [
":mac_address",
"//proto:connections_enums_cc_proto",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
@@ -83,7 +82,6 @@ cc_library(
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:span",
],
)
@@ -17,6 +17,7 @@
#include <winsock2.h>
#include <ws2ipdef.h>
#include <cstdint>
#include <cstring>
#include <string>
@@ -201,4 +202,22 @@ std::string SocketAddress::ToString() const {
return address_string;
}
bool SocketAddress::IsV6LinkLocal() const {
if (address_.ss_family != AF_INET6) {
return false;
}
const sockaddr_in6* v6_address =
reinterpret_cast<const sockaddr_in6*>(&address_);
return IN6_IS_ADDR_LINKLOCAL(&v6_address->sin6_addr);
}
bool SocketAddress::SetScopeId(uint32_t scope_id) {
if (address_.ss_family != AF_INET6) {
return false;
}
sockaddr_in6* v6_address = reinterpret_cast<sockaddr_in6*>(&address_);
v6_address->sin6_scope_id = scope_id;
return true;
}
} // namespace nearby::windows
@@ -18,6 +18,7 @@
#include <winsock2.h>
#include <ws2ipdef.h>
#include <cstdint>
#include <cstring>
#include <string>
@@ -72,6 +73,14 @@ class SocketAddress {
std::string ToString() const;
// Returns true if the address is a link local IPv6 address, ie. FE80::XXXX.
// Returns false if the address is not IPv6 or is not link local.
bool IsV6LinkLocal() const;
// Sets the scope id of the address.
// Returns false if the address is not IPv6.
bool SetScopeId(uint32_t scope_id);
// Returns a pointer to the internal sockaddr_storage.
// This can be used to modify the address directly. However, the dual_stack
// state is not honored, ie. it will not convert IPv4 addresses to mapped IPv6
@@ -86,6 +95,13 @@ class SocketAddress {
return const_cast<sockaddr*>(reinterpret_cast<const sockaddr*>(&address_));
}
const sockaddr_in* ipv4_address() const {
return reinterpret_cast<const sockaddr_in*>(&address_);
}
const sockaddr_in6* ipv6_address() const {
return reinterpret_cast<const sockaddr_in6*>(&address_);
}
private:
// If `address_` is AF_INET, then rewrite into mapped ipv6 address, e.g.
// [::ffff:192.0.2.1].
@@ -185,5 +185,21 @@ TEST(SocketAddressTest, DualStack) {
EXPECT_FALSE(address2.dual_stack());
}
TEST(SocketAddressTest, IPv6LinkLocalSuccess) {
SocketAddress address(/*dual_stack=*/true);
char bytes[16] = {0xfe, 0x80, 0, 0, 0, 0, 0, 0,
0x4d, 0xb2, 0xb3, 0x5c, 0x22, 0x03, 0x98, 0xa1};
EXPECT_TRUE(SocketAddress::FromBytes(address, bytes, 8080));
EXPECT_TRUE(address.IsV6LinkLocal());
}
TEST(SocketAddressTest, IPv6LinkLocalFail) {
SocketAddress address(/*dual_stack=*/true);
char bytes[16] = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0,
0x4d, 0xb2, 0xb3, 0x5c, 0x22, 0x03, 0x98, 0xa1};
EXPECT_TRUE(SocketAddress::FromBytes(address, bytes, 8080));
EXPECT_FALSE(address.IsV6LinkLocal());
}
} // namespace
} // namespace nearby::windows
@@ -310,6 +310,23 @@ std::vector<std::string> InspectableReader::ReadStringArray(
return result;
}
GUID InspectableReader::ReadGuid(IInspectable inspectable) {
if (inspectable == nullptr) {
return GUID{};
}
auto property_value =
inspectable.try_as<winrt::Windows::Foundation::IPropertyValue>();
if (property_value == nullptr) {
throw std::invalid_argument("no property value interface.");
}
if (property_value.Type() !=
winrt::Windows::Foundation::PropertyType::Guid) {
throw std::invalid_argument("not guid data type.");
}
return property_value.GetGuid();
}
std::optional<std::wstring> GetDnsHostName() {
DWORD size = 0;
@@ -78,6 +78,7 @@ class InspectableReader {
static uint32_t ReadUint32(IInspectable inspectable);
static std::string ReadString(IInspectable inspectable);
static std::vector<std::string> ReadStringArray(IInspectable inspectable);
static GUID ReadGuid(IInspectable inspectable);
};
} // namespace windows
@@ -269,13 +269,9 @@ class WifiLanMedium : public api::WifiLanMedium {
return (medium_status_ & kMediumStatusDiscovering) != 0;
}
// Checks whether the IP address is connectable in the given timeout.
// Parameters:
// ip - IP string in format as 192.168.1.1
// port - The IP port to connect.
// timeout - IP is not connectable if cannot connect in the duration.
// Result - return true if the IP is connectable, otherwise return false.
bool IsConnectableIpAddress(absl::string_view ip, int port,
// Checks whether the service in the given timeout.
// Returns true if the IP is connectable, otherwise return false.
bool IsConnectableIpAddress(const NsdServiceInfo& nsd_service_info,
absl::Duration timeout = absl::Seconds(1));
// Methods to manage discovred services.
@@ -311,9 +307,6 @@ class WifiLanMedium : public api::WifiLanMedium {
winrt::Windows::Devices::Enumeration::DeviceInformationUpdate
deviceInfoUpdate);
// Gets error message from exception pointer
std::string GetErrorMessage(std::exception_ptr eptr);
void RestartScanning();
//
@@ -16,10 +16,12 @@
// Windows headers
#include <windows.h>
#include <winsock.h>
#include <winsock2.h>
#include <iphlpapi.h>
// Standard C/C++ headers
#include <cstdint>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
@@ -68,8 +70,6 @@ constexpr absl::string_view kDeviceEndpointInfo = "n";
constexpr absl::string_view kDeviceIpv4 = "IPv4";
// mDNS information for advertising and discovery
constexpr std::wstring_view kMdnsHostName = L"Windows.local";
const char kMdnsInstanceNameFormat[] = "%s.%slocal";
constexpr absl::string_view kMdnsDeviceSelectorFormat =
"System.Devices.AepService.ProtocolId:=\"{4526e8c1-8aac-4153-9b16-"
"55e86ada0e54}\" "
@@ -79,6 +79,85 @@ constexpr absl::string_view kMdnsDeviceSelectorFormat =
constexpr absl::Duration kConnectTimeout = absl::Seconds(2);
constexpr absl::Duration kConnectServiceTimeout = absl::Seconds(3);
bool IsSelfInstance(IMapView<winrt::hstring, IInspectable> properties,
absl::string_view self_instance_name) {
IInspectable inspectable =
properties.TryLookup(L"System.Devices.Dnssd.InstanceName");
if (inspectable == nullptr) {
VLOG(1) << "no service name information in device information.";
// Unable to find instance name, so treat it as a self instance.
return true;
}
// Don't discover itself
if (InspectableReader::ReadString(inspectable) == self_instance_name) {
VLOG(1) << "Don't update WIFI_LAN device for itself.";
return true;
}
return false;
}
bool GetMdnsIpv4Address(const std::string& address_str,
NsdServiceInfo& nsd_service_info) {
SocketAddress ipv4_address(/*dual_stack=*/false);
if (!SocketAddress::FromString(ipv4_address, address_str)) {
return false;
}
DCHECK_EQ(ipv4_address.address()->sa_family, AF_INET);
if (ipv4_address.address()->sa_family == AF_INET) {
std::string ip_address_bytes;
ip_address_bytes.resize(4);
const sockaddr_in* ipv4_addr = ipv4_address.ipv4_address();
std::memcpy(ip_address_bytes.data(), &ipv4_addr->sin_addr.s_addr, 4);
nsd_service_info.SetIPAddress(ip_address_bytes);
VLOG(1) << "Found ipv4 address: " <<ipv4_address.ToString();
return true;
}
// Should not reach here.
return false;
}
bool GetMdnsIpv6Address(const std::string& address_str,
IMapView<winrt::hstring, IInspectable> properties,
NsdServiceInfo& nsd_service_info) {
SocketAddress ipv6_address(/*dual_stack=*/true);
if (!SocketAddress::FromString(ipv6_address, address_str)) {
return false;
}
DCHECK_EQ(ipv6_address.address()->sa_family, AF_INET6);
if (ipv6_address.address()->sa_family == AF_INET6) {
if (ipv6_address.IsV6LinkLocal()) {
// Skip link local addresses if the interface index is not available.
NET_IFINDEX interface_index = 0;
IInspectable inspectable =
properties.TryLookup(L"System.Devices.Dnssd.NetworkAdapterId");
if (inspectable == nullptr) {
return false;
}
GUID network_adapter_id = InspectableReader::ReadGuid(inspectable);
NET_LUID luid;
if (ConvertInterfaceGuidToLuid(&network_adapter_id, &luid) !=
NO_ERROR) {
VLOG(1) << "Failed to get interface luid";
return false;
}
if (ConvertInterfaceLuidToIndex(&luid, &interface_index) !=
NO_ERROR) {
VLOG(1) << "Failed to get interface index";
return false;
}
VLOG(1) << "network adapter luid: " << luid.Value
<< ", interface index: " << interface_index;
ipv6_address.SetScopeId(interface_index);
}
nsd_service_info.SetIPv6Address(ipv6_address.ToString());
VLOG(1) << "Found ipv6 address: " <<ipv6_address.ToString();
return true;
}
// Should not reach here.
return false;
}
} // namespace
bool WifiLanMedium::IsNetworkConnected() const {
@@ -167,6 +246,7 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_type,
std::vector<winrt::hstring> requestedProperties{
L"System.Devices.IpAddress",
L"System.Devices.Dnssd.NetworkAdapterId",
L"System.Devices.Dnssd.HostName",
L"System.Devices.Dnssd.InstanceName",
L"System.Devices.Dnssd.PortNumber",
@@ -284,10 +364,7 @@ std::unique_ptr<api::WifiLanServerSocket> WifiLanMedium::ListenForService(
kEnableIpv6DualStack);
if (server_socket->Listen(dual_stack)) {
int port = server_socket_ptr->GetPort();
LOG(INFO) << "started to listen serive on IP:port "
<< ipaddr_4bytes_to_dotdecimal_string(
server_socket_ptr->GetIPAddress())
<< ":" << port;
LOG(INFO) << "started to listen serive on port: " << port;
port_to_server_socket_map_.insert({port, server_socket_ptr});
server_socket->SetCloseNotifier([this, server_socket_ptr, port]() {
@@ -358,11 +435,25 @@ ExceptionOr<NsdServiceInfo> WifiLanMedium::GetNsdServiceInformation(
std::string value = text_attribute.substr(pos + 1);
nsd_service_info.SetTxtRecord(key, value);
}
// mDNS record must have device endpoint info.
if (nsd_service_info.GetTxtRecord(kDeviceEndpointInfo.data()).empty()) {
LOG(ERROR) << "No device endpoint info found.";
return Exception{Exception::kFailed};
}
if (!is_device_found) {
return ExceptionOr<NsdServiceInfo>(nsd_service_info);
}
// Read IP port
inspectable = properties.TryLookup(L"System.Devices.Dnssd.PortNumber");
if (inspectable == nullptr) {
VLOG(1) << "no IP port property in device information.";
return Exception{Exception::kFailed};
}
int port = InspectableReader::ReadUint16(inspectable);
nsd_service_info.SetPort(port);
// Read IP Address information
// Use the mDNS resolved IP addresses first. If not available, use the IP
// addresses from TXT record.
@@ -378,46 +469,39 @@ ExceptionOr<NsdServiceInfo> WifiLanMedium::GetNsdServiceInformation(
ip_address_candidates.push_back(ipv4_address);
}
if (ip_address_candidates.empty()) {
VLOG(1) << "No IP address information in device information.";
return Exception{Exception::kFailed};
}
// Gets 4 bytes string
std::string ip_address;
ip_address.resize(4);
bool has_ipv4_address = false;
bool has_ipv6_address = false;
for (std::string& address : ip_address_candidates) {
uint32_t addr = inet_addr(address.data());
if (addr == INADDR_NONE) {
continue;
if (has_ipv4_address && has_ipv6_address) {
// Windows only provide 1 of each of IPv4 and IPv6 addresses.
break;
}
if (!has_ipv4_address) {
if (GetMdnsIpv4Address(address, nsd_service_info)) {
has_ipv4_address = true;
continue;
}
}
if (!has_ipv6_address) {
if (GetMdnsIpv6Address(address, properties, nsd_service_info)) {
has_ipv6_address = true;
continue;
}
}
in_addr ipv4_addr;
ipv4_addr.S_un.S_addr = addr;
ip_address[0] = static_cast<char>(ipv4_addr.S_un.S_un_b.s_b1);
ip_address[1] = static_cast<char>(ipv4_addr.S_un.S_un_b.s_b2);
ip_address[2] = static_cast<char>(ipv4_addr.S_un.S_un_b.s_b3);
ip_address[3] = static_cast<char>(ipv4_addr.S_un.S_un_b.s_b4);
break;
}
nsd_service_info.SetIPAddress(ip_address);
// Read IP port
inspectable = properties.TryLookup(L"System.Devices.Dnssd.PortNumber");
if (inspectable == nullptr) {
VLOG(1) << "no IP port property in device information.";
if (!has_ipv4_address && !has_ipv6_address) {
VLOG(1) << "no IP addresses in mDNS record.";
return Exception{Exception::kFailed};
}
int port = InspectableReader::ReadUint16(inspectable);
nsd_service_info.SetPort(port);
return ExceptionOr<NsdServiceInfo>(nsd_service_info);
}
fire_and_forget WifiLanMedium::Watcher_DeviceAdded(
DeviceWatcher sender, DeviceInformation deviceInfo) {
VLOG(1) << "WifiLanMedium::Watcher_DeviceAdded";
if (IsSelfInstance(deviceInfo.Properties(), service_name_)) {
return fire_and_forget{};
}
// need to read IP address and port information from deviceInfo
ExceptionOr<NsdServiceInfo> nsd_service_info_except =
GetNsdServiceInformation(deviceInfo.Properties(),
@@ -430,29 +514,13 @@ fire_and_forget WifiLanMedium::Watcher_DeviceAdded(
}
NsdServiceInfo nsd_service_info = nsd_service_info_except.GetResult();
std::string endpoint =
nsd_service_info.GetTxtRecord(kDeviceEndpointInfo.data());
if (endpoint.empty()) {
VLOG(1) << "No endpoint information! Don't add WIFI_LAN device.";
return fire_and_forget{};
}
// Don't discover itself
if (nsd_service_info.GetServiceName() == service_name_) {
VLOG(1) << "Don't add WIFI_LAN device for itself";
return fire_and_forget{};
}
LOG(INFO) << "device found for service name "
<< nsd_service_info.GetServiceName() << ", address: "
<< ipaddr_4bytes_to_dotdecimal_string(
nsd_service_info.GetIPAddress())
<< ":" << nsd_service_info.GetPort();
<< nsd_service_info.GetServiceName()
<< " on port " << nsd_service_info.GetPort();
if (!IsConnectableIpAddress(
ipaddr_4bytes_to_dotdecimal_string(nsd_service_info.GetIPAddress()),
nsd_service_info.GetPort(), kConnectTimeout)) {
LOG(WARNING) << "Don't add WIFI_LAN device since it is not reachable.";
if (!IsConnectableIpAddress(nsd_service_info, kConnectTimeout)) {
VLOG(1) << "mDNS service " << nsd_service_info.GetServiceName()
<< " is not reachable.";
return fire_and_forget{};
}
@@ -464,6 +532,10 @@ fire_and_forget WifiLanMedium::Watcher_DeviceAdded(
fire_and_forget WifiLanMedium::Watcher_DeviceUpdated(
DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) {
VLOG(1) << "WifiLanMedium::Watcher_DeviceUpdated";
if (IsSelfInstance(deviceInfoUpdate.Properties(), service_name_)) {
return fire_and_forget{};
}
ExceptionOr<NsdServiceInfo> nsd_service_info_except =
GetNsdServiceInformation(deviceInfoUpdate.Properties(),
/*is_device_found*/ true);
@@ -475,24 +547,14 @@ fire_and_forget WifiLanMedium::Watcher_DeviceUpdated(
NsdServiceInfo nsd_service_info = nsd_service_info_except.GetResult();
// Don't discover itself
if (nsd_service_info.GetServiceName() == service_name_) {
VLOG(1) << "Don't update WIFI_LAN device for itself.";
return fire_and_forget{};
}
// check having any changes
std::optional<NsdServiceInfo> last_nsd_service_info =
GetDiscoveredService(winrt::to_string(deviceInfoUpdate.Id()));
if (!last_nsd_service_info.has_value()) {
LOG(INFO) << "device updated for service name "
<< nsd_service_info.GetServiceName() << ", address: "
<< ipaddr_4bytes_to_dotdecimal_string(
nsd_service_info.GetIPAddress())
<< ":" << nsd_service_info.GetPort();
if (IsConnectableIpAddress(
ipaddr_4bytes_to_dotdecimal_string(nsd_service_info.GetIPAddress()),
nsd_service_info.GetPort(), kConnectTimeout)) {
<< nsd_service_info.GetServiceName()
<< " on port " << nsd_service_info.GetPort();
if (IsConnectableIpAddress(nsd_service_info, kConnectTimeout)) {
// If the device is not in the discovered service list, but it is
// connectable during update, we add it to the discovered service list.
UpdateDiscoveredService(winrt::to_string(deviceInfoUpdate.Id()),
@@ -501,7 +563,8 @@ fire_and_forget WifiLanMedium::Watcher_DeviceUpdated(
return fire_and_forget{};
}
LOG(WARNING) << "Don't update WIFI_LAN device since it is not reachable.";
VLOG(1) << "mDNS service " << nsd_service_info.GetServiceName()
<< " is not reachable.";
return fire_and_forget{};
}
@@ -511,6 +574,8 @@ fire_and_forget WifiLanMedium::Watcher_DeviceUpdated(
nsd_service_info.GetServiceName()) &&
(last_nsd_service_info->GetIPAddress() ==
nsd_service_info.GetIPAddress()) &&
(last_nsd_service_info->GetIPv6Address() ==
nsd_service_info.GetIPv6Address()) &&
(last_nsd_service_info->GetPort() == nsd_service_info.GetPort())) {
VLOG(1) << "Don't update WIFI_LAN device since there is no change.";
return fire_and_forget{};
@@ -542,6 +607,10 @@ fire_and_forget WifiLanMedium::Watcher_DeviceUpdated(
fire_and_forget WifiLanMedium::Watcher_DeviceRemoved(
DeviceWatcher sender, DeviceInformationUpdate deviceInfoUpdate) {
VLOG(1) << "WifiLanMedium::Watcher_DeviceRemoved";
if (IsSelfInstance(deviceInfoUpdate.Properties(), service_name_)) {
return fire_and_forget{};
}
// need to read IP address and port information from deviceInfo
ExceptionOr<NsdServiceInfo> nsd_service_info_except =
GetNsdServiceInformation(deviceInfoUpdate.Properties(),
@@ -555,13 +624,6 @@ fire_and_forget WifiLanMedium::Watcher_DeviceRemoved(
NsdServiceInfo nsd_service_info = nsd_service_info_except.GetResult();
LOG(INFO) << "device removed for service name "
<< nsd_service_info.GetServiceName();
std::string endpoint =
nsd_service_info.GetTxtRecord(kDeviceEndpointInfo.data());
if (endpoint.empty()) {
return fire_and_forget{};
}
RemoveDiscoveredService(winrt::to_string(deviceInfoUpdate.Id()));
discovered_service_callback_.service_lost_cb(nsd_service_info);
@@ -598,22 +660,28 @@ void WifiLanMedium::RemoveDiscoveredService(absl::string_view id) {
}
}
bool WifiLanMedium::IsConnectableIpAddress(absl::string_view ip, int port,
absl::Duration timeout) {
bool WifiLanMedium::IsConnectableIpAddress(
const NsdServiceInfo& nsd_service_info, absl::Duration timeout) {
bool result = false;
int error = -1;
int size = sizeof(int);
timeval tm;
fd_set set;
unsigned long non_blocking = 1; // NOLINT
struct sockaddr_in serv_addr;
if (nsd_service_info.GetIPAddress().empty()) {
return false;
}
SocketAddress service_address(/*dual_stack=*/false);
if (!SocketAddress::FromBytes(service_address,
nsd_service_info.GetIPAddress(),
nsd_service_info.GetPort())) {
LOG(ERROR) << "no valid service address and port to connect.";
return false;
}
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.S_un.S_addr = inet_addr(std::string(ip).c_str());
ioctlsocket(sock, /*cmd=*/FIONBIO, /*argp=*/&non_blocking);
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) ==
if (connect(sock, service_address.address(), sizeof(sockaddr_storage)) ==
SOCKET_ERROR) {
tm.tv_sec = timeout / absl::Seconds(1);
tm.tv_usec = 0;
@@ -641,16 +709,4 @@ bool WifiLanMedium::IsConnectableIpAddress(absl::string_view ip, int port,
return result;
}
std::string WifiLanMedium::GetErrorMessage(std::exception_ptr eptr) {
try {
if (eptr) {
std::rethrow_exception(eptr);
} else {
return "";
}
} catch (const std::exception& e) {
return e.what();
}
}
} // namespace nearby::windows
+15 -5
View File
@@ -69,13 +69,22 @@ class NsdServiceInfo {
txt_records_ = txt_records;
}
// Gets IP Address, which is in byte sequence, in network order.
std::string GetIPAddress() const { return ip_address_; }
// Gets IPv4 Address, which is in byte sequence, in network order.
std::string GetIPAddress() const { return ipv4_address_; }
// Sets IP Address. Not used in advertising.
// Sets IPv4 Address. Not used in advertising.
// mDNS system should advertise IP address based on the interface used to
// start advertising.
void SetIPAddress(const std::string& ip_address) { ip_address_ = ip_address; }
void SetIPAddress(const std::string& ip_address) {
ipv4_address_ = ip_address;
}
// IPv6 address is in string format. The address can include a scope ID which
// cannot be represented in byte sequence without some hackery.
std::string GetIPv6Address() const { return ipv6_address_; }
void SetIPv6Address(const std::string& ipv6_address) {
ipv6_address_ = ipv6_address;
}
// Gets the port number
int GetPort() const { return port_; }
@@ -96,7 +105,8 @@ class NsdServiceInfo {
private:
std::string service_name_;
absl::flat_hash_map<std::string, std::string> txt_records_;
std::string ip_address_;
std::string ipv4_address_;
std::string ipv6_address_;
int port_;
std::string service_type_;
};