File Coverage

blib/lib/NetworkInfo/Discovery/Rendezvous.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package NetworkInfo::Discovery::Rendezvous;
2 4     4   159708 use strict;
  4         11  
  4         167  
3 4     4   33 use Carp;
  4         8  
  4         554  
4 4     4   6399 use Encode;
  4         55198  
  4         611  
5 4     4   8271 use Net::Rendezvous;
  4         354396  
  4         120  
6 4     4   5461 use NetworkInfo::Discovery::Detect;
  0            
  0            
7             #use Data::Dumper; $Data::Dumper::Indent = 0; $Data::Dumper::Terse = 1;
8              
9             { no strict;
10             $VERSION = '0.06';
11             @ISA = qw(NetworkInfo::Discovery::Detect);
12             }
13              
14             =head1 NAME
15              
16             NetworkInfo::Discovery::Rendezvous - NetworkInfo::Discovery extension to find Rendezvous services
17              
18             =head1 VERSION
19              
20             Version 0.06
21              
22             =head1 SYNOPSIS
23              
24             use NetworkInfo::Discovery::Rendezvous;
25              
26             my $scanner = new NetworkInfo::Discovery::Rendezvous domain => 'example.net';
27             $scanner->do_it;
28              
29             # print the list of services sorted by host
30             for my $host ($scanner->get_interfaces) {
31             printf "%s (%s)\n", $host->{nodename}, $host->{ip};
32              
33             for my $service (@{$host->{services}}) {
34             printf " %s (%s:%d)\n", $service->{name}, $service->{protocol}, $service->{port}
35             }
36             }
37              
38             # print the list of services by service name
39             for my $service (sort {$a->{name} cmp $b->{name}} $scanner->get_services) {
40             printf "--- %s ---\n", $service->{name};
41              
42             for my $host (sort @{$service->{hosts}}) {
43             printf " %s (%s:%s:%d)\n %s\n", $host->{nodename}, $host->{ip},
44             $host->{services}[0]{protocol}, $host->{services}[0]{port}
45             }
46             }
47              
48              
49             See also F for a more complete example.
50              
51             =head1 DESCRIPTION
52              
53             This module is an extension to C which can find
54             services that register themselves using DNS-SD (DNS Service Discovery),
55             the services discovery protocol behind Apple Rendezvous.
56              
57             It will first try to enumerate all the registered services by querying
58             the C pseudo-service, which is available since the latest versions
59             of mDNSResponder. If nothing is returned, it will then query some well-known
60             services like C.
61              
62             =head1 METHODS
63              
64             =over 4
65              
66             =item B
67              
68             Creates and returns a new C object,
69             which derives from C.
70              
71             B
72              
73             =over 4
74              
75             =item *
76              
77             C - expects a scalar or an arrayref of domains
78              
79             =back
80              
81             B
82              
83             # specify one domain
84             my $scanner = new NetworkInfo::Discovery::Rendezvous domain => 'example.net';
85              
86             # specify several domains
87             my $scanner = new NetworkInfo::Discovery::Rendezvous domain => [ qw(local example.net) ];
88              
89             =cut
90              
91             sub new {
92             my $class = shift;
93             my $self = $class->SUPER::new();
94             my %args = @_;
95             #printf STDERR ">>> new(): args=(%s)\n", join ', ', map{Dumper($_)}@_;
96            
97             $class = ref($class) || $class;
98             bless $self, $class;
99            
100             # add private fiels
101             $self->{_domains_to_scan} ||= [];
102            
103             # treat given arguments
104             for my $attr (keys %args) {
105             $self->$attr($args{$attr}) if $self->can($attr);
106             }
107            
108             return $self
109             }
110              
111             =item B
112              
113             Run the services discovery.
114              
115             =cut
116              
117             sub do_it {
118             my $self = shift;
119            
120             for my $domain (@{$self->{_domains_to_scan}}) {
121             # first, try to find all registered services in the domain
122             my @services = Net::Rendezvous->all_services;
123            
124             # if services enumeration worked, try to find all instances of each service
125             if(@services) {
126             for my $service (@services) {
127             $self->discover_service($service->service, $service->protocol, $domain)
128             }
129            
130             # if it failed, try to find common services
131             } else {
132             $self->discover_service('afpovertcp', 'tcp', $domain); # AFP over TCP shares
133             $self->discover_service('ipp', 'tcp', $domain); # CUPS servers
134             $self->discover_service('presence', 'tcp', $domain); # iChat nodes
135             $self->discover_service('printer', 'tcp', $domain); # printing servers
136             $self->discover_service('workstation','tcp', $domain); # workgroup manager
137             }
138             }
139            
140             # group services
141             for my $host ($self->get_interfaces) {
142             for my $service (@{$host->{services}}) {
143             my $name = $service->{name};
144             $self->{servicelist}{$name}{name} ||= $name;
145             push @{$self->{servicelist}{$name}{hosts}}, $host
146             }
147             }
148            
149             # return list of found hosts
150             return $self->get_interfaces
151             }
152              
153             =item B
154              
155             Returns the list of discovered services.
156              
157             =cut
158              
159             sub get_services { return values %{$_[0]->{servicelist}} }
160              
161             =item B
162              
163             Discover instances of a given service.
164              
165             =cut
166              
167             sub discover_service {
168             my $self = shift;
169             my($service,$protocol,$domain) = @_;
170             #printf STDERR ">>> discover_service(): args=(%s)\n", join ', ', map{Dumper($_)}@_;
171            
172             my $rsrc = new Net::Rendezvous;
173             $rsrc->application($service, $protocol);
174             $rsrc->domain($domain);
175             $rsrc->discover;
176            
177             for my $entry ($rsrc->entries) {
178             # host name: $entry->name
179             # host addr: $entry->address
180             # host services:
181             # > this service:
182             # service name: $service
183             # service fqdn: $entry->fqdn
184             # service port: $entry->port
185             # service attr: $entry->all_attrs
186             $self->add_interface({
187             ip => $entry->address, nodename => $entry->name, services => [{
188             name => $service, port => $entry->port, protocol => $protocol,
189             fqdn => $entry->fqdn, attrs => { map {decode('utf-8',$_)} $entry->all_attrs }
190             }]
191             })
192             }
193             }
194              
195             =item B
196              
197             Add domains to the search list.
198              
199             B
200              
201             $scanner->domain('zeroconf.org');
202             $scanner->domain(qw(local zeroconf.org example.com));
203              
204             =cut
205              
206             sub domain {
207             my $self = shift;
208             if(ref $_[0] eq 'ARRAY') {
209             push @{$self->{_domains_to_scan}}, @{$_[0]}
210             } elsif(ref $_[0]) {
211             croak "Don't know how to deal with a ", lc(ref($_[0])), "ref."
212             } else {
213             push @{$self->{_domains_to_scan}}, @_
214             }
215             }
216              
217             =back
218              
219             =head1 SEE ALSO
220              
221             L, L
222              
223             =head1 AUTHOR
224              
225             SEbastien Aperghis-Tramoni, Esebastien@aperghis.netE
226              
227             =head1 BUGS
228              
229             Please report any bugs or feature requests to
230             C, or through the web interface at
231             L.
232             I will be notified, and then you'll automatically be notified of progress on
233             your bug as I make changes.
234              
235             =head1 COPYRIGHT & LICENSE
236              
237             Copyright 2004-2006 SEbastien Aperghis-Tramoni, All Rights Reserved.
238              
239             This program is free software; you can redistribute it and/or modify it
240             under the same terms as Perl itself.
241              
242             =cut
243              
244             1; # End of NetworkInfo::Discovery::Rendezvous