aes67-daemon/daemon/mdns_server.cpp
Andrea Bondavalli 0dbfe78a10 - Added support for mDNS/RTSP sources advertisement compatible with Ravenna standard.
- mDNS advertisement for all local Sources is implemented by mdns_server.[cpp,hpp] and based on Linux Avahi.
  - RTSP server implementation supports DESCRIBE method to return SDP of local Sources and supports persistent connection but doesn't provide service updates via UPDATE method.
- Modified RTSP client to browse for _ravenna_session subtype of _rtsp._tcp services only.
- Modified SAP and mDNS discovery to avoid returning local Sources advertised by the daemon.
- Added "rtsp_port" and "node_id" config parameters.
  - rtsp_port is a read/write parameter that contains the port of the RTSP server.
  - node_id is a read only parameter that contains the unique daemon identifier used in mDNS and SAP sources announcements.
- Modified session manager to check that every Source and Sink created by the user has a unique name.
- Modified WebUI to visualize node_id and to visualize and edit rtsp_port parameters in Config tab.
- Extended regression test to verify proper behaviour of mDNS/RTSP sources advertisement and discovery.
- Modified REST API to browse remote sources to allow browsing of SAP, mDNS and all sources via HTTP GET /api/browse/sources/[all|mdns|sap].
- Amended daemon documentation.
2020-04-23 11:45:58 -07:00

339 lines
11 KiB
C++

//
// mdns_server.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 <http://www.gnu.org/licenses/>.
//
#include "mdns_server.hpp"
#include <boost/asio.hpp>
#include "config.hpp"
#include "interface.hpp"
#include "log.hpp"
#include "utils.hpp"
#ifdef _USE_AVAHI_
struct AvahiLockGuard {
AvahiLockGuard() = delete;
AvahiLockGuard(AvahiThreadedPoll* poll) : poll_(poll) {
if (poll_ != nullptr) {
avahi_threaded_poll_lock(poll_);
}
}
~AvahiLockGuard() {
if (poll_ != nullptr) {
avahi_threaded_poll_unlock(poll_);
}
}
private:
AvahiThreadedPoll* poll_{nullptr};
};
void MDNSServer::entry_group_callback(AvahiEntryGroup* g,
AvahiEntryGroupState state,
void* userdata) {
MDNSServer& mdns = *(reinterpret_cast<MDNSServer*>(userdata));
/* Called whenever the entry group state changes */
auto it = mdns.groups_.right.find(g);
if (it == mdns.groups_.right.end()) {
BOOST_LOG_TRIVIAL(debug)
<< "mdns_server:: cannot find name associated to group, skipping ...";
return;
}
switch (state) {
case AVAHI_ENTRY_GROUP_ESTABLISHED:
/* The entry group has been established successfully */
BOOST_LOG_TRIVIAL(debug) << "mdns_server:: entry group name ("
<< it->second << ") established";
break;
case AVAHI_ENTRY_GROUP_COLLISION: {
BOOST_LOG_TRIVIAL(warning)
<< "mdns_server:: entry group name (" << it->second << ") collision";
break;
}
case AVAHI_ENTRY_GROUP_FAILURE:
BOOST_LOG_TRIVIAL(error) << "mdns_server:: entry group name "
<< "(" << it->second << ") "
<< "failure"
<< avahi_strerror(avahi_client_errno(
avahi_entry_group_get_client(g)));
/* Some kind of failure happened while we were registering our services */
avahi_threaded_poll_quit(mdns.poll_.get());
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
case AVAHI_ENTRY_GROUP_REGISTERING:
break;
}
}
bool MDNSServer::create_services(AvahiClient* client) {
if (!config_->get_mdns_enabled()) {
return false;
}
std::unique_ptr<AvahiEntryGroup, decltype(&avahi_entry_group_free)> group{
avahi_entry_group_new(client, entry_group_callback, this),
&avahi_entry_group_free};
if (group == nullptr) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: create_services() failed avahi_entry_group_new(): "
<< avahi_strerror(avahi_client_errno(client));
return false;
}
/* register ravenna services, without user defined name */
int ret = avahi_entry_group_add_service(
group.get(), this->config_->get_interface_idx(), AVAHI_PROTO_INET, {},
node_id_.c_str(), "_http._tcp", nullptr, nullptr,
this->config_->get_http_port(), nullptr);
if (ret < 0) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: add_service() " << node_id_
<< "failed to add entry _http._tcp" << avahi_strerror(ret);
return false;
}
BOOST_LOG_TRIVIAL(info) << "mdns_server:: adding service _http._tcp for "
<< node_id_;
ret = avahi_entry_group_add_service_subtype(
group.get(), this->config_->get_interface_idx(), AVAHI_PROTO_INET, {},
node_id_.c_str(), "_http._tcp", nullptr, "_ravenna._sub._http._tcp");
if (ret < 0) {
BOOST_LOG_TRIVIAL(error) << "mdns_server:: add_service() " << node_id_
<< "failed to add subtype _ravenna._sub._http._tcp"
<< avahi_strerror(ret);
return false;
}
ret = avahi_entry_group_add_service(
group.get(), this->config_->get_interface_idx(), AVAHI_PROTO_INET, {},
node_id_.c_str(), "_rtsp._tcp", nullptr, nullptr,
this->config_->get_rtsp_port(), nullptr);
if (ret < 0) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: add_service() " << node_id_
<< "failed to add entry _rtsp._tcp" << avahi_strerror(ret);
return false;
}
BOOST_LOG_TRIVIAL(info) << "mdns_server:: adding service _rtsp._tcp for "
<< node_id_;
ret = avahi_entry_group_add_service_subtype(
group.get(), this->config_->get_interface_idx(), AVAHI_PROTO_INET, {},
node_id_.c_str(), "_rtsp._tcp", nullptr, "_ravenna._sub._rtsp._tcp");
if (ret < 0) {
BOOST_LOG_TRIVIAL(error) << "mdns_server:: add_service() " << node_id_
<< "failed to add subtype _ravenna._sub._rtsp._tcp"
<< avahi_strerror(ret);
return false;
}
/* Tell the server to register the service */
ret = avahi_entry_group_commit(group.get());
if (ret < 0) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: create_services() failed to commit entry group "
<< avahi_strerror(ret);
return false;
}
groups_.insert(entry_group_bimap_t::value_type(node_id_, group.release()));
return true;
}
#endif
bool MDNSServer::add_service(const std::string& name, const std::string& sdp) {
if (!running_ || !config_->get_mdns_enabled()) {
return false;
}
#ifdef _USE_AVAHI_
AvahiLockGuard avahi_lock(this->poll_.get());
if (avahi_client_get_state(client_.get()) != AVAHI_CLIENT_S_RUNNING) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: add_service() failed client is not running";
return false;
}
std::unique_ptr<AvahiEntryGroup, decltype(&avahi_entry_group_free)> group{
avahi_entry_group_new(client_.get(), entry_group_callback, this),
&avahi_entry_group_free};
if (group == nullptr) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: add_service() failed avahi_entry_group_new(): "
<< avahi_strerror(avahi_client_errno(client_.get()));
return false;
}
std::stringstream ss;
ss << node_id_ << " " << name;
int ret = avahi_entry_group_add_service(
group.get(), this->config_->get_interface_idx(), AVAHI_PROTO_INET, {},
ss.str().c_str(), "_rtsp._tcp", nullptr, nullptr,
this->config_->get_rtsp_port(), nullptr);
if (ret < 0) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: add_service() " << ss.str()
<< "failed to add service _rtsp._tcp" << avahi_strerror(ret);
return false;
}
ret = avahi_entry_group_add_service_subtype(
group.get(), this->config_->get_interface_idx(), AVAHI_PROTO_INET, {},
ss.str().c_str(), "_rtsp._tcp", nullptr,
"_ravenna_session._sub._rtsp._tcp");
if (ret < 0) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: add_service() " << ss.str()
<< "failed to add subtype _ravenna_session._sub._rtsp._tcp"
<< avahi_strerror(ret);
return false;
}
/* Tell the server to register the service */
ret = avahi_entry_group_commit(group.get());
if (ret < 0) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: add_service() " << ss.str()
<< "failed to commit entry group " << avahi_strerror(ret);
return false;
}
BOOST_LOG_TRIVIAL(info) << "mdns_server:: adding service for " << ss.str();
groups_.insert(entry_group_bimap_t::value_type(name, group.release()));
#endif
return true;
}
bool MDNSServer::remove_service(const std::string& name) {
if (!running_ || !config_->get_mdns_enabled()) {
return false;
}
#ifdef _USE_AVAHI_
AvahiLockGuard avahi_lock(poll_.get());
if (avahi_client_get_state(client_.get()) != AVAHI_CLIENT_S_RUNNING) {
BOOST_LOG_TRIVIAL(error)
<< "mdns_server:: remove_sub_service() failed client is not running";
return false;
}
auto it = groups_.left.find(name);
if (it == groups_.left.end()) {
return false;
}
BOOST_LOG_TRIVIAL(info) << "mdns_server:: removing service _rtsp._tcp for "
<< name;
avahi_entry_group_free(it->second);
groups_.left.erase(name);
#endif
return true;
}
#ifdef _USE_AVAHI_
void MDNSServer::client_callback(AvahiClient* client,
AvahiClientState state,
void* userdata) {
MDNSServer& mdns = *(reinterpret_cast<MDNSServer*>(userdata));
/* Called whenever the client or server state changes */
switch (state) {
case AVAHI_CLIENT_FAILURE:
BOOST_LOG_TRIVIAL(fatal) << "mdns_server:: 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_RUNNING:
/* The server has startup successfully and registered its host
* name on the network, so it's time to create our services */
if (!mdns.create_services(client)) {
avahi_threaded_poll_quit(mdns.poll_.get());
}
break;
case AVAHI_CLIENT_S_REGISTERING:
/* The server records are now being established. This
* might be caused by a host name change. We need to wait
* for our own records to register until the host name is
* properly esatblished. */
case AVAHI_CLIENT_S_COLLISION:
/* Let's drop our registered services. When the server is back
* in AVAHI_SERVER_RUNNING state we will register them
* again with the new host name. */
/* TODO */
break;
case AVAHI_CLIENT_CONNECTING:
break;
}
}
#endif
bool MDNSServer::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_server:: 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_server:: failed to create client: " << avahi_strerror(error);
return false;
}
(void)avahi_threaded_poll_start(poll_.get());
#endif
running_ = true;
return true;
}
bool MDNSServer::terminate() {
if (running_) {
running_ = false;
#ifdef _USE_AVAHI_
/* remove base services */
groups_.left.erase(node_id_);
avahi_threaded_poll_stop(poll_.get());
#endif
}
return true;
}