//
// mdns_client.cpp
//
// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved.
//
// 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
// 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 .
//
#include
#include "config.hpp"
#include "interface.hpp"
#include "log.hpp"
#include "rtsp_client.hpp"
#include "mdns_client.hpp"
#ifdef _USE_AVAHI_
void MDNSClient::resolve_callback(AvahiServiceResolver* r,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiResolverEvent event,
const char* name,
const char* type,
const char* domain,
const char* host_name,
const AvahiAddress* address,
uint16_t port,
AvahiStringList* txt,
AvahiLookupResultFlags flags,
void* userdata) {
MDNSClient& mdns = *(reinterpret_cast(userdata));
/* Called whenever a service has been resolved successfully or timed out */
switch (event) {
case AVAHI_RESOLVER_FAILURE:
BOOST_LOG_TRIVIAL(error) << "mdns_client:: (Resolver) failed to resolve "
<< "service " << name << " of type " << type
<< " in domain " << domain << " : "
<< avahi_strerror(avahi_client_errno(
avahi_service_resolver_get_client(r)));
break;
case AVAHI_RESOLVER_FOUND:
BOOST_LOG_TRIVIAL(debug) << "mdns_client:: (Resolver) "
<< "service " << name << " of type " << type
<< " in domain " << domain;
char addr[AVAHI_ADDRESS_STR_MAX];
if ((flags & AVAHI_LOOKUP_RESULT_LOCAL) &&
(mdns.config_->get_interface_name() == "lo")) {
::strncpy(addr, mdns.config_->get_ip_addr_str().c_str(),
sizeof addr - 1);
addr[sizeof addr - 1] = 0;
} else {
avahi_address_snprint(addr, sizeof(addr), address);
}
char info[256];
snprintf(info, sizeof(info),
"%s:%u (%s), "
"local: %i, "
"our_own: %i, "
"wide_area: %i, "
"multicast: %i, "
"cached: %i",
host_name, port, addr, !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
!!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
!!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
!!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
!!(flags & AVAHI_LOOKUP_RESULT_CACHED));
BOOST_LOG_TRIVIAL(debug) << "mdns_client:: (Resolver) " << info;
/* if not on loopback interface we don't want to receive self announced
* sessions or if on loopback interface we want only local announced
* sessions */
if ((!(flags & AVAHI_LOOKUP_RESULT_LOCAL) &&
(mdns.config_->get_interface_name() != "lo")) ||
((flags & AVAHI_LOOKUP_RESULT_LOCAL) &&
(mdns.config_->get_interface_name() == "lo"))) {
std::scoped_lock lock{mdns.sources_res_mutex_};
/* process RTSP client in async task */
mdns.sources_res_.emplace_back(std::async(
std::launch::async,
[&mdns, name_ = std::forward(name),
domain_ = std::forward(domain),
addr_ = std::forward(addr),
port_ = std::forward(std::to_string(port))] {
RtspClient::process(
std::bind(&MDNSClient::on_change_rtsp_source, &mdns,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3),
name_, domain_, std::string("/by-name/") + name_, addr_,
port_);
}));
}
break;
}
/* remove the resolver from the active pool */
mdns.active_resolvers.erase({name, domain});
avahi_service_resolver_free(r);
}
void MDNSClient::browse_callback(AvahiServiceBrowser* b,
AvahiIfIndex interface,
AvahiProtocol protocol,
AvahiBrowserEvent event,
const char* name,
const char* type,
const char* domain,
AvahiLookupResultFlags flags,
void* userdata) {
MDNSClient& mdns = *(reinterpret_cast(userdata));
/* Called whenever a new services becomes available on the LAN or is removed
* from the LAN */
switch (event) {
case AVAHI_BROWSER_FAILURE:
BOOST_LOG_TRIVIAL(fatal) << "mdns_client:: (Browser) "
<< avahi_strerror(avahi_client_errno(
avahi_service_browser_get_client(b)));
avahi_threaded_poll_quit(mdns.poll_.get());
return;
case AVAHI_BROWSER_NEW:
BOOST_LOG_TRIVIAL(info) << "mdns_client:: (Browser) NEW: "
<< "service " << name << " of type " << type
<< " in domain " << domain;
/* check if a resolver is already running for this name and domain */
if (mdns.active_resolvers.find({name, domain}) !=
mdns.active_resolvers.end()) {
/* if already running we don't run a new resolver */
BOOST_LOG_TRIVIAL(info)
<< "mdns_client:: (Browser): resolution already ongoing ...";
} else if (!(avahi_service_resolver_new(
mdns.client_.get(), interface, protocol, name, type,
domain, AVAHI_PROTO_UNSPEC, AVAHI_LOOKUP_NO_TXT,
resolve_callback, &mdns))) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_client:: "
<< "Failed to resolve service " << name << " : "
<< avahi_strerror(avahi_client_errno(mdns.client_.get()));
} else {
/* add the resolver to the active pool */
mdns.active_resolvers.emplace(name, domain);
}
break;
case AVAHI_BROWSER_REMOVE:
BOOST_LOG_TRIVIAL(info) << "mdns_client:: (Browser) REMOVE: "
<< "service " << name << " of type " << type
<< " in domain " << domain;
RtspClient::stop(name, domain);
mdns.on_remove_rtsp_source(name, domain);
break;
case AVAHI_BROWSER_ALL_FOR_NOW:
BOOST_LOG_TRIVIAL(debug) << "mdns_client:: (Browser) ALL_FOR_NOW";
break;
case AVAHI_BROWSER_CACHE_EXHAUSTED:
BOOST_LOG_TRIVIAL(debug) << "mdns_client:: (Browser) CACHE_EXHAUSTED";
break;
}
}
void MDNSClient::client_callback(AvahiClient* client,
AvahiClientState state,
void* userdata) {
MDNSClient& mdns = *(reinterpret_cast(userdata));
/* Called whenever the client or server state changes */
switch (state) {
case AVAHI_CLIENT_FAILURE:
BOOST_LOG_TRIVIAL(fatal) << "mdns_client:: server connection failure: "
<< avahi_strerror(avahi_client_errno(client));
/* TODO reconnect if disconnected */
avahi_threaded_poll_quit(mdns.poll_.get());
break;
case AVAHI_CLIENT_S_REGISTERING:
case AVAHI_CLIENT_S_RUNNING:
case AVAHI_CLIENT_S_COLLISION:
/* Create the service browser */
mdns.sb_.reset(avahi_service_browser_new(
client, mdns.config_->get_interface_idx(), AVAHI_PROTO_INET,
"_ravenna_session._sub._rtsp._tcp", nullptr, {}, browse_callback,
&mdns));
if (mdns.sb_ == nullptr) {
BOOST_LOG_TRIVIAL(fatal)
<< "mdns_client:: failed to create service browser: "
<< avahi_strerror(avahi_client_errno(mdns.client_.get()));
avahi_threaded_poll_quit(mdns.poll_.get());
}
break;
case AVAHI_CLIENT_CONNECTING:
break;
}
}
#endif
bool MDNSClient::init() {
if (running_) {
return true;
}
#ifdef _USE_AVAHI_
/* allocate poll loop object */
poll_.reset(avahi_threaded_poll_new());
if (poll_ == nullptr) {
BOOST_LOG_TRIVIAL(fatal)
<< "mdns_client:: failed to create threaded poll object";
return false;
}
/* allocate a new client */
int error;
client_.reset(avahi_client_new(avahi_threaded_poll_get(poll_.get()),
AVAHI_CLIENT_NO_FAIL, client_callback, this,
&error));
if (client_ == nullptr) {
BOOST_LOG_TRIVIAL(fatal)
<< "mdns_client:: failed to create client: " << avahi_strerror(error);
return false;
}
(void)avahi_threaded_poll_start(poll_.get());
#endif
running_ = true;
return true;
}
void MDNSClient::process_results() {
#ifdef _USE_AVAHI_
std::scoped_lock lock{sources_res_mutex_};
/* remove all completed results and populate remote sources list */
sources_res_.remove_if([](auto& result) {
if (!result.valid()) {
/* if invalid future remove from the list */
return true;
}
auto status = result.wait_for(std::chrono::milliseconds(0));
if (status == std::future_status::ready) {
result.get();
/* if completed remove from the list */
return true;
}
/* if not completed leave in the list */
return false;
});
#endif
}
bool MDNSClient::terminate() {
if (running_) {
running_ = false;
RtspClient::stop_all();
#ifdef _USE_AVAHI_
/* wait for all pending results and remove from list */
std::scoped_lock lock{sources_res_mutex_};
BOOST_LOG_TRIVIAL(info) << "mdns_client:: waiting for "
<< sources_res_.size() << " RTSP clients";
sources_res_.remove_if([](auto& result) {
if (result.valid()) {
result.wait();
}
return true;
});
avahi_threaded_poll_stop(poll_.get());
#endif
}
return true;
}