| /* |
| * |
| * 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 "DnssdImpl.h" |
| #include "MdnsError.h" |
| #include "lib/dnssd/platform/Dnssd.h" |
| |
| #include "dns_sd.h" |
| #include "lwip/mld6.h" |
| #include "mdns.h" |
| #include <lwip/ip4_addr.h> |
| #include <lwip/ip6_addr.h> |
| |
| #include "FreeRTOS.h" |
| #include "event_groups.h" |
| #include "lwip/sockets.h" |
| #undef write |
| #undef read |
| #include <cstdio> |
| #include <sstream> |
| #include <string.h> |
| #include <string> |
| |
| #include "mDNSDebug.h" |
| #include "task.h" |
| #include "task_def.h" |
| #include <errno.h> |
| |
| #include "platform/CHIPDeviceLayer.h" |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/SafeInt.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| |
| using namespace chip::Dnssd; |
| |
| extern "C" { |
| extern void mDNSPlatformWriteLogRedirect(void (*)(const char *, const char *)); |
| } |
| |
| namespace { |
| constexpr char kLocalDot[] = "local."; |
| constexpr char kProtocolTcp[] = "._tcp"; |
| constexpr char kProtocolUdp[] = "._udp"; |
| static constexpr uint32_t kTimeoutMilli = 3000; |
| static constexpr size_t kMaxResults = 20; |
| |
| constexpr DNSServiceFlags kRegisterFlags = kDNSServiceFlagsNoAutoRename; |
| constexpr DNSServiceFlags kBrowseFlags = 0; |
| constexpr DNSServiceFlags kGetAddrInfoFlags = kDNSServiceFlagsTimeout | kDNSServiceFlagsShareConnection; |
| constexpr DNSServiceFlags kResolveFlags = kDNSServiceFlagsShareConnection; |
| constexpr DNSServiceFlags kReconfirmRecordFlags = 0; |
| |
| bool IsSupportedProtocol(DnssdServiceProtocol protocol) |
| { |
| return (protocol == DnssdServiceProtocol::kDnssdProtocolUdp) || (protocol == DnssdServiceProtocol::kDnssdProtocolTcp); |
| } |
| |
| uint32_t GetInterfaceId(chip::Inet::InterfaceId interfaceId) |
| { |
| return interfaceId.IsPresent() ? (uint32_t) (void *) interfaceId.GetPlatformInterface() : kDNSServiceInterfaceIndexAny; |
| } |
| |
| std::string GetFullType(const char * type, DnssdServiceProtocol protocol) |
| { |
| std::ostringstream typeBuilder; |
| typeBuilder << type; |
| typeBuilder << (protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? kProtocolUdp : kProtocolTcp); |
| return typeBuilder.str(); |
| } |
| |
| std::string GetFullType(const DnssdService * service) |
| { |
| return GetFullType(service->mType, service->mProtocol); |
| } |
| |
| std::string GetFullTypeWithSubTypes(const char * type, DnssdServiceProtocol protocol, const char * subTypes[], size_t subTypeSize) |
| { |
| std::ostringstream typeBuilder; |
| typeBuilder << type; |
| typeBuilder << (protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? kProtocolUdp : kProtocolTcp); |
| for (int i = 0; i < (int) subTypeSize; i++) |
| { |
| typeBuilder << ","; |
| typeBuilder << subTypes[i]; |
| } |
| return typeBuilder.str(); |
| } |
| |
| std::string GetFullTypeWithSubTypes(const char * type, DnssdServiceProtocol protocol) |
| { |
| auto fullType = GetFullType(type, protocol); |
| |
| std::string subtypeDelimiter = "._sub."; |
| size_t position = fullType.find(subtypeDelimiter); |
| if (position != std::string::npos) |
| { |
| fullType = fullType.substr(position + subtypeDelimiter.size()) + "," + fullType.substr(0, position); |
| } |
| |
| return fullType; |
| } |
| |
| std::string GetFullTypeWithSubTypes(const DnssdService * service) |
| { |
| return GetFullTypeWithSubTypes(service->mType, service->mProtocol, service->mSubTypes, service->mSubTypeSize); |
| } |
| |
| std::string GetHostNameWithDomain(const char * hostname) |
| { |
| return std::string(hostname) + '.' + kLocalDot; |
| } |
| |
| void LogOnFailure(const char * name, DNSServiceErrorType err) |
| { |
| if (kDNSServiceErr_NoError != err) |
| { |
| ChipLogError(Discovery, "%s (%s)", name, Error::ToString(err)); |
| } |
| } |
| |
| class ScopedTXTRecord |
| { |
| public: |
| ScopedTXTRecord() {} |
| |
| ~ScopedTXTRecord() |
| { |
| if (mDataSize != 0) |
| { |
| TXTRecordDeallocate(&mRecordRef); |
| } |
| } |
| |
| CHIP_ERROR Init(TextEntry * textEntries, size_t textEntrySize) |
| { |
| VerifyOrReturnError(textEntrySize <= kDnssdTextMaxSize, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| TXTRecordCreate(&mRecordRef, sizeof(mRecordBuffer), mRecordBuffer); |
| |
| for (size_t i = 0; i < textEntrySize; i++) |
| { |
| TextEntry entry = textEntries[i]; |
| VerifyOrReturnError(chip::CanCastTo<uint8_t>(entry.mDataSize), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| auto err = TXTRecordSetValue(&mRecordRef, entry.mKey, static_cast<uint8_t>(entry.mDataSize), entry.mData); |
| VerifyOrReturnError(err == kDNSServiceErr_NoError, CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| mDataSize = TXTRecordGetLength(&mRecordRef); |
| if (mDataSize == 0) |
| { |
| TXTRecordDeallocate(&mRecordRef); |
| } |
| |
| mData = TXTRecordGetBytesPtr(&mRecordRef); |
| return CHIP_NO_ERROR; |
| } |
| |
| uint16_t size() { return mDataSize; } |
| const void * data() { return mData; } |
| |
| private: |
| uint16_t mDataSize = 0; |
| const void * mData = nullptr; |
| |
| TXTRecordRef mRecordRef; |
| char mRecordBuffer[kDnssdTextMaxSize]; |
| }; |
| } // namespace |
| |
| namespace chip { |
| namespace Dnssd { |
| |
| #define SERVICE_DOMAIN ("local") |
| |
| MdnsContexts MdnsContexts::sInstance; |
| static DNSServiceRef BrowseClient = NULL; |
| static TaskHandle_t gResolveTask = NULL; |
| static EventGroupHandle_t gResolveTaskWakeEvent; |
| |
| void ChipDnssdMdnsLog(const char * level, const char * msg) |
| { |
| ChipLogProgress(ServiceProvisioning, "%s %s", StringOrNullMarker(level), StringOrNullMarker(msg)); |
| } |
| |
| static void OnRegister(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType err, const char * name, const char * type, |
| const char * domain, void * context) |
| { |
| ChipLogDetail(Discovery, "Mdns: %s name: %s, type: %s, domain: %s, flags: %ld", __func__, name, type, domain, flags); |
| |
| auto sdCtx = reinterpret_cast<RegisterContext *>(context); |
| sdCtx->Finalize(err); |
| }; |
| |
| CHIP_ERROR Register(void * context, DnssdPublishCallback callback, uint32_t interfaceId, const char * type, const char * name, |
| uint16_t port, ScopedTXTRecord & record, Inet::IPAddressType addressType, const char * hostname) |
| { |
| ChipLogDetail(Discovery, "Registering service %s on host %s with port %u and type: %s on interface id: %" PRIu32, name, |
| hostname, port, type, interfaceId); |
| |
| RegisterContext * sdCtx = nullptr; |
| if (CHIP_NO_ERROR == MdnsContexts::GetInstance().GetRegisterContextOfType(type, &sdCtx)) |
| { |
| auto err = DNSServiceUpdateRecord(sdCtx->serviceRef, nullptr, kRegisterFlags, record.size(), record.data(), 0 /* ttl */); |
| VerifyOrReturnError(kDNSServiceErr_NoError == err, sdCtx->Finalize(err)); |
| return CHIP_NO_ERROR; |
| } |
| |
| sdCtx = chip::Platform::New<RegisterContext>(type, name, callback, context); |
| VerifyOrReturnError(nullptr != sdCtx, CHIP_ERROR_NO_MEMORY); |
| |
| DNSServiceRef sdRef; |
| auto err = DNSServiceRegister(&sdRef, kRegisterFlags, interfaceId, name, type, kLocalDot, hostname, htons(port), record.size(), |
| record.data(), OnRegister, sdCtx); |
| VerifyOrReturnError(kDNSServiceErr_NoError == err, sdCtx->Finalize(err)); |
| |
| return MdnsContexts::GetInstance().Add(sdCtx, sdRef); |
| } |
| |
| static void mdnsd_entry(void * not_used) |
| { |
| ChipLogProgress(ServiceProvisioning, "mdnsd_entry start"); |
| mdnsd_start(); |
| ChipLogProgress(ServiceProvisioning, "mdnsd_entry return"); |
| vTaskDelete(NULL); |
| } |
| |
| CHIP_ERROR ChipDnssdInit(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context) |
| { |
| CHIP_ERROR error = CHIP_NO_ERROR; |
| #if LWIP_IPV6 |
| struct netif * sta_if = netif_default; |
| ip6_addr_t mld_address; |
| #endif |
| |
| #if LWIP_IPV6 |
| sta_if->ip6_autoconfig_enabled = 1; |
| ip6_addr_set_solicitednode(&mld_address, netif_ip6_addr(sta_if, 0)->addr[3]); |
| mld6_joingroup(netif_ip6_addr(sta_if, 0), &mld_address); |
| #endif |
| |
| ChipLogProgress(ServiceProvisioning, "create mdnsd_task"); |
| |
| mDNSPlatformWriteLogRedirect(ChipDnssdMdnsLog); |
| |
| // xTaskHandle create mDNS daemon task |
| if (pdPASS != xTaskCreate(mdnsd_entry, "mdnsd", (15 * 1024) / sizeof(portSTACK_TYPE), NULL, TASK_PRIORITY_NORMAL, NULL)) |
| { |
| ChipLogProgress(ServiceProvisioning, "Cannot create mdnsd_task"); |
| error = CHIP_ERROR_INTERNAL; |
| } |
| |
| initCallback(context, error); |
| |
| return error; |
| } |
| |
| void ChipDnssdShutdown(void) |
| { |
| ChipLogProgress(ServiceProvisioning, "shutdown mdnsd_task not implemented"); |
| } |
| |
| static const char * GetProtocolString(DnssdServiceProtocol protocol) |
| { |
| return protocol == DnssdServiceProtocol::kDnssdProtocolTcp ? "_tcp" : "_udp"; |
| } |
| |
| CHIP_ERROR ChipDnssdPublishService(const DnssdService * service, DnssdPublishCallback callback, void * context) |
| { |
| VerifyOrReturnError(service != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(IsSupportedProtocol(service->mProtocol), CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(callback != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(strcmp(service->mHostName, "") != 0, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| ScopedTXTRecord record; |
| ReturnErrorOnFailure(record.Init(service->mTextEntries, service->mTextEntrySize)); |
| |
| auto regtype = GetFullTypeWithSubTypes(service); |
| auto interfaceId = GetInterfaceId(service->mInterface); |
| auto hostname = GetHostNameWithDomain(service->mHostName); |
| |
| return Register(context, callback, interfaceId, regtype.c_str(), service->mName, service->mPort, record, service->mAddressType, |
| hostname.c_str()); |
| } |
| |
| CHIP_ERROR ChipDnssdRemoveServices() |
| { |
| auto err = MdnsContexts::GetInstance().RemoveAllOfType(ContextType::Register); |
| if (CHIP_ERROR_KEY_NOT_FOUND == err) |
| { |
| err = CHIP_NO_ERROR; |
| } |
| return err; |
| } |
| |
| CHIP_ERROR ChipDnssdFinalizeServiceUpdate() |
| { |
| return CHIP_NO_ERROR; |
| } |
| |
| void ChipDNSServiceBrowseReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, |
| const char * serviceName, const char * regtype, const char * replyDomain, void * context) |
| { |
| DnssdBrowseCallback ChipBrowseHandler = (DnssdBrowseCallback) context; |
| DnssdService service; |
| |
| ChipLogProgress(ServiceProvisioning, "ChipDNSServiceBrowseReply %s", StringOrNullMarker(serviceName)); |
| strcpy(service.mName, serviceName); |
| |
| ChipBrowseHandler(NULL, &service, 1, true, CHIP_NO_ERROR); |
| } |
| |
| CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, chip::Inet::IPAddressType addressType, |
| chip::Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, |
| intptr_t * browseIdentifier) |
| { |
| CHIP_ERROR error = CHIP_NO_ERROR; |
| DNSServiceErrorType err; |
| char ServiceType[kDnssdTypeMaxSize + 10] = { 0 }; |
| |
| (void) addressType; |
| ChipLogProgress(ServiceProvisioning, "ChipDnssdBrowse %s", StringOrNullMarker(type)); |
| strcpy(ServiceType, type); |
| strcat(ServiceType, "."); |
| strcat(ServiceType, GetProtocolString(protocol)); |
| err = DNSServiceBrowse(&BrowseClient, 0, 0, ServiceType, SERVICE_DOMAIN, ChipDNSServiceBrowseReply, (void *) callback); |
| ChipLogProgress(ServiceProvisioning, "DNSServiceBrowse %d", (int) err); |
| if (err) |
| { |
| error = CHIP_ERROR_INTERNAL; |
| } |
| else |
| { |
| *browseIdentifier = reinterpret_cast<intptr_t>(nullptr); |
| } |
| return error; |
| } |
| |
| CHIP_ERROR ChipDnssdStopBrowse(intptr_t browseIdentifier) |
| { |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| static void resolve_client_task(void * parameter) |
| { |
| EventBits_t uxBits; |
| struct timeval tv; |
| int result; |
| int fd; |
| fd_set readfds; |
| |
| while (1) |
| { |
| int nfds = 0; |
| FD_ZERO(&readfds); |
| nfds = MdnsContexts::GetInstance().GetSelectFd(&readfds); |
| |
| if (nfds == 0) |
| { |
| uxBits = xEventGroupWaitBits(gResolveTaskWakeEvent, 1, pdTRUE, pdFALSE, portMAX_DELAY); |
| continue; |
| } |
| |
| tv.tv_sec = 0; |
| tv.tv_usec = 100000; // 100ms |
| |
| result = select(nfds + 1, &readfds, (fd_set *) NULL, (fd_set *) NULL, &tv); |
| if (result > 0) |
| { |
| DNSServiceErrorType error = kDNSServiceErr_NoError; |
| |
| for (fd = 0; fd <= nfds; ++fd) |
| { |
| if (FD_ISSET(fd, &readfds)) |
| { |
| DNSServiceRef resolveClient = NULL; |
| void * context = MdnsContexts::GetInstance().GetBySockFd(fd); |
| if (context) |
| { |
| auto sdCtx = reinterpret_cast<ResolveContext *>(context); |
| error = DNSServiceProcessResult(sdCtx->serviceRef); |
| { |
| mdnslogInfo("DNSServiceProcessResult(%d) returned %d\n", fd, error); |
| } |
| } // if context |
| } |
| } // for |
| } |
| else |
| { |
| // remove timeout contexts |
| for (fd = 0; fd <= nfds; ++fd) |
| { |
| GenericContext * context = MdnsContexts::GetInstance().GetBySockFd(fd); |
| if (context && context->mSelectCount > 10) |
| { |
| context->Finalize(kDNSServiceErr_Timeout); |
| } |
| } |
| } |
| } // while |
| |
| gResolveTask = NULL; |
| vTaskDelete(NULL); |
| } |
| |
| static void OnGetAddrInfo(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceId, DNSServiceErrorType err, |
| const char * hostname, const struct sockaddr * address, uint32_t ttl, void * context) |
| { |
| ChipLogProgress(ServiceProvisioning, "DNSServiceGetAddrInfo: OnGetAddrInfo"); |
| |
| auto sdCtx = reinterpret_cast<ResolveContext *>(context); |
| ReturnOnFailure(MdnsContexts::GetInstance().Has(sdCtx)); |
| |
| if (kDNSServiceErr_NoError == err) |
| { |
| sdCtx->OnNewAddress(interfaceId, address); |
| } |
| |
| if (!(flags & kDNSServiceFlagsMoreComing)) |
| { |
| VerifyOrReturn(sdCtx->HasAddress(), sdCtx->Finalize(kDNSServiceErr_BadState)); |
| sdCtx->Finalize(); |
| } |
| } |
| |
| static void GetAddrInfo(ResolveContext * sdCtx) |
| { |
| ChipLogProgress(ServiceProvisioning, "ChipDNSServiceResolveReply: GetAddrInfo"); |
| |
| int dns_fd; |
| auto protocol = sdCtx->protocol; |
| |
| for (auto & interface : sdCtx->interfaces) |
| { |
| DNSServiceRef resolveClient = NULL; |
| auto interfaceId = interface.first; |
| auto hostname = interface.second.fullyQualifiedDomainName.c_str(); |
| |
| DNSServiceRefDeallocate(sdCtx->serviceRef); |
| |
| auto err = DNSServiceGetAddrInfo(&resolveClient, 0, interfaceId, protocol, hostname, OnGetAddrInfo, sdCtx); |
| sdCtx->serviceRef = resolveClient; |
| VerifyOrReturn(kDNSServiceErr_NoError == err, sdCtx->Finalize(err)); |
| } |
| } |
| |
| void ChipDNSServiceResolveReply(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, |
| const char * fullname, const char * hosttarget, uint16_t port, |
| /* In network byte order */ uint16_t txtLen, const unsigned char * txtRecord, void * context) |
| { |
| ChipLogProgress(ServiceProvisioning, "ChipDNSServiceResolveReply"); |
| if (context) |
| { |
| auto sdCtx = reinterpret_cast<ResolveContext *>(context); |
| |
| sdCtx->OnNewInterface(interfaceIndex, fullname, hosttarget, port, txtLen, txtRecord); |
| if (kDNSServiceInterfaceIndexLocalOnly == interfaceIndex) |
| { |
| sdCtx->OnNewLocalOnlyAddress(); |
| sdCtx->Finalize(); |
| return; |
| } |
| if (!(flags & kDNSServiceFlagsMoreComing)) |
| { |
| VerifyOrReturn(sdCtx->HasInterface(), sdCtx->Finalize(kDNSServiceErr_BadState)); |
| GetAddrInfo(sdCtx); |
| } |
| } |
| } |
| |
| CHIP_ERROR ChipDnssdResolve(DnssdService * service, chip::Inet::InterfaceId interface, DnssdResolveCallback callback, |
| void * context) |
| { |
| DNSServiceRef resolveClient = NULL; |
| CHIP_ERROR error = CHIP_NO_ERROR; |
| DNSServiceErrorType err; |
| char ServiceType[kDnssdTypeMaxSize + 10] = { 0 }; |
| int dns_fd; |
| uint32_t interfaceIndex = GetInterfaceId(interface); |
| |
| ChipLogProgress(ServiceProvisioning, "ChipDnssdResolve %s", service->mName); |
| strcpy(ServiceType, service->mType); |
| strcat(ServiceType, "."); |
| strcat(ServiceType, GetProtocolString(service->mProtocol)); |
| |
| auto sdCtx = chip::Platform::New<ResolveContext>(context, callback, service->mAddressType); |
| VerifyOrReturnError(nullptr != sdCtx, CHIP_ERROR_NO_MEMORY); |
| |
| err = DNSServiceResolve(&resolveClient, 0, interfaceIndex, service->mName, ServiceType, SERVICE_DOMAIN, |
| ChipDNSServiceResolveReply, sdCtx); |
| ChipLogProgress(ServiceProvisioning, "DNSServiceResolve %d", (int) err); |
| if (err) |
| { |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| sdCtx->serviceRef = resolveClient; |
| error = MdnsContexts::GetInstance().Add(sdCtx, sdCtx->serviceRef); |
| if (error == CHIP_NO_ERROR) |
| { |
| if (gResolveTask == NULL) |
| { |
| gResolveTaskWakeEvent = xEventGroupCreate(); |
| if (gResolveTaskWakeEvent == NULL) |
| { |
| ChipLogProgress(ServiceProvisioning, "Cannot create resolve_task event group"); |
| return CHIP_ERROR_INTERNAL; |
| } |
| |
| if (pdPASS != |
| xTaskCreate(resolve_client_task, "mdnsr", (15 * 1024) / sizeof(portSTACK_TYPE), (void *) service, |
| TASK_PRIORITY_NORMAL, &gResolveTask)) |
| { |
| ChipLogProgress(ServiceProvisioning, "Cannot create resolve_task"); |
| return CHIP_ERROR_INTERNAL; |
| } |
| } |
| else |
| { |
| // wake up task |
| xEventGroupSetBits(gResolveTaskWakeEvent, 1); |
| } |
| } |
| |
| return error; |
| } |
| |
| void ChipDnssdResolveNoLongerNeeded(const char * instanceName) {} |
| |
| CHIP_ERROR ChipDnssdReconfirmRecord(const char * hostname, chip::Inet::IPAddress address, chip::Inet::InterfaceId interface) |
| { |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| } |
| |
| } // namespace Dnssd |
| } // namespace chip |