File Coverage

blib/lib/Net/DHCP/Watch.pm
Criterion Covered Total %
statement 82 96 85.4
branch 16 36 44.4
condition 2 5 40.0
subroutine 14 15 93.3
pod 4 6 66.6
total 118 158 74.6


line stmt bran cond sub pod time code
1             #
2             #$Id: Watch.pm,v 2.3 2003/10/28 11:09:59 edelrio Exp $
3             #
4             # Net::DHCP::Watch
5             #
6             package Net::DHCP::Watch;
7              
8 2     2   52755 use strict;
  2         6  
  2         86  
9 2     2   12 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  2         4  
  2         166  
10              
11 2     2   11 use Carp;
  2         8  
  2         189  
12 2     2   10 use Config;
  2         4  
  2         102  
13 2     2   2961 use Socket;
  2         9528  
  2         1326  
14 2     2   2021 use Net::hostent;
  2         11804  
  2         14  
15 2     2   2078 use IO::Socket;
  2         53657  
  2         12  
16              
17             require Exporter;
18              
19             @ISA = qw(Exporter);
20             @EXPORT = qw();
21             @EXPORT_OK = qw();
22              
23             $VERSION = do { my @r=(q$Revision: 2.3 $=~/\d+/g); sprintf "%d."."%02d"x$#r,@r };
24              
25             #
26             # new
27             #
28             sub new {
29 1     1 1 1781 my $proto = shift;
30 1         3 my $params = shift;
31 1   33     10 my $class = ref($proto) || $proto;
32 1         3 my $self = {};
33 1         4 bless($self, $class);
34 1         6 $self->init($params);
35 1         10 return $self;
36             }
37             #
38             # init: initalize parameters.
39             #
40             sub init {
41 1     1 0 3 my $self = shift;
42 1         2 my $params = shift;
43 1         2 my $h;
44              
45             # test if server hostname given is known (name or IP)
46 1         9 $self->{Server} = $params->{server};
47 1 50       8 unless ( $h = gethost($self->{Server}) ) {
48 0         0 carp "Can not resolve: ",$self->{Server};
49             }
50              
51             # test if client hostname given is known (name or IP)
52             # and keep only the first IP address.
53 1         412 $self->{Client} = $params->{client};
54 1 50       5 unless ( $h = gethost($self->{Client}) ) {
55 0         0 carp "Can not resolve: ",$self->{Client};
56             }
57 1         171 $self->{Client} = $h->addr_list->[0];
58              
59              
60             # test if ethernet address is either an array of six bytes or
61             # a string of hex bytes separated by ':'
62 1         9 $self->{Ether} = $params->{ether};
63              
64 1 50       10 if ( $self->{Ether} =~ m/^([0-9a-f]{1,2}:)+[0-9a-f]{1,2}$/i ) {
    0          
65 1         13 my @eth = map( hex, split(':', $self->{Ether}) );
66 1         3 $self->{Ether} = \@eth;
67             }
68             elsif ( scalar($self->{Ether}) != 6 ) {
69 0         0 croak "Not a good ethernet addres: ",$params->{ether};
70             }
71              
72             # can we use alarm() ?
73 1 50       971 if ( $Config{d_alarm} eq 'define' ) {
74 1         3383 $self->{_Alarm} = 1;
75             }
76             else {
77 0         0 carp "No alarm() function, network operation may hang";
78 0         0 $self->{_Alarm} = 0;
79             }
80              
81             # set the timeout (alarm)
82 1   50     15 $self->{TimeOut} = $params->{timeout} || 10;
83              
84             # initialize status result to zero
85 1         7 $self->{Last} = {
86             Ok => 0,
87             Bad => 0,
88             Time => '0000-00-00 00:00:00 GMT'
89             };
90 1         7 return;
91             }
92              
93             #
94             # watch: opens the udp socket to the server
95             #
96             sub watch {
97 1     1 1 3 my $self = shift;
98 1 50       7 if ( $self->{Watcher} ) {
99 0         0 carp "Already watching.";
100             }
101             else {
102 1 50       28 $self->{Watcher} = new IO::Socket::INET(
103             PeerAddr => $self->{Server},
104             PeerPort => 'bootps(67)',
105             LocalAddr => inet_ntoa($self->{Client}),
106             LocalPort => 'bootpc(68)',
107             Proto => 'udp',
108             Timeout => $self->{TimeOut}
109             )
110             or carp "Can not watch: $!";
111             }
112 1         640 return $self->{Watcher};
113             }
114              
115             #
116             # status: returns the present status
117             #
118             sub status {
119 1     1 1 3 my $self = shift;
120             # now the watch/unwatch cycle is carried by status.
121 1 50       5 $self->watch unless( $self->{Watcher} );
122 1 50       5 $self->dhcp_query or return;
123 1         6 $self->unwatch;
124 1         5 return $self->{Last};
125             }
126              
127             #
128             # dhcp_query: sends an udp packet containig a DHCP message
129             # of type DHCPDISCOVER and listens to the reply. The random transaction id
130             # must match.
131             #
132             sub dhcp_query {
133 1     1 0 2 my $self = shift;
134 1         3 my $reply; # holdspace for udp reply
135             #
136             # Test if socket is ok
137             #
138 1 50       5 unless ( $self->{Watcher} ) {
139 0         0 carp "Not watching yet!";
140 0         0 return;
141             };
142             #
143             # Transaction ID
144             #
145 1         43 my $xid = int(rand(2**32-1));
146             #
147             # DHCP Message: Fixed-Format + Options
148             # (see Droms & Lemon, 1999, Apendixes C and D).
149             #
150 1         11 my @fields = (
151             # op
152             1,
153             # htype
154             1,
155             # hlen
156             6,
157             # hops
158             0,
159             # xid
160             $xid,
161             # secs
162             0,
163             # flags
164             0,
165             # ciaddr
166             $self->{Client},
167             # yiaddr
168             0,
169             # siaddr
170             0,
171             # giaddr
172             0,
173             # chaddr
174 1         3 @{ $self->{Ether} },
175             0, 0, 0, 0, 0, 0,
176             0, 0, 0, 0,
177             # sname
178             "\0",
179             # file
180             "\0",
181             # Magic cookie (RFC)
182             99,130,83,99,
183             # option1 = DHCP-Message
184             53,
185             # length1 = 1
186             1,
187             # value1 = DHCPREQUEST
188             3
189             );
190 1         12 my $query = pack(
191             # It's horrible, but it works
192             'CCCCNnna4NNNCCCCCCCCCCCCCCCCa64a128C*',
193             @fields
194             );
195 1         2 my $serv_address;
196             # I/O eval block
197 1         2 eval {
198             # SIG handling for alarm()
199 1     0   35 local $SIG{ALRM} = sub { die "Alarm timeout\n" };
  0         0  
200             # Send query
201 1 50       15 alarm($self->{TimeOut})
202             if $self->{_Alarm};
203 1         17 $self->{Watcher}->send($query, 0);
204 1 50       130 alarm(0)
205             if $self->{_Alarm};
206             # Get reply
207 1 50       10 alarm($self->{TimeOut})
208             if $self->{_Alarm};
209 1         9 $serv_address = $self->{Watcher}->recv($reply, 1024, 0);
210 1 50       40 alarm(0)
211             if $self->{_Alarm};
212             };
213             # Die if not alarm
214 1 50       5 if($@) {
215 0 0       0 carp $@ unless $@ =~ /alarm/i;
216             }
217             # Verify
218             # be sure $ret_xid is not equal to $xid
219 1         2 my $ret_xid = !$xid;
220 1 50       4 if ( $reply ) {
221 0         0 $ret_xid = unpack('x4N',$reply);
222             }
223             # only if we've got a reply and the reply was correct all is ok.
224 1 50       5 if ( $ret_xid == $xid ) {
225             # Increment Ok count (max: 2**31-1)
226 0         0 $self->{Last}->{Ok} %= 2147483647;
227 0         0 $self->{Last}->{Ok}++;
228             # Zero Bad
229 0         0 $self->{Last}->{Bad} = 0;
230             }
231             else {
232             # Zero ok
233 1         3 $self->{Last}->{Ok} = 0;
234             # Increment Bad count (max: 2**31-1)
235 1         3 $self->{Last}->{Bad} %= 2147483647;
236 1         3 $self->{Last}->{Bad}++;
237             }
238             # Get present time (GMT)
239 1         24 $self->{Last}->{Time} = gmtime;
240             }
241             #
242             # close: just closes socket.
243             #
244             sub unwatch {
245 2     2 1 5 my $self = shift;
246 2         160 delete $self->{Watcher};
247             }
248             #
249             # Cleanup
250             #
251             sub DESTROY {
252 1     1   306 my $self = shift;
253 1         4 $self->unwatch;
254             }
255              
256             1;
257             __END__