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