LCOV - code coverage report
Current view: top level - servus/avahi - servus.h (source / functions) Hit Total Coverage
Test: Servus Lines: 139 192 72.4 %
Date: 2018-10-03 03:09:57 Functions: 20 23 87.0 %

          Line data    Source code
       1             : /* Copyright (c) 2014-2017, Stefan.Eilemann@epfl.ch
       2             :  *                          Juan Hernando <jhernando@fi.upm.es>
       3             :  *
       4             :  * This file is part of Servus <https://github.com/HBPVIS/Servus>
       5             :  *
       6             :  * This library is free software; you can redistribute it and/or modify it under
       7             :  * the terms of the GNU Lesser General Public License version 3.0 as published
       8             :  * by the Free Software Foundation.
       9             :  *
      10             :  * This library is distributed in the hope that it will be useful, but WITHOUT
      11             :  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
      12             :  * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
      13             :  * details.
      14             :  *
      15             :  * You should have received a copy of the GNU Lesser General Public License
      16             :  * along with this library; if not, write to the Free Software Foundation, Inc.,
      17             :  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
      18             :  */
      19             : 
      20             : #include <avahi-client/client.h>
      21             : #include <avahi-client/lookup.h>
      22             : #include <avahi-client/publish.h>
      23             : #include <avahi-common/error.h>
      24             : #include <avahi-common/simple-watch.h>
      25             : 
      26             : #include <net/if.h>
      27             : #include <stdexcept>
      28             : 
      29             : #include <cassert>
      30             : #include <mutex>
      31             : 
      32             : using ScopedLock = std::unique_lock<std::mutex>;
      33             : namespace chrono = std::chrono;
      34             : 
      35             : #define WARN std::cerr << __FILE__ << ":" << __LINE__ << ": "
      36             : 
      37             : // http://stackoverflow.com/questions/14430906
      38             : //   Proper way of doing this is using the threaded polling in avahi
      39             : namespace
      40             : {
      41             : static std::mutex _mutex;
      42             : 
      43          40 : int64_t _elapsedMilliseconds(
      44             :     const chrono::high_resolution_clock::time_point& startTime)
      45             : {
      46             :     const chrono::high_resolution_clock::time_point& endTime =
      47          40 :         chrono::high_resolution_clock::now();
      48          40 :     return chrono::nanoseconds(endTime - startTime).count() / 1000000;
      49             : }
      50             : }
      51             : 
      52             : namespace servus
      53             : {
      54             : namespace avahi
      55             : {
      56             : namespace
      57             : {
      58           5 : AvahiSimplePoll* _newSimplePoll()
      59             : {
      60          10 :     ScopedLock lock(_mutex);
      61          10 :     return avahi_simple_poll_new();
      62             : }
      63             : }
      64             : 
      65             : class Servus : public servus::Servus::Impl
      66             : {
      67             : public:
      68           5 :     explicit Servus(const std::string& name)
      69           5 :         : servus::Servus::Impl(name)
      70           5 :         , _poll(_newSimplePoll())
      71             :         , _client(0)
      72             :         , _browser(0)
      73             :         , _group(0)
      74             :         , _result(servus::Servus::Result::PENDING)
      75             :         , _port(0)
      76             :         , _announcable(false)
      77          10 :         , _scope(servus::Servus::IF_ALL)
      78             :     {
      79           5 :         if (!_poll)
      80           0 :             throw std::runtime_error("Can't setup avahi poll device");
      81             : 
      82           5 :         int error = 0;
      83          10 :         ScopedLock lock(_mutex);
      84           5 :         _client =
      85           5 :             avahi_client_new(avahi_simple_poll_get(_poll),
      86             :                              (AvahiClientFlags)(0), _clientCBS, this, &error);
      87           5 :         if (!_client)
      88           0 :             throw std::runtime_error(std::string("Can't setup avahi client: ") +
      89           0 :                                      avahi_strerror(error));
      90           5 :     }
      91             : 
      92          10 :     virtual ~Servus()
      93          10 :     {
      94           5 :         withdraw();
      95           5 :         endBrowsing();
      96             : 
      97          10 :         ScopedLock lock(_mutex);
      98           5 :         if (_client)
      99           5 :             avahi_client_free(_client);
     100           5 :         if (_poll)
     101           5 :             avahi_simple_poll_free(_poll);
     102          10 :     }
     103             : 
     104           0 :     std::string getClassName() const { return "avahi"; }
     105           3 :     servus::Servus::Result announce(const unsigned short port,
     106             :                                     const std::string& instance) final
     107             :     {
     108           6 :         ScopedLock lock(_mutex);
     109             : 
     110           3 :         _result = servus::Servus::Result::PENDING;
     111           3 :         _port = port;
     112           3 :         if (instance.empty())
     113           0 :             _announce = getHostname();
     114             :         else
     115           3 :             _announce = instance;
     116             : 
     117           3 :         if (_announcable)
     118           3 :             _createServices();
     119             :         else
     120             :         {
     121             :             const chrono::high_resolution_clock::time_point& startTime =
     122           0 :                 chrono::high_resolution_clock::now();
     123           0 :             while (!_announcable &&
     124           0 :                    _result == servus::Servus::Result::PENDING &&
     125           0 :                    _elapsedMilliseconds(startTime) < ANNOUNCE_TIMEOUT)
     126             :             {
     127           0 :                 avahi_simple_poll_iterate(_poll, ANNOUNCE_TIMEOUT);
     128             :             }
     129             :         }
     130             : 
     131           6 :         return servus::Servus::Result(_result);
     132             :     }
     133             : 
     134           7 :     void withdraw() final
     135             :     {
     136          14 :         ScopedLock lock(_mutex);
     137           7 :         _announce.clear();
     138           7 :         _port = 0;
     139           7 :         if (_group)
     140           4 :             avahi_entry_group_reset(_group);
     141           7 :     }
     142             : 
     143           0 :     bool isAnnounced() const final
     144             :     {
     145           0 :         ScopedLock lock(_mutex);
     146           0 :         return (_group && !avahi_entry_group_is_empty(_group));
     147             :     }
     148             : 
     149           3 :     servus::Servus::Result beginBrowsing(
     150             :         const ::servus::Servus::Interface addr) final
     151             :     {
     152           3 :         if (_browser)
     153           0 :             return servus::Servus::Result(servus::Servus::Result::PENDING);
     154             : 
     155           6 :         ScopedLock lock(_mutex);
     156           3 :         _scope = addr;
     157           3 :         _instanceMap.clear();
     158           3 :         _result = servus::Servus::Result::SUCCESS;
     159           3 :         _browser =
     160           3 :             avahi_service_browser_new(_client, AVAHI_IF_UNSPEC,
     161             :                                       AVAHI_PROTO_UNSPEC, _name.c_str(), 0,
     162             :                                       (AvahiLookupFlags)(0), _browseCBS, this);
     163           3 :         if (_browser)
     164           3 :             return servus::Servus::Result(_result);
     165             : 
     166           0 :         _result = avahi_client_errno(_client);
     167           0 :         WARN << "Failed to create browser for " << _name << ": "
     168           0 :              << avahi_strerror(_result) << std::endl;
     169           0 :         return servus::Servus::Result(_result);
     170             :     }
     171             : 
     172          34 :     servus::Servus::Result browse(const int32_t timeout) final
     173             :     {
     174          68 :         ScopedLock lock(_mutex);
     175          34 :         _result = servus::Servus::Result::PENDING;
     176             :         const chrono::high_resolution_clock::time_point& startTime =
     177          34 :             chrono::high_resolution_clock::now();
     178             : 
     179          34 :         size_t nErrors = 0;
     180          40 :         do
     181             :         {
     182          40 :             if (avahi_simple_poll_iterate(_poll, timeout) != 0)
     183             :             {
     184           0 :                 if (++nErrors < 10)
     185           0 :                     continue;
     186             : 
     187           0 :                 _result = servus::Servus::Result::POLL_ERROR;
     188           0 :                 break;
     189             :             }
     190          40 :         } while (_elapsedMilliseconds(startTime) < timeout);
     191             : 
     192          34 :         if (_result != servus::Servus::Result::POLL_ERROR)
     193          34 :             _result = servus::Servus::Result::SUCCESS;
     194             : 
     195          68 :         return servus::Servus::Result(_result);
     196             :     }
     197             : 
     198           8 :     void endBrowsing() final
     199             :     {
     200          16 :         ScopedLock lock(_mutex);
     201           8 :         if (_browser)
     202           3 :             avahi_service_browser_free(_browser);
     203           8 :         _browser = 0;
     204           8 :     }
     205             : 
     206           0 :     bool isBrowsing() const final { return _browser; }
     207             : private:
     208             :     AvahiSimplePoll* const _poll;
     209             :     AvahiClient* _client;
     210             :     AvahiServiceBrowser* _browser;
     211             :     AvahiEntryGroup* _group;
     212             :     int32_t _result;
     213             :     std::string _announce;
     214             :     unsigned short _port;
     215             :     bool _announcable;
     216             :     servus::Servus::Interface _scope;
     217             : 
     218             :     // Client state change
     219           5 :     static void _clientCBS(AvahiClient*, AvahiClientState state, void* servus)
     220             :     {
     221           5 :         ((Servus*)servus)->_clientCB(state);
     222           5 :     }
     223             : 
     224           5 :     void _clientCB(AvahiClientState state)
     225             :     {
     226           5 :         switch (state)
     227             :         {
     228             :         case AVAHI_CLIENT_S_RUNNING:
     229           5 :             _announcable = true;
     230           5 :             if (!_announce.empty())
     231           0 :                 _createServices();
     232           5 :             break;
     233             : 
     234             :         case AVAHI_CLIENT_FAILURE:
     235           0 :             _result = avahi_client_errno(_client);
     236           0 :             WARN << "Client failure: " << avahi_strerror(_result) << std::endl;
     237           0 :             avahi_simple_poll_quit(_poll);
     238           0 :             break;
     239             : 
     240             :         case AVAHI_CLIENT_S_COLLISION:
     241             :             // Can't setup client
     242           0 :             _result = EEXIST;
     243           0 :             avahi_simple_poll_quit(_poll);
     244           0 :             break;
     245             : 
     246             :         case AVAHI_CLIENT_S_REGISTERING:
     247             :             // The server records are now being established. This might be
     248             :             // caused by a host name change. We need to wait for our own records
     249             :             // to register until the host name is properly established.
     250             :             throw std::runtime_error(
     251           0 :                 "Unimplemented AVAHI_CLIENT_S_REGISTERING event");
     252             :             // withdraw & _createServices ?
     253             :             break;
     254             : 
     255             :         case AVAHI_CLIENT_CONNECTING:
     256             :             /*nop*/;
     257             :         }
     258           5 :     }
     259             : 
     260             :     // Browsing
     261           7 :     static void _browseCBS(AvahiServiceBrowser*, AvahiIfIndex ifIndex,
     262             :                            AvahiProtocol protocol, AvahiBrowserEvent event,
     263             :                            const char* name, const char* type,
     264             :                            const char* domain, AvahiLookupResultFlags,
     265             :                            void* servus)
     266             :     {
     267             :         ((Servus*)servus)
     268           7 :             ->_browseCB(ifIndex, protocol, event, name, type, domain);
     269           7 :     }
     270             : 
     271           7 :     void _browseCB(const AvahiIfIndex ifIndex, const AvahiProtocol protocol,
     272             :                    const AvahiBrowserEvent event, const char* name,
     273             :                    const char* type, const char* domain)
     274             :     {
     275           7 :         switch (event)
     276             :         {
     277             :         case AVAHI_BROWSER_FAILURE:
     278           0 :             _result = avahi_client_errno(_client);
     279           0 :             WARN << "Browser failure: " << avahi_strerror(_result) << std::endl;
     280           0 :             avahi_simple_poll_quit(_poll);
     281           0 :             break;
     282             : 
     283             :         case AVAHI_BROWSER_NEW:
     284             :             // We ignore the returned resolver object. In the callback function
     285             :             // we free it. If the server is terminated before the callback
     286             :             // function is called the server will free the resolver for us.
     287           3 :             if (!avahi_service_resolver_new(_client, ifIndex, protocol, name,
     288             :                                             type, domain, AVAHI_PROTO_UNSPEC,
     289             :                                             (AvahiLookupFlags)(0), _resolveCBS,
     290             :                                             this))
     291             :             {
     292           0 :                 _result = avahi_client_errno(_client);
     293           0 :                 WARN << "Error creating resolver: " << avahi_strerror(_result)
     294           0 :                      << std::endl;
     295           0 :                 avahi_simple_poll_quit(_poll);
     296             :             }
     297           3 :             break;
     298             : 
     299             :         case AVAHI_BROWSER_REMOVE:
     300           1 :             _instanceMap.erase(name);
     301           3 :             for (Listener* listener : _listeners)
     302           2 :                 listener->instanceRemoved(name);
     303             : 
     304           1 :             break;
     305             : 
     306             :         case AVAHI_BROWSER_ALL_FOR_NOW:
     307             :         case AVAHI_BROWSER_CACHE_EXHAUSTED:
     308           3 :             _result = servus::Result::SUCCESS;
     309           3 :             break;
     310             :         }
     311           7 :     }
     312             : 
     313           3 :     static void _resolveCBS(AvahiServiceResolver* resolver, AvahiIfIndex,
     314             :                             AvahiProtocol, AvahiResolverEvent event,
     315             :                             const char* name, const char*, const char*,
     316             :                             const char* host, const AvahiAddress*, uint16_t,
     317             :                             AvahiStringList* txt, AvahiLookupResultFlags flags,
     318             :                             void* servus)
     319             :     {
     320           3 :         ((Servus*)servus)->_resolveCB(resolver, event, name, host, txt, flags);
     321           3 :     }
     322             : 
     323           3 :     void _resolveCB(AvahiServiceResolver* resolver,
     324             :                     const AvahiResolverEvent event, const char* name,
     325             :                     const char* host, AvahiStringList* txt,
     326             :                     const AvahiLookupResultFlags flags)
     327             :     {
     328             :         // If browsing through the local interface, consider only the local
     329             :         // instances
     330           3 :         if (_scope == servus::Servus::IF_LOCAL &&
     331           0 :             !(flags & AVAHI_LOOKUP_RESULT_LOCAL))
     332           0 :             return;
     333             : 
     334           3 :         switch (event)
     335             :         {
     336             :         case AVAHI_RESOLVER_FAILURE:
     337           0 :             _result = avahi_client_errno(_client);
     338           0 :             WARN << "Resolver error: " << avahi_strerror(_result) << std::endl;
     339           0 :             break;
     340             : 
     341             :         case AVAHI_RESOLVER_FOUND:
     342             :         {
     343           3 :             ValueMap& values = _instanceMap[name];
     344           3 :             values["servus_host"] = host;
     345           9 :             for (; txt; txt = txt->next)
     346             :             {
     347             :                 const std::string entry(reinterpret_cast<const char*>(
     348             :                                             txt->text),
     349           6 :                                         txt->size);
     350           3 :                 const size_t pos = entry.find_first_of("=");
     351           6 :                 const std::string key = entry.substr(0, pos);
     352           6 :                 const std::string value = entry.substr(pos + 1);
     353           3 :                 values[key] = value;
     354             :             }
     355           9 :             for (Listener* listener : _listeners)
     356           6 :                 listener->instanceAdded(name);
     357             :         }
     358           3 :         break;
     359             :         }
     360             : 
     361           3 :         avahi_service_resolver_free(resolver);
     362             :     }
     363             : 
     364           2 :     void _updateRecord() final
     365             :     {
     366           2 :         if (_announce.empty() || !_announcable)
     367           1 :             return;
     368             : 
     369           1 :         if (_group)
     370           1 :             avahi_entry_group_reset(_group);
     371           1 :         _createServices();
     372             :     }
     373             : 
     374           4 :     void _createServices()
     375             :     {
     376           4 :         if (!_group)
     377           2 :             _group = avahi_entry_group_new(_client, _groupCBS, this);
     378             :         else
     379           2 :             avahi_entry_group_reset(_group);
     380             : 
     381           4 :         if (!_group)
     382           0 :             return;
     383             : 
     384           4 :         AvahiStringList* data = 0;
     385           6 :         for (const auto& i : _data)
     386           2 :             data = avahi_string_list_add_pair(data, i.first.c_str(),
     387           2 :                                               i.second.c_str());
     388             : 
     389           4 :         _result = avahi_entry_group_add_service_strlst(
     390             :             _group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, (AvahiPublishFlags)(0),
     391           4 :             _announce.c_str(), _name.c_str(), 0, 0, _port, data);
     392             : 
     393           4 :         if (data)
     394           2 :             avahi_string_list_free(data);
     395             : 
     396           4 :         if (_result != servus::Result::SUCCESS)
     397             :         {
     398           0 :             avahi_simple_poll_quit(_poll);
     399           0 :             return;
     400             :         }
     401             : 
     402           4 :         _result = avahi_entry_group_commit(_group);
     403           4 :         if (_result != servus::Result::SUCCESS)
     404           0 :             avahi_simple_poll_quit(_poll);
     405             :     }
     406             : 
     407           5 :     static void _groupCBS(AvahiEntryGroup*, AvahiEntryGroupState state,
     408             :                           void* servus)
     409             :     {
     410           5 :         ((Servus*)servus)->_groupCB(state);
     411           5 :     }
     412             : 
     413           5 :     void _groupCB(const AvahiEntryGroupState state)
     414             :     {
     415           5 :         switch (state)
     416             :         {
     417             :         case AVAHI_ENTRY_GROUP_ESTABLISHED:
     418           0 :             break;
     419             : 
     420             :         case AVAHI_ENTRY_GROUP_COLLISION:
     421             :         case AVAHI_ENTRY_GROUP_FAILURE:
     422           0 :             _result = EEXIST;
     423           0 :             avahi_simple_poll_quit(_poll);
     424           0 :             break;
     425             : 
     426             :         case AVAHI_ENTRY_GROUP_UNCOMMITED:
     427             :         case AVAHI_ENTRY_GROUP_REGISTERING:
     428             :             /*nop*/;
     429             :         }
     430           5 :     }
     431             : };
     432             : }
     433             : }

Generated by: LCOV version 1.11