mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-24 22:46:14 +02:00
merged Outurnate's bonjour service
This commit is contained in:
parent
010e797e1d
commit
68d73321e0
8 changed files with 331 additions and 26 deletions
|
@ -36,9 +36,9 @@ else ifeq ($(TARGET), MAC)
|
||||||
|
|
||||||
CXX = /usr/bin/g++
|
CXX = /usr/bin/g++
|
||||||
STRIP = strip
|
STRIP = strip
|
||||||
CXXFLAGS += -DHAS_TREMOR -DHAS_COREAUDIO -DFREEBSD -I/usr/local/include -Wno-unused-local-typedef -Wno-deprecated
|
CXXFLAGS += -DHAS_TREMOR -DHAS_COREAUDIO -DFREEBSD -DHAS_BONJOUR -I/usr/local/include -Wno-unused-local-typedef -Wno-deprecated
|
||||||
LDFLAGS = -logg -lvorbisidec -lFLAC -L/usr/local/lib -framework AudioToolbox -framework CoreFoundation
|
LDFLAGS = -logg -lvorbisidec -lFLAC -L/usr/local/lib -framework AudioToolbox -framework CoreFoundation
|
||||||
OBJ += player/coreAudioPlayer.o
|
OBJ += player/coreAudioPlayer.o browseBonjour.o
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
|
|
4
client/browseAvahi.cpp
Normal file → Executable file
4
client/browseAvahi.cpp
Normal file → Executable file
|
@ -180,7 +180,7 @@ void BrowseAvahi::client_callback(AvahiClient *c, AvahiClientState state, AVAHI_
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool BrowseAvahi::browse(const std::string& serviceName, int proto, AvahiResult& result, int timeout)
|
bool BrowseAvahi::browse(const std::string& serviceName, mDNSResult& result, int timeout)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -194,7 +194,7 @@ bool BrowseAvahi::browse(const std::string& serviceName, int proto, AvahiResult&
|
||||||
throw SnapException("BrowseAvahi - Failed to create client: " + std::string(avahi_strerror(error)));
|
throw SnapException("BrowseAvahi - Failed to create client: " + std::string(avahi_strerror(error)));
|
||||||
|
|
||||||
/* Create the service browser */
|
/* Create the service browser */
|
||||||
if (!(sb_ = avahi_service_browser_new(client_, AVAHI_IF_UNSPEC, proto, serviceName.c_str(), NULL, (AvahiLookupFlags)0, browse_callback, this)))
|
if (!(sb_ = avahi_service_browser_new(client_, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, serviceName.c_str(), NULL, (AvahiLookupFlags)0, browse_callback, this)))
|
||||||
throw SnapException("BrowseAvahi - Failed to create service browser: " + std::string(avahi_strerror(avahi_client_errno(client_))));
|
throw SnapException("BrowseAvahi - Failed to create service browser: " + std::string(avahi_strerror(avahi_client_errno(client_))));
|
||||||
|
|
||||||
result_.valid_ = false;
|
result_.valid_ = false;
|
||||||
|
|
23
client/browseAvahi.h
Normal file → Executable file
23
client/browseAvahi.h
Normal file → Executable file
|
@ -15,6 +15,8 @@
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
***/
|
***/
|
||||||
|
#ifndef BROWSEAVAHI_H
|
||||||
|
#define BROWSEAVAHI_H
|
||||||
|
|
||||||
#include <avahi-client/client.h>
|
#include <avahi-client/client.h>
|
||||||
#include <avahi-client/lookup.h>
|
#include <avahi-client/lookup.h>
|
||||||
|
@ -23,25 +25,16 @@
|
||||||
#include <avahi-common/malloc.h>
|
#include <avahi-common/malloc.h>
|
||||||
#include <avahi-common/error.h>
|
#include <avahi-common/error.h>
|
||||||
|
|
||||||
#include <string>
|
class BrowseAvahi;
|
||||||
|
|
||||||
|
#include "browsemDNS.h"
|
||||||
|
|
||||||
struct AvahiResult
|
class BrowseAvahi : public BrowsemDNS
|
||||||
{
|
|
||||||
int proto_;
|
|
||||||
std::string ip_;
|
|
||||||
std::string host_;
|
|
||||||
size_t port_;
|
|
||||||
bool valid_;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class BrowseAvahi
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BrowseAvahi();
|
BrowseAvahi();
|
||||||
~BrowseAvahi();
|
~BrowseAvahi();
|
||||||
bool browse(const std::string& serviceName, int proto, AvahiResult& result, int timeout);
|
bool browse(const std::string& serviceName, mDNSResult& result, int timeout) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void cleanUp();
|
void cleanUp();
|
||||||
|
@ -49,8 +42,8 @@ private:
|
||||||
static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata);
|
static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata);
|
||||||
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata);
|
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata);
|
||||||
AvahiClient* client_;
|
AvahiClient* client_;
|
||||||
AvahiResult result_;
|
mDNSResult result_;
|
||||||
AvahiServiceBrowser* sb_;
|
AvahiServiceBrowser* sb_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
268
client/browseBonjour.cpp
Executable file
268
client/browseBonjour.cpp
Executable file
|
@ -0,0 +1,268 @@
|
||||||
|
#include "browseBonjour.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <iostream>
|
||||||
|
#include <deque>
|
||||||
|
#ifdef WINDOWS
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#include <Ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/snapException.h"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct DNSServiceRefDeleter
|
||||||
|
{
|
||||||
|
void operator () (DNSServiceRef* ref)
|
||||||
|
{
|
||||||
|
DNSServiceRefDeallocate(*ref);
|
||||||
|
delete ref;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::unique_ptr<DNSServiceRef, DNSServiceRefDeleter> DNSServiceHandle;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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 { 0, "", "", 0, false });
|
||||||
|
{
|
||||||
|
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->proto_ = address->sa_family;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultCollection.size() == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (resultCollection.size() != 1)
|
||||||
|
logO << "Multiple servers found. Using first" << endl;
|
||||||
|
|
||||||
|
result = resultCollection[0];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef CHECKED
|
15
client/browseBonjour.h
Executable file
15
client/browseBonjour.h
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef BROWSEBONJOUR_H
|
||||||
|
#define BROWSEBONJOUR_H
|
||||||
|
|
||||||
|
#include <dns_sd.h>
|
||||||
|
|
||||||
|
class BrowseBonjour;
|
||||||
|
|
||||||
|
#include "browsemDNS.h"
|
||||||
|
|
||||||
|
class BrowseBonjour : public BrowsemDNS
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool browse(const std::string& serviceName, mDNSResult& result, int timeout) override;
|
||||||
|
};
|
||||||
|
#endif
|
29
client/browsemDNS.h
Executable file
29
client/browsemDNS.h
Executable file
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef BROWSEMDNS_H
|
||||||
|
#define BROWSEMDNS_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct mDNSResult
|
||||||
|
{
|
||||||
|
int proto_;
|
||||||
|
std::string ip_;
|
||||||
|
std::string host_;
|
||||||
|
uint16_t port_;
|
||||||
|
bool valid_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BrowsemDNS
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual bool browse(const std::string& serviceName, mDNSResult& result, int timeout) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(HAS_AVAHI)
|
||||||
|
#include "browseAvahi.h"
|
||||||
|
typedef BrowseAvahi BrowseZeroConf;
|
||||||
|
#elif defined(HAS_BONJOUR)
|
||||||
|
#include "browseBonjour.h"
|
||||||
|
typedef BrowseBonjour BrowseZeroConf;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -21,12 +21,11 @@
|
||||||
|
|
||||||
#include "popl.hpp"
|
#include "popl.hpp"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
#include "browsemDNS.h"
|
||||||
|
|
||||||
#ifdef HAS_ALSA
|
#ifdef HAS_ALSA
|
||||||
#include "player/alsaPlayer.h"
|
#include "player/alsaPlayer.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAS_AVAHI
|
|
||||||
#include "browseAvahi.h"
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_DAEMON
|
#ifdef HAS_DAEMON
|
||||||
#include "common/daemon.h"
|
#include "common/daemon.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -166,14 +165,14 @@ int main (int argc, char **argv)
|
||||||
|
|
||||||
if (host.empty())
|
if (host.empty())
|
||||||
{
|
{
|
||||||
#ifdef HAS_AVAHI
|
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
||||||
BrowseAvahi browseAvahi;
|
BrowseZeroConf browser;
|
||||||
AvahiResult avahiResult;
|
mDNSResult avahiResult;
|
||||||
while (!g_terminated)
|
while (!g_terminated)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (browseAvahi.browse("_snapcast._tcp", AVAHI_PROTO_INET, avahiResult, 5000))
|
if (browser.browse("_snapcast._tcp", avahiResult, 5000))
|
||||||
{
|
{
|
||||||
host = avahiResult.ip_;
|
host = avahiResult.ip_;
|
||||||
port = avahiResult.port_;
|
port = avahiResult.port_;
|
||||||
|
|
|
@ -297,6 +297,7 @@ static std::string getMacAddress(int sock)
|
||||||
if (!(ifr.ifr_flags & IFF_LOOPBACK)) // don't count loopback
|
if (!(ifr.ifr_flags & IFF_LOOPBACK)) // don't count loopback
|
||||||
{
|
{
|
||||||
#ifdef __MACH__
|
#ifdef __MACH__
|
||||||
|
/// Dirty Mac version
|
||||||
struct ifaddrs *ifap, *ifaptr;
|
struct ifaddrs *ifap, *ifaptr;
|
||||||
unsigned char *ptr;
|
unsigned char *ptr;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue