/* * * Copyright (c) 2020-2022 Project CHIP Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "Advertiser.h" #include #include #include "MinimalMdnsServer.h" #include "ServiceNaming.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Enable detailed mDNS logging for received queries #undef DETAIL_LOGGING // #define DETAIL_LOGGING namespace chip { namespace Dnssd { namespace { using chip::Platform::UniquePtr; using namespace mdns::Minimal; #ifdef DETAIL_LOGGING const char * ToString(QClass qClass) { switch (qClass) { case QClass::IN: return "IN"; default: return "???"; } } const char * ToString(QType qType) { switch (qType) { case QType::ANY: return "ANY"; case QType::A: return "A"; case QType::AAAA: return "AAAA"; case QType::TXT: return "TXT"; case QType::SRV: return "SRV"; case QType::PTR: return "PTR"; default: return "???"; } } void LogQuery(const QueryData & data) { StringBuilder<128> logString; logString.Add("QUERY ").Add(ToString(data.GetClass())).Add("/").Add(ToString(data.GetType())).Add(": "); SerializedQNameIterator name = data.GetName(); while (name.Next()) { logString.Add(name.Value()).Add("."); } ChipLogDetail(Discovery, "%s", logString.c_str()); } #else void LogQuery(const QueryData & data) {} #endif // Max number of records for operational = PTR, SRV, TXT, A, AAAA, I subtype. constexpr size_t kMaxOperationalRecords = 6; /// Represents an allocated operational responder. /// /// Wraps a QueryResponderAllocator. class OperationalQueryAllocator : public chip::IntrusiveListNodeBase<> { public: using Allocator = QueryResponderAllocator; /// Prefer to use `::New` for allocations instead of this direct call explicit OperationalQueryAllocator(Allocator * allocator) : mAllocator(allocator) {} ~OperationalQueryAllocator() { chip::Platform::Delete(mAllocator); mAllocator = nullptr; } Allocator * GetAllocator() { return mAllocator; } const Allocator * GetAllocator() const { return mAllocator; } /// Allocate a new entry for this type. /// /// May return null on allocation failures. static OperationalQueryAllocator * New() { Allocator * allocator = chip::Platform::New(); if (allocator == nullptr) { return nullptr; } OperationalQueryAllocator * result = chip::Platform::New(allocator); if (result == nullptr) { chip::Platform::Delete(allocator); return nullptr; } return result; } private: Allocator * mAllocator = nullptr; }; enum BroadcastAdvertiseType { kStarted, // Advertise at startup of all records added, as required by RFC 6762. kRemovingAll, // sent a TTL 0 for all records, as records are removed }; class AdvertiserMinMdns : public ServiceAdvertiser, public MdnsPacketDelegate, // receive query packets public ParserDelegate // parses queries { public: AdvertiserMinMdns() : mResponseSender(&GlobalMinimalMdnsServer::Server()) { GlobalMinimalMdnsServer::Instance().SetQueryDelegate(this); CHIP_ERROR err = mResponseSender.AddQueryResponder(mQueryResponderAllocatorCommissionable.GetQueryResponder()); if (err != CHIP_NO_ERROR) { ChipLogError(Discovery, "Failed to set up commissionable responder: %" CHIP_ERROR_FORMAT, err.Format()); } err = mResponseSender.AddQueryResponder(mQueryResponderAllocatorCommissioner.GetQueryResponder()); if (err != CHIP_NO_ERROR) { ChipLogError(Discovery, "Failed to set up commissioner responder: %" CHIP_ERROR_FORMAT, err.Format()); } } ~AdvertiserMinMdns() override { ClearServices(); } // Service advertiser CHIP_ERROR Init(chip::Inet::EndPointManager * udpEndPointManager) override; bool IsInitialized() override { return mIsInitialized; } void Shutdown() override; CHIP_ERROR RemoveServices() override; CHIP_ERROR Advertise(const OperationalAdvertisingParameters & params) override; CHIP_ERROR Advertise(const CommissionAdvertisingParameters & params) override; CHIP_ERROR FinalizeServiceUpdate() override; CHIP_ERROR GetCommissionableInstanceName(char * instanceName, size_t maxLength) const override; CHIP_ERROR UpdateCommissionableInstanceName() override; // MdnsPacketDelegate void OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info) override; // ParserDelegate void OnHeader(ConstHeaderRef & header) override { mMessageId = header.GetMessageId(); } void OnResource(ResourceType type, const ResourceData & data) override {} void OnQuery(const QueryData & data) override; private: /// Advertise available records configured within the server. /// /// Establishes a type of 'Advertise all currently configured items' /// for a specific purpose (e.g. boot time advertises everything, shut-down /// removes all records by advertising a 0 TTL) void AdvertiseRecords(BroadcastAdvertiseType type); FullQName GetCommissioningTxtEntries(const CommissionAdvertisingParameters & params); FullQName GetOperationalTxtEntries(OperationalQueryAllocator::Allocator * allocator, const OperationalAdvertisingParameters & params); struct CommonTxtEntryStorage { // +2 for all to account for '=' and terminating nullchar char sessionIdleIntervalBuf[KeySize(TxtFieldKey::kSessionIdleInterval) + ValSize(TxtFieldKey::kSessionIdleInterval) + 2]; char sessionActiveIntervalBuf[KeySize(TxtFieldKey::kSessionActiveInterval) + ValSize(TxtFieldKey::kSessionActiveInterval) + 2]; char sessionActiveThresholdBuf[KeySize(TxtFieldKey::kSessionActiveThreshold) + ValSize(TxtFieldKey::kSessionActiveThreshold) + 2]; char tcpSupportedBuf[KeySize(TxtFieldKey::kTcpSupported) + ValSize(TxtFieldKey::kTcpSupported) + 2]; char operatingICDAsLITBuf[KeySize(TxtFieldKey::kLongIdleTimeICD) + ValSize(TxtFieldKey::kLongIdleTimeICD) + 2]; }; template CHIP_ERROR AddCommonTxtEntries(const BaseAdvertisingParams & params, CommonTxtEntryStorage & storage, char ** txtFields, size_t & numTxtFields) { if (const auto & optionalMrp = params.GetLocalMRPConfig(); optionalMrp.has_value()) { auto mrp = *optionalMrp; // An ICD operating as a LIT shall not advertise its slow polling interval. // Don't include the SII key in the advertisement when operating as so. if (params.GetICDModeToAdvertise() != ICDModeAdvertise::kLIT) { if (mrp.mIdleRetransTimeout > kMaxRetryInterval) { ChipLogProgress(Discovery, "MRP retry interval idle value exceeds allowed range of 1 hour, using maximum available"); mrp.mIdleRetransTimeout = kMaxRetryInterval; } size_t writtenCharactersNumber = static_cast(snprintf(storage.sessionIdleIntervalBuf, sizeof(storage.sessionIdleIntervalBuf), "SII=%" PRIu32, mrp.mIdleRetransTimeout.count())); VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.sessionIdleIntervalBuf)), CHIP_ERROR_INVALID_STRING_LENGTH); txtFields[numTxtFields++] = storage.sessionIdleIntervalBuf; } { if (mrp.mActiveRetransTimeout > kMaxRetryInterval) { ChipLogProgress(Discovery, "MRP retry interval active value exceeds allowed range of 1 hour, using maximum available"); mrp.mActiveRetransTimeout = kMaxRetryInterval; } size_t writtenCharactersNumber = static_cast(snprintf(storage.sessionActiveIntervalBuf, sizeof(storage.sessionActiveIntervalBuf), "SAI=%" PRIu32, mrp.mActiveRetransTimeout.count())); VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.sessionActiveIntervalBuf)), CHIP_ERROR_INVALID_STRING_LENGTH); txtFields[numTxtFields++] = storage.sessionActiveIntervalBuf; } { size_t writtenCharactersNumber = static_cast(snprintf(storage.sessionActiveThresholdBuf, sizeof(storage.sessionActiveThresholdBuf), "SAT=%u", mrp.mActiveThresholdTime.count())); VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.sessionActiveThresholdBuf)), CHIP_ERROR_INVALID_STRING_LENGTH); txtFields[numTxtFields++] = storage.sessionActiveThresholdBuf; } } if (params.GetTCPSupportModes() != TCPModeAdvertise::kNone) { size_t writtenCharactersNumber = static_cast(snprintf(storage.tcpSupportedBuf, sizeof(storage.tcpSupportedBuf), "T=%d", static_cast(params.GetTCPSupportModes()))); VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.tcpSupportedBuf)), CHIP_ERROR_INVALID_STRING_LENGTH); txtFields[numTxtFields++] = storage.tcpSupportedBuf; } if (params.GetICDModeToAdvertise() != ICDModeAdvertise::kNone) { size_t writtenCharactersNumber = static_cast(snprintf(storage.operatingICDAsLITBuf, sizeof(storage.operatingICDAsLITBuf), "ICD=%d", (params.GetICDModeToAdvertise() == ICDModeAdvertise::kLIT))); VerifyOrReturnError((writtenCharactersNumber > 0) && (writtenCharactersNumber < sizeof(storage.operatingICDAsLITBuf)), CHIP_ERROR_INVALID_STRING_LENGTH); txtFields[numTxtFields++] = storage.operatingICDAsLITBuf; } return CHIP_NO_ERROR; } IntrusiveList mOperationalResponders; // Max number of records for commissionable = 7 x PTR (base + 6 sub types - _S, _L, _D, _T, _C, _A), SRV, TXT, A, AAAA static constexpr size_t kMaxCommissionRecords = 11; QueryResponderAllocator mQueryResponderAllocatorCommissionable; QueryResponderAllocator mQueryResponderAllocatorCommissioner; OperationalQueryAllocator::Allocator * FindOperationalAllocator(const FullQName & qname); OperationalQueryAllocator::Allocator * FindEmptyOperationalAllocator(); void ClearServices(); ResponseSender mResponseSender; uint8_t mCommissionableInstanceName[sizeof(uint64_t)]; bool mIsInitialized = false; // current request handling const chip::Inet::IPPacketInfo * mCurrentSource = nullptr; uint16_t mMessageId = 0; const char * mEmptyTextEntries[1] = { "=", }; }; void AdvertiserMinMdns::OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info) { #ifdef DETAIL_LOGGING char srcAddressString[chip::Inet::IPAddress::kMaxStringLength]; VerifyOrDie(info->SrcAddress.ToString(srcAddressString) != nullptr); ChipLogDetail(Discovery, "Received an mDNS query from %s", srcAddressString); #endif mCurrentSource = info; if (!ParsePacket(data, this)) { ChipLogError(Discovery, "Failed to parse mDNS query"); } mCurrentSource = nullptr; } void AdvertiserMinMdns::OnQuery(const QueryData & data) { if (mCurrentSource == nullptr) { ChipLogError(Discovery, "INTERNAL CONSISTENCY ERROR: missing query source"); return; } LogQuery(data); const ResponseConfiguration defaultResponseConfiguration; CHIP_ERROR err = mResponseSender.Respond(mMessageId, data, mCurrentSource, defaultResponseConfiguration); if (err != CHIP_NO_ERROR) { ChipLogError(Discovery, "Failed to reply to query: %" CHIP_ERROR_FORMAT, err.Format()); } } CHIP_ERROR AdvertiserMinMdns::Init(chip::Inet::EndPointManager * udpEndPointManager) { // TODO: Per API documentation, Init() should be a no-op if mIsInitialized // is true. But we don't handle updates to our set of interfaces right now, // so rely on the logic in this function to shut down and restart the // GlobalMinimalMdnsServer to handle that. GlobalMinimalMdnsServer::Server().ShutdownEndpoints(); if (!mIsInitialized) { UpdateCommissionableInstanceName(); } // Re-set the server in the response sender in case this has been swapped in the // GlobalMinimalMdnsServer (used for testing). mResponseSender.SetServer(&GlobalMinimalMdnsServer::Server()); ReturnErrorOnFailure(GlobalMinimalMdnsServer::Instance().StartServer(udpEndPointManager, kMdnsPort)); ChipLogProgress(Discovery, "CHIP minimal mDNS started advertising."); AdvertiseRecords(BroadcastAdvertiseType::kStarted); mIsInitialized = true; return CHIP_NO_ERROR; } void AdvertiserMinMdns::Shutdown() { VerifyOrReturn(mIsInitialized); AdvertiseRecords(BroadcastAdvertiseType::kRemovingAll); GlobalMinimalMdnsServer::Server().Shutdown(); mIsInitialized = false; } CHIP_ERROR AdvertiserMinMdns::RemoveServices() { VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE); // Send a "goodbye" packet for each RR being removed, as defined in RFC 6762. // This allows mDNS clients to remove stale cached records which may not be re-added with // subsequent Advertise() calls. In the case the same records are re-added, this extra // is not harmful though suboptimal, so this is a subject to improvement in the future. AdvertiseRecords(BroadcastAdvertiseType::kRemovingAll); ClearServices(); return CHIP_NO_ERROR; } void AdvertiserMinMdns::ClearServices() { while (mOperationalResponders.begin() != mOperationalResponders.end()) { auto it = mOperationalResponders.begin(); // Need to free the memory once it is out of the list OperationalQueryAllocator * ptr = &*it; // Mark as unused ptr->GetAllocator()->Clear(); CHIP_ERROR err = mResponseSender.RemoveQueryResponder(ptr->GetAllocator()->GetQueryResponder()); if (err != CHIP_NO_ERROR) { ChipLogError(Discovery, "Failed to remove query responder: %" CHIP_ERROR_FORMAT, err.Format()); } mOperationalResponders.Remove(ptr); // Finally release the memory chip::Platform::Delete(ptr); } mQueryResponderAllocatorCommissionable.Clear(); mQueryResponderAllocatorCommissioner.Clear(); } OperationalQueryAllocator::Allocator * AdvertiserMinMdns::FindOperationalAllocator(const FullQName & qname) { for (auto & it : mOperationalResponders) { if (it.GetAllocator()->GetResponder(QType::SRV, qname) != nullptr) { return it.GetAllocator(); } } return nullptr; } OperationalQueryAllocator::Allocator * AdvertiserMinMdns::FindEmptyOperationalAllocator() { OperationalQueryAllocator * result = OperationalQueryAllocator::New(); if (result == nullptr) { return nullptr; } CHIP_ERROR err = mResponseSender.AddQueryResponder(result->GetAllocator()->GetQueryResponder()); if (err != CHIP_NO_ERROR) { ChipLogError(Discovery, "Failed to register query responder: %" CHIP_ERROR_FORMAT, err.Format()); Platform::Delete(result); return nullptr; } mOperationalResponders.PushBack(result); return result->GetAllocator(); } CHIP_ERROR AdvertiserMinMdns::Advertise(const OperationalAdvertisingParameters & params) { VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE); char nameBuffer[Operational::kInstanceNameMaxLength + 1] = ""; // need to set server name ReturnErrorOnFailure(MakeInstanceName(nameBuffer, sizeof(nameBuffer), params.GetPeerId())); QNamePart nameCheckParts[] = { nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain }; FullQName nameCheck = FullQName(nameCheckParts); auto * operationalAllocator = FindOperationalAllocator(nameCheck); if (operationalAllocator != nullptr) { operationalAllocator->Clear(); } else { operationalAllocator = FindEmptyOperationalAllocator(); if (operationalAllocator == nullptr) { ChipLogError(Discovery, "Failed to find an open operational allocator"); return CHIP_ERROR_NO_MEMORY; } } FullQName serviceName = operationalAllocator->AllocateQName(kOperationalServiceName, kOperationalProtocol, kLocalDomain); FullQName instanceName = operationalAllocator->AllocateQName(nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain); ReturnErrorOnFailure(MakeHostName(nameBuffer, sizeof(nameBuffer), params.GetMac())); FullQName hostName = operationalAllocator->AllocateQName(nameBuffer, kLocalDomain); if ((serviceName.nameCount == 0) || (instanceName.nameCount == 0) || (hostName.nameCount == 0)) { ChipLogError(Discovery, "Failed to allocate QNames."); return CHIP_ERROR_NO_MEMORY; } if (!operationalAllocator->AddResponder(serviceName, instanceName) .SetReportAdditional(instanceName) .SetReportInServiceListing(true) .IsValid()) { ChipLogError(Discovery, "Failed to add service PTR record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } // We are the sole owner of our instanceName, so records keyed on the // instanceName should have the cache-flush bit set. SrvResourceRecord srvRecord(instanceName, hostName, params.GetPort()); srvRecord.SetCacheFlush(true); if (!operationalAllocator->AddResponder(srvRecord).SetReportAdditional(hostName).IsValid()) { ChipLogError(Discovery, "Failed to add SRV record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } TxtResourceRecord txtRecord(instanceName, GetOperationalTxtEntries(operationalAllocator, params)); txtRecord.SetCacheFlush(true); if (!operationalAllocator->AddResponder(txtRecord).SetReportAdditional(hostName).IsValid()) { ChipLogError(Discovery, "Failed to add TXT record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } if (!operationalAllocator->AddResponder(hostName).IsValid()) { ChipLogError(Discovery, "Failed to add IPv6 mDNS responder"); return CHIP_ERROR_NO_MEMORY; } if (params.IsIPv4Enabled()) { if (!operationalAllocator->AddResponder(hostName).IsValid()) { ChipLogError(Discovery, "Failed to add IPv4 mDNS responder"); return CHIP_ERROR_NO_MEMORY; } } MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kCompressedFabricId, params.GetPeerId().GetCompressedFabricId())); FullQName compressedFabricIdSubtype = operationalAllocator->AllocateQName( nameBuffer, kSubtypeServiceNamePart, kOperationalServiceName, kOperationalProtocol, kLocalDomain); VerifyOrReturnError(compressedFabricIdSubtype.nameCount != 0, CHIP_ERROR_NO_MEMORY); if (!operationalAllocator->AddResponder(compressedFabricIdSubtype, instanceName) .SetReportAdditional(instanceName) .SetReportInServiceListing(true) .IsValid()) { ChipLogError(Discovery, "Failed to add device type PTR record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Operational device'; instance name: %s.", instanceName.names[0]); AdvertiseRecords(BroadcastAdvertiseType::kStarted); ChipLogProgress(Discovery, "mDNS service published: %s.%s", StringOrNullMarker(instanceName.names[1]), StringOrNullMarker(instanceName.names[2])); return CHIP_NO_ERROR; } CHIP_ERROR AdvertiserMinMdns::FinalizeServiceUpdate() { VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE); return CHIP_NO_ERROR; } CHIP_ERROR AdvertiserMinMdns::GetCommissionableInstanceName(char * instanceName, size_t maxLength) const { if (maxLength < (Commission::kInstanceNameMaxLength + 1)) { return CHIP_ERROR_NO_MEMORY; } return chip::Encoding::BytesToUppercaseHexString(&mCommissionableInstanceName[0], sizeof(mCommissionableInstanceName), instanceName, maxLength); } CHIP_ERROR AdvertiserMinMdns::UpdateCommissionableInstanceName() { uint64_t random_instance_name = chip::Crypto::GetRandU64(); static_assert(sizeof(mCommissionableInstanceName) == sizeof(random_instance_name), "Not copying the right amount of data"); memcpy(&mCommissionableInstanceName[0], &random_instance_name, sizeof(mCommissionableInstanceName)); return CHIP_NO_ERROR; } CHIP_ERROR AdvertiserMinMdns::Advertise(const CommissionAdvertisingParameters & params) { VerifyOrReturnError(mIsInitialized, CHIP_ERROR_INCORRECT_STATE); if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode) { mQueryResponderAllocatorCommissionable.Clear(); } else { mQueryResponderAllocatorCommissioner.Clear(); } // TODO: need to detect colisions here char nameBuffer[64] = ""; ReturnErrorOnFailure(GetCommissionableInstanceName(nameBuffer, sizeof(nameBuffer))); QueryResponderAllocator * allocator = params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? &mQueryResponderAllocatorCommissionable : &mQueryResponderAllocatorCommissioner; const char * serviceType = params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? kCommissionableServiceName : kCommissionerServiceName; FullQName serviceName = allocator->AllocateQName(serviceType, kCommissionProtocol, kLocalDomain); FullQName instanceName = allocator->AllocateQName(nameBuffer, serviceType, kCommissionProtocol, kLocalDomain); ReturnErrorOnFailure(MakeHostName(nameBuffer, sizeof(nameBuffer), params.GetMac())); FullQName hostName = allocator->AllocateQName(nameBuffer, kLocalDomain); if ((serviceName.nameCount == 0) || (instanceName.nameCount == 0) || (hostName.nameCount == 0)) { ChipLogError(Discovery, "Failed to allocate QNames."); return CHIP_ERROR_NO_MEMORY; } if (!allocator->AddResponder(serviceName, instanceName) .SetReportAdditional(instanceName) .SetReportInServiceListing(true) .IsValid()) { ChipLogError(Discovery, "Failed to add service PTR record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } SrvResourceRecord srvRecord(instanceName, hostName, params.GetPort()); srvRecord.SetCacheFlush(true); if (!allocator->AddResponder(srvRecord).SetReportAdditional(hostName).IsValid()) { ChipLogError(Discovery, "Failed to add SRV record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } if (!allocator->AddResponder(hostName).IsValid()) { ChipLogError(Discovery, "Failed to add IPv6 mDNS responder"); return CHIP_ERROR_NO_MEMORY; } if (params.IsIPv4Enabled()) { if (!allocator->AddResponder(hostName).IsValid()) { ChipLogError(Discovery, "Failed to add IPv4 mDNS responder"); return CHIP_ERROR_NO_MEMORY; } } if (const auto & vendorId = params.GetVendorId(); vendorId.has_value()) { MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kVendorId, *vendorId)); FullQName vendorServiceName = allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain); VerifyOrReturnError(vendorServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY); if (!allocator->AddResponder(vendorServiceName, instanceName) .SetReportAdditional(instanceName) .SetReportInServiceListing(true) .IsValid()) { ChipLogError(Discovery, "Failed to add vendor PTR record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } } if (const auto & deviceType = params.GetDeviceType(); deviceType.has_value()) { MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kDeviceType, *deviceType)); FullQName vendorServiceName = allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain); VerifyOrReturnError(vendorServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY); if (!allocator->AddResponder(vendorServiceName, instanceName) .SetReportAdditional(instanceName) .SetReportInServiceListing(true) .IsValid()) { ChipLogError(Discovery, "Failed to add device type PTR record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } } // the following sub types only apply to commissionable node advertisements if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode) { { MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kShortDiscriminator, params.GetShortDiscriminator())); FullQName shortServiceName = allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain); VerifyOrReturnError(shortServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY); if (!allocator->AddResponder(shortServiceName, instanceName) .SetReportAdditional(instanceName) .SetReportInServiceListing(true) .IsValid()) { ChipLogError(Discovery, "Failed to add short discriminator PTR record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } } { MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kLongDiscriminator, params.GetLongDiscriminator())); FullQName longServiceName = allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain); VerifyOrReturnError(longServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY); if (!allocator->AddResponder(longServiceName, instanceName) .SetReportAdditional(instanceName) .SetReportInServiceListing(true) .IsValid()) { ChipLogError(Discovery, "Failed to add long discriminator PTR record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } } if (params.GetCommissioningMode() != CommissioningMode::kDisabled) { MakeServiceSubtype(nameBuffer, sizeof(nameBuffer), DiscoveryFilter(DiscoveryFilterType::kCommissioningMode)); FullQName longServiceName = allocator->AllocateQName(nameBuffer, kSubtypeServiceNamePart, serviceType, kCommissionProtocol, kLocalDomain); VerifyOrReturnError(longServiceName.nameCount != 0, CHIP_ERROR_NO_MEMORY); if (!allocator->AddResponder(longServiceName, instanceName) .SetReportAdditional(instanceName) .SetReportInServiceListing(true) .IsValid()) { ChipLogError(Discovery, "Failed to add commissioning mode PTR record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } } } TxtResourceRecord txtRecord(instanceName, GetCommissioningTxtEntries(params)); txtRecord.SetCacheFlush(true); if (!allocator->AddResponder(txtRecord).SetReportAdditional(hostName).IsValid()) { ChipLogError(Discovery, "Failed to add TXT record mDNS responder"); return CHIP_ERROR_NO_MEMORY; } if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode) { ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Commissionable node device'; instance name: %s.", StringOrNullMarker(instanceName.names[0])); } else { ChipLogProgress(Discovery, "CHIP minimal mDNS configured as 'Commissioner device'; instance name: %s.", StringOrNullMarker(instanceName.names[0])); } AdvertiseRecords(BroadcastAdvertiseType::kStarted); ChipLogProgress(Discovery, "mDNS service published: %s.%s", StringOrNullMarker(instanceName.names[1]), StringOrNullMarker(instanceName.names[2])); return CHIP_NO_ERROR; } FullQName AdvertiserMinMdns::GetOperationalTxtEntries(OperationalQueryAllocator::Allocator * allocator, const OperationalAdvertisingParameters & params) { char * txtFields[OperationalAdvertisingParameters::kTxtMaxNumber]; size_t numTxtFields = 0; struct CommonTxtEntryStorage commonStorage; AddCommonTxtEntries(params, commonStorage, txtFields, numTxtFields); if (numTxtFields == 0) { return allocator->AllocateQNameFromArray(mEmptyTextEntries, 1); } return allocator->AllocateQNameFromArray(txtFields, numTxtFields); } FullQName AdvertiserMinMdns::GetCommissioningTxtEntries(const CommissionAdvertisingParameters & params) { char * txtFields[CommissionAdvertisingParameters::kTxtMaxNumber]; size_t numTxtFields = 0; QueryResponderAllocator * allocator = params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode ? &mQueryResponderAllocatorCommissionable : &mQueryResponderAllocatorCommissioner; char txtVidPid[chip::Dnssd::kKeyVendorProductMaxLength + 4]; { const auto & productId = params.GetProductId(); const auto & vendorId = params.GetVendorId(); if (productId.has_value() && vendorId.has_value()) { snprintf(txtVidPid, sizeof(txtVidPid), "VP=%d+%d", *vendorId, *productId); txtFields[numTxtFields++] = txtVidPid; } else if (vendorId.has_value()) { snprintf(txtVidPid, sizeof(txtVidPid), "VP=%d", *vendorId); txtFields[numTxtFields++] = txtVidPid; } } char txtDeviceType[chip::Dnssd::kKeyDeviceTypeMaxLength + 4]; if (const auto & deviceType = params.GetDeviceType(); deviceType.has_value()) { snprintf(txtDeviceType, sizeof(txtDeviceType), "DT=%" PRIu32, *deviceType); txtFields[numTxtFields++] = txtDeviceType; } char txtDeviceName[chip::Dnssd::kKeyDeviceNameMaxLength + 4]; if (const auto & deviceName = params.GetDeviceName(); deviceName.has_value()) { snprintf(txtDeviceName, sizeof(txtDeviceName), "DN=%s", *deviceName); txtFields[numTxtFields++] = txtDeviceName; } CommonTxtEntryStorage commonStorage; AddCommonTxtEntries(params, commonStorage, txtFields, numTxtFields); // the following sub types only apply to commissionable node advertisements char txtDiscriminator[chip::Dnssd::kKeyLongDiscriminatorMaxLength + 3]; char txtCommissioningMode[chip::Dnssd::kKeyCommissioningModeMaxLength + 4]; char txtRotatingDeviceId[chip::Dnssd::kKeyRotatingDeviceIdMaxLength + 4]; char txtPairingHint[chip::Dnssd::kKeyPairingInstructionMaxLength + 4]; char txtPairingInstr[chip::Dnssd::kKeyPairingInstructionMaxLength + 4]; // the following sub types only apply to commissioner discovery advertisements char txtCommissionerPasscode[chip::Dnssd::kKeyCommissionerPasscodeMaxLength + 4]; if (params.GetCommissionAdvertiseMode() == CommssionAdvertiseMode::kCommissionableNode) { // a discriminator always exists snprintf(txtDiscriminator, sizeof(txtDiscriminator), "D=%d", params.GetLongDiscriminator()); txtFields[numTxtFields++] = txtDiscriminator; snprintf(txtCommissioningMode, sizeof(txtCommissioningMode), "CM=%d", static_cast(params.GetCommissioningMode())); txtFields[numTxtFields++] = txtCommissioningMode; if (const auto & rotatingDeviceId = params.GetRotatingDeviceId(); rotatingDeviceId.has_value()) { snprintf(txtRotatingDeviceId, sizeof(txtRotatingDeviceId), "RI=%s", *rotatingDeviceId); txtFields[numTxtFields++] = txtRotatingDeviceId; } if (const auto & pairingHint = params.GetPairingHint(); pairingHint.has_value()) { snprintf(txtPairingHint, sizeof(txtPairingHint), "PH=%d", *pairingHint); txtFields[numTxtFields++] = txtPairingHint; } if (const auto & pairingInstruction = params.GetPairingInstruction(); pairingInstruction.has_value()) { snprintf(txtPairingInstr, sizeof(txtPairingInstr), "PI=%s", *pairingInstruction); txtFields[numTxtFields++] = txtPairingInstr; } } else { if (params.GetCommissionerPasscodeSupported().value_or(false)) { snprintf(txtCommissionerPasscode, sizeof(txtCommissionerPasscode), "CP=%d", static_cast(1)); txtFields[numTxtFields++] = txtCommissionerPasscode; } } if (numTxtFields == 0) { return allocator->AllocateQNameFromArray(mEmptyTextEntries, 1); } return allocator->AllocateQNameFromArray(txtFields, numTxtFields); } void AdvertiserMinMdns::AdvertiseRecords(BroadcastAdvertiseType type) { ResponseConfiguration responseConfiguration; if (type == BroadcastAdvertiseType::kRemovingAll) { // make a "remove all records now" broadcast responseConfiguration.SetTtlSecondsOverride(0); } UniquePtr allInterfaces = GetAddressPolicy()->GetListenEndpoints(); VerifyOrDieWithMsg(allInterfaces != nullptr, Discovery, "Failed to allocate memory for endpoints."); chip::Inet::InterfaceId interfaceId; chip::Inet::IPAddressType addressType; while (allInterfaces->Next(&interfaceId, &addressType)) { UniquePtr allIps = GetAddressPolicy()->GetIpAddressesForEndpoint(interfaceId, addressType); VerifyOrDieWithMsg(allIps != nullptr, Discovery, "Failed to allocate memory for ip addresses."); chip::Inet::IPPacketInfo packetInfo; packetInfo.Clear(); // advertising on every interface requires a valid IP address // since we use "BROADCAST" (unicast is false), we do not actually care about // the source IP address value, just that it has the right "type" // // NOTE: cannot use Broadcast address as the source as they have the type kAny. // // TODO: ideally we may want to have a destination that is explicit as "unicast/destIp" // vs "multicast/addressType". Such a change requires larger code updates. packetInfo.SrcAddress = chip::Inet::IPAddress::Loopback(addressType); packetInfo.DestAddress = BroadcastIpAddresses::Get(addressType); packetInfo.SrcPort = kMdnsPort; packetInfo.DestPort = kMdnsPort; packetInfo.Interface = interfaceId; // Advertise all records // // TODO: Consider advertising delta changes. // // Current advertisement does not have a concept of "delta" to only // advertise changes. Current implementation is to always // 1. advertise TTL=0 (clear all caches) // 2. advertise available records (with longer TTL) // // It would be nice if we could selectively advertise what changes, like // send TTL=0 for anything removed/about to be removed (and only those), // then only advertise new items added. // // This optimization likely will take more logic and state storage, so // for now it is not done. QueryData queryData(QType::PTR, QClass::IN, false /* unicast */); queryData.SetIsAnnounceBroadcast(true); for (auto & it : mOperationalResponders) { it.GetAllocator()->GetQueryResponder()->ClearBroadcastThrottle(); } mQueryResponderAllocatorCommissionable.GetQueryResponder()->ClearBroadcastThrottle(); mQueryResponderAllocatorCommissioner.GetQueryResponder()->ClearBroadcastThrottle(); CHIP_ERROR err = mResponseSender.Respond(0, queryData, &packetInfo, responseConfiguration); if (err != CHIP_NO_ERROR) { ChipLogError(Discovery, "Failed to advertise records: %" CHIP_ERROR_FORMAT, err.Format()); } } // Once all automatic broadcasts are done, allow immediate replies once. for (auto & it : mOperationalResponders) { it.GetAllocator()->GetQueryResponder()->ClearBroadcastThrottle(); } mQueryResponderAllocatorCommissionable.GetQueryResponder()->ClearBroadcastThrottle(); mQueryResponderAllocatorCommissioner.GetQueryResponder()->ClearBroadcastThrottle(); } AdvertiserMinMdns gAdvertiser; } // namespace #if CHIP_DNSSD_DEFAULT_MINIMAL ServiceAdvertiser & GetDefaultAdvertiser() { return gAdvertiser; } #endif // CHIP_DNSSD_DEFAULT_MINIMAL } // namespace Dnssd } // namespace chip