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 : }
|