Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020-2022 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include "Advertiser.h"
19 :
20 : #include <inttypes.h>
21 : #include <stdio.h>
22 :
23 : #include "MinimalMdnsServer.h"
24 : #include "ServiceNaming.h"
25 :
26 : #include <app/icd/server/ICDServerConfig.h>
27 : #include <crypto/RandUtils.h>
28 : #include <lib/dnssd/Advertiser_ImplMinimalMdnsAllocator.h>
29 : #include <lib/dnssd/minimal_mdns/AddressPolicy.h>
30 : #include <lib/dnssd/minimal_mdns/ResponseSender.h>
31 : #include <lib/dnssd/minimal_mdns/Server.h>
32 : #include <lib/dnssd/minimal_mdns/core/FlatAllocatedQName.h>
33 : #include <lib/dnssd/minimal_mdns/responders/IP.h>
34 : #include <lib/dnssd/minimal_mdns/responders/Ptr.h>
35 : #include <lib/dnssd/minimal_mdns/responders/QueryResponder.h>
36 : #include <lib/dnssd/minimal_mdns/responders/Srv.h>
37 : #include <lib/dnssd/minimal_mdns/responders/Txt.h>
38 : #include <lib/support/BytesToHex.h>
39 : #include <lib/support/CHIPMem.h>
40 : #include <lib/support/IntrusiveList.h>
41 : #include <lib/support/StringBuilder.h>
42 :
43 : // Enable detailed mDNS logging for received queries
44 : #undef DETAIL_LOGGING
45 : // #define DETAIL_LOGGING
46 :
47 : namespace chip {
48 : namespace Dnssd {
49 : namespace {
50 :
51 : using chip::Platform::UniquePtr;
52 : using namespace mdns::Minimal;
53 :
54 : #ifdef DETAIL_LOGGING
55 : const char * ToString(QClass qClass)
56 : {
57 : switch (qClass)
58 : {
59 : case QClass::IN:
60 : return "IN";
61 : default:
62 : return "???";
63 : }
64 : }
65 :
66 : const char * ToString(QType qType)
67 : {
68 : switch (qType)
69 : {
70 : case QType::ANY:
71 : return "ANY";
72 : case QType::A:
73 : return "A";
74 : case QType::AAAA:
75 : return "AAAA";
76 : case QType::TXT:
77 : return "TXT";
78 : case QType::SRV:
79 : return "SRV";
80 : case QType::PTR:
81 : return "PTR";
82 : default:
83 : return "???";
84 : }
85 : }
86 :
87 : void LogQuery(const QueryData & data)
88 : {
89 : StringBuilder<128> logString;
90 :
91 : logString.Add("QUERY ").Add(ToString(data.GetClass())).Add("/").Add(ToString(data.GetType())).Add(": ");
92 :
93 : SerializedQNameIterator name = data.GetName();
94 : while (name.Next())
95 : {
96 : logString.Add(name.Value()).Add(".");
97 : }
98 :
99 : ChipLogDetail(Discovery, "%s", logString.c_str());
100 : }
101 : #else
102 24 : void LogQuery(const QueryData & data) {}
103 : #endif
104 :
105 : // Max number of records for operational = PTR, SRV, TXT, A, AAAA, I subtype.
106 : constexpr size_t kMaxOperationalRecords = 6;
107 :
108 : /// Represents an allocated operational responder.
109 : ///
110 : /// Wraps a QueryResponderAllocator.
111 : class OperationalQueryAllocator : public chip::IntrusiveListNodeBase<>
112 : {
113 : public:
114 : using Allocator = QueryResponderAllocator<kMaxOperationalRecords>;
115 :
116 : /// Prefer to use `::New` for allocations instead of this direct call
117 7 : explicit OperationalQueryAllocator(Allocator * allocator) : mAllocator(allocator) {}
118 7 : ~OperationalQueryAllocator()
119 : {
120 7 : chip::Platform::Delete(mAllocator);
121 7 : mAllocator = nullptr;
122 7 : }
123 :
124 125 : Allocator * GetAllocator() { return mAllocator; }
125 : const Allocator * GetAllocator() const { return mAllocator; }
126 :
127 : /// Allocate a new entry for this type.
128 : ///
129 : /// May return null on allocation failures.
130 7 : static OperationalQueryAllocator * New()
131 : {
132 7 : Allocator * allocator = chip::Platform::New<Allocator>();
133 :
134 7 : if (allocator == nullptr)
135 : {
136 0 : return nullptr;
137 : }
138 :
139 7 : OperationalQueryAllocator * result = chip::Platform::New<OperationalQueryAllocator>(allocator);
140 7 : if (result == nullptr)
141 : {
142 0 : chip::Platform::Delete(allocator);
143 0 : return nullptr;
144 : }
145 :
146 7 : return result;
147 : }
148 :
149 : private:
150 : Allocator * mAllocator = nullptr;
151 : };
152 :
153 : enum BroadcastAdvertiseType
154 : {
155 : kStarted, // Advertise at startup of all records added, as required by RFC 6762.
156 : kRemovingAll, // sent a TTL 0 for all records, as records are removed
157 : };
158 :
159 : class AdvertiserMinMdns : public ServiceAdvertiser,
160 : public MdnsPacketDelegate, // receive query packets
161 : public ParserDelegate // parses queries
162 : {
163 : public:
164 2 : AdvertiserMinMdns() : mResponseSender(&GlobalMinimalMdnsServer::Server())
165 : {
166 2 : GlobalMinimalMdnsServer::Instance().SetQueryDelegate(this);
167 :
168 2 : CHIP_ERROR err = mResponseSender.AddQueryResponder(mQueryResponderAllocatorCommissionable.GetQueryResponder());
169 :
170 2 : if (err != CHIP_NO_ERROR)
171 : {
172 0 : ChipLogError(Discovery, "Failed to set up commissionable responder: %" CHIP_ERROR_FORMAT, err.Format());
173 : }
174 :
175 2 : err = mResponseSender.AddQueryResponder(mQueryResponderAllocatorCommissioner.GetQueryResponder());
176 2 : if (err != CHIP_NO_ERROR)
177 : {
178 0 : ChipLogError(Discovery, "Failed to set up commissioner responder: %" CHIP_ERROR_FORMAT, err.Format());
179 : }
180 2 : }
181 2 : ~AdvertiserMinMdns() override { ClearServices(); }
182 :
183 : // Service advertiser
184 : CHIP_ERROR Init(chip::Inet::EndPointManager<chip::Inet::UDPEndPoint> * udpEndPointManager) override;
185 0 : bool IsInitialized() override { return mIsInitialized; }
186 : void Shutdown() override;
187 : CHIP_ERROR RemoveServices() override;
188 : CHIP_ERROR Advertise(const OperationalAdvertisingParameters & params) override;
189 : CHIP_ERROR Advertise(const CommissionAdvertisingParameters & params) override;
190 : CHIP_ERROR FinalizeServiceUpdate() override;
191 : CHIP_ERROR GetCommissionableInstanceName(char * instanceName, size_t maxLength) const override;
192 : CHIP_ERROR UpdateCommissionableInstanceName() override;
193 :
194 : // MdnsPacketDelegate
195 : void OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info) override;
196 :
197 : // ParserDelegate
198 24 : void OnHeader(ConstHeaderRef & header) override { mMessageId = header.GetMessageId(); }
199 0 : void OnResource(ResourceType type, const ResourceData & data) override {}
200 : void OnQuery(const QueryData & data) override;
201 :
202 : private:
203 : /// Advertise available records configured within the server.
204 : ///
205 : /// Establishes a type of 'Advertise all currently configured items'
206 : /// for a specific purpose (e.g. boot time advertises everything, shut-down
207 : /// removes all records by advertising a 0 TTL)
208 : void AdvertiseRecords(BroadcastAdvertiseType type);
209 :
210 : FullQName GetCommissioningTxtEntries(const CommissionAdvertisingParameters & params);
211 : FullQName GetOperationalTxtEntries(OperationalQueryAllocator::Allocator * allocator,
212 : const OperationalAdvertisingParameters & params);
213 :
214 : struct CommonTxtEntryStorage
215 : {
216 : // +2 for all to account for '=' and terminating nullchar
217 : char sessionIdleIntervalBuf[KeySize(TxtFieldKey::kSessionIdleInterval) + ValSize(TxtFieldKey::kSessionIdleInterval) + 2];
218 : char sessionActiveIntervalBuf[KeySize(TxtFieldKey::kSessionActiveInterval) + ValSize(TxtFieldKey::kSessionActiveInterval) +
219 : 2];
220 : char sessionActiveThresholdBuf[KeySize(TxtFieldKey::kSessionActiveThreshold) +
221 : ValSize(TxtFieldKey::kSessionActiveThreshold) + 2];
222 : char tcpSupportedBuf[KeySize(TxtFieldKey::kTcpSupported) + ValSize(TxtFieldKey::kTcpSupported) + 2];
223 : char operatingICDAsLITBuf[KeySize(TxtFieldKey::kLongIdleTimeICD) + ValSize(TxtFieldKey::kLongIdleTimeICD) + 2];
224 : };
225 : template <class Derived>
226 20 : CHIP_ERROR AddCommonTxtEntries(const BaseAdvertisingParams<Derived> & params, CommonTxtEntryStorage & storage,
227 : char ** txtFields, size_t & numTxtFields)
228 : {
229 20 : auto optionalMrp = params.GetLocalMRPConfig();
230 :
231 20 : if (optionalMrp.HasValue())
232 : {
233 5 : auto mrp = optionalMrp.Value();
234 :
235 : // An ICD operating as a LIT shall not advertise its slow polling interval.
236 : // Don't include the SII key in the advertisement when operating as so.
237 5 : if (params.GetICDModeToAdvertise() != ICDModeAdvertise::kLIT)
238 : {
239 5 : if (mrp.mIdleRetransTimeout > kMaxRetryInterval)
240 : {
241 0 : ChipLogProgress(Discovery,
242 : "MRP retry interval idle value exceeds allowed range of 1 hour, using maximum available");
243 0 : mrp.mIdleRetransTimeout = kMaxRetryInterval;
244 : }
245 5 : size_t writtenCharactersNumber =
246 5 : static_cast<size_t>(snprintf(storage.sessionIdleIntervalBuf, sizeof(storage.sessionIdleIntervalBuf),
247 : "SII=%" PRIu32, mrp.mIdleRetransTimeout.count()));
248 5 : VerifyOrReturnError((writtenCharactersNumber > 0) &&
249 : (writtenCharactersNumber < sizeof(storage.sessionIdleIntervalBuf)),
250 : CHIP_ERROR_INVALID_STRING_LENGTH);
251 :
252 5 : txtFields[numTxtFields++] = storage.sessionIdleIntervalBuf;
253 : }
254 :
255 : {
256 5 : if (mrp.mActiveRetransTimeout > kMaxRetryInterval)
257 : {
258 2 : ChipLogProgress(Discovery,
259 : "MRP retry interval active value exceeds allowed range of 1 hour, using maximum available");
260 2 : mrp.mActiveRetransTimeout = kMaxRetryInterval;
261 : }
262 5 : size_t writtenCharactersNumber =
263 5 : static_cast<size_t>(snprintf(storage.sessionActiveIntervalBuf, sizeof(storage.sessionActiveIntervalBuf),
264 : "SAI=%" PRIu32, mrp.mActiveRetransTimeout.count()));
265 5 : VerifyOrReturnError((writtenCharactersNumber > 0) &&
266 : (writtenCharactersNumber < sizeof(storage.sessionActiveIntervalBuf)),
267 : CHIP_ERROR_INVALID_STRING_LENGTH);
268 5 : txtFields[numTxtFields++] = storage.sessionActiveIntervalBuf;
269 : }
270 :
271 : {
272 5 : size_t writtenCharactersNumber =
273 5 : static_cast<size_t>(snprintf(storage.sessionActiveThresholdBuf, sizeof(storage.sessionActiveThresholdBuf),
274 5 : "SAT=%u", mrp.mActiveThresholdTime.count()));
275 5 : VerifyOrReturnError((writtenCharactersNumber > 0) &&
276 : (writtenCharactersNumber < sizeof(storage.sessionActiveThresholdBuf)),
277 : CHIP_ERROR_INVALID_STRING_LENGTH);
278 5 : txtFields[numTxtFields++] = storage.sessionActiveThresholdBuf;
279 : }
280 : }
281 20 : if (params.GetTcpSupported().HasValue())
282 : {
283 0 : size_t writtenCharactersNumber = static_cast<size_t>(
284 0 : snprintf(storage.tcpSupportedBuf, sizeof(storage.tcpSupportedBuf), "T=%d", params.GetTcpSupported().Value()));
285 0 : VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.tcpSupportedBuf)),
286 : CHIP_ERROR_INVALID_STRING_LENGTH);
287 0 : txtFields[numTxtFields++] = storage.tcpSupportedBuf;
288 : }
289 20 : if (params.GetICDModeToAdvertise() != ICDModeAdvertise::kNone)
290 : {
291 2 : size_t writtenCharactersNumber =
292 2 : static_cast<size_t>(snprintf(storage.operatingICDAsLITBuf, sizeof(storage.operatingICDAsLITBuf), "ICD=%d",
293 2 : (params.GetICDModeToAdvertise() == ICDModeAdvertise::kLIT)));
294 2 : VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.operatingICDAsLITBuf)),
295 : CHIP_ERROR_INVALID_STRING_LENGTH);
296 2 : txtFields[numTxtFields++] = storage.operatingICDAsLITBuf;
297 : }
298 20 : return CHIP_NO_ERROR;
299 20 : }
300 :
301 : IntrusiveList<OperationalQueryAllocator> mOperationalResponders;
302 :
303 : // Max number of records for commissionable = 7 x PTR (base + 6 sub types - _S, _L, _D, _T, _C, _A), SRV, TXT, A, AAAA
304 : static constexpr size_t kMaxCommissionRecords = 11;
305 : QueryResponderAllocator<kMaxCommissionRecords> mQueryResponderAllocatorCommissionable;
306 : QueryResponderAllocator<kMaxCommissionRecords> mQueryResponderAllocatorCommissioner;
307 :
308 : OperationalQueryAllocator::Allocator * FindOperationalAllocator(const FullQName & qname);
309 : OperationalQueryAllocator::Allocator * FindEmptyOperationalAllocator();
310 :
311 : void ClearServices();
312 :
313 : ResponseSender mResponseSender;
314 : uint8_t mCommissionableInstanceName[sizeof(uint64_t)];
315 :
316 : bool mIsInitialized = false;
317 :
318 : // current request handling
319 : const chip::Inet::IPPacketInfo * mCurrentSource = nullptr;
320 : uint16_t mMessageId = 0;
321 :
322 : const char * mEmptyTextEntries[1] = {
323 : "=",
324 : };
325 : };
326 :
327 24 : void AdvertiserMinMdns::OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info)
328 : {
329 : #ifdef DETAIL_LOGGING
330 : char srcAddressString[chip::Inet::IPAddress::kMaxStringLength];
331 : VerifyOrDie(info->SrcAddress.ToString(srcAddressString) != nullptr);
332 : ChipLogDetail(Discovery, "Received an mDNS query from %s", srcAddressString);
333 : #endif
334 :
335 24 : mCurrentSource = info;
336 24 : if (!ParsePacket(data, this))
337 : {
338 0 : ChipLogError(Discovery, "Failed to parse mDNS query");
339 : }
340 24 : mCurrentSource = nullptr;
341 24 : }
342 :
343 24 : void AdvertiserMinMdns::OnQuery(const QueryData & data)
344 : {
345 24 : if (mCurrentSource == nullptr)
346 : {
347 0 : ChipLogError(Discovery, "INTERNAL CONSISTENCY ERROR: missing query source");
348 0 : return;
349 : }
350 :
351 24 : LogQuery(data);
352 :
353 24 : const ResponseConfiguration defaultResponseConfiguration;
354 24 : CHIP_ERROR err = mResponseSender.Respond(mMessageId, data, mCurrentSource, defaultResponseConfiguration);
355 24 : if (err != CHIP_NO_ERROR)
356 : {
357 0 : ChipLogError(Discovery, "Failed to reply to query: %" CHIP_ERROR_FORMAT, err.Format());
358 : }
359 24 : }
360 :
361 16 : CHIP_ERROR AdvertiserMinMdns::Init(chip::Inet::EndPointManager<chip::Inet::UDPEndPoint> * udpEndPointManager)
362 : {
363 : // TODO: Per API documentation, Init() should be a no-op if mIsInitialized
364 : // is true. But we don't handle updates to our set of interfaces right now,
365 : // so rely on the logic in this function to shut down and restart the
366 : // GlobalMinimalMdnsServer to handle that.
367 16 : GlobalMinimalMdnsServer::Server().ShutdownEndpoints();
368 :
369 16 : if (!mIsInitialized)
370 : {
371 2 : UpdateCommissionableInstanceName();
372 : }
373 :
374 : // Re-set the server in the response sender in case this has been swapped in the
375 : // GlobalMinimalMdnsServer (used for testing).
376 16 : mResponseSender.SetServer(&GlobalMinimalMdnsServer::Server());
377 :
378 16 : ReturnErrorOnFailure(GlobalMinimalMdnsServer::Instance().StartServer(udpEndPointManager, kMdnsPort));
379 :
380 16 : ChipLogProgress(Discovery, "CHIP minimal mDNS started advertising.");
381 :
382 16 : AdvertiseRecords(BroadcastAdvertiseType::kStarted);
383 :
384 16 : mIsInitialized = true;
385 :
386 16 : return CHIP_NO_ERROR;
387 : }
388 :
389 3 : void AdvertiserMinMdns::Shutdown()
390 : {
391 3 : VerifyOrReturn(mIsInitialized);
392 :
393 2 : AdvertiseRecords(BroadcastAdvertiseType::kRemovingAll);
394 :
395 2 : GlobalMinimalMdnsServer::Server().Shutdown();
396 2 : mIsInitialized = false;
397 : }
398 :
399 20 : CHIP_ERROR AdvertiserMinMdns::RemoveServices()
400 : {
401 20 : VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
402 :
403 : // Send a "goodbye" packet for each RR being removed, as defined in RFC 6762.
404 : // This allows mDNS clients to remove stale cached records which may not be re-added with
405 : // subsequent Advertise() calls. In the case the same records are re-added, this extra
406 : // is not harmful though suboptimal, so this is a subject to improvement in the future.
407 19 : AdvertiseRecords(BroadcastAdvertiseType::kRemovingAll);
408 19 : ClearServices();
409 :
410 19 : return CHIP_NO_ERROR;
411 : }
412 :
413 21 : void AdvertiserMinMdns::ClearServices()
414 : {
415 28 : while (mOperationalResponders.begin() != mOperationalResponders.end())
416 : {
417 7 : auto it = mOperationalResponders.begin();
418 :
419 : // Need to free the memory once it is out of the list
420 7 : OperationalQueryAllocator * ptr = &*it;
421 :
422 : // Mark as unused
423 7 : ptr->GetAllocator()->Clear();
424 :
425 7 : CHIP_ERROR err = mResponseSender.RemoveQueryResponder(ptr->GetAllocator()->GetQueryResponder());
426 7 : if (err != CHIP_NO_ERROR)
427 : {
428 0 : ChipLogError(Discovery, "Failed to remove query responder: %" CHIP_ERROR_FORMAT, err.Format());
429 : }
430 :
431 7 : mOperationalResponders.Remove(ptr);
432 :
433 : // Finally release the memory
434 7 : chip::Platform::Delete(ptr);
435 : }
436 :
437 21 : mQueryResponderAllocatorCommissionable.Clear();
438 21 : mQueryResponderAllocatorCommissioner.Clear();
439 21 : }
440 :
441 8 : OperationalQueryAllocator::Allocator * AdvertiserMinMdns::FindOperationalAllocator(const FullQName & qname)
442 : {
443 19 : for (auto & it : mOperationalResponders)
444 : {
445 12 : if (it.GetAllocator()->GetResponder(QType::SRV, qname) != nullptr)
446 : {
447 1 : return it.GetAllocator();
448 : }
449 : }
450 :
451 7 : return nullptr;
452 : }
453 :
454 7 : OperationalQueryAllocator::Allocator * AdvertiserMinMdns::FindEmptyOperationalAllocator()
455 : {
456 7 : OperationalQueryAllocator * result = OperationalQueryAllocator::New();
457 :
458 7 : if (result == nullptr)
459 : {
460 0 : return nullptr;
461 : }
462 :
463 7 : CHIP_ERROR err = mResponseSender.AddQueryResponder(result->GetAllocator()->GetQueryResponder());
464 :
465 7 : if (err != CHIP_NO_ERROR)
466 : {
467 0 : ChipLogError(Discovery, "Failed to register query responder: %" CHIP_ERROR_FORMAT, err.Format());
468 0 : Platform::Delete(result);
469 0 : return nullptr;
470 : }
471 :
472 7 : mOperationalResponders.PushBack(result);
473 7 : return result->GetAllocator();
474 : }
475 :
476 8 : CHIP_ERROR AdvertiserMinMdns::Advertise(const OperationalAdvertisingParameters & params)
477 : {
478 8 : VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
479 :
480 8 : char nameBuffer[Operational::kInstanceNameMaxLength + 1] = "";
481 :
482 : // need to set server name
483 8 : ReturnErrorOnFailure(MakeInstanceName(nameBuffer, sizeof(nameBuffer), params.GetPeerId()));
484 :
485 8 : QNamePart nameCheckParts[] = { nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain };
486 8 : FullQName nameCheck = FullQName(nameCheckParts);
487 8 : auto * operationalAllocator = FindOperationalAllocator(nameCheck);
488 8 : if (operationalAllocator != nullptr)
489 : {
490 1 : operationalAllocator->Clear();
491 : }
492 : else
493 : {
494 7 : operationalAllocator = FindEmptyOperationalAllocator();
495 7 : if (operationalAllocator == nullptr)
496 : {
497 0 : ChipLogError(Discovery, "Failed to find an open operational allocator");
498 0 : return CHIP_ERROR_NO_MEMORY;
499 : }
500 : }
501 :
502 8 : FullQName serviceName = operationalAllocator->AllocateQName(kOperationalServiceName, kOperationalProtocol, kLocalDomain);
503 : FullQName instanceName =
504 8 : operationalAllocator->AllocateQName(nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain);
505 :
506 8 : ReturnErrorOnFailure(MakeHostName(nameBuffer, sizeof(nameBuffer), params.GetMac()));
507 8 : FullQName hostName = operationalAllocator->AllocateQName(nameBuffer, kLocalDomain);
508 :
509 8 : if ((serviceName.nameCount == 0) || (instanceName.nameCount == 0) || (hostName.nameCount == 0))
510 : {
511 0 : ChipLogError(Discovery, "Failed to allocate QNames.");
512 0 : return CHIP_ERROR_NO_MEMORY;
513 : }
514 :
515 16 : if (!operationalAllocator->AddResponder<PtrResponder>(serviceName, instanceName)
516 8 : .SetReportAdditional(instanceName)
517 8 : .SetReportInServiceListing(true)
518 8 : .IsValid())
519 : {
520 0 : ChipLogError(Discovery, "Failed to add service PTR record mDNS responder");
521 0 : return CHIP_ERROR_NO_MEMORY;
522 : }
523 :
524 : // We are the sole owner of our instanceName, so records keyed on the
525 : // instanceName should have the cache-flush bit set.
526 8 : SrvResourceRecord srvRecord(instanceName, hostName, params.GetPort());
527 8 : srvRecord.SetCacheFlush(true);
528 8 : if (!operationalAllocator->AddResponder<SrvResponder>(srvRecord).SetReportAdditional(hostName).IsValid())
529 : {
530 0 : ChipLogError(Discovery, "Failed to add SRV record mDNS responder");
531 0 : return CHIP_ERROR_NO_MEMORY;
532 : }
533 :
534 8 : TxtResourceRecord txtRecord(instanceName, GetOperationalTxtEntries(operationalAllocator, params));
535 8 : txtRecord.SetCacheFlush(true);
536 8 : if (!operationalAllocator->AddResponder<TxtResponder>(txtRecord).SetReportAdditional(hostName).IsValid())
537 : {
538 0 : ChipLogError(Discovery, "Failed to add TXT record mDNS responder");
539 0 : return CHIP_ERROR_NO_MEMORY;
540 : }
541 :
542 8 : if (!operationalAllocator->AddResponder<IPv6Responder>(hostName).IsValid())
543 : {
544 0 : ChipLogError(Discovery, "Failed to add IPv6 mDNS responder");
545 0 : return CHIP_ERROR_NO_MEMORY;
546 : }
547 :
548 8 : if (params.IsIPv4Enabled())
549 : {
550 8 : if (!operationalAllocator->AddResponder<IPv4Responder>(hostName).IsValid())
551 : {
552 0 : ChipLogError(Discovery, "Failed to add IPv4 mDNS responder");
553 0 : return CHIP_ERROR_NO_MEMORY;
554 : }
555 : }
556 8 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
557 8 : DiscoveryFilter(DiscoveryFilterType::kCompressedFabricId, params.GetPeerId().GetCompressedFabricId()));
558 8 : FullQName compressedFabricIdSubtype = operationalAllocator->AllocateQName(
559 : nameBuffer, kSubtypeServiceNamePart, kOperationalServiceName, kOperationalProtocol, kLocalDomain);
560 8 : ReturnErrorCodeIf(compressedFabricIdSubtype.nameCount == 0, CHIP_ERROR_NO_MEMORY);
561 :
562 16 : if (!operationalAllocator->AddResponder<PtrResponder>(compressedFabricIdSubtype, instanceName)
563 8 : .SetReportAdditional(instanceName)
564 8 : .SetReportInServiceListing(true)
565 8 : .IsValid())
566 : {
567 0 : ChipLogError(Discovery, "Failed to add device type PTR record mDNS responder");
568 0 : return CHIP_ERROR_NO_MEMORY;
569 : }
570 :
571 8 : ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Operational device'; instance name: %s.", instanceName.names[0]);
572 :
573 8 : AdvertiseRecords(BroadcastAdvertiseType::kStarted);
574 :
575 8 : ChipLogProgress(Discovery, "mDNS service published: %s.%s", StringOrNullMarker(instanceName.names[1]),
576 : StringOrNullMarker(instanceName.names[2]));
577 :
578 8 : return CHIP_NO_ERROR;
579 8 : }
580 :
581 22 : CHIP_ERROR AdvertiserMinMdns::FinalizeServiceUpdate()
582 : {
583 22 : VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
584 22 : return CHIP_NO_ERROR;
585 : }
586 :
587 13 : CHIP_ERROR AdvertiserMinMdns::GetCommissionableInstanceName(char * instanceName, size_t maxLength) const
588 : {
589 13 : if (maxLength < (Commission::kInstanceNameMaxLength + 1))
590 : {
591 0 : return CHIP_ERROR_NO_MEMORY;
592 : }
593 :
594 13 : return chip::Encoding::BytesToUppercaseHexString(&mCommissionableInstanceName[0], sizeof(mCommissionableInstanceName),
595 13 : instanceName, maxLength);
596 : }
597 :
598 8 : CHIP_ERROR AdvertiserMinMdns::UpdateCommissionableInstanceName()
599 : {
600 8 : uint64_t random_instance_name = chip::Crypto::GetRandU64();
601 : static_assert(sizeof(mCommissionableInstanceName) == sizeof(random_instance_name), "Not copying the right amount of data");
602 8 : memcpy(&mCommissionableInstanceName[0], &random_instance_name, sizeof(mCommissionableInstanceName));
603 8 : return CHIP_NO_ERROR;
604 : }
605 :
606 12 : CHIP_ERROR AdvertiserMinMdns::Advertise(const CommissionAdvertisingParameters & params)
607 : {
608 12 : VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE);
609 :
610 12 : if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
611 : {
612 12 : mQueryResponderAllocatorCommissionable.Clear();
613 : }
614 : else
615 : {
616 0 : mQueryResponderAllocatorCommissioner.Clear();
617 : }
618 :
619 : // TODO: need to detect colisions here
620 12 : char nameBuffer[64] = "";
621 12 : ReturnErrorOnFailure(GetCommissionableInstanceName(nameBuffer, sizeof(nameBuffer)));
622 :
623 : QueryResponderAllocator<kMaxCommissionRecords> * allocator =
624 12 : params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? &mQueryResponderAllocatorCommissionable
625 12 : : &mQueryResponderAllocatorCommissioner;
626 12 : const char * serviceType = params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode
627 : ? kCommissionableServiceName
628 12 : : kCommissionerServiceName;
629 :
630 12 : FullQName serviceName = allocator->AllocateQName(serviceType, kCommissionProtocol, kLocalDomain);
631 12 : FullQName instanceName = allocator->AllocateQName(nameBuffer, serviceType, kCommissionProtocol, kLocalDomain);
632 :
633 12 : ReturnErrorOnFailure(MakeHostName(nameBuffer, sizeof(nameBuffer), params.GetMac()));
634 12 : FullQName hostName = allocator->AllocateQName(nameBuffer, kLocalDomain);
635 :
636 12 : if ((serviceName.nameCount == 0) || (instanceName.nameCount == 0) || (hostName.nameCount == 0))
637 : {
638 0 : ChipLogError(Discovery, "Failed to allocate QNames.");
639 0 : return CHIP_ERROR_NO_MEMORY;
640 : }
641 :
642 24 : if (!allocator->AddResponder<PtrResponder>(serviceName, instanceName)
643 12 : .SetReportAdditional(instanceName)
644 12 : .SetReportInServiceListing(true)
645 12 : .IsValid())
646 : {
647 0 : ChipLogError(Discovery, "Failed to add service PTR record mDNS responder");
648 0 : return CHIP_ERROR_NO_MEMORY;
649 : }
650 :
651 24 : if (!allocator->AddResponder<SrvResponder>(SrvResourceRecord(instanceName, hostName, params.GetPort()))
652 12 : .SetReportAdditional(hostName)
653 12 : .IsValid())
654 : {
655 0 : ChipLogError(Discovery, "Failed to add SRV record mDNS responder");
656 0 : return CHIP_ERROR_NO_MEMORY;
657 : }
658 12 : if (!allocator->AddResponder<IPv6Responder>(hostName).IsValid())
659 : {
660 0 : ChipLogError(Discovery, "Failed to add IPv6 mDNS responder");
661 0 : return CHIP_ERROR_NO_MEMORY;
662 : }
663 :
664 12 : if (params.IsIPv4Enabled())
665 : {
666 12 : if (!allocator->AddResponder<IPv4Responder>(hostName).IsValid())
667 : {
668 0 : ChipLogError(Discovery, "Failed to add IPv4 mDNS responder");
669 0 : return CHIP_ERROR_NO_MEMORY;
670 : }
671 : }
672 :
673 12 : if (params.GetVendorId().HasValue())
674 : {
675 11 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
676 22 : DiscoveryFilter(DiscoveryFilterType::kVendorId, params.GetVendorId().Value()));
677 : FullQName vendorServiceName =
678 11 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
679 11 : ReturnErrorCodeIf(vendorServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
680 :
681 22 : if (!allocator->AddResponder<PtrResponder>(vendorServiceName, instanceName)
682 11 : .SetReportAdditional(instanceName)
683 11 : .SetReportInServiceListing(true)
684 11 : .IsValid())
685 : {
686 0 : ChipLogError(Discovery, "Failed to add vendor PTR record mDNS responder");
687 0 : return CHIP_ERROR_NO_MEMORY;
688 : }
689 : }
690 :
691 12 : if (params.GetDeviceType().HasValue())
692 : {
693 3 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
694 6 : DiscoveryFilter(DiscoveryFilterType::kDeviceType, params.GetDeviceType().Value()));
695 : FullQName vendorServiceName =
696 3 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
697 3 : ReturnErrorCodeIf(vendorServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
698 :
699 6 : if (!allocator->AddResponder<PtrResponder>(vendorServiceName, instanceName)
700 3 : .SetReportAdditional(instanceName)
701 3 : .SetReportInServiceListing(true)
702 3 : .IsValid())
703 : {
704 0 : ChipLogError(Discovery, "Failed to add device type PTR record mDNS responder");
705 0 : return CHIP_ERROR_NO_MEMORY;
706 : }
707 : }
708 :
709 : // the following sub types only apply to commissionable node advertisements
710 12 : if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
711 : {
712 : {
713 12 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
714 12 : DiscoveryFilter(DiscoveryFilterType::kShortDiscriminator, params.GetShortDiscriminator()));
715 : FullQName shortServiceName =
716 12 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
717 12 : ReturnErrorCodeIf(shortServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
718 :
719 24 : if (!allocator->AddResponder<PtrResponder>(shortServiceName, instanceName)
720 12 : .SetReportAdditional(instanceName)
721 12 : .SetReportInServiceListing(true)
722 12 : .IsValid())
723 : {
724 0 : ChipLogError(Discovery, "Failed to add short discriminator PTR record mDNS responder");
725 0 : return CHIP_ERROR_NO_MEMORY;
726 : }
727 : }
728 :
729 : {
730 12 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer),
731 12 : DiscoveryFilter(DiscoveryFilterType::kLongDiscriminator, params.GetLongDiscriminator()));
732 : FullQName longServiceName =
733 12 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
734 12 : ReturnErrorCodeIf(longServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
735 24 : if (!allocator->AddResponder<PtrResponder>(longServiceName, instanceName)
736 12 : .SetReportAdditional(instanceName)
737 12 : .SetReportInServiceListing(true)
738 12 : .IsValid())
739 : {
740 0 : ChipLogError(Discovery, "Failed to add long discriminator PTR record mDNS responder");
741 0 : return CHIP_ERROR_NO_MEMORY;
742 : }
743 : }
744 :
745 12 : if (params.GetCommissioningMode() != CommissioningMode::kDisabled)
746 : {
747 11 : MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kCommissioningMode));
748 : FullQName longServiceName =
749 11 : allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain);
750 11 : ReturnErrorCodeIf(longServiceName.nameCount == 0, CHIP_ERROR_NO_MEMORY);
751 22 : if (!allocator->AddResponder<PtrResponder>(longServiceName, instanceName)
752 11 : .SetReportAdditional(instanceName)
753 11 : .SetReportInServiceListing(true)
754 11 : .IsValid())
755 : {
756 0 : ChipLogError(Discovery, "Failed to add commissioning mode PTR record mDNS responder");
757 0 : return CHIP_ERROR_NO_MEMORY;
758 : }
759 : }
760 : }
761 :
762 24 : if (!allocator->AddResponder<TxtResponder>(TxtResourceRecord(instanceName, GetCommissioningTxtEntries(params)))
763 12 : .SetReportAdditional(hostName)
764 12 : .IsValid())
765 : {
766 0 : ChipLogError(Discovery, "Failed to add TXT record mDNS responder");
767 0 : return CHIP_ERROR_NO_MEMORY;
768 : }
769 :
770 12 : if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
771 : {
772 12 : ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Commissionable node device'; instance name: %s.",
773 : StringOrNullMarker(instanceName.names[0]));
774 : }
775 : else
776 : {
777 0 : ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Commissioner device'; instance name: %s.",
778 : StringOrNullMarker(instanceName.names[0]));
779 : }
780 :
781 12 : AdvertiseRecords(BroadcastAdvertiseType::kStarted);
782 :
783 12 : ChipLogProgress(Discovery, "mDNS service published: %s.%s", StringOrNullMarker(instanceName.names[1]),
784 : StringOrNullMarker(instanceName.names[2]));
785 :
786 12 : return CHIP_NO_ERROR;
787 : }
788 :
789 8 : FullQName AdvertiserMinMdns::GetOperationalTxtEntries(OperationalQueryAllocator::Allocator * allocator,
790 : const OperationalAdvertisingParameters & params)
791 : {
792 : char * txtFields[OperationalAdvertisingParameters::kTxtMaxNumber];
793 8 : size_t numTxtFields = 0;
794 :
795 : struct CommonTxtEntryStorage commonStorage;
796 8 : AddCommonTxtEntries<OperationalAdvertisingParameters>(params, commonStorage, txtFields, numTxtFields);
797 8 : if (numTxtFields == 0)
798 : {
799 5 : return allocator->AllocateQNameFromArray(mEmptyTextEntries, 1);
800 : }
801 :
802 3 : return allocator->AllocateQNameFromArray(txtFields, numTxtFields);
803 : }
804 :
805 12 : FullQName AdvertiserMinMdns::GetCommissioningTxtEntries(const CommissionAdvertisingParameters & params)
806 : {
807 : char * txtFields[CommissionAdvertisingParameters::kTxtMaxNumber];
808 12 : size_t numTxtFields = 0;
809 :
810 : QueryResponderAllocator<kMaxCommissionRecords> * allocator =
811 12 : params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? &mQueryResponderAllocatorCommissionable
812 12 : : &mQueryResponderAllocatorCommissioner;
813 :
814 : char txtVidPid[chip::Dnssd::kKeyVendorProductMaxLength + 4];
815 12 : if (params.GetProductId().HasValue() && params.GetVendorId().HasValue())
816 : {
817 11 : snprintf(txtVidPid, sizeof(txtVidPid), "VP=%d+%d", params.GetVendorId().Value(), params.GetProductId().Value());
818 11 : txtFields[numTxtFields++] = txtVidPid;
819 : }
820 1 : else if (params.GetVendorId().HasValue())
821 : {
822 0 : snprintf(txtVidPid, sizeof(txtVidPid), "VP=%d", params.GetVendorId().Value());
823 0 : txtFields[numTxtFields++] = txtVidPid;
824 : }
825 :
826 : char txtDeviceType[chip::Dnssd::kKeyDeviceTypeMaxLength + 4];
827 12 : if (params.GetDeviceType().HasValue())
828 : {
829 3 : snprintf(txtDeviceType, sizeof(txtDeviceType), "DT=%" PRIu32, params.GetDeviceType().Value());
830 3 : txtFields[numTxtFields++] = txtDeviceType;
831 : }
832 :
833 : char txtDeviceName[chip::Dnssd::kKeyDeviceNameMaxLength + 4];
834 12 : if (params.GetDeviceName().HasValue())
835 : {
836 3 : snprintf(txtDeviceName, sizeof(txtDeviceName), "DN=%s", params.GetDeviceName().Value());
837 3 : txtFields[numTxtFields++] = txtDeviceName;
838 : }
839 : CommonTxtEntryStorage commonStorage;
840 12 : AddCommonTxtEntries<CommissionAdvertisingParameters>(params, commonStorage, txtFields, numTxtFields);
841 :
842 : // the following sub types only apply to commissionable node advertisements
843 : char txtDiscriminator[chip::Dnssd::kKeyLongDiscriminatorMaxLength + 3];
844 : char txtCommissioningMode[chip::Dnssd::kKeyCommissioningModeMaxLength + 4];
845 : char txtRotatingDeviceId[chip::Dnssd::kKeyRotatingDeviceIdMaxLength + 4];
846 : char txtPairingHint[chip::Dnssd::kKeyPairingInstructionMaxLength + 4];
847 : char txtPairingInstr[chip::Dnssd::kKeyPairingInstructionMaxLength + 4];
848 :
849 : // the following sub types only apply to commissioner discovery advertisements
850 : char txtCommissionerPasscode[chip::Dnssd::kKeyCommissionerPasscodeMaxLength + 4];
851 :
852 12 : if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode)
853 : {
854 : // a discriminator always exists
855 12 : snprintf(txtDiscriminator, sizeof(txtDiscriminator), "D=%d", params.GetLongDiscriminator());
856 12 : txtFields[numTxtFields++] = txtDiscriminator;
857 :
858 12 : snprintf(txtCommissioningMode, sizeof(txtCommissioningMode), "CM=%d", static_cast<int>(params.GetCommissioningMode()));
859 12 : txtFields[numTxtFields++] = txtCommissioningMode;
860 :
861 12 : if (params.GetRotatingDeviceId().HasValue())
862 : {
863 3 : snprintf(txtRotatingDeviceId, sizeof(txtRotatingDeviceId), "RI=%s", params.GetRotatingDeviceId().Value());
864 3 : txtFields[numTxtFields++] = txtRotatingDeviceId;
865 : }
866 :
867 12 : if (params.GetPairingHint().HasValue())
868 : {
869 11 : snprintf(txtPairingHint, sizeof(txtPairingHint), "PH=%d", params.GetPairingHint().Value());
870 11 : txtFields[numTxtFields++] = txtPairingHint;
871 : }
872 :
873 12 : if (params.GetPairingInstruction().HasValue())
874 : {
875 11 : snprintf(txtPairingInstr, sizeof(txtPairingInstr), "PI=%s", params.GetPairingInstruction().Value());
876 11 : txtFields[numTxtFields++] = txtPairingInstr;
877 : }
878 : }
879 : else
880 : {
881 0 : if (params.GetCommissionerPasscodeSupported().ValueOr(false))
882 : {
883 0 : snprintf(txtCommissionerPasscode, sizeof(txtCommissionerPasscode), "CP=%d", static_cast<int>(1));
884 0 : txtFields[numTxtFields++] = txtCommissionerPasscode;
885 : }
886 : }
887 12 : if (numTxtFields == 0)
888 : {
889 0 : return allocator->AllocateQNameFromArray(mEmptyTextEntries, 1);
890 : }
891 :
892 12 : return allocator->AllocateQNameFromArray(txtFields, numTxtFields);
893 : }
894 :
895 57 : void AdvertiserMinMdns::AdvertiseRecords(BroadcastAdvertiseType type)
896 : {
897 57 : ResponseConfiguration responseConfiguration;
898 57 : if (type == BroadcastAdvertiseType::kRemovingAll)
899 : {
900 : // make a "remove all records now" broadcast
901 21 : responseConfiguration.SetTtlSecondsOverride(0);
902 : }
903 :
904 57 : UniquePtr<ListenIterator> allInterfaces = GetAddressPolicy()->GetListenEndpoints();
905 57 : VerifyOrDieWithMsg(allInterfaces != nullptr, Discovery, "Failed to allocate memory for endpoints.");
906 :
907 57 : chip::Inet::InterfaceId interfaceId;
908 : chip::Inet::IPAddressType addressType;
909 :
910 171 : while (allInterfaces->Next(&interfaceId, &addressType))
911 : {
912 114 : UniquePtr<IpAddressIterator> allIps = GetAddressPolicy()->GetIpAddressesForEndpoint(interfaceId, addressType);
913 114 : VerifyOrDieWithMsg(allIps != nullptr, Discovery, "Failed to allocate memory for ip addresses.");
914 :
915 114 : chip::Inet::IPPacketInfo packetInfo;
916 :
917 114 : packetInfo.Clear();
918 :
919 : // advertising on every interface requires a valid IP address
920 : // since we use "BROADCAST" (unicast is false), we do not actually care about
921 : // the source IP address value, just that it has the right "type"
922 : //
923 : // NOTE: cannot use Broadcast address as the source as they have the type kAny.
924 : //
925 : // TODO: ideally we may want to have a destination that is explicit as "unicast/destIp"
926 : // vs "multicast/addressType". Such a change requires larger code updates.
927 114 : packetInfo.SrcAddress = chip::Inet::IPAddress::Loopback(addressType);
928 114 : packetInfo.DestAddress = BroadcastIpAddresses::Get(addressType);
929 114 : packetInfo.SrcPort = kMdnsPort;
930 114 : packetInfo.DestPort = kMdnsPort;
931 114 : packetInfo.Interface = interfaceId;
932 :
933 : // Advertise all records
934 : //
935 : // TODO: Consider advertising delta changes.
936 : //
937 : // Current advertisement does not have a concept of "delta" to only
938 : // advertise changes. Current implementation is to always
939 : // 1. advertise TTL=0 (clear all caches)
940 : // 2. advertise available records (with longer TTL)
941 : //
942 : // It would be nice if we could selectively advertise what changes, like
943 : // send TTL=0 for anything removed/about to be removed (and only those),
944 : // then only advertise new items added.
945 : //
946 : // This optimization likely will take more logic and state storage, so
947 : // for now it is not done.
948 114 : QueryData queryData(QType::PTR, QClass::IN, false /* unicast */);
949 114 : queryData.SetIsAnnounceBroadcast(true);
950 :
951 170 : for (auto & it : mOperationalResponders)
952 : {
953 56 : it.GetAllocator()->GetQueryResponder()->ClearBroadcastThrottle();
954 : }
955 114 : mQueryResponderAllocatorCommissionable.GetQueryResponder()->ClearBroadcastThrottle();
956 114 : mQueryResponderAllocatorCommissioner.GetQueryResponder()->ClearBroadcastThrottle();
957 :
958 114 : CHIP_ERROR err = mResponseSender.Respond(0, queryData, &packetInfo, responseConfiguration);
959 :
960 114 : if (err != CHIP_NO_ERROR)
961 : {
962 30 : ChipLogError(Discovery, "Failed to advertise records: %" CHIP_ERROR_FORMAT, err.Format());
963 : }
964 114 : }
965 :
966 : // Once all automatic broadcasts are done, allow immediate replies once.
967 85 : for (auto & it : mOperationalResponders)
968 : {
969 28 : it.GetAllocator()->GetQueryResponder()->ClearBroadcastThrottle();
970 : }
971 57 : mQueryResponderAllocatorCommissionable.GetQueryResponder()->ClearBroadcastThrottle();
972 57 : mQueryResponderAllocatorCommissioner.GetQueryResponder()->ClearBroadcastThrottle();
973 57 : }
974 :
975 : AdvertiserMinMdns gAdvertiser;
976 : } // namespace
977 :
978 65 : ServiceAdvertiser & ServiceAdvertiser::Instance()
979 : {
980 65 : return gAdvertiser;
981 : }
982 :
983 : } // namespace Dnssd
984 : } // namespace chip
|