1 /*
2 * Copyright (c) 2021, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /**
30 * @file
31 * The file implements the DNS-SD Discovery Proxy.
32 */
33
34 #if OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
35
36 #define OTBR_LOG_TAG "DPROXY"
37
38 #include "sdp_proxy/discovery_proxy.hpp"
39
40 #include <algorithm>
41 #include <string>
42
43 #include <assert.h>
44
45 #include <openthread/dnssd_server.h>
46
47 #include "common/code_utils.hpp"
48 #include "common/dns_utils.hpp"
49 #include "common/logging.hpp"
50 #include "utils/dns_utils.hpp"
51 #include "utils/string_utils.hpp"
52
53 namespace otbr {
54 namespace Dnssd {
55
DnsLabelsEqual(const std::string & aLabel1,const std::string & aLabel2)56 static inline bool DnsLabelsEqual(const std::string &aLabel1, const std::string &aLabel2)
57 {
58 return StringUtils::EqualCaseInsensitive(aLabel1, aLabel2);
59 }
60
DiscoveryProxy(Ncp::RcpHost & aHost,Mdns::Publisher & aPublisher)61 DiscoveryProxy::DiscoveryProxy(Ncp::RcpHost &aHost, Mdns::Publisher &aPublisher)
62 : mHost(aHost)
63 , mMdnsPublisher(aPublisher)
64 , mIsEnabled(false)
65 {
66 mHost.RegisterResetHandler([this]() {
67 otDnssdQuerySetCallbacks(mHost.GetInstance(), &DiscoveryProxy::OnDiscoveryProxySubscribe,
68 &DiscoveryProxy::OnDiscoveryProxyUnsubscribe, this);
69 });
70 }
71
SetEnabled(bool aIsEnabled)72 void DiscoveryProxy::SetEnabled(bool aIsEnabled)
73 {
74 VerifyOrExit(IsEnabled() != aIsEnabled);
75 mIsEnabled = aIsEnabled;
76 if (mIsEnabled)
77 {
78 Start();
79 }
80 else
81 {
82 Stop();
83 }
84 exit:
85 return;
86 }
87
Start(void)88 void DiscoveryProxy::Start(void)
89 {
90 assert(mSubscriberId == 0);
91
92 otDnssdQuerySetCallbacks(mHost.GetInstance(), &DiscoveryProxy::OnDiscoveryProxySubscribe,
93 &DiscoveryProxy::OnDiscoveryProxyUnsubscribe, this);
94
95 mSubscriberId = mMdnsPublisher.AddSubscriptionCallbacks(
96 [this](const std::string &aType, const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo) {
97 if (!aInstanceInfo.mRemoved)
98 {
99 OnServiceDiscovered(aType, aInstanceInfo);
100 }
101 },
102
103 [this](const std::string &aHostName, const Mdns::Publisher::DiscoveredHostInfo &aHostInfo) {
104 OnHostDiscovered(aHostName, aHostInfo);
105 });
106
107 otbrLogInfo("Started");
108 }
109
Stop(void)110 void DiscoveryProxy::Stop(void)
111 {
112 otDnssdQuerySetCallbacks(mHost.GetInstance(), nullptr, nullptr, nullptr);
113
114 if (mSubscriberId > 0)
115 {
116 mMdnsPublisher.RemoveSubscriptionCallbacks(mSubscriberId);
117 mSubscriberId = 0;
118 }
119
120 otbrLogInfo("Stopped");
121 }
122
OnDiscoveryProxySubscribe(void * aContext,const char * aFullName)123 void DiscoveryProxy::OnDiscoveryProxySubscribe(void *aContext, const char *aFullName)
124 {
125 reinterpret_cast<DiscoveryProxy *>(aContext)->OnDiscoveryProxySubscribe(aFullName);
126 }
127
OnDiscoveryProxySubscribe(const char * aFullName)128 void DiscoveryProxy::OnDiscoveryProxySubscribe(const char *aFullName)
129 {
130 std::string fullName(aFullName);
131 DnsNameInfo nameInfo = SplitFullDnsName(fullName);
132
133 otbrLogInfo("Subscribe: %s", fullName.c_str());
134
135 if (GetServiceSubscriptionCount(nameInfo) == 1)
136 {
137 if (nameInfo.mHostName.empty())
138 {
139 mMdnsPublisher.SubscribeService(nameInfo.mServiceName, nameInfo.mInstanceName);
140 }
141 else
142 {
143 mMdnsPublisher.SubscribeHost(nameInfo.mHostName);
144 }
145 }
146 }
147
OnDiscoveryProxyUnsubscribe(void * aContext,const char * aFullName)148 void DiscoveryProxy::OnDiscoveryProxyUnsubscribe(void *aContext, const char *aFullName)
149 {
150 reinterpret_cast<DiscoveryProxy *>(aContext)->OnDiscoveryProxyUnsubscribe(aFullName);
151 }
152
OnDiscoveryProxyUnsubscribe(const char * aFullName)153 void DiscoveryProxy::OnDiscoveryProxyUnsubscribe(const char *aFullName)
154 {
155 std::string fullName(aFullName);
156 DnsNameInfo nameInfo = SplitFullDnsName(fullName);
157
158 otbrLogInfo("Unsubscribe: %s", fullName.c_str());
159
160 if (GetServiceSubscriptionCount(nameInfo) == 1)
161 {
162 if (nameInfo.mHostName.empty())
163 {
164 mMdnsPublisher.UnsubscribeService(nameInfo.mServiceName, nameInfo.mInstanceName);
165 }
166 else
167 {
168 mMdnsPublisher.UnsubscribeHost(nameInfo.mHostName);
169 }
170 }
171 }
172
OnServiceDiscovered(const std::string & aType,const Mdns::Publisher::DiscoveredInstanceInfo & aInstanceInfo)173 void DiscoveryProxy::OnServiceDiscovered(const std::string &aType,
174 const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo)
175 {
176 otDnssdServiceInstanceInfo instanceInfo;
177 const otDnssdQuery *query = nullptr;
178 std::string unescapedInstanceName = DnsUtils::UnescapeInstanceName(aInstanceInfo.mName);
179
180 otbrLogInfo("Service discovered: %s, instance %s hostname %s addresses %zu port %d priority %d "
181 "weight %d",
182 aType.c_str(), aInstanceInfo.mName.c_str(), aInstanceInfo.mHostName.c_str(),
183 aInstanceInfo.mAddresses.size(), aInstanceInfo.mPort, aInstanceInfo.mPriority, aInstanceInfo.mWeight);
184
185 instanceInfo.mAddressNum = aInstanceInfo.mAddresses.size();
186
187 if (!aInstanceInfo.mAddresses.empty())
188 {
189 instanceInfo.mAddresses = reinterpret_cast<const otIp6Address *>(&aInstanceInfo.mAddresses[0]);
190 }
191 else
192 {
193 instanceInfo.mAddresses = nullptr;
194 }
195
196 instanceInfo.mPort = aInstanceInfo.mPort;
197 instanceInfo.mPriority = aInstanceInfo.mPriority;
198 instanceInfo.mWeight = aInstanceInfo.mWeight;
199 instanceInfo.mTxtLength = static_cast<uint16_t>(aInstanceInfo.mTxtData.size());
200 instanceInfo.mTxtData = aInstanceInfo.mTxtData.data();
201 instanceInfo.mTtl = CapTtl(aInstanceInfo.mTtl);
202
203 while ((query = otDnssdGetNextQuery(mHost.GetInstance(), query)) != nullptr)
204 {
205 std::string instanceName;
206 std::string serviceName;
207 std::string hostName;
208 std::string domain;
209 char queryName[OT_DNS_MAX_NAME_SIZE];
210 otDnssdQueryType type = otDnssdGetQueryTypeAndName(query, &queryName);
211 otbrError splitError;
212
213 switch (type)
214 {
215 case OT_DNSSD_QUERY_TYPE_BROWSE:
216 splitError = SplitFullServiceName(queryName, serviceName, domain);
217 break;
218 case OT_DNSSD_QUERY_TYPE_RESOLVE:
219 splitError = SplitFullServiceInstanceName(queryName, instanceName, serviceName, domain);
220 break;
221 default:
222 splitError = OTBR_ERROR_NOT_FOUND;
223 break;
224 }
225 if (splitError != OTBR_ERROR_NONE)
226 {
227 // Incoming service/instance was not what current query wanted to see, move on.
228 continue;
229 }
230
231 if (DnsLabelsEqual(serviceName, aType) &&
232 (instanceName.empty() || DnsLabelsEqual(instanceName, unescapedInstanceName)))
233 {
234 std::string serviceFullName = aType + "." + domain;
235 std::string translatedHostName = TranslateDomain(aInstanceInfo.mHostName, domain);
236 std::string instanceFullName = unescapedInstanceName + "." + serviceFullName;
237
238 instanceInfo.mFullName = instanceFullName.c_str();
239 instanceInfo.mHostName = translatedHostName.c_str();
240
241 otDnssdQueryHandleDiscoveredServiceInstance(mHost.GetInstance(), serviceFullName.c_str(), &instanceInfo);
242 }
243 }
244 }
245
OnHostDiscovered(const std::string & aHostName,const Mdns::Publisher::DiscoveredHostInfo & aHostInfo)246 void DiscoveryProxy::OnHostDiscovered(const std::string &aHostName,
247 const Mdns::Publisher::DiscoveredHostInfo &aHostInfo)
248 {
249 otDnssdHostInfo hostInfo;
250 const otDnssdQuery *query = nullptr;
251 std::string resolvedHostName = aHostInfo.mHostName;
252
253 otbrLogInfo("Host discovered: %s hostname %s addresses %zu", aHostName.c_str(), aHostInfo.mHostName.c_str(),
254 aHostInfo.mAddresses.size());
255
256 if (resolvedHostName.empty())
257 {
258 resolvedHostName = aHostName + ".local.";
259 }
260
261 hostInfo.mAddressNum = aHostInfo.mAddresses.size();
262 if (!aHostInfo.mAddresses.empty())
263 {
264 hostInfo.mAddresses = reinterpret_cast<const otIp6Address *>(&aHostInfo.mAddresses[0]);
265 }
266 else
267 {
268 hostInfo.mAddresses = nullptr;
269 }
270
271 hostInfo.mTtl = CapTtl(aHostInfo.mTtl);
272
273 while ((query = otDnssdGetNextQuery(mHost.GetInstance(), query)) != nullptr)
274 {
275 std::string hostName, domain;
276 char queryName[OT_DNS_MAX_NAME_SIZE];
277 otDnssdQueryType type = otDnssdGetQueryTypeAndName(query, &queryName);
278 otbrError splitError;
279
280 OTBR_UNUSED_VARIABLE(splitError);
281
282 if (type != OT_DNSSD_QUERY_TYPE_RESOLVE_HOST)
283 {
284 continue;
285 }
286
287 splitError = SplitFullHostName(queryName, hostName, domain);
288
289 if (splitError != OTBR_ERROR_NONE)
290 {
291 continue;
292 }
293
294 if (DnsLabelsEqual(hostName, aHostName))
295 {
296 std::string hostFullName = TranslateDomain(resolvedHostName, domain);
297
298 otDnssdQueryHandleDiscoveredHost(mHost.GetInstance(), hostFullName.c_str(), &hostInfo);
299 }
300 }
301 }
302
TranslateDomain(const std::string & aName,const std::string & aTargetDomain)303 std::string DiscoveryProxy::TranslateDomain(const std::string &aName, const std::string &aTargetDomain)
304 {
305 std::string targetName;
306 std::string hostName;
307 std::string domain;
308
309 VerifyOrExit(OTBR_ERROR_NONE == SplitFullHostName(aName, hostName, domain), targetName = aName);
310 VerifyOrExit(DnsLabelsEqual(domain, "local."), targetName = aName);
311
312 targetName = hostName + "." + aTargetDomain;
313
314 exit:
315 otbrLogDebug("Translate domain: %s => %s", aName.c_str(), targetName.c_str());
316 return targetName;
317 }
318
GetServiceSubscriptionCount(const DnsNameInfo & aNameInfo) const319 int DiscoveryProxy::GetServiceSubscriptionCount(const DnsNameInfo &aNameInfo) const
320 {
321 const otDnssdQuery *query = nullptr;
322 int count = 0;
323
324 while ((query = otDnssdGetNextQuery(mHost.GetInstance(), query)) != nullptr)
325 {
326 char queryName[OT_DNS_MAX_NAME_SIZE];
327 DnsNameInfo queryInfo;
328
329 otDnssdGetQueryTypeAndName(query, &queryName);
330 queryInfo = SplitFullDnsName(queryName);
331
332 count += (DnsLabelsEqual(aNameInfo.mInstanceName, queryInfo.mInstanceName) &&
333 DnsLabelsEqual(aNameInfo.mServiceName, queryInfo.mServiceName) &&
334 DnsLabelsEqual(aNameInfo.mHostName, queryInfo.mHostName));
335 }
336
337 return count;
338 }
339
CapTtl(uint32_t aTtl)340 uint32_t DiscoveryProxy::CapTtl(uint32_t aTtl)
341 {
342 return std::min(aTtl, static_cast<uint32_t>(kServiceTtlCapLimit));
343 }
344
345 } // namespace Dnssd
346 } // namespace otbr
347
348 #endif // OTBR_ENABLE_DNSSD_DISCOVERY_PROXY
349