merged Outurnate's bonjour service

This commit is contained in:
Johannes Pohl 2016-09-19 10:09:32 +02:00
parent 010e797e1d
commit 68d73321e0
8 changed files with 331 additions and 26 deletions

View file

@ -36,9 +36,9 @@ else ifeq ($(TARGET), MAC)
CXX = /usr/bin/g++
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
OBJ += player/coreAudioPlayer.o
OBJ += player/coreAudioPlayer.o browseBonjour.o
else

4
client/browseAvahi.cpp Normal file → Executable file
View 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
{
@ -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)));
/* 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_))));
result_.valid_ = false;

23
client/browseAvahi.h Normal file → Executable file
View file

@ -15,6 +15,8 @@
You should have received a copy of the GNU General Public License
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/lookup.h>
@ -23,25 +25,16 @@
#include <avahi-common/malloc.h>
#include <avahi-common/error.h>
#include <string>
class BrowseAvahi;
#include "browsemDNS.h"
struct AvahiResult
{
int proto_;
std::string ip_;
std::string host_;
size_t port_;
bool valid_;
};
class BrowseAvahi
class BrowseAvahi : public BrowsemDNS
{
public:
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:
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 client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata);
AvahiClient* client_;
AvahiResult result_;
mDNSResult result_;
AvahiServiceBrowser* sb_;
};
#endif

268
client/browseBonjour.cpp Executable file
View 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
View 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
View 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

View file

@ -21,12 +21,11 @@
#include "popl.hpp"
#include "controller.h"
#include "browsemDNS.h"
#ifdef HAS_ALSA
#include "player/alsaPlayer.h"
#endif
#ifdef HAS_AVAHI
#include "browseAvahi.h"
#endif
#ifdef HAS_DAEMON
#include "common/daemon.h"
#endif
@ -166,14 +165,14 @@ int main (int argc, char **argv)
if (host.empty())
{
#ifdef HAS_AVAHI
BrowseAvahi browseAvahi;
AvahiResult avahiResult;
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
BrowseZeroConf browser;
mDNSResult avahiResult;
while (!g_terminated)
{
try
{
if (browseAvahi.browse("_snapcast._tcp", AVAHI_PROTO_INET, avahiResult, 5000))
if (browser.browse("_snapcast._tcp", avahiResult, 5000))
{
host = avahiResult.ip_;
port = avahiResult.port_;

View file

@ -297,6 +297,7 @@ static std::string getMacAddress(int sock)
if (!(ifr.ifr_flags & IFF_LOOPBACK)) // don't count loopback
{
#ifdef __MACH__
/// Dirty Mac version
struct ifaddrs *ifap, *ifaptr;
unsigned char *ptr;