snapcast/client/browseZeroConf/browse_bonjour.cpp
2025-01-09 15:06:35 +01:00

280 lines
8.6 KiB
C++

/***
This file is part of snapcast
Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
// prototype/interface header file
#include "browse_bonjour.hpp"
// local headers
#include "common/aixlog.hpp"
#include "common/snap_exception.hpp"
// standard headers
#include <deque>
#include <iostream>
#ifdef WINDOWS
#include <WinSock2.h>
#include <Ws2tcpip.h>
#else
#include <netdb.h>
#include <sys/socket.h>
#endif
using namespace std;
static constexpr auto LOG_TAG = "Bonjour";
struct DNSServiceRefDeleter
{
void operator()(DNSServiceRef* ref)
{
DNSServiceRefDeallocate(*ref);
delete ref;
}
};
using DNSServiceHandle = std::unique_ptr<DNSServiceRef, DNSServiceRefDeleter>;
string BonjourGetError(DNSServiceErrorType error)
{
switch (error)
{
case kDNSServiceErr_NoError:
return "NoError";
default:
case kDNSServiceErr_Unknown:
return "Unknown";
case kDNSServiceErr_NoSuchName:
return "NoSuchName";
case kDNSServiceErr_NoMemory:
return "NoMemory";
case kDNSServiceErr_BadParam:
return "BadParam";
case kDNSServiceErr_BadReference:
return "BadReference";
case kDNSServiceErr_BadState:
return "BadState";
case kDNSServiceErr_BadFlags:
return "BadFlags";
case kDNSServiceErr_Unsupported:
return "Unsupported";
case kDNSServiceErr_NotInitialized:
return "NotInitialized";
case kDNSServiceErr_AlreadyRegistered:
return "AlreadyRegistered";
case kDNSServiceErr_NameConflict:
return "NameConflict";
case kDNSServiceErr_Invalid:
return "Invalid";
case kDNSServiceErr_Firewall:
return "Firewall";
case kDNSServiceErr_Incompatible:
return "Incompatible";
case kDNSServiceErr_BadInterfaceIndex:
return "BadInterfaceIndex";
case kDNSServiceErr_Refused:
return "Refused";
case kDNSServiceErr_NoSuchRecord:
return "NoSuchRecord";
case kDNSServiceErr_NoAuth:
return "NoAuth";
case kDNSServiceErr_NoSuchKey:
return "NoSuchKey";
case kDNSServiceErr_NATTraversal:
return "NATTraversal";
case kDNSServiceErr_DoubleNAT:
return "DoubleNAT";
case kDNSServiceErr_BadTime:
return "BadTime";
case kDNSServiceErr_BadSig:
return "BadSig";
case kDNSServiceErr_BadKey:
return "BadKey";
case kDNSServiceErr_Transient:
return "Transient";
case kDNSServiceErr_ServiceNotRunning:
return "ServiceNotRunning";
case kDNSServiceErr_NATPortMappingUnsupported:
return "NATPortMappingUnsupported";
case kDNSServiceErr_NATPortMappingDisabled:
return "NATPortMappingDisabled";
case kDNSServiceErr_NoRouter:
return "NoRouter";
case kDNSServiceErr_PollingMode:
return "PollingMode";
case kDNSServiceErr_Timeout:
return "Timeout";
}
}
struct mDNSReply
{
string name, regtype, domain;
};
struct mDNSResolve
{
string fullName;
uint16_t port;
};
#define CHECKED(err) \
if ((err) != kDNSServiceErr_NoError) \
throw SnapException(BonjourGetError(err) + ":" + to_string(__LINE__));
void runService(const DNSServiceHandle& service)
{
if (!*service)
return;
auto socket = DNSServiceRefSockFD(*service);
fd_set set;
FD_ZERO(&set);
FD_SET(socket, &set);
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
while (select(FD_SETSIZE, &set, NULL, NULL, &timeout))
{
CHECKED(DNSServiceProcessResult(*service));
timeout.tv_sec = 0;
timeout.tv_usec = 500000;
}
}
bool BrowseBonjour::browse(const string& serviceName, mDNSResult& result, int /*timeout*/)
{
result.valid = false;
// Discover
deque<mDNSReply> replyCollection;
{
DNSServiceHandle service(new DNSServiceRef(NULL));
CHECKED(DNSServiceBrowse(service.get(), 0, 0, serviceName.c_str(), "local.",
[](DNSServiceRef /*service*/, DNSServiceFlags /*flags*/, uint32_t /*interfaceIndex*/, DNSServiceErrorType errorCode,
const char* serviceName, const char* regtype, const char* replyDomain, void* context)
{
auto replyCollection = static_cast<deque<mDNSReply>*>(context);
CHECKED(errorCode);
replyCollection->push_back(mDNSReply{string(serviceName), string(regtype), string(replyDomain)});
},
&replyCollection));
runService(service);
}
// Resolve
deque<mDNSResolve> resolveCollection;
{
DNSServiceHandle service(new DNSServiceRef(NULL));
for (auto& reply : replyCollection)
CHECKED(DNSServiceResolve(service.get(), 0, 0, reply.name.c_str(), reply.regtype.c_str(), reply.domain.c_str(),
[](DNSServiceRef /*service*/, DNSServiceFlags /*flags*/, uint32_t /*interfaceIndex*/, DNSServiceErrorType errorCode,
const char* /*fullName*/, const char* hosttarget, uint16_t port, uint16_t /*txtLen*/,
const unsigned char* /*txtRecord*/, void* context)
{
auto resultCollection = static_cast<deque<mDNSResolve>*>(context);
CHECKED(errorCode);
resultCollection->push_back(mDNSResolve{string(hosttarget), ntohs(port)});
},
&resolveCollection));
runService(service);
}
// DNS/mDNS Resolve
deque<mDNSResult> resultCollection(resolveCollection.size(), mDNSResult{});
{
DNSServiceHandle service(new DNSServiceRef(NULL));
unsigned i = 0;
for (auto& resolve : resolveCollection)
{
resultCollection[i].port = resolve.port;
CHECKED(DNSServiceGetAddrInfo(service.get(), kDNSServiceFlagsLongLivedQuery, 0, kDNSServiceProtocol_IPv4, resolve.fullName.c_str(),
[](DNSServiceRef /*service*/, DNSServiceFlags /*flags*/, uint32_t interfaceIndex, DNSServiceErrorType /*errorCode*/,
const char* hostname, const sockaddr* address, uint32_t /*ttl*/, void* context)
{
auto result = static_cast<mDNSResult*>(context);
result->host = string(hostname);
result->ip_version = (address->sa_family == AF_INET) ? (IPVersion::IPv4) : (IPVersion::IPv6);
result->iface_idx = static_cast<int>(interfaceIndex);
char hostIP[NI_MAXHOST];
char hostService[NI_MAXSERV];
if (getnameinfo(address, sizeof(*address), hostIP, sizeof(hostIP), hostService, sizeof(hostService), NI_NUMERICHOST | NI_NUMERICSERV) == 0)
result->ip = string(hostIP);
else
return;
result->valid = true;
},
&resultCollection[i++]));
}
runService(service);
}
resultCollection.erase(std::remove_if(resultCollection.begin(), resultCollection.end(), [](const mDNSResult& res) { return res.ip.empty(); }),
resultCollection.end());
if (resultCollection.empty())
return false;
if (resultCollection.size() > 1)
LOG(NOTICE, LOG_TAG) << "Multiple servers found. Using first" << "\n";
result = resultCollection.front();
return true;
}
#undef CHECKED