File Coverage

blib/lib/Net/Bonjour.pm
Criterion Covered Total %
statement 15 119 12.6
branch 0 26 0.0
condition 0 2 0.0
subroutine 5 20 25.0
pod 8 13 61.5
total 28 180 15.5


line stmt bran cond sub pod time code
1             package Net::Bonjour;
2              
3             =head1 NAME
4              
5             Net::Bonjour - Module for DNS service discovery (Apple's Bonjour)
6              
7             =head1 SYNOPSIS
8              
9             use Net::Bonjour;
10            
11             my $res = Net::Bonjour->new([, ]);
12              
13             $res->discover;
14              
15             foreach my $entry ( $res->entries ) {
16             printf "%s %s:%s\n", $entry->name, $entry->address, $entry->port;
17             }
18              
19             Or the cyclical way:
20              
21             use Net::Bonjour;
22              
23             my $res = Net::Bonjour->new([, ]);
24            
25             $res->discover;
26              
27             while ( 1 ) {
28             foreach my $entry ( $res->entries ) {
29             print $entry->name, "\n";
30             }
31             $res->discover;
32             }
33              
34             =head1 DESCRIPTION
35              
36             Net::Bonjour is a set of modules that allow one to discover local services via multicast DNS (mDNS)
37             or enterprise services via traditional DNS. This method of service discovery has been branded as
38             Bonjour by Apple Computer.
39              
40             =head2 Base Object
41              
42             The base object would be of the Net::Bonjour class. This object contains the resolver for DNS service discovery.
43              
44             =head2 Entry Object
45              
46             The base object (Net::Bonjour) will return entry objects of the class L.
47              
48             =head1 METHODS
49              
50             =head2 new([, , ])
51              
52             Creates a new Net::Bonjour discovery object. First argument specifies the service to discover,
53             e.g. http, ftp, afpovertcp, and ssh. The second argument specifies the protocol, i.e. tcp or udp.
54             I. The third argument specifies the discovery domain, the default is 'local'.
55              
56             If no arguments are specified, the resulting Net::Bonjour object will be empty and will not perform an
57             automatic discovery upon creation.
58              
59             =head2 all_services([])
60              
61             Returns an array of new Net::Renedezvous objects for each service type advertised in the domain. The argument
62             specifies the discovery domain, the default is 'local'. Please note that the resulting Net::Bonjour objects
63             will not have performed a discovery during the creation. Therefore, the discovery process will need to be run
64             prior to retriving a list of entries for that Net::Bonjour object.
65              
66             =head2 domain([])
67              
68             Get/sets current discovery domain. By default, the discovery domain is 'local'. Discovery for the 'local'
69             domain is done via MDNS while all other domains will be done via traditional DNS.
70              
71             =head2 discover
72              
73             Repeats the discovery process and reloads the entry list from this discovery.
74              
75             =head2 entries
76              
77             Returns an array of L objects for the last discovery.
78              
79             =head2 protocol([])
80              
81             Get/sets current protocol of the service type, i.e. TCP or UDP. Please note that this is not the protocol for
82             DNS connection.
83              
84             =head2 service([])
85              
86             Get/sets current service type.
87              
88             =head2 shift_entry
89              
90             Shifts off the first entry of the last discovery. The returned object will be a L object.
91              
92             =head1 EXAMPLES
93              
94             =head2 Print out a list of local websites
95              
96             print "Local Websites";
97            
98             use Net::Bonjour;
99              
100             my $res = Net::Bonjour->new('http');
101             $res->discover;
102              
103             foreach my $entry ( $res->entries) {
104             printf "%s
", $entry->address,
105             $entry->attribute('path'), $entry->name;
106             }
107            
108             print "";
109              
110             =head2 Find a service and connect to it
111              
112             use Socket;
113             use Net::Bonjour;
114            
115             my $res = Net::Bonjour->new('custom');
116             $res->discover;
117            
118             my $entry = $res->shift_entry;
119            
120             socket SOCK, PF_INET, SOCK_STREAM, scalar(getprotobyname('tcp'));
121            
122             connect SOCK, $entry->sockaddr;
123            
124             print SOCK "Send a message to the service";
125            
126             while ($line = ) { print $line; }
127            
128             close SOCK;
129              
130             =head2 Find all service types and print.
131              
132             use Net::Bonjour;
133              
134             foreach my $res ( Net::Bonjour->all_services ) {
135             printf "%s (%s)\n", $res->service, $res->protocol;
136             }
137              
138             =head2 Find and print all service types and entries.
139              
140             use Net::Bonjour;
141              
142             foreach my $res ( Net::Bonjour->all_services ) {
143             printf "-- %s (%s) ---\n", $res->service, $res->protocol;
144             $res->discover;
145             foreach my $entry ( $res->entries) {
146             printf "\t%s (%s:%s)\n", $entry->name, $entry->address, $entry->port;
147             }
148             }
149              
150             =head1 SEE ALSO
151              
152             L
153              
154             =head1 COPYRIGHT
155              
156             This library is free software and can be distributed or modified under the same terms as Perl itself.
157              
158             Bonjour (in this context) is a trademark of Apple Computer, Inc.
159              
160             =head1 AUTHORS
161              
162             The Net::Bonjour module was created by George Chlipala
163              
164             =cut
165              
166 3     3   60309 use strict;
  3         7  
  3         113  
167 3     3   13 use vars qw($VERSION $AUTOLOAD);
  3         14  
  3         220  
168              
169 3     3   3269 use Net::DNS;
  3         434106  
  3         336  
170 3     3   8284 use Net::Bonjour::Entry;
  3         24  
  3         108  
171 3     3   21 use Socket;
  3         7  
  3         8965  
172              
173             $VERSION = '0.96';
174              
175             sub new {
176 0     0 1   my $self = {};
177 0           bless $self, shift;
178 0           $self->_init(@_);
179 0           return $self;
180             }
181              
182             sub _init {
183 0     0     my $self = shift;
184              
185 0           $self->{'_dns_server'} = [ '224.0.0.251' ];
186 0           $self->{'_dns_port'} = '5353';
187 0           $self->{'_dns_domain'} = 'local';
188              
189 0 0         if (@_) {
190 0 0         $self->domain(pop) if $_[$#_] =~ /\./;
191 0           $self->service(@_);
192 0           $self->discover;
193             }
194 0           return;
195             }
196            
197             sub service {
198 0     0 1   my $self = shift;
199              
200 0 0         if (@_) {
201 0           $self->{'_service'} = shift;
202 0   0       $self->{'_proto'} = shift || 'tcp';
203             }
204 0           return $self->{'_service'};
205             }
206              
207             sub application {
208 0     0 0   my $self = shift;
209 0           return $self->service(@_);
210             }
211              
212             sub protocol {
213 0     0 1   my $self = shift;
214 0 0         if (@_) {
215 0           $self->{'_proto'} = shift;
216             }
217 0           return $self->{'_proto'};
218            
219             }
220            
221             sub fqdn {
222 0     0 0   my $self = shift;
223 0           return sprintf '_%s._%s.%s', $self->{'_service'}, $self->{'_proto'},
224             $self->{'_dns_domain'};
225             }
226              
227             sub dns_refresh {
228 0     0 0   my $self = shift;
229            
230 0           my $resolv = Net::DNS::Resolver->new();
231            
232 0           my $query = $resolv->query($self->fqdn, 'PTR');
233 0 0         return 0 if $query eq '';
234 0           $self->{'_dns_server'} = [$resolv->nameservers];
235 0           $self->{'_dns_port'} = $resolv->port;
236              
237 0           my @list;
238              
239 0           foreach my $rr ($query->answer) {
240 0 0         next if $rr->type ne 'PTR';
241 0           push(@list, $rr->ptrdname);
242             }
243              
244 0           return @list;
245             }
246              
247             sub mdns_refresh {
248 0     0 0   my $self = shift;
249              
250 0           my $query = Net::DNS::Packet->new($self->fqdn, 'PTR');
251              
252 0           socket DNS, PF_INET, SOCK_DGRAM, scalar(getprotobyname('udp'));
253 0           bind DNS, sockaddr_in(0,inet_aton('0.0.0.0'));
254 0           send DNS, $query->data, 0, sockaddr_in($self->{'_dns_port'}, inet_aton($self->{'_dns_server'}[0]));
255              
256 0           my $rout = '';
257 0           my $rin = '';
258 0           my %list;
259              
260 0           vec($rin, fileno(DNS), 1) = 1;
261              
262 0           while ( select($rout = $rin, undef, undef, 1.0) ) {
263 0           my $data;
264 0           recv(DNS, $data, 1000, 0);
265              
266 0           my($ans,$err) = Net::DNS::Packet->new(\$data, $self->{'_debug'});
267 0 0         next if $query->header->id != $ans->header->id;
268              
269 0           foreach my $rr ($ans->answer) {
270 0 0         next if $rr->type ne 'PTR';
271 0           $list{$rr->ptrdname} = 1;
272             }
273             }
274              
275 0           return keys(%list);
276              
277             }
278              
279             sub entries {
280 0     0 1   my $self = shift;
281 0           return @{$self->{'_results'}};
  0            
282             }
283              
284             sub shift_entry {
285 0     0 1   my $self = shift;
286 0           return shift(@{$self->{'_results'}});
  0            
287             }
288              
289             sub domain {
290 0     0 1   my $self = shift;
291            
292 0 0         if ( @_ ) {
293 0           $self->{'_dns_domain'} = shift;
294 0           $self->{'_dns_domain'} =~ s/(^\.|\.$)//;
295             }
296 0           return $self->{'_dns_domain'};
297             }
298              
299             sub refresh {
300 0     0 0   my $self = shift;
301 0           return $self->discover(@_);
302             }
303              
304             sub discover {
305 0     0 1   my $self = shift;
306              
307 0           my @list;
308 0           my $ptrs = [];
309              
310 0 0         if ( $self->domain(@_) eq 'local' ) {
311 0           @list = $self->mdns_refresh;
312             } else {
313 0           @list = $self->dns_refresh;
314             }
315              
316 0           foreach my $x ( 0..$#list ) {
317 0           my $host = Net::Bonjour::Entry->new($list[$x]);
318 0           $host->dns_server($self->{'_dns_server'});
319 0           $host->dns_port($self->{'_dns_port'});
320 0           $host->fetch;
321 0           $list[$x] = $host;
322             }
323              
324 0           $self->{'_results'} = [ @list ];
325 0           return scalar(@list);
326             }
327              
328             sub all_services {
329 0     0 1   my $self = {};
330 0           bless $self, shift;
331 0           $self->_init;
332 0           $self->service('services._dns-sd', 'udp');
333            
334 0           my @list;
335 0 0         if ( $self->domain(@_) eq 'local' ) {
336 0           @list = $self->mdns_refresh;
337             } else {
338 0           @list = $self->dns_refresh;
339             }
340              
341 0           foreach my $i ( 0..$#list ) {
342 0 0         next unless $list[$i] =~ /^_(.+)\._(\w+)/;
343 0           my $srvc = Net::Bonjour->new();
344 0           $srvc->service($1, $2);
345 0           $srvc->domain($self->domain);
346 0           $list[$i] = $srvc;
347             }
348 0           return @list;
349             }
350              
351             sub AUTOLOAD {
352 0     0     my $self = shift;
353 0           my $key = $AUTOLOAD;
354 0           $key =~ s/^.*:://;
355 0           $key = '_' . $key;
356 0 0         if ( @_ ) {
357 0           $self->{$key} = shift;
358             }
359 0           return $self->{$key};
360             }
361             1;