diff options
author | Handa Wang <handaw@google.com> | 2024-02-23 02:09:02 +0000 |
---|---|---|
committer | Handa Wang <handaw@google.com> | 2024-03-22 06:45:15 +0000 |
commit | 5f180108a775e25a4d7d4377fd95d4310357340c (patch) | |
tree | db48ff9fe1f5327356f9114225d019636ed8ba83 | |
parent | d9e60db7f6a7050f7491fc09d52c2f9c781df428 (diff) | |
download | ot-br-posix-5f180108a775e25a4d7d4377fd95d4310357340c.tar.gz |
[mdns] support discovering and resolving services
Bug: 305866779
Test: atest ThreadNetworkIntegrationTests:android.net.thread.ServiceDiscoveryTest
Change-Id: I9cc34964e1f32077caa643778826e6271883d0c3
6 files changed, 408 insertions, 18 deletions
@@ -141,7 +141,7 @@ cc_defaults { // the related source files. "-DOTBR_ENABLE_MDNS_MDNSSD=1", "-DOTBR_ENABLE_SRP_ADVERTISING_PROXY=1", - "-DOTBR_ENABLE_DNSSD_DISCOVERY_PROXY=0", + "-DOTBR_ENABLE_DNSSD_DISCOVERY_PROXY=1", "-DOTBR_ENABLE_SRP_SERVER_AUTO_ENABLE_MODE=1", "-DOTBR_PACKAGE_NAME=\"OTBR_AGENT\"", "-DOTBR_STOP_BORDER_AGENT_ON_INIT=1", diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl new file mode 100644 index 00000000..21de2d12 --- /dev/null +++ b/src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.server.thread.openthread; + +/** Receives the information when a service instance is found/lost. */ +oneway interface INsdDiscoverServiceCallback { + void onServiceDiscovered(in String name, + in String type, + boolean isFound); +} diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl index f3fdf6cb..84549f9b 100644 --- a/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl +++ b/src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl @@ -30,6 +30,8 @@ package com.android.server.thread.openthread; import com.android.server.thread.openthread.DnsTxtAttribute; import com.android.server.thread.openthread.INsdStatusReceiver; +import com.android.server.thread.openthread.INsdDiscoverServiceCallback; +import com.android.server.thread.openthread.INsdResolveServiceCallback; /** * The service which supports mDNS advertising and discovery by {@link NsdManager}. @@ -94,4 +96,58 @@ oneway interface INsdPublisher { /** Resets the NsdPublisher, i.e. clear all registrations. */ void reset(); + + /** + * Discovers mDNS services of a specific type. + * + * <p>To stop discovering services, the caller must pass in the same listener ID which was used + * when starting discoverying the services. + * + * @param type the service type + * @param callback the callback when a service is found/lost + * @param listenerId the ID of the NsdManager.DiscoveryListener which is used to identify the + * service discovery operation + */ + void discoverService(in String type, + in INsdDiscoverServiceCallback callback, + int listenerId); + + /** + * Stops discovering services of a specific type. + * + * <p>To stop discovering services, the caller must pass in the same listener ID which was used + * when starting discoverying the services. + * + * @param listenerId the ID of the NsdManager.DiscoveryListener which is used to identify the + * service discovery operation + */ + void stopServiceDiscovery(int listenerId); + + /** + * Resolves an mDNS service instance. + * + * <p>To stop resolving a service, the caller must pass in the same listener ID which was used + * when starting resolving the service. + * + * @param name the service instance name + * @param type the service type + * @param callback the callback when a service is updated + * @param listenerId the ID of the NsdManager.ServiceInfoCallback which is used to identify the + * service resolution operation + */ + void resolveService(in String name, + in String type, + in INsdResolveServiceCallback callback, + int listenerId); + + /** + * Stops resolving an mDNS service instance. + * + * <p>To stop resolving a service, the caller must pass in the same listener ID which was used + * when starting resolving the service. + * + * @param listenerId the ID of the NsdManager.ServiceInfoCallback which is used to identify the + * service resolution operation + */ + void stopServiceResolution(int listenerId); } diff --git a/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl b/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl new file mode 100644 index 00000000..48d7b5cc --- /dev/null +++ b/src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.android.server.thread.openthread; + +import com.android.server.thread.openthread.DnsTxtAttribute; + +/** Receives the information of a resolved service instance. */ +oneway interface INsdResolveServiceCallback { + void onServiceResolved(in String hostname, + in String name, + in String type, + int port, + in List<String> addresses, + in List<DnsTxtAttribute> txt, + int ttlSeconds); +} diff --git a/src/android/mdns_publisher.cpp b/src/android/mdns_publisher.cpp index f9e84052..6febeccb 100644 --- a/src/android/mdns_publisher.cpp +++ b/src/android/mdns_publisher.cpp @@ -38,9 +38,6 @@ Mdns::Publisher *Mdns::Publisher::Create(Mdns::Publisher::StateCallback aCallbac } namespace Android { - -using Status = ::ndk::ScopedAStatus; - otbrError DnsErrorToOtbrErrorImpl(int32_t aError) { return aError == 0 ? OTBR_ERROR_NONE : OTBR_ERROR_MDNS; @@ -61,6 +58,56 @@ Status MdnsPublisher::NsdStatusReceiver::onSuccess() return Status::ok(); } +Status MdnsPublisher::NsdDiscoverServiceCallback::onServiceDiscovered(const std::string &aName, + const std::string &aType, + bool aIsFound) +{ + VerifyOrExit(aIsFound, mSubscription.mPublisher.OnServiceRemoved(0, aType, aName)); + + mSubscription.Resolve(aName, aType); + +exit: + return Status::ok(); +} + +Status MdnsPublisher::NsdResolveServiceCallback::onServiceResolved(const std::string &aHostname, + const std::string &aName, + const std::string &aType, + int aPort, + const std::vector<std::string> &aAddresses, + const std::vector<DnsTxtAttribute> &aTxt, + int aTtlSeconds) +{ + DiscoveredInstanceInfo info; + TxtList txtList; + + info.mHostName = aHostname + ".local."; + info.mName = aName; + info.mPort = aPort; + info.mTtl = std::clamp(aTtlSeconds, kMinResolvedTtl, kMaxResolvedTtl); + for (const auto &addressStr : aAddresses) + { + Ip6Address address; + int error = Ip6Address::FromString(addressStr.c_str(), address); + + if (error != OTBR_ERROR_NONE) + { + otbrLogInfo("Failed to parse resolved IPv6 address: %s", addressStr.c_str()); + continue; + } + info.mAddresses.push_back(address); + } + for (const auto &entry : aTxt) + { + txtList.emplace_back(entry.name.c_str(), entry.value.data(), entry.value.size()); + } + EncodeTxtData(txtList, info.mTxtData); + + mSubscription.mPublisher.OnServiceResolved(aType, info); + + return Status::ok(); +} + void MdnsPublisher::SetINsdPublisher(std::shared_ptr<INsdPublisher> aINsdPublisher) { otbrLogInfo("Set INsdPublisher %p", aINsdPublisher.get()); @@ -92,6 +139,18 @@ std::shared_ptr<MdnsPublisher::NsdStatusReceiver> CreateReceiver(Mdns::Publisher return ndk::SharedRefBase::make<MdnsPublisher::NsdStatusReceiver>(std::move(aCallback)); } +std::shared_ptr<MdnsPublisher::NsdDiscoverServiceCallback> CreateNsdDiscoverServiceCallback( + MdnsPublisher::ServiceSubscription &aServiceSubscription) +{ + return ndk::SharedRefBase::make<MdnsPublisher::NsdDiscoverServiceCallback>(aServiceSubscription); +} + +std::shared_ptr<MdnsPublisher::NsdResolveServiceCallback> CreateNsdResolveServiceCallback( + MdnsPublisher::ServiceSubscription &aServiceSubscription) +{ + return ndk::SharedRefBase::make<MdnsPublisher::NsdResolveServiceCallback>(aServiceSubscription); +} + void DieForNotImplemented(const char *aFuncName) { VerifyOrDie(false, (std::string(aFuncName) + " is not implemented").c_str()); @@ -250,32 +309,62 @@ void MdnsPublisher::UnpublishKey(const std::string &aName, ResultCallback &&aCal void MdnsPublisher::SubscribeService(const std::string &aType, const std::string &aInstanceName) { - OTBR_UNUSED_VARIABLE(aType); - OTBR_UNUSED_VARIABLE(aInstanceName); + auto service = MakeUnique<ServiceSubscription>(aType, aInstanceName, *this, mNsdPublisher); - DieForNotImplemented(__func__); + VerifyOrExit(IsStarted(), otbrLogWarning("No platform mDNS implementation registered!")); + + mServiceSubscriptions.push_back(std::move(service)); + + otbrLogInfo("Subscribe service %s.%s (total %zu)", aInstanceName.c_str(), aType.c_str(), + mServiceSubscriptions.size()); + + if (aInstanceName.empty()) + { + mServiceSubscriptions.back()->Browse(); + } + else + { + mServiceSubscriptions.back()->Resolve(aInstanceName, aType); + } +exit: + return; } void MdnsPublisher::UnsubscribeService(const std::string &aType, const std::string &aInstanceName) { - OTBR_UNUSED_VARIABLE(aType); - OTBR_UNUSED_VARIABLE(aInstanceName); + ServiceSubscriptionList::iterator it; - DieForNotImplemented(__func__); + VerifyOrExit(IsStarted()); + + it = std::find_if(mServiceSubscriptions.begin(), mServiceSubscriptions.end(), + [&aType, &aInstanceName](const std::unique_ptr<ServiceSubscription> &aService) { + return aService->mType == aType && aService->mName == aInstanceName; + }); + + VerifyOrExit(it != mServiceSubscriptions.end(), + otbrLogWarning("The service %s.%s is already unsubscribed.", aInstanceName.c_str(), aType.c_str())); + + { + std::unique_ptr<ServiceSubscription> service = std::move(*it); + + mServiceSubscriptions.erase(it); + } + + otbrLogInfo("Unsubscribe service %s.%s (left %zu)", aInstanceName.c_str(), aType.c_str(), + mServiceSubscriptions.size()); + +exit: + return; } void MdnsPublisher::SubscribeHost(const std::string &aHostName) { OTBR_UNUSED_VARIABLE(aHostName); - - DieForNotImplemented(__func__); } void MdnsPublisher::UnsubscribeHost(const std::string &aHostName) { OTBR_UNUSED_VARIABLE(aHostName); - - DieForNotImplemented(__func__); } void MdnsPublisher::OnServiceResolveFailedImpl(const std::string &aType, @@ -344,5 +433,74 @@ exit: return; } +void MdnsPublisher::ServiceSubscription::Release(void) +{ + otbrLogInfo("Browsing service type %s", mType.c_str()); + + std::vector<std::string> instanceNames; + + for (const auto &nameAndResolvers : mResolvers) + { + instanceNames.push_back(nameAndResolvers.first); + } + for (const auto &name : instanceNames) + { + RemoveServiceResolver(name); + } + + mNsdPublisher->stopServiceDiscovery(mBrowseListenerId); +} + +void MdnsPublisher::ServiceSubscription::Browse(void) +{ + VerifyOrExit(mPublisher.IsStarted()); + + otbrLogInfo("Browsing service type %s", mType.c_str()); + + mNsdPublisher->discoverService(mType, CreateNsdDiscoverServiceCallback(*this), mBrowseListenerId); + +exit: + return; +} + +void MdnsPublisher::ServiceSubscription::Resolve(const std::string &aName, const std::string &aType) +{ + ServiceResolver *resolver = new ServiceResolver(mPublisher.AllocateListenerId(), mNsdPublisher); + + VerifyOrExit(mPublisher.IsStarted()); + + otbrLogInfo("Resolving service %s.%s", aName.c_str(), aType.c_str()); + + AddServiceResolver(aName, resolver); + mNsdPublisher->resolveService(aName, aType, CreateNsdResolveServiceCallback(*this), resolver->mListenerId); + +exit: + return; +} + +void MdnsPublisher::ServiceSubscription::AddServiceResolver(const std::string &aName, ServiceResolver *aResolver) +{ + mResolvers[aName].insert(aResolver); +} +void MdnsPublisher::ServiceSubscription::RemoveServiceResolver(const std::string &aName) +{ + int numResolvers = 0; + + VerifyOrExit(mResolvers.find(aName) != mResolvers.end()); + + numResolvers = mResolvers[aName].size(); + + for (auto resolver : mResolvers[aName]) + { + delete resolver; + } + + mResolvers.erase(aName); + +exit: + otbrLogDebug("Removed %d service resolver for instance %s", numResolvers, aName.c_str()); + return; +} + } // namespace Android } // namespace otbr diff --git a/src/android/mdns_publisher.hpp b/src/android/mdns_publisher.hpp index 4777ba3a..64793dc5 100644 --- a/src/android/mdns_publisher.hpp +++ b/src/android/mdns_publisher.hpp @@ -31,12 +31,18 @@ #include "mdns/mdns.hpp" +#include <aidl/com/android/server/thread/openthread/BnNsdDiscoverServiceCallback.h> +#include <aidl/com/android/server/thread/openthread/BnNsdResolveServiceCallback.h> #include <aidl/com/android/server/thread/openthread/BnNsdStatusReceiver.h> #include <aidl/com/android/server/thread/openthread/DnsTxtAttribute.h> #include <aidl/com/android/server/thread/openthread/INsdPublisher.h> +#include <set> namespace otbr { namespace Android { +using Status = ::ndk::ScopedAStatus; +using aidl::com::android::server::thread::openthread::BnNsdDiscoverServiceCallback; +using aidl::com::android::server::thread::openthread::BnNsdResolveServiceCallback; using aidl::com::android::server::thread::openthread::BnNsdStatusReceiver; using aidl::com::android::server::thread::openthread::DnsTxtAttribute; using aidl::com::android::server::thread::openthread::INsdPublisher; @@ -45,9 +51,10 @@ class MdnsPublisher : public Mdns::Publisher { public: explicit MdnsPublisher(Publisher::StateCallback aCallback) + : mStateCallback(std::move(aCallback)) + , mNextListenerId(0) + { - mNextListenerId = 0; - mStateCallback = std::move(aCallback); } ~MdnsPublisher(void) { Stop(); } @@ -91,14 +98,99 @@ public: { } - ::ndk::ScopedAStatus onSuccess(void) override; + Status onSuccess(void) override; - ::ndk::ScopedAStatus onError(int aError) override; + Status onError(int aError) override; private: Mdns::Publisher::ResultCallback mCallback; }; + struct ServiceResolver : private ::NonCopyable + { + explicit ServiceResolver(int aListenerId, std::shared_ptr<INsdPublisher> aNsdPublisher) + : mListenerId(aListenerId) + , mNsdPublisher(std::move(aNsdPublisher)) + { + } + + ~ServiceResolver(void) + { + if (mNsdPublisher) + { + mNsdPublisher->stopServiceResolution(mListenerId); + } + } + + int mListenerId; + std::shared_ptr<INsdPublisher> mNsdPublisher; + }; + + struct ServiceSubscription : private ::NonCopyable + { + explicit ServiceSubscription(std::string aType, + std::string aName, + MdnsPublisher &aPublisher, + std::shared_ptr<INsdPublisher> aNsdPublisher) + : mType(std::move(aType)) + , mName(std::move(aName)) + , mPublisher(aPublisher) + , mNsdPublisher(std::move(aNsdPublisher)) + , mBrowseListenerId(-1) + { + } + + ~ServiceSubscription(void) { Release(); } + + void Release(void); + void Browse(void); + void Resolve(const std::string &aName, const std::string &aType); + void AddServiceResolver(const std::string &aName, ServiceResolver *aResolver); + void RemoveServiceResolver(const std::string &aInstanceName); + + std::string mType; + std::string mName; + MdnsPublisher &mPublisher; + std::shared_ptr<INsdPublisher> mNsdPublisher; + int32_t mBrowseListenerId; + + std::map<std::string, std::set<ServiceResolver *>> mResolvers; + }; + + class NsdDiscoverServiceCallback : public BnNsdDiscoverServiceCallback + { + public: + explicit NsdDiscoverServiceCallback(ServiceSubscription &aSubscription) + : mSubscription(aSubscription) + { + } + + Status onServiceDiscovered(const std::string &aName, const std::string &aType, bool aIsFound); + + private: + ServiceSubscription &mSubscription; + }; + + class NsdResolveServiceCallback : public BnNsdResolveServiceCallback + { + public: + explicit NsdResolveServiceCallback(ServiceSubscription &aSubscription) + : mSubscription(aSubscription) + { + } + + Status onServiceResolved(const std::string &aHostname, + const std::string &aName, + const std::string &aType, + int aPort, + const std::vector<std::string> &aAddresses, + const std::vector<DnsTxtAttribute> &aTxt, + int aTtlSeconds); + + private: + ServiceSubscription &mSubscription; + }; + protected: otbrError PublishServiceImpl(const std::string &aHostName, const std::string &aName, @@ -179,11 +271,17 @@ private: std::weak_ptr<INsdPublisher> mNsdPublisher; }; + typedef std::vector<std::unique_ptr<ServiceSubscription>> ServiceSubscriptionList; + + static constexpr int kMinResolvedTtl = 1; + static constexpr int kMaxResolvedTtl = 10; + int32_t AllocateListenerId(void); StateCallback mStateCallback; int32_t mNextListenerId; std::shared_ptr<INsdPublisher> mNsdPublisher = nullptr; + ServiceSubscriptionList mServiceSubscriptions; }; } // namespace Android |