File Coverage

blib/lib/Net/DHCPClientLive.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Net::DHCPClientLive;
2              
3 1     1   22996 use 5.008;
  1         3  
  1         33  
4 1     1   5 use warnings;
  1         1  
  1         23  
5 1     1   5 use Carp;
  1         5  
  1         153  
6             our @ISA = qw();
7             our $VERSION = '0.02';
8              
9 1     1   366 use Net::RawIP;
  0            
  0            
10             use Net::ARP;
11             use Net::PcapUtils;
12             use NetPacket::ARP;
13             use NetPacket::Ethernet;
14             use NetPacket::IP;
15             use NetPacket::UDP;
16             my @msgtype = qw(Invalid DISCOVER OFFER REQUEST DECLINE ACK NAK RELEASE);
17             my %fields = (
18             state => 'INIT',
19             interface => undef,
20             cltmac => undef,
21             srvmac => undef,
22             op => 1,
23             htype => 1,
24             hlen => 6,
25             hops => 0,
26             xid => undef,
27             secs => 0,
28             flags => 0,
29             ciaddr => '0.0.0.0',
30             yiaddr => undef,
31             siaddr => undef,
32             giaddr => undef,
33             sname => 0,
34             bootfile => 0,
35             debug => undef,
36             verb => undef,
37             options => {},
38             timeout => 10,
39             serverid => undef,
40             requestip => undef,
41             );
42            
43             sub new {
44             my $that = shift;
45             my $class = ref( $that ) || $that;
46             my $self = { %fields };
47             bless $self, $class;
48             my ($tobeState);
49              
50             if ( @_ ) {
51             my %conf = @_;
52             if (exists $conf{"state"}) {
53             $tobeState = $conf{"state"};
54             delete $conf{"state"};
55             }
56             while ( my ($k, $v) = each %conf ) {
57             $self->{"$k"} = $v;
58             }
59             $self->init();
60             }
61             $self->{"chaddr"} = $self->{"cltmac"};
62             $self->{'verb'} = 1 if ($self->{'debug'});
63             return undef unless(ref ($self->pktcaphd()));
64             return undef if ($tobeState && ! $self->goState($tobeState));
65             return $self;
66             }
67              
68             sub init {
69             my $self = shift;
70             $self->{'cltmac'} = $self->GenMAC() unless(defined $self->{'cltmac'});
71             $self->{'serverid'} = '0.0.0.0';
72             $self->{'requestip'} = '0.0.0.0';
73             $self->{'srvmac'} = 0;
74             $self->{'xid'} = sprintf "%0.8d", int( rand 99999999 );
75             $self->{'state'} = 'INIT';
76             }
77              
78             sub goState {
79             my ($self,$tobeState,$pkt) = @_;
80             my $currState = $self->{state};
81             print "\n\n$self->{cltmac} ($self->{xid}) "."$currState ----> $tobeState\n" if ($self->{'verb'});
82             if ($currState eq 'INIT') {
83             if ($tobeState eq 'INIT') {
84             return 1;
85             }else{
86             return 0 unless($pkt = $self->discover());
87             $self->prtpkt($pkt) if ($self->{'debug'});
88             return 0 if ($tobeState ne 'SELECT' && ! $self->goState($tobeState,$pkt));
89             }
90             }elsif ($currState eq 'SELECT') {
91             if ($tobeState eq 'SELECT') {
92             $self->{'state'} = 'INIT';
93             return 0 unless($self->goState($tobeState));
94             }elsif($pkt) {
95             $self->{'srvmac'} = $pkt->{'macsrc'};
96             $self->{'requestip'} = $pkt->{'yiaddr'};
97             $self->{'serverid'} = opt2dot($pkt->{'options'}{'54'});
98             return 0 unless($pkt = $self->request());
99             $self->prtpkt($pkt) if ($self->{'debug'});
100             return 0 if ($tobeState ne 'REQUEST' && ! $self->goState($tobeState,$pkt));
101             }else{
102             $self->{'state'} = 'INIT';
103             }
104             }elsif ($currState eq 'REQUEST') {
105             if ($tobeState eq 'REQUEST') {
106             $self->{'state'} = 'SELECT';
107             return 0 unless($self->goState($tobeState));
108             }elsif ($tobeState eq 'INIT') {
109             $self->decline();
110             $self->init();
111             }elsif($pkt) {
112             if ($pkt->{'options'}{'53'} == 6) {
113             $self->init();
114             }else{
115             $self->{'state'} = 'BOUND';
116             $self->{'ciaddr'} = $pkt->{'yiaddr'};
117             $self->{'siaddr'} = $pkt->{'siaddr'};
118             $self->{'giaddr'} = $pkt->{'giaddr'};
119             $self->{'lease'} = $pkt->{'options'}{'51'};
120             $self->{'t1'} = $pkt->{'options'}{'58'};
121             $self->{'t2'} = $pkt->{'options'}{'59'};
122             return 0 if ($tobeState ne 'BOUND' && ! $self->goState($tobeState));
123             }
124             }else{
125             return 0 unless($pkt = $self->request());
126             $self->prtpkt($pkt) if ($self->{'debug'});
127             return 0 unless($self->goState($tobeState,$pkt));
128             }
129             }elsif ($currState eq 'BOUND') {
130             if ($tobeState eq 'RENEW' || $tobeState eq 'BOUND') {
131             return 0 unless($pkt = $self->renew());
132             $self->prtpkt($pkt) if ($self->{'debug'});
133             return 0 if ($tobeState ne 'RENEW' && ! $self->goState($tobeState,$pkt));
134             }elsif($tobeState eq 'REBIND') {
135             $pkt = $self->renew();
136             $self->prtpkt($pkt) if ($self->{'debug'} && $pkt);
137             return 0 if (! $self->goState($tobeState));
138             }else{
139             $self->release();
140             $self->init();
141             return 0 unless($self->goState($tobeState));
142             }
143             }elsif ($currState eq 'RENEW') {
144             if ($tobeState eq 'RENEW') {
145             $self->{'state'} = 'BOUND';
146             return 0 unless($self->goState($tobeState));
147             }elsif($tobeState eq 'BOUND' && $pkt) {
148             $self->{'state'} = 'BOUND';
149             $self->{'lease'} = $pkt->{'options'}{'51'};
150             $self->{'t1'} = $pkt->{'options'}{'58'};
151             $self->{'t2'} = $pkt->{'options'}{'59'};
152             }elsif($tobeState eq 'BOUND') {
153             return 0 unless($pkt = $self->rebind());
154             $self->prtpkt($pkt) if ($self->{'debug'});
155             return 0 unless($self->goState($tobeState,$pkt));
156             }elsif($tobeState eq 'REBIND') {
157             return 0 unless($pkt = $self->rebind());
158             $self->prtpkt($pkt) if ($self->{'debug'});
159             }else{
160             $self->release();
161             $self->init();
162             return 0 unless($self->goState($tobeState));
163             }
164             }elsif ($currState eq 'REBIND') {
165             if ($tobeState eq 'REBIND') {
166             $self->{'state'} = 'RENEW';
167             return 0 unless($self->goState($tobeState));
168             }elsif($tobeState eq 'BOUND' && $pkt) {
169             $self->{'state'} = 'BOUND';
170             }elsif($tobeState eq 'BOUND') {
171             $self->{'state'} = 'RENEW';
172             return 0 unless($self->goState($tobeState));
173             }else{
174             $self->release();
175             $self->init();
176             return 0 unless($self->goState($tobeState));
177             }
178             }else{
179             }
180             1;
181             }
182              
183             sub discover {
184             my $self = shift;
185             my %options = %{$self->{options}};
186              
187             $self->{'ciaddr'} = '0.0.0.0';
188             $self->{'yiaddr'} = '0.0.0.0';
189             $self->{'siaddr'} = '0.0.0.0';
190             $self->{'giaddr'} = '0.0.0.0';
191              
192             $options{53} = '1';
193             $options{61} = mac2opt($self->{"cltmac"});
194             $self->{"options"} = \%options;
195              
196             $self->pktsend();
197             $self->{'state'} = 'SELECT';
198             return $self->pktrcv();
199             }
200              
201             sub request {
202             my $self = shift;
203             my %options = %{$self->{options}};
204             my $pkt;
205             $options{53} = '3';
206             $options{61} = mac2opt($self->{"cltmac"});
207             $options{54} = dot2opt($self->{'serverid'});
208             $options{50} = dot2opt($self->{'requestip'});
209             $self->{"options"} = \%options;
210             $self->pktsend();
211             $self->{'state'} = 'REQUEST';
212             return $self->pktrcv();
213             }
214             sub renew {
215             my $self = shift;
216             my %options = %{$self->{options}};
217             my $pkt;
218             $self->{'siaddr'} = '0.0.0.0';
219             $options{53} = '3';
220             $options{61} = mac2opt($self->{"cltmac"});
221             $self->{"options"} = \%options;
222             $self->pktsend();
223             $self->{'state'} = 'RENEW';
224             while ($pkt = $self->pktrcv()) {
225             if ($pkt->{ethertype} eq '0806') {
226             $self->arpreply();
227             }else{
228             return $pkt;
229             }
230             }
231             return 0;
232             }
233              
234             sub rebind {
235             my $self = shift;
236             my %options = %{$self->{options}};
237             my $pkt;
238             $self->{'siaddr'} = '0.0.0.0';
239             $options{53} = '3';
240             $options{61} = mac2opt($self->{"cltmac"});
241             $self->{"options"} = \%options;
242             $self->pktsend();
243             $self->{'state'} = 'REBIND';
244             while ($pkt = $self->pktrcv()) {
245             if ($pkt->{ethertype} eq '0806') {
246             $self->arpreply();
247             }else{
248             return $pkt;
249             }
250             }
251             }
252              
253             sub decline {
254             my $self = shift;
255             my %options = %{$self->{options}};
256              
257             $options{53} = '4';
258             $options{50} = dot2opt($self->{'requestip'});
259             $options{54} = dot2opt($self->{'serverid'});
260             $options{61} = mac2opt($self->{"cltmac"});
261             $self->{"options"} = \%options;
262             $self->pktsend();
263             }
264            
265             sub release {
266             my $self = shift;
267             my %options = %{$self->{options}};
268              
269             $options{53} = '7';
270             $options{54} = dot2opt($self->{'serverid'});
271             $options{61} = mac2opt($self->{"cltmac"});
272             $self->{"options"} = \%options;
273             $self->{'yiaddr'} = '0.0.0.0';
274             $self->{'siaddr'} = '0.0.0.0';
275             $self->{'giaddr'} = '0.0.0.0';
276             $self->pktsend();
277             }
278            
279             sub pktsend {
280             my $self = shift;
281             my $macaddr = $self->{'cltmac'};
282             my $interface = $self->{'interface'};
283             my $data = $self->encode();
284             my $p;
285             if (exists $self->{rawip}) {
286             $p = $self->{rawip};
287             }else{
288             $p = new Net::RawIP( {udp => {}} );
289             $p->ethnew( $interface );
290             }
291             if ($self->{"state"} =~ /INIT|SELECT|REQUEST|REBIND|RENEW/) {
292             $p->ethset( source => $macaddr, dest => 'ff:ff:ff:ff:ff:ff');
293             $p->set( {ip => {saddr => '0.0.0.0', daddr => '255.255.255.255'},
294             udp => {source => 68, dest => 67, data => $data}} );
295             }else{
296             $p->ethset( source => $macaddr, dest => $self->{"srvmac"});
297             $p->set( {ip => {saddr => $self->{"requestip"}, daddr => $self->{"serverid"} },
298             udp => {source => 68, dest => 67, data => $data}} );
299             }
300             $p->ethsend;
301              
302             if ($self->{'verb'}) {
303             my ($dstmac, $srcmac, $srcip, $dstip, $srcport, $dstport) = $p->get( {eth => [qw(dest source )], ip => [qw(saddr daddr)], udp => [qw(source dest)]} );
304             print "\nXMIT: ", $self->{'xid'},"\n";
305             printf "\t%s (%s) :%d ===> %s (%s) :%d\n", ip2dot($srcip), net2mac($srcmac), $srcport, ip2dot($dstip), net2mac($dstmac), $dstport;
306             print "\tDHCP ", $msgtype[$self->{'options'}{53}], "\n";
307             }
308             $self->{rawip} = $p unless(exists $self->{rawip});
309             return 1;
310             }
311              
312             sub arpreply {
313             my $self = shift;
314             Net::ARP::send_packet("$self->{interface}","$self->{requestip}","$self->{serverid}","$self->{cltmac}","$self->{srvmac}",'2');
315             if ($self->{verb}) {
316             print "\nXMIT ARP on $self->{requestip}:\n";
317             print "\t$self->{cltmac} $self->{requestip} => $self->{serverid}$self->{srvmac}\n";
318             }
319             }
320              
321             sub pktcaphd {
322             my $self = shift;
323             my $filter;
324             $filter = "udp dst port 68 or arp host $self->{'ciaddr'}";
325             my $pkt_descriptor = Net::PcapUtils::open( FILTER => "$filter",
326             DEV => $self->{'interface'},
327             SNAPLEN => 400,
328             PROMISC => 1,
329             ) or die "@_\n";
330             if ( ! ref($pkt_descriptor) ) {
331             die "Net::PcapUtils::open returned: $pkt_descriptor\n";
332             }
333             $self->{pcaphd} = $pkt_descriptor;
334             return $pkt_descriptor;
335             }
336              
337              
338             sub pktrcv {
339             my $self = shift;
340             my $pkt_descriptor = $self->{pcaphd};
341             my $pkt;
342              
343             $SIG{ALRM} = sub { die "timeout"; };
344             alarm($self->{timeout});
345              
346             eval {
347             my ($ttl,$macdst,$macsrc,$ethertype);
348             while(1) {
349             my ($packet,%hdr) = Net::PcapUtils::next($pkt_descriptor);
350             my $eth_frame = NetPacket::Ethernet->decode($packet);
351             $macdst = str2mac($eth_frame->{dest_mac});
352             $macsrc = str2mac($eth_frame->{src_mac});
353             $ethertype = sprintf("%0.4x",$eth_frame->{type});
354             if ($self->{'debug'}) {
355             print "\nGot a pkt \n\t";
356             print "$macsrc ==> $macdst";
357             printf " (EtherType = %04x)\n\n", $eth_frame->{type};
358             }
359             if ( $ethertype eq '0806') {
360             my $arp_obj = NetPacket::ARP->decode( $eth_frame->{data},$eth_frame );
361             if ($self->{'debug'}) {
362             print "\tARP header:\n";
363             printf "\tprotocol ==> %0.4x",$arp_obj->{proto};
364             print " harware type ==> $arp_obj->{htype}, ";
365             print "harware length ==> $arp_obj->{hlen}, ";
366             print "proto length ==> $arp_obj->{plen}\n";
367             print "\topcode ==> $arp_obj->{opcode}\n";
368             print "\tsrc mac ==> ", str2mac($arp_obj->{sha});
369             print ", src ip ==> $arp_obj->{spa} => ", hex2dot($arp_obj->{spa}),"\n";
370             print "\tdst mac ==> ", str2mac($arp_obj->{tha});
371             print ", dst ip ==> $arp_obj->{tpa} => ", hex2dot($arp_obj->{tpa}),"\n\n";
372             }
373             if ($arp_obj->{opcode} == 1 && hex2dot($arp_obj->{tpa}) eq $self->{requestip}) {
374             $pkt->{'macdst'} = $macdst;
375             $pkt->{"macsrc"} = $macsrc;
376             $pkt->{'ethertype'} = $ethertype;
377             if ($self->{'verb'}) {
378             print "\nRCVD: ARP Request\n";
379             print "\t$pkt->{macsrc} ===> $pkt->{'macdst'} asking for ",hex2dot($arp_obj->{tpa}),"\n";
380             }
381             last;
382             }
383             }else{
384             my $ip_datagram = NetPacket::IP->decode( $eth_frame->{data} );
385             my $udp_datagram = NetPacket::UDP->decode( $ip_datagram->{data} );
386             my $bootp_datagram = $self->bootpdecode( $udp_datagram->{data} );
387             if ($self->{'verb'}) {
388             print "\nRCVD: DHCP $msgtype[$bootp_datagram->{'options'}{53}]\n";
389             print "\t$ip_datagram->{src_ip} -> $ip_datagram->{dest_ip}";
390             print "\t( id: $ip_datagram->{id}, ttl: $ip_datagram->{ttl} )\n";
391             print "\tUDP Source: $udp_datagram->{src_port} -> ";
392             print "UDP Destination: $udp_datagram->{dest_port}\n";
393             print "\tUDP Length: $udp_datagram->{len}, ";
394             print "UDP Checksum: $udp_datagram->{cksum}, ";
395             print "UDP Data length:", length($udp_datagram->{data}),"\n";
396             print "\txid: $bootp_datagram->{xid}\n";
397             }
398             next unless($self->{"xid"} == $bootp_datagram->{"xid"});
399             print "\t=====> $self->{xid} matched\n" if ($self->{'verb'});
400             $pkt = $bootp_datagram;
401             $pkt->{'ttl'} = $ip_datagram->{ttl};
402             $pkt->{'saddr'} = $ip_datagram->{src_ip};
403             $pkt->{'daddr'} = $ip_datagram->{dest_ip};
404             $pkt->{'macdst'} = $macdst;
405             $pkt->{"macsrc"} = $macsrc;
406             $pkt->{'ethertype'} = $ethertype;
407             last;
408             }
409             }
410             };
411              
412             alarm( 0 );
413              
414             if ( $@ ) {
415             if ( $@ =~ /timeout/ ) {
416             print "TIMEOUT\n";
417             return 0;
418             } else {
419             print "\n\n<$@>\n\n";
420             }
421             }
422             print "return from pkt capture, looks good\n\n" if ($self->{'debug'});
423             return $pkt;
424             }
425              
426             sub bootpdecode {
427             my $self = shift;
428             my $data = shift;
429             my $pkt = {};
430              
431             my $bootpkeys = [qw(op htype hlen hops xid secs flags ciaddr yiaddr siaddr giaddr chaddr )];
432             my $bootpvals = [map { hex($_) } unpack "H2 H2 H2 H2 H8 H4 H4 H8 H8 H8 H8 H12", substr($data, 0, 33)];
433             for ( my $i = 0; $i < scalar @$bootpkeys; $i++ ) {
434             print "\t$bootpkeys->[$i] => $bootpvals->[$i]\n" if ($self->{'debug'});
435             $pkt->{$bootpkeys->[$i]} = $bootpvals->[$i];
436             }
437             for (@$bootpkeys) {
438             if ( /^xid$/) {
439             $pkt->{$_} = sprintf "%x", $pkt->{$_};
440             }elsif( /iaddr/) {
441             $pkt->{$_} = ip2dot($pkt->{$_});
442             }
443             }
444             my %options;
445             my @opts = unpack "C" x ( length( $data ) - 240 ), substr $data, 240;
446             for ( my $i = 0; $i <= $#opts; $i++ ) {
447             my $opt = $opts[$i++];
448             my $len = $opts[$i++];
449             my $offset = $len + $i - 1;
450             my $string = "";
451             for ( my $q = $i; $q <= $offset; $q++ ) {
452             if ( $string ) {
453             $string = sprintf "%s %d", $string, $opts[$q];
454             } else {
455             $string = sprintf "%d", $opts[$q];
456             }
457             }
458             last if ($opt eq '255');
459             $options{$opt} = $string;
460             $i = $i + $len - 1;
461             }
462             $pkt->{'options'} = \%options;
463             return $pkt;
464             }
465              
466             sub prtpkt {
467             my ($self,$pkt) = @_;
468             print "\nRCVD: $pkt->{'xid'}\n";
469             printf
470             "\t%s (%s) ===> %s (%s)\n\tDHCP%s packet:\n",
471             $pkt->{'saddr'}, $pkt->{'macsrc'},$pkt->{'daddr'}, $pkt->{'macdst'}, $msgtype[$pkt->{"options"}{'53'}];
472             printf "\tClient IP: %s\n\tYour IP: %s\n\tNext server IP: %s\n\tRelay agent IP: %s\n", $pkt->{'ciaddr'}, $pkt->{'yiaddr'}, $pkt->{'siaddr'},$pkt->{'giaddr'};
473             $self->prtoptions($pkt);
474             }
475              
476              
477             # print the DHCP options
478             # refer to rfc1533 for these options
479             sub prtoptions {
480             my ($self,$pkt) = @_;
481             print "\tOptions:\n";
482             for my $option (keys %{$pkt->{'options'}}) {
483             if ($option eq '53') {
484             my $msgtype = $msgtype[$pkt->{'options'}{$option}];
485             print "\t53: \n";
486             }elsif ($option eq '1') {
487             my $mask = opt2dot($pkt->{"options"}{$option});
488             print "\t1: Client IP Mask => <$mask>\n";
489             }elsif ($option eq '3') {
490             my $routers;
491             my @r = split /\s/, $pkt->{"options"}{$option};
492             for (my $i = 0; $i <= @r/4; $i += 4) {
493             my $router = join '.', $r[$i],$r[$i + 1],$r[$i + 2],$r[$i + 3];
494             ($i == 0) ? $routers = $router : $routers .= ','."$router";
495             }
496             print "\t3: Router(s) on Client subnet => <$routers>\n";
497             }elsif ($option eq '4') {
498             # the same format at '3', will expand it later
499             print "\t4: Time server(s)=> <", $pkt->{'options'}{$option}, ">\n";
500             }elsif ($option eq '6') {
501             # the same format at '3', will expand it later
502             print "\t6: DNS server(s)=> <", $pkt->{'options'}{$option}, ">\n";
503             }elsif ($option eq '15') {
504             my $domain = join '', map { chr ($_) } split /\s/, $pkt->{"options"}{$option};
505             print "\t15: Domain Name => <$domain>\n";
506             }elsif ($option eq '50') {
507             my $ciaddr = opt2dot($pkt->{"options"}{$option});
508             print "\t50: Requested IP address => <$ciaddr>\n";
509             }elsif ($option eq '51') {
510             my $lease = $pkt->{"options"}{$option};
511             print "\t51: Lease time => <$lease>\n";
512             }elsif ($option eq '54') {
513             my $srvid = opt2dot($pkt->{"options"}{$option});
514             print "\t54: Server Identifier => <$srvid>\n";
515             }elsif ($option eq '58') {
516             my $t1 = $pkt->{"options"}{$option};
517             print "\t58: T1 => <$t1>\n";
518             }elsif ($option eq '59') {
519             my $t2 = $pkt->{"options"}{$option};
520             print "\t59: T2 => <$t2>\n";
521             }else{
522             print "\tUnknow option <", $pkt->{"options"}{$option}, ">\n";
523             }
524             }
525             }
526              
527             sub GenMAC {
528             my $self = shift;
529             my $tmp_mac="00:4d:5a";
530             my $i=0;
531             while($i++ < 3) {
532             $tmp_mac.=":" . sprintf("%x",int rand 16);
533             $tmp_mac.=sprintf("%x",int rand 16);
534             }
535             return($tmp_mac);
536             }
537              
538             sub dot2opt {
539             my $dotip = shift;
540             return join ' ', map {sprintf("%x",$_)} split /\./, $dotip;
541             }
542              
543              
544             sub opt2dot {
545             my $opt = shift;
546             return join '.', split /\s/, $opt;
547             }
548              
549             sub hex2dot {
550             my @hexip = split //, shift;
551             my @h;
552             for (my $i = 0; $i < 8; $i += 2) {
553             push @h, join '', $hexip[$i],$hexip[$i + 1];
554             }
555             return join '.', map { hex($_) } @h;
556             }
557              
558             sub mac2opt {
559             my $mac = shift;
560             return join ' ', split /:/, $mac;
561             }
562              
563             sub prtStatus {
564             my $self = shift;
565             for my $fd (keys %$self) {
566             if ( $fd eq 'options' ) {
567             for (keys %{$self->{$fd}}) { print "\t\t$_ ==> ", $self->{$fd}{$_}, "\n" }
568             }else{
569             print "\t$fd => ", $self->{$fd}, "\n";
570             }
571             }
572             }
573              
574              
575             sub encode {
576             my $self = shift;
577             my $magic = pack "C4", 99, 130, 83, 99;
578             my @ciaddr = split /\./, $self->{'ciaddr'};
579             my @yiaddr = split /\./, $self->{'yiaddr'};
580             my @siaddr = split /\./, $self->{'siaddr'};
581             my @giaddr = split /\./, $self->{'giaddr'};
582              
583             my @chaddr = mac2net( $self->{'chaddr'} );
584              
585             my $data = pack "C4 H8 H4 H4 C4 C4 C4 C4 C16 H128 H256",
586             $self->{'op'}, $self->{'htype'}, $self->{'hlen'}, $self->{'hops'}, $self->{'xid'}, $self->{'secs'},
587             $self->{'flags'}, @ciaddr, @yiaddr, @siaddr, @giaddr, @chaddr, $self->{'sname'}, $self->{'bootfile'};
588              
589             $data = join '', $data, $magic;
590              
591             my $o = $self->{'options'};
592             my %options = %$o;
593              
594             foreach my $key ( keys %options ) {
595             my @p = split / /, $options{$key};
596             map { $_ = hex( $_ ); } @p;
597              
598             my $format = sprintf "C%d", $#p+3;
599              
600             my $options = pack $format, $key, $#p+1, @p;
601              
602             $data = join '', $data, $options;
603             }
604              
605             my $end = pack "C2", 255, 0;
606              
607             $data = join '', $data, $end;
608              
609             return $data;
610             }
611              
612             sub str2mac {
613             return sprintf( "%s%s:%s%s:%s%s:%s%s:%s%s:%s%s", split //, shift);
614             }
615              
616             sub net2mac {
617             return sprintf( "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", unpack( "C6", shift ) );
618             }
619              
620             sub mac2net {
621             my @a = split /:/, shift;
622            
623             for ( 1..10 ) {
624             push @a, '0';
625             }
626            
627             map { $_ = hex($_); } @a;
628            
629             return @a;
630             }
631              
632             sub ip2dot {
633             return sprintf( "%u.%u.%u.%u", unpack( "C4", pack( "N", shift ) ) );
634             }
635              
636              
637             1;
638             __END__;