File Coverage

blib/lib/Net/Bonjour/Entry.pm
Criterion Covered Total %
statement 90 141 63.8
branch 19 32 59.3
condition 2 8 25.0
subroutine 13 16 81.2
pod 8 10 80.0
total 132 207 63.7


line stmt bran cond sub pod time code
1             package Net::Bonjour::Entry;
2              
3             =head1 NAME
4              
5             Net::Bonjour::Entry - Support module for mDNS service discovery (Apple's Bonjour)
6              
7             =head1 SYNOPSIS
8              
9             use Net::Bonjour;
10            
11             my $res = Net::Bonjour->new([, ]);
12             $res->discover;
13            
14             foreach my $entry ( $res->entries ) {
15             print $entry->name, "\n";
16             }
17            
18             =head1 DESCRIPTION
19              
20             Net::Bonjour::Entry is a module used to manage entries returned by a mDNS
21             service discovery (Apple's Bonjour). See L for more information.
22              
23             =head1 METHODS
24              
25             =head2 new([])
26              
27             Creates a new Net::Bonjour::Entry object. The optional argument defines the
28             fully qualifed domain name (FQDN) of the entry. Normal usage of the
29             L module will not require the construction of
30             Net::Bonjour::Entry objects, as they are automatically created during the
31             discovery process.
32              
33             =head2 address
34              
35             Returns the IP address of the entry.
36              
37             =head2 all_attrs
38              
39             Returns all the current attributes in the form of hashed array.
40              
41             =head2 attribute()
42              
43             Returns the specified attribute from the TXT record of the entry. TXT records
44             are used to specify additional information, e.g. path for http.
45              
46             =head2 dnsrr([])
47              
48             Returns an DNS answer packet of the entry. The output will be in the format
49             of a L object. The I designates the resource
50             record to answer with, i.e. PTR, SRV, or TXT. The default is PTR.
51              
52             =head2 fetch
53              
54             Reloads the information for the entry via mDNS.
55              
56             =head2 fqdn
57              
58             Returns the fully qualifed domain name (FQDN) of entry. An example FQDN is server._afpovertcp._tcp.local
59              
60             =head2 hostname
61              
62             Returns the hostname of the server, e.g. 'server.local'.
63              
64             =head2 name
65              
66             Returns the name of the entry. In the case of the fqdn example, the name
67             would be 'server'. This name may not be the hostname of the server. For
68             example, names for presence/tcp will be the name of the user and http/tcp will
69             be title of the web resource.
70              
71             =head2 port
72              
73             Returns the TCP or UDP port of the entry.
74              
75             =head2 sockaddr
76              
77             Returns the binary socket address for the resource and can be used directly to bind() sockets.
78              
79             =head1 EXAMPLES
80              
81             =head2 Print out a list of local websites
82              
83             print "Local Websites";
84            
85             use Net::Bonjour;
86              
87             my $res = Net::Bonjour->new('http');
88             $res->discover;
89              
90             foreach my $entry ( $res->entries) {
91             printf "%s
",
92             $entry->address, $entry->attribute('path'),
93             $entry->name;
94             }
95            
96             print "";
97            
98             =head2 Find a service and connect to it
99              
100             use Net::Bonjour;
101            
102             my $res = Net::Bonjour->new('custom');
103             $res->discover;
104            
105             my $entry = $res->shift_entry;
106            
107             socket SOCK, PF_INET, SOCK_STREAM, scalar(getprotobyname('tcp'));
108            
109             connect SOCK, $entry->sockaddr;
110            
111             print SOCK "Send a message to the service";
112            
113             while ($line = ) { print $line; }
114            
115             close SOCK;
116            
117             =head1 SEE ALSO
118              
119             L
120              
121             =head1 COPYRIGHT
122              
123             This library is free software and can be distributed or modified under the same terms as Perl itself.
124              
125             Bonjour (in this context) is a trademark of Apple Computer, Inc.
126              
127             =head1 AUTHORS
128              
129             The Net::Bonjour::Entry module was created by George Chlipala
130              
131             =cut
132              
133 3     3   22 use strict;
  3         7  
  3         378  
134 3     3   20 use vars qw($AUTOLOAD);
  3         5  
  3         262  
135 3     3   23 use Socket;
  3         5  
  3         2952  
136 3     3   20 use Net::DNS;
  3         5  
  3         6112  
137              
138             sub new {
139 2     2 1 57 my $self = {};
140 2         7 bless $self, shift;
141 2         33 $self->_init(@_);
142 2         15 return $self;
143             }
144              
145             sub _init {
146 2     2   6 my $self = shift;
147 2         19 $self->{'_dns_server'} = [ '224.0.0.251' ];
148 2         6 $self->{'_dns_port'} = '5353';
149 2         7 $self->{'_ip_type'} = 'A';
150 2         4 $self->{'_index'} = 0;
151 2         5 $self->{'_ttl'} = 3600;
152 2 50       16 if ( ref($_[0]) eq 'HASH') {
    50          
153 0         0 my $attrs = shift;
154 0         0 foreach my $k ( keys(%{$attrs}) ) {
  0         0  
155 0         0 $self->{'_' . $k} = $attrs->{$k};
156             }
157 0 0       0 $self->all_attrs if ref( $attrs->{'attr'} ) eq 'HASH';
158             } elsif ( $#_ == 0 ) {
159 0         0 $self->fqdn(shift);
160             }
161 2         3 return;
162             }
163              
164             sub fetch {
165 0     0 1 0 my $self = shift;
166              
167 0         0 my $res = Net::DNS::Resolver->new(
168             nameservers => $self->{'_dns_server'},
169             port => $self->{'_dns_port'}
170             );
171              
172 0         0 my ($name, $protocol, $ipType) = split(/(?fqdn,3);
173              
174 0         0 $self->{'_name'} = $name;
175 0         0 $self->type($protocol, $ipType);
176              
177 0   0     0 my $srv = $res->query($self->fqdn(), 'SRV') || return;
178 0         0 my $srvrr = ($srv->answer)[0];
179              
180 0         0 $self->priority($srvrr->priority);
181 0         0 $self->weight($srvrr->weight);
182 0         0 $self->port($srvrr->port);
183 0         0 $self->hostname($srvrr->target);
184              
185 0 0       0 if ($srv->additional) {
186 0         0 foreach my $additional ($srv->additional) {
187 0         0 $self->{'_' . uc($additional->type)} = $additional->address;
188             }
189             } else {
190 0         0 my $aquery = $res->query($srvrr->target, 'A');
191 0         0 my $arr = ($aquery->answer)[0];
192 0 0       0 if ( $arr->type eq 'A' ) {
193 0         0 $self->{'_' . uc($arr->type)} = $arr->address;
194             }
195             }
196              
197 0         0 my $txt = $res->query($self->fqdn, 'TXT');
198              
199             # Text::Parsewords, which is called by Net::DNS::RR::TXT can spew
200 0 0       0 if ( $txt ) {
201 0         0 local $^W = 0;
202 0         0 my $txti = 0;
203              
204 0         0 foreach my $txtrr ( $txt->answer ) {
205 0         0 $self->txtdata([$txtrr->char_str_list ]);
206 0         0 $self->index($txti++);
207 0         0 foreach my $txtln ( $txtrr->char_str_list ) {
208 0         0 my ($key,$val) = split(/=/,$txtln,2);
209 0         0 $self->attribute($key, $val);
210             }
211 0         0 $txti++;
212             }
213             }
214              
215 0         0 $self->text($txt);
216              
217 0         0 return;
218             }
219              
220             sub all_attrs {
221 2     2 1 6 my $self = shift;
222 2         10 my $index = $self->index;;
223 2 50       9 if ( @_ ) {
224 0         0 my $hash = shift;
225 0   0     0 $index = (shift || 0);
226 0         0 $self->{'_attr'}[$index] = { %{$hash} };
  0         0  
227             }
228 2         5 my @txts;
229 2         3 foreach ( keys(%{$self->{'_attr'}[$index]}) ) {
  2         11  
230 2         19 push(@txts, sprintf('%s=%s', $_, $self->{'_attr'}[$index]{$_}));
231             }
232 2         13 $self->txtdata( \@txts );
233 2         3 return %{$self->{'_attr'}[$index]};
  2         17  
234             }
235              
236             sub attribute {
237 4     4 1 7 my $self = shift;
238 4         7 my $key = shift;
239 4         32 my $index = $self->index;
240 4 100       12 if ( @_ ) {
241 2         8 $self->{'_attr'}[$index]{$key} = shift;
242             }
243 4         18 return $self->{'_attr'}[$index]{$key};
244             }
245              
246             sub type {
247 0     0 0 0 my $self = shift;
248 0 0       0 if ( @_ ) {
249 0         0 my $type = sprintf '%s/%s', shift, shift;
250 0         0 $type =~ s/_//g;
251 0         0 $self->{'_type'} = $type;
252             }
253 0         0 return $self->{'_type'};
254             }
255              
256             sub address {
257 4     4 1 8 my $self = shift;
258 4         9 my $key = '_' . $self->{'_ip_type'};
259 4 100       12 if ( @_ ) {
260 2         6 $self->{$key} = shift;
261             }
262 4         14 return $self->{$key};
263             }
264            
265             sub sockaddr {
266 0     0 1 0 my $self = shift;
267 0         0 return sockaddr_in($self->port, inet_aton($self->address));
268             }
269              
270             sub dnsrr {
271 6     6 1 14 my $self = shift;
272 6         143 my $type = uc(shift);
273              
274 6         12 my $packet;
275              
276 6   50     41 my $srv = Net::DNS::RR->new(
      50        
277             'type' => 'SRV',
278             'ttl' => $self->ttl,
279             'name' => $self->fqdn,
280             'port' => $self->port,
281             'priority' => ( $self->priority || 0 ),
282             'weight' => ( $self->weight || 0 ),
283             'target' => $self->hostname
284             );
285              
286 6         5369 my $txt = Net::DNS::RR->new(
287             'type' => 'TXT',
288             'ttl' => $self->ttl,
289             'name' => $self->fqdn,
290             'char_str_list' => $self->txtdata
291             );
292              
293 6 100       13680 if ($type eq 'SRV') {
    100          
294              
295 2         14 $packet = Net::DNS::Packet->new($self->fqdn, 'SRV', 'IN');
296 2         191 $packet->push('answer', $srv);
297              
298             } elsif ($type eq 'TXT') {
299              
300 2         11 $packet = Net::DNS::Packet->new($self->fqdn, 'TXT', 'IN');
301 2         173 $packet->push('answer', $txt);
302              
303             } else {
304              
305 2         21 my $app = (split(/\./, $self->fqdn,2))[1];
306              
307 2         25 $packet = Net::DNS::Packet->new($app, 'PTR', 'IN');
308              
309 2         320 $packet->push('answer', Net::DNS::RR->new(
310             'type' => 'PTR',
311             'ttl' => $self->ttl,
312             'ptrdname' => $self->fqdn,
313             'name' => $app
314             ));
315              
316 2         3305 $packet->push('additional', $srv, $txt);
317             }
318            
319 6         190 $packet->header->qr(1);
320 6         107 $packet->header->aa(1);
321 6         103 $packet->header->rd(0);
322              
323 6         109 my @addrs = ();
324              
325 6         15 foreach my $type (qw(A AAAA)) {
326              
327 12         61 my $rr = Net::DNS::RR->new(
328             'type' => $type,
329             'ttl' => $self->ttl,
330             'address' => $self->{'_' . $type},
331             'name' => $self->hostname
332             );
333              
334 12 100       7927 push(@addrs, $rr) if $self->{'_' . $type};
335             }
336              
337 6         31 $packet->push('additional', @addrs);
338 6         238 return $packet;
339             }
340              
341             sub name {
342 4     4 1 7 my $self = shift;
343 4 100       16 if ( $_[0] ) {
344 2         10 $self->{'_name'} = quotemeta($_[0]);
345             }
346 4         8 my $name = $self->{'_name'};
347 4         14 $name =~ s/\\([0-9]{3})/chr($1)/ge;
  0         0  
348 4         5 $name =~ s/\\x([0-9A-Fa-f]{2})/chr(hex($1))/ge;
  0         0  
349 4         44 $name =~ s/\\(.)/$1/g;
350 4         21 return $name;
351             }
352              
353             sub txtdata {
354 8     8 0 24 my $self = shift;
355 8         40 my $index = $self->index;
356 8 100       37 if ( ref($_[0]) eq 'ARRAY' ) {
357 2         4 my $list = shift;
358 2         3 $self->{'_txtdata'}[$index] = [ @{$list} ];
  2         11  
359             }
360 8         42 return $self->{'_txtdata'}[$index];
361             }
362              
363             sub AUTOLOAD {
364 108     108   142 my $self = shift;
365 108         2504 my $key = $AUTOLOAD;
366 108         422 $key =~ s/^.*:://;
367 108         249 $key = '_' . $key;
368 108 100       242 if ( @_ ) {
369 6         15 $self->{$key} = shift;
370             }
371 108         638 return $self->{$key};
372             }
373              
374             1;