Line data Source code
1 :
2 : /* Copyright (c) 2012, Stefan Eilemann <eile@eyescale.ch>
3 : *
4 : * This file is part of Lunchbox <https://github.com/Eyescale/Lunchbox>
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 2.1 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 "servus.h"
21 :
22 : #include "debug.h"
23 :
24 : #ifdef LUNCHBOX_USE_DNSSD
25 : # ifdef _MSC_VER
26 : # define SERVUS_BONJOUR
27 : # include "os.h"
28 : # else
29 : # include <arpa/inet.h>
30 : # include <sys/time.h>
31 : # include <unistd.h>
32 : # endif
33 : # ifdef __APPLE__
34 : # define SERVUS_BONJOUR
35 : # endif
36 : # ifndef SERVUS_BONJOUR
37 : # define SERVUS_AVAHI
38 : # include "lock.h"
39 : # include "scopedMutex.h"
40 : # endif
41 : # include <dns_sd.h>
42 : #endif
43 :
44 : #include <algorithm>
45 : #include <cassert>
46 : #include <cerrno>
47 : #include <cstdio>
48 : #include <cstdlib>
49 : #include <cstring>
50 : #include <iostream>
51 : #include <sstream>
52 :
53 : namespace lunchbox
54 : {
55 : typedef std::map< std::string, std::string > ValueMap;
56 : typedef std::map< std::string, ValueMap > InstanceMap;
57 : typedef ValueMap::const_iterator ValueMapCIter;
58 : typedef InstanceMap::const_iterator InstanceMapCIter;
59 29 : static const std::string empty_;
60 :
61 : #ifdef SERVUS_AVAHI
62 29 : static Lock lock_;
63 : #endif
64 :
65 : #define ANNOUNCE_TIMEOUT 1000 /*ms*/
66 :
67 : namespace detail
68 : {
69 : class Servus
70 : {
71 : public:
72 : #ifdef LUNCHBOX_USE_DNSSD
73 1 : explicit Servus( const std::string& name )
74 : : name_( name )
75 : , service_( 0 )
76 1 : , result_( lunchbox::Servus::Result::PENDING )
77 1 : {}
78 : #else
79 : explicit Servus( const std::string& ) {}
80 : #endif
81 :
82 1 : ~Servus()
83 1 : {
84 : #ifdef LUNCHBOX_USE_DNSSD
85 1 : withdraw();
86 : #endif
87 1 : }
88 :
89 : #ifdef LUNCHBOX_USE_DNSSD
90 2 : void set( const std::string& key, const std::string& value )
91 : {
92 2 : data_[ key ] = value;
93 2 : updateRecord_();
94 2 : }
95 : #else
96 : void set( const std::string&, const std::string& ) {}
97 : #endif
98 :
99 1 : Strings getKeys() const
100 : {
101 1 : Strings keys;
102 : #ifdef LUNCHBOX_USE_DNSSD
103 3 : for( ValueMapCIter i = data_.begin(); i != data_.end(); ++i )
104 2 : keys.push_back( i->first );
105 : #endif
106 1 : return keys;
107 : }
108 :
109 : #ifdef LUNCHBOX_USE_DNSSD
110 0 : const std::string& get( const std::string& key ) const
111 : {
112 0 : ValueMapCIter i = data_.find( key );
113 0 : if( i != data_.end( ))
114 0 : return i->second;
115 0 : return empty_;
116 : }
117 : #else
118 : const std::string& get( const std::string& ) const
119 : {
120 : return empty_;
121 : }
122 : #endif
123 :
124 : #ifdef LUNCHBOX_USE_DNSSD
125 2 : lunchbox::Servus::Result announce( const unsigned short port,
126 : const std::string& instance )
127 : {
128 2 : if( service_ )
129 0 : return lunchbox::Servus::Result( kDNSServiceErr_NotInitialized );
130 :
131 : TXTRecordRef record;
132 2 : createTXTRecord_( record );
133 :
134 : const lunchbox::Servus::Result result(
135 : DNSServiceRegister( &service_, 0 /* flags */,
136 : 0 /* all interfaces */,
137 2 : instance.empty() ? 0 : instance.c_str(),
138 : name_.c_str(), 0 /* default domains */,
139 2 : 0 /* hostname */, htons( port ),
140 2 : TXTRecordGetLength( &record ),
141 : TXTRecordGetBytesPtr( &record ),
142 : (DNSServiceRegisterReply)registerCBS_,
143 8 : this ));
144 2 : TXTRecordDeallocate( &record );
145 :
146 2 : if( result )
147 2 : return handleEvents_( service_, ANNOUNCE_TIMEOUT );
148 :
149 0 : LBWARN << "DNSServiceRegister returned: " << result << std::endl;
150 0 : return result;
151 : }
152 : #else
153 : lunchbox::Servus::Result announce( const unsigned short, const std::string&)
154 : {
155 : return lunchbox::Servus::Result( lunchbox::Servus::Result::NOT_SUPPORTED);
156 : }
157 : #endif
158 :
159 2 : void withdraw()
160 : {
161 : #ifdef LUNCHBOX_USE_DNSSD
162 2 : if( !service_ )
163 2 : return;
164 :
165 2 : DNSServiceRefDeallocate( service_ );
166 2 : service_ = 0;
167 : #endif
168 : }
169 :
170 0 : bool isAnnounced() const
171 : {
172 : #ifdef LUNCHBOX_USE_DNSSD
173 0 : return service_ != 0;
174 : #endif
175 : return false;
176 : }
177 :
178 : #ifdef LUNCHBOX_USE_DNSSD
179 2 : Strings discover( const lunchbox::Servus::Interface interface_,
180 : const unsigned browseTime )
181 : {
182 2 : instanceMap_.clear();
183 :
184 : # ifdef SERVUS_BONJOUR
185 : const lunchbox::Servus::Interface addr = interface_;
186 : # endif
187 : # ifdef SERVUS_AVAHI // no kDNSServiceInterfaceIndexLocalOnly support in avahi
188 2 : const lunchbox::Servus::Interface addr = lunchbox::Servus::IF_ALL;
189 : # endif
190 : DNSServiceRef service;
191 : const DNSServiceErrorType error = DNSServiceBrowse( &service, 0,
192 : addr,
193 : name_.c_str(),
194 : "",
195 : (DNSServiceBrowseReply)browseCBS_,
196 2 : this );
197 2 : if( error != kDNSServiceErr_NoError )
198 : {
199 0 : LBWARN << "DNSServiceDiscovery error: " << error << std::endl;
200 0 : DNSServiceRefDeallocate( service );
201 0 : return getInstances();
202 : }
203 :
204 2 : handleEvents_( service, browseTime );
205 2 : DNSServiceRefDeallocate( service );
206 :
207 : # ifdef SERVUS_AVAHI // let's implement InterfaceIndexLocalOnly for avahi
208 2 : if( interface_ == lunchbox::Servus::IF_LOCAL )
209 : {
210 2 : Strings hosts;
211 2 : char hostname[256] = {0};
212 :
213 2 : gethostname( hostname, 256 );
214 :
215 4 : std::string name = hostname;
216 2 : const size_t dotPos = name.find( '.' );
217 2 : if( dotPos != std::string::npos )
218 2 : name = name.substr( 0, dotPos );
219 :
220 2 : hosts.push_back( name );
221 2 : hosts.push_back( name + ".local." );
222 :
223 4 : InstanceMap localData;
224 12 : for( InstanceMapCIter i = instanceMap_.begin();
225 8 : i != instanceMap_.end(); ++i )
226 : {
227 2 : const ValueMap& values = i->second;
228 2 : const ValueMapCIter j = values.find( "servus_host" );
229 2 : const std::string& current = j->second;
230 2 : if( std::find( hosts.begin(), hosts.end(), current ) !=
231 : hosts.end( ))
232 : {
233 2 : localData[ i->first ] = i->second;
234 : }
235 : }
236 4 : instanceMap_.swap( localData );
237 : }
238 : # endif
239 2 : return getInstances();
240 : }
241 : #else
242 : Strings discover( const lunchbox::Servus::Interface, const unsigned )
243 : {
244 : return getInstances();
245 : }
246 : #endif
247 :
248 2 : Strings getInstances() const
249 : {
250 2 : Strings instances;
251 : #ifdef LUNCHBOX_USE_DNSSD
252 12 : for( InstanceMapCIter i = instanceMap_.begin();
253 8 : i != instanceMap_.end(); ++i )
254 : {
255 2 : instances.push_back( i->first );
256 : }
257 : #endif
258 2 : return instances;
259 : }
260 :
261 : #ifdef LUNCHBOX_USE_DNSSD
262 0 : Strings getKeys( const std::string& instance ) const
263 : {
264 0 : Strings keys;
265 0 : InstanceMapCIter i = instanceMap_.find( instance );
266 0 : if( i == instanceMap_.end( ))
267 0 : return keys;
268 :
269 0 : const ValueMap& values = i->second;
270 0 : for( ValueMapCIter j = values.begin(); j != values.end(); ++j )
271 0 : keys.push_back( j->first );
272 0 : return keys;
273 : }
274 : #else
275 : Strings getKeys( const std::string& ) const
276 : {
277 : return Strings();
278 : }
279 : #endif
280 :
281 : #ifdef LUNCHBOX_USE_DNSSD
282 0 : bool containsKey( const std::string& instance,
283 : const std::string& key ) const
284 : {
285 0 : InstanceMapCIter i = instanceMap_.find( instance );
286 0 : if( i == instanceMap_.end( ))
287 0 : return false;
288 :
289 0 : const ValueMap& values = i->second;
290 0 : ValueMapCIter j = values.find( key );
291 0 : if( j == values.end( ))
292 0 : return false;
293 0 : return true;
294 : }
295 : #else
296 : bool containsKey( const std::string&, const std::string& ) const
297 : {
298 : return false;
299 : }
300 : #endif
301 :
302 : #ifdef LUNCHBOX_USE_DNSSD
303 2 : const std::string& get( const std::string& instance,
304 : const std::string& key ) const
305 : {
306 2 : InstanceMapCIter i = instanceMap_.find( instance );
307 2 : if( i == instanceMap_.end( ))
308 0 : return empty_;
309 :
310 2 : const ValueMap& values = i->second;
311 2 : ValueMapCIter j = values.find( key );
312 2 : if( j == values.end( ))
313 0 : return empty_;
314 2 : return j->second;
315 : }
316 : #else
317 : const std::string& get( const std::string&, const std::string& ) const
318 : {
319 : return empty_;
320 : }
321 : #endif
322 :
323 : #ifdef LUNCHBOX_USE_DNSSD
324 0 : void getData( lunchbox::Servus::Data& data )
325 : {
326 0 : data = instanceMap_;
327 0 : }
328 : #else
329 : void getData( lunchbox::Servus::Data& ) {}
330 : #endif
331 :
332 : private:
333 : #ifdef LUNCHBOX_USE_DNSSD
334 : const std::string name_;
335 : InstanceMap instanceMap_; //!< last discovered data
336 : ValueMap data_; //!< self data to announce
337 : DNSServiceRef service_; //!< used for announce()
338 : int32_t result_;
339 : std::string browsedName_;
340 :
341 2 : void updateRecord_()
342 : {
343 2 : if( !service_ )
344 3 : return;
345 :
346 : TXTRecordRef record;
347 1 : createTXTRecord_( record );
348 :
349 : const DNSServiceErrorType error =
350 : DNSServiceUpdateRecord( service_, 0, 0,
351 1 : TXTRecordGetLength( &record ),
352 2 : TXTRecordGetBytesPtr( &record ), 0 );
353 1 : TXTRecordDeallocate( &record );
354 1 : if( error != kDNSServiceErr_NoError )
355 0 : LBWARN << "DNSServiceUpdateRecord error: " << error << std::endl;
356 : }
357 :
358 3 : void createTXTRecord_( TXTRecordRef& record )
359 : {
360 3 : TXTRecordCreate( &record, 0, 0 );
361 6 : for( ValueMapCIter i = data_.begin(); i != data_.end(); ++i )
362 : {
363 3 : const std::string& key = i->first;
364 3 : const std::string& value = i->second;
365 3 : const uint8_t valueSize = value.length() > 255 ?
366 3 : 255 : uint8_t( value.length( ));
367 3 : TXTRecordSetValue( &record, key.c_str(), valueSize, value.c_str( ));
368 : }
369 3 : }
370 :
371 8 : lunchbox::Servus::Result handleEvents_( DNSServiceRef service,
372 : const int32_t timeout = -1 )
373 : {
374 8 : assert( service );
375 8 : if( !service )
376 0 : return lunchbox::Servus::Result( kDNSServiceErr_Unknown );
377 :
378 8 : const int fd = DNSServiceRefSockFD( service );
379 8 : const int nfds = fd + 1;
380 :
381 55 : while( result_ == lunchbox::Servus::Result::PENDING )
382 : {
383 : fd_set fdSet;
384 41 : FD_ZERO( &fdSet );
385 41 : FD_SET( fd, &fdSet );
386 :
387 : struct timeval tv;
388 41 : tv.tv_sec = timeout / 1000;
389 41 : tv.tv_usec = (timeout % 1000) * 1000;
390 :
391 : const int result = ::select( nfds, &fdSet, 0, 0,
392 41 : timeout < 0 ? 0 : &tv );
393 41 : switch( result )
394 : {
395 : case 0: // timeout
396 4 : return lunchbox::Servus::Result( result_ );
397 :
398 : case -1: // error
399 0 : LBWARN << "Select error: " << strerror( errno ) << " ("
400 0 : << errno << ")" << std::endl;
401 0 : if( errno != EINTR )
402 : {
403 0 : withdraw();
404 0 : result_ = errno;
405 : }
406 0 : break;
407 :
408 : default:
409 39 : if( FD_ISSET( fd, &fdSet ))
410 : {
411 : const DNSServiceErrorType error =
412 39 : DNSServiceProcessResult( service );
413 :
414 39 : if( error != kDNSServiceErr_NoError )
415 : {
416 0 : LBWARN << "DNSServiceProcessResult error: " << error
417 0 : << std::endl;
418 0 : withdraw();
419 0 : result_ = error;
420 : }
421 : }
422 39 : break;
423 : }
424 : }
425 :
426 6 : lunchbox::Servus::Result result( result_ );
427 6 : result_ = lunchbox::Servus::Result::PENDING; // reset for next operation
428 6 : return result;
429 : }
430 :
431 2 : static void registerCBS_( DNSServiceRef, DNSServiceFlags,
432 : DNSServiceErrorType error, const char* name,
433 : const char* type, const char* domain,
434 : Servus* servus )
435 : {
436 2 : servus->registerCB_( name, type, domain, error );
437 2 : }
438 :
439 2 : void registerCB_( const char* name, const char* type, const char* domain,
440 : DNSServiceErrorType error )
441 : {
442 2 : if( error == kDNSServiceErr_NoError)
443 6 : LBINFO << "Registered " << name << "." << type << "." << domain
444 6 : << std::endl;
445 : else
446 : {
447 0 : LBWARN << "Register callback error: " << error << std::endl;
448 0 : withdraw();
449 : }
450 2 : result_ = error;
451 2 : }
452 :
453 4 : static void browseCBS_( DNSServiceRef, DNSServiceFlags flags,
454 : uint32_t interfaceIdx, DNSServiceErrorType error,
455 : const char* name, const char* type,
456 : const char* domain, Servus* servus )
457 : {
458 4 : servus->browseCB_( flags, interfaceIdx, error, name, type, domain );
459 4 : }
460 :
461 4 : void browseCB_( DNSServiceFlags flags, uint32_t interfaceIdx,
462 : DNSServiceErrorType error, const char* name,
463 : const char* type, const char* domain )
464 : {
465 4 : if( error != kDNSServiceErr_NoError)
466 : {
467 0 : LBWARN << "Browse callback error: " << error << std::endl;
468 0 : return;
469 : }
470 :
471 4 : if( !( flags & kDNSServiceFlagsAdd ))
472 0 : return;
473 :
474 4 : browsedName_ = name;
475 :
476 4 : DNSServiceRef service = 0;
477 : const DNSServiceErrorType resolve =
478 : DNSServiceResolve( &service, 0, interfaceIdx, name, type, domain,
479 4 : (DNSServiceResolveReply)resolveCBS_, this );
480 4 : if( resolve != kDNSServiceErr_NoError)
481 0 : LBWARN << "DNSServiceResolve error: " << resolve << std::endl;
482 :
483 4 : if( service )
484 : {
485 4 : handleEvents_( service, 500 );
486 4 : DNSServiceRefDeallocate( service );
487 : }
488 : }
489 :
490 4 : static void resolveCBS_( DNSServiceRef, DNSServiceFlags,
491 : uint32_t /*interfaceIdx*/,
492 : DNSServiceErrorType error,
493 : const char* /*name*/, const char* host,
494 : uint16_t /*port*/,
495 : uint16_t txtLen, const unsigned char* txt,
496 : Servus* servus )
497 : {
498 4 : if( error == kDNSServiceErr_NoError)
499 4 : servus->resolveCB_( host, txtLen, txt );
500 4 : servus->result_ = error;
501 4 : }
502 :
503 4 : void resolveCB_( const char* host, uint16_t txtLen,
504 : const unsigned char* txt )
505 : {
506 4 : ValueMap& values = instanceMap_[ browsedName_ ];
507 4 : values[ "servus_host" ] = host;
508 :
509 4 : char key[256] = {0};
510 4 : const char* value = 0;
511 4 : uint8_t valueLen = 0;
512 :
513 4 : uint16_t i = 0;
514 14 : while( TXTRecordGetItemAtIndex( txtLen, txt, i, sizeof( key ), key,
515 10 : &valueLen, (const void**)( &value )) ==
516 : kDNSServiceErr_NoError )
517 : {
518 :
519 6 : values[ key ] = std::string( value, valueLen );
520 6 : ++i;
521 : }
522 4 : }
523 : #endif
524 : };
525 : }
526 :
527 1 : Servus::Servus( const std::string& name )
528 1 : : impl_( new detail::Servus( name ))
529 1 : {}
530 :
531 1 : Servus::~Servus()
532 : {
533 1 : delete impl_;
534 1 : }
535 :
536 0 : std::string Servus::Result::getString() const
537 : {
538 0 : const int32_t code = getCode();
539 0 : switch( code )
540 : {
541 : #ifdef LUNCHBOX_USE_DNSSD
542 0 : case kDNSServiceErr_Unknown: return "unknown error";
543 0 : case kDNSServiceErr_NoSuchName: return "name not found";
544 0 : case kDNSServiceErr_NoMemory: return "out of memory";
545 0 : case kDNSServiceErr_BadParam: return "bad parameter";
546 0 : case kDNSServiceErr_BadReference: return "bad reference";
547 0 : case kDNSServiceErr_BadState: return "bad state";
548 0 : case kDNSServiceErr_BadFlags: return "bad flags";
549 0 : case kDNSServiceErr_Unsupported: return "unsupported";
550 0 : case kDNSServiceErr_NotInitialized: return "not initialized";
551 0 : case kDNSServiceErr_AlreadyRegistered: return "already registered";
552 0 : case kDNSServiceErr_NameConflict: return "name conflict";
553 0 : case kDNSServiceErr_Invalid: return "invalid value";
554 0 : case kDNSServiceErr_Firewall: return "firewall";
555 : case kDNSServiceErr_Incompatible:
556 0 : return "client library incompatible with daemon";
557 0 : case kDNSServiceErr_BadInterfaceIndex: return "bad interface index";
558 0 : case kDNSServiceErr_Refused: return "refused";
559 0 : case kDNSServiceErr_NoSuchRecord: return "no such record";
560 0 : case kDNSServiceErr_NoAuth: return "no authentication";
561 0 : case kDNSServiceErr_NoSuchKey: return "no such key";
562 0 : case kDNSServiceErr_NATTraversal: return "NAT traversal";
563 0 : case kDNSServiceErr_DoubleNAT: return "double NAT";
564 0 : case kDNSServiceErr_BadTime: return "bad time";
565 : #endif
566 :
567 0 : case PENDING: return "operation did not complete";
568 0 : case NOT_SUPPORTED: return "Lunchbox compiled without ZeroConf support";
569 :
570 : default:
571 0 : if( code > 0 )
572 0 : return ::strerror( code );
573 0 : return lunchbox::Result::getString();
574 : }
575 : }
576 :
577 2 : void Servus::set( const std::string& key, const std::string& value )
578 : {
579 2 : impl_->set( key, value );
580 2 : }
581 :
582 1 : Strings Servus::getKeys() const
583 : {
584 1 : return impl_->getKeys();
585 : }
586 :
587 0 : const std::string& Servus::get( const std::string& key ) const
588 : {
589 0 : return impl_->get( key );
590 : }
591 :
592 2 : Servus::Result Servus::announce( const unsigned short port,
593 : const std::string& instance )
594 : {
595 : #ifdef SERVUS_AVAHI
596 2 : ScopedWrite mutex( lock_ );
597 : #endif
598 2 : return impl_->announce( port, instance );
599 : }
600 :
601 1 : void Servus::withdraw()
602 : {
603 : #ifdef SERVUS_AVAHI
604 1 : ScopedWrite mutex( lock_ );
605 : #endif
606 1 : impl_->withdraw();
607 1 : }
608 :
609 0 : bool Servus::isAnnounced() const
610 : {
611 0 : return impl_->isAnnounced();
612 : }
613 :
614 2 : Strings Servus::discover( const Interface addr, const unsigned browseTime )
615 : {
616 : #ifdef SERVUS_AVAHI
617 2 : ScopedWrite mutex( lock_ );
618 : #endif
619 2 : return impl_->discover( addr, browseTime );
620 : }
621 :
622 0 : Strings Servus::getInstances() const
623 : {
624 0 : return impl_->getInstances();
625 : }
626 :
627 0 : Strings Servus::getKeys( const std::string& instance ) const
628 : {
629 0 : return impl_->getKeys( instance );
630 : }
631 :
632 0 : bool Servus::containsKey( const std::string& instance,
633 : const std::string& key ) const
634 : {
635 0 : return impl_->containsKey( instance, key );
636 : }
637 :
638 2 : const std::string& Servus::get( const std::string& instance,
639 : const std::string& key ) const
640 : {
641 2 : return impl_->get( instance, key );
642 : }
643 :
644 0 : void Servus::getData( Data& data )
645 : {
646 0 : impl_->getData( data );
647 0 : }
648 :
649 : #ifdef LUNCHBOX_USE_DNSSD
650 0 : std::ostream& operator << ( std::ostream& os, const Servus& servus )
651 : {
652 0 : os << disableFlush << disableHeader << "Servus instance"
653 0 : << (servus.isAnnounced() ? " " : " not ") << "announced" << indent;
654 :
655 0 : const Strings& keys = servus.getKeys();
656 0 : for( StringsCIter i = keys.begin(); i != keys.end(); ++i )
657 0 : os << std::endl << *i << " = " << servus.get( *i );
658 :
659 0 : return os << exdent << enableHeader << enableFlush;
660 : }
661 : #else
662 : std::ostream& operator << ( std::ostream& os, const Servus& )
663 : {
664 : return os << "No dnssd support, empty Servus implementation";
665 : }
666 : #endif
667 :
668 87 : }
|