diff --git a/internal/platform/implementation/windows/BUILD b/internal/platform/implementation/windows/BUILD index 820c172d..d30fb177 100644 --- a/internal/platform/implementation/windows/BUILD +++ b/internal/platform/implementation/windows/BUILD @@ -105,6 +105,7 @@ cc_library( "wifi_hotspot_native.h", "wifi_intel.h", "wifi_lan.h", + "wifi_lan_mdns.h", ], copts = ["-DNO_INTEL_PIE"], visibility = ["//visibility:private"], @@ -204,6 +205,7 @@ cc_library( "wifi_hotspot_server_socket.cc", "wifi_hotspot_socket.cc", "wifi_intel.cc", + "wifi_lan_mdns.cc", "wifi_lan_medium.cc", "wifi_lan_server_socket.cc", "wifi_lan_socket.cc", diff --git a/internal/platform/implementation/windows/wifi_lan_mdns.cc b/internal/platform/implementation/windows/wifi_lan_mdns.cc new file mode 100644 index 00000000..792b7d8d --- /dev/null +++ b/internal/platform/implementation/windows/wifi_lan_mdns.cc @@ -0,0 +1,209 @@ +// 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/wifi_lan_mdns.h" + +// clang-format off +#include +#include +// clang-format on + +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/str_format.h" +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" +#include "absl/time/time.h" +#include "internal/platform/implementation/windows/string_utils.h" +#include "internal/platform/logging.h" + +namespace nearby::windows { +namespace { +// mDNS information for advertising and discovery +const char kMdnsHostName[] = "%s.local"; +const char kMdnsInstanceNameFormat[] = "%s.%slocal"; + +// Timeout for starting mDNS service +constexpr absl::Duration kDnsServiceTimeout = absl::Seconds(3); +} // namespace + +WifiLanMdns::~WifiLanMdns() { + if (is_service_started_) { + StopMdnsService(); + } +} + +bool WifiLanMdns::StartMdnsService( + const std::string& service_name, const std::string& service_type, int port, + absl::flat_hash_map text_records) { + absl::MutexLock lock(&mutex_); + LOG(INFO) << "StartMdnsService: " << service_name << " " << service_type + << " " << port; + if (is_service_started_) { + LOG(WARNING) << "The mDNS service is already started."; + return false; + } + + memset(&dns_service_instance_, 0, sizeof(dns_service_instance_)); + memset(&dns_service_register_request_, 0, + sizeof(dns_service_register_request_)); + + // Composite the service request. + std::string instance_name = + absl::StrFormat(kMdnsInstanceNameFormat, service_name, service_type); + dns_service_instance_name_ = std::make_unique( + string_utils::StringToWideString(instance_name)); + + std::optional computer_name = GetComputerName(); + if (!computer_name.has_value()) { + LOG(ERROR) << "Failed to get computer name."; + return false; + } + + std::string host_name = absl::StrFormat(kMdnsHostName, *computer_name); + host_name_ = std::make_unique( + string_utils::StringToWideString(host_name)); + + dns_service_instance_.pszInstanceName = + (LPWSTR)dns_service_instance_name_->c_str(); + dns_service_instance_.pszHostName = (LPWSTR)host_name_->c_str(); + dns_service_instance_.wPort = port; + + // Allocate memory for filling text records, it should be freed in + // stopping mDNS service. + if (!text_records.empty()) { + dns_service_instance_.dwPropertyCount = text_records.size(); + + for (const auto& [key, value] : text_records) { + text_keys_.push_back(string_utils::StringToWideString(key)); + text_values_.push_back(string_utils::StringToWideString(value)); + } + + keys_ = new PWSTR[text_records.size()]; + values_ = new PWSTR[text_records.size()]; + + for (int i = 0; i < text_records.size(); ++i) { + keys_[i] = text_keys_[i].data(); + values_[i] = text_values_[i].data(); + } + + dns_service_instance_.keys = keys_; + dns_service_instance_.values = values_; + } + + // Init DNS service register request + dns_service_register_request_.Version = DNS_QUERY_REQUEST_VERSION1; + dns_service_register_request_.InterfaceIndex = + 0; // all interfaces will be considered + dns_service_register_request_.unicastEnabled = false; + dns_service_register_request_.hCredentials = nullptr; + dns_service_register_request_.pServiceInstance = &dns_service_instance_; + dns_service_register_request_.pQueryContext = this; // callback use it + dns_service_register_request_.pRegisterCompletionCallback = + WifiLanMdns::DnsServiceRegisterComplete; + + dns_service_notification_ = std::make_unique(); + + DWORD status = DnsServiceRegister(&dns_service_register_request_, nullptr); + + if (status != DNS_REQUEST_PENDING) { + NEARBY_LOGS(ERROR) << "Failed to start mDNS advertising for service type =" + << service_type; + return false; + } + + if (!dns_service_notification_->WaitForNotificationWithTimeout( + kDnsServiceTimeout)) { + LOG(ERROR) << "Failed to start mDNS advertising for service type =" + << service_type; + return false; + } + + dns_service_notification_ = nullptr; + is_service_started_ = true; + + LOG(INFO) << "Succeeded to start mDNS advertising for service type " + << service_type; + + return true; +} + +bool WifiLanMdns::StopMdnsService() { + absl::MutexLock lock(&mutex_); + LOG(INFO) << "StopMdnsService is called"; + if (!is_service_started_) { + LOG(WARNING) << "The mDNS service is not started."; + return false; + } + + dns_service_notification_ = std::make_unique(); + + DWORD status = DnsServiceDeRegister(&dns_service_register_request_, nullptr); + + if (status != DNS_REQUEST_PENDING) { + NEARBY_LOGS(ERROR) << "Failed to stop mDNS advertising."; + return false; + } + + if (!dns_service_notification_->WaitForNotificationWithTimeout( + kDnsServiceTimeout)) { + LOG(ERROR) << "Failed to start mDNS advertising."; + return false; + } + + dns_service_notification_ = nullptr; + if (dns_service_instance_.keys != nullptr) { + delete[] dns_service_instance_.keys; + delete[] dns_service_instance_.values; + dns_service_instance_.keys = nullptr; + dns_service_instance_.values = nullptr; + } + + is_service_started_ = false; + LOG(INFO) << "Succeeded to stop mDNS advertising."; + + return true; +} + +void WifiLanMdns::NotifyStatusUpdated(DWORD status) { + VLOG(1) << "NotifyStatusUpdated: " << status; + if (dns_service_notification_ != nullptr) { + dns_service_notification_->Notify(); + } +} + +std::optional WifiLanMdns::GetComputerName() { + char computer_name[MAX_COMPUTERNAME_LENGTH + 1]; + DWORD size = sizeof(computer_name); + + // Get the computer name. + if (::GetComputerNameA(computer_name, &size)) { + return std::string(computer_name, size); + } else { + return std::nullopt; + } +} + +void WifiLanMdns::DnsServiceRegisterComplete(DWORD Status, PVOID pQueryContext, + PDNS_SERVICE_INSTANCE pInstance) { + LOG(INFO) << "DnsServiceRegisterComplete: " << Status; + WifiLanMdns* mdns = static_cast(pQueryContext); + mdns->NotifyStatusUpdated(Status); +} + +} // namespace nearby::windows diff --git a/internal/platform/implementation/windows/wifi_lan_mdns.h b/internal/platform/implementation/windows/wifi_lan_mdns.h new file mode 100644 index 00000000..d83e6e7a --- /dev/null +++ b/internal/platform/implementation/windows/wifi_lan_mdns.h @@ -0,0 +1,70 @@ +// 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_WIFI_LAN_MDNS_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_WIFI_LAN_MDNS_H_ + +// clang-format off +#include +#include +// clang-format on + +#include +#include +#include + +#include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" + +namespace nearby::windows { + +class WifiLanMdns { + public: + WifiLanMdns() = default; + ~WifiLanMdns(); + + bool StartMdnsService( + const std::string& service_name, const std::string& service_type, + int port, absl::flat_hash_map text_records); + + bool StopMdnsService(); + + void NotifyStatusUpdated(DWORD status); + + private: + static void DnsServiceRegisterComplete(DWORD Status, PVOID pQueryContext, + PDNS_SERVICE_INSTANCE pInstance); + std::optional GetComputerName(); + + absl::Mutex mutex_; + std::unique_ptr dns_service_notification_ = nullptr; + bool is_service_started_ ABSL_GUARDED_BY(mutex_) = false; + std::unique_ptr dns_service_instance_name_ + ABSL_GUARDED_BY(mutex_); + std::unique_ptr host_name_ ABSL_GUARDED_BY(mutex_); + DNS_SERVICE_INSTANCE + dns_service_instance_ ABSL_GUARDED_BY(mutex_); + DNS_SERVICE_REGISTER_REQUEST dns_service_register_request_ + ABSL_GUARDED_BY(mutex_); + std::vector text_keys_ ABSL_GUARDED_BY(mutex_); + std::vector text_values_ ABSL_GUARDED_BY(mutex_); + PWSTR* keys_ ABSL_GUARDED_BY(mutex_) = nullptr; + PWSTR* values_ ABSL_GUARDED_BY(mutex_) = nullptr; +}; + +} // namespace nearby::windows + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_WINDOWS_WIFI_LAN_MDNS_H_