From bd218434644c8ebbabe2c0e0836a4d413e179821 Mon Sep 17 00:00:00 2001 From: Francis Tsui Date: Wed, 22 Oct 2025 14:34:37 -0700 Subject: [PATCH] Read IPv6 address from mDNS record. PiperOrigin-RevId: 822743670 --- internal/platform/BUILD | 2 - .../implementation/windows/socket_address.cc | 19 ++ .../implementation/windows/socket_address.h | 16 ++ .../windows/socket_address_test.cc | 16 ++ .../platform/implementation/windows/utils.cc | 17 ++ .../platform/implementation/windows/utils.h | 1 + .../implementation/windows/wifi_lan.h | 13 +- .../implementation/windows/wifi_lan_medium.cc | 254 +++++++++++------- internal/platform/nsd_service_info.h | 20 +- 9 files changed, 242 insertions(+), 116 deletions(-) diff --git a/internal/platform/BUILD b/internal/platform/BUILD index ea81dcdc..1fc327b8 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -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", ], ) diff --git a/internal/platform/implementation/windows/socket_address.cc b/internal/platform/implementation/windows/socket_address.cc index 42883b2d..7965588b 100644 --- a/internal/platform/implementation/windows/socket_address.cc +++ b/internal/platform/implementation/windows/socket_address.cc @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -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(&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(&address_); + v6_address->sin6_scope_id = scope_id; + return true; +} + } // namespace nearby::windows diff --git a/internal/platform/implementation/windows/socket_address.h b/internal/platform/implementation/windows/socket_address.h index 28f4e581..de2e9a6f 100644 --- a/internal/platform/implementation/windows/socket_address.h +++ b/internal/platform/implementation/windows/socket_address.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -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(reinterpret_cast(&address_)); } + const sockaddr_in* ipv4_address() const { + return reinterpret_cast(&address_); + } + const sockaddr_in6* ipv6_address() const { + return reinterpret_cast(&address_); + } + private: // If `address_` is AF_INET, then rewrite into mapped ipv6 address, e.g. // [::ffff:192.0.2.1]. diff --git a/internal/platform/implementation/windows/socket_address_test.cc b/internal/platform/implementation/windows/socket_address_test.cc index 21e829aa..50e22629 100644 --- a/internal/platform/implementation/windows/socket_address_test.cc +++ b/internal/platform/implementation/windows/socket_address_test.cc @@ -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 diff --git a/internal/platform/implementation/windows/utils.cc b/internal/platform/implementation/windows/utils.cc index e2678f1f..3976b2b0 100644 --- a/internal/platform/implementation/windows/utils.cc +++ b/internal/platform/implementation/windows/utils.cc @@ -310,6 +310,23 @@ std::vector InspectableReader::ReadStringArray( return result; } +GUID InspectableReader::ReadGuid(IInspectable inspectable) { + if (inspectable == nullptr) { + return GUID{}; + } + auto property_value = + inspectable.try_as(); + 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 GetDnsHostName() { DWORD size = 0; diff --git a/internal/platform/implementation/windows/utils.h b/internal/platform/implementation/windows/utils.h index 12a459b5..77ff28f0 100644 --- a/internal/platform/implementation/windows/utils.h +++ b/internal/platform/implementation/windows/utils.h @@ -78,6 +78,7 @@ class InspectableReader { static uint32_t ReadUint32(IInspectable inspectable); static std::string ReadString(IInspectable inspectable); static std::vector ReadStringArray(IInspectable inspectable); + static GUID ReadGuid(IInspectable inspectable); }; } // namespace windows diff --git a/internal/platform/implementation/windows/wifi_lan.h b/internal/platform/implementation/windows/wifi_lan.h index 6d77c5a8..0952c9dd 100644 --- a/internal/platform/implementation/windows/wifi_lan.h +++ b/internal/platform/implementation/windows/wifi_lan.h @@ -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(); // diff --git a/internal/platform/implementation/windows/wifi_lan_medium.cc b/internal/platform/implementation/windows/wifi_lan_medium.cc index 4dbf5d43..7a0aa982 100644 --- a/internal/platform/implementation/windows/wifi_lan_medium.cc +++ b/internal/platform/implementation/windows/wifi_lan_medium.cc @@ -16,10 +16,12 @@ // Windows headers #include -#include +#include +#include // Standard C/C++ headers #include +#include #include #include #include @@ -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 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: " < 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: " < 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 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 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(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 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(ipv4_addr.S_un.S_un_b.s_b1); - ip_address[1] = static_cast(ipv4_addr.S_un.S_un_b.s_b2); - ip_address[2] = static_cast(ipv4_addr.S_un.S_un_b.s_b3); - ip_address[3] = static_cast(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(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 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 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 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 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 diff --git a/internal/platform/nsd_service_info.h b/internal/platform/nsd_service_info.h index 3197b343..c8f05dfc 100644 --- a/internal/platform/nsd_service_info.h +++ b/internal/platform/nsd_service_info.h @@ -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 txt_records_; - std::string ip_address_; + std::string ipv4_address_; + std::string ipv6_address_; int port_; std::string service_type_; };