// // 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 . // #include #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(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 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 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(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::SourceObserverType::add_source, std::bind(&MDNSServer::add_service, this, std::placeholders::_2, std::placeholders::_3)); session_manager_->add_source_observer( SessionManager::SourceObserverType::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; }