aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHanda Wang <handaw@google.com>2024-02-23 02:09:02 +0000
committerHanda Wang <handaw@google.com>2024-03-22 06:45:15 +0000
commit5f180108a775e25a4d7d4377fd95d4310357340c (patch)
treedb48ff9fe1f5327356f9114225d019636ed8ba83
parentd9e60db7f6a7050f7491fc09d52c2f9c781df428 (diff)
downloadot-br-posix-5f180108a775e25a4d7d4377fd95d4310357340c.tar.gz
[mdns] support discovering and resolving services
Bug: 305866779 Test: atest ThreadNetworkIntegrationTests:android.net.thread.ServiceDiscoveryTest Change-Id: I9cc34964e1f32077caa643778826e6271883d0c3
-rw-r--r--Android.bp2
-rw-r--r--src/android/aidl/com/android/server/thread/openthread/INsdDiscoverServiceCallback.aidl36
-rw-r--r--src/android/aidl/com/android/server/thread/openthread/INsdPublisher.aidl56
-rw-r--r--src/android/aidl/com/android/server/thread/openthread/INsdResolveServiceCallback.aidl42
-rw-r--r--src/android/mdns_publisher.cpp184
-rw-r--r--src/android/mdns_publisher.hpp106
6 files changed, 408 insertions, 18 deletions
diff --git a/Android.bp b/Android.bp
index 3381e1d1..7cf0bb77 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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