349 lines
11 KiB
C++
349 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 <boost/asio.hpp>
|
|
|
|
#include "config.hpp"
|
|
#include "interface.hpp"
|
|
#include "log.hpp"
|
|
#include "utils.hpp"
|
|
#include "mdns_server.hpp"
|
|
|
|
#ifdef _USE_AVAHI_
|
|
struct AvahiLockGuard {
|
|
AvahiLockGuard() = delete;
|
|
explicit AvahiLockGuard(AvahiThreadedPoll* poll) : poll_(poll) {
|
|
if (poll_ != nullptr) {
|
|
avahi_threaded_poll_lock(poll_);
|
|
}
|
|
}
|
|
~AvahiLockGuard() {
|
|
if (poll_ != nullptr) {
|
|
avahi_threaded_poll_unlock(poll_);
|
|
}
|
|
}
|
|
AvahiLockGuard(const AvahiLockGuard&) = delete;
|
|
AvahiLockGuard& operator=(const AvahiLockGuard&) = delete;
|
|
|
|
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
|
|
|
|
session_manager_->add_source_observer(
|
|
SessionManager::ObserverType::add_source,
|
|
std::bind(&MDNSServer::add_service, this, std::placeholders::_2,
|
|
std::placeholders::_3));
|
|
|
|
session_manager_->add_source_observer(
|
|
SessionManager::ObserverType::remove_source,
|
|
std::bind(&MDNSServer::remove_service, this, std::placeholders::_2));
|
|
|
|
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;
|
|
}
|