# Net::DHCP::Packet.pm # Version 0.0 # Author: F. van Dun # # Some information about a DHCP packet from RFC 1497: # FIELD OCTETS DESCRIPTION # ----- ------ ----------- # # op 1 Message op code / message type. # 1 = BOOTREQUEST, 2 = BOOTREPLY # htype 1 Hardware address type, see ARP section in "Assigned # Numbers" RFC; e.g., '1' = 10mb ethernet. # hlen 1 Hardware address length (e.g. '6' for 10mb # ethernet). # hops 1 Client sets to zero, optionally used by relay agents # when booting via a relay agent. # xid 4 Transaction ID, a random number chosen by the # client, used by the client and server to associate # messages and responses between a client and a # server. # secs 2 Filled in by client, seconds elapsed since client # began address acquisition or renewal process. # flags 2 Flags (see figure 2). # ciaddr 4 Client IP address; only filled in if client is in # BOUND, RENEW or REBINDING state and can respond # to ARP requests. # yiaddr 4 'your' (client) IP address. # siaddr 4 IP address of next server to use in bootstrap; # returned in DHCPOFFER, DHCPACK by server. # giaddr 4 Relay agent IP address, used in booting via a # relay agent. # chaddr 16 Client hardware address. # sname 64 Optional server host name, null terminated string. # file 128 Boot file name, null terminated string; "generic" # name or null in DHCPDISCOVER, fully qualified # directory-path name in DHCPOFFER. # options var Optional parameters field. See the options # documents for a list of defined options. # # Table 1: Description of fields in a DHCP message # # Difference between bootp and DHCP: # The first four octets of the 'options' field of the DHCP message # contain the (decimal) values 99, 130, 83 and 99, respectively (this # # is the same magic cookie as is defined in RFC 1497). The remainder # of the 'options' field consists a list of tagged parameters that are # called "options". All of the "vendor extensions" listed in RFC 1497 # are also DHCP options. A separate document gives the complete set of # options defined for use with DHCP [2]. package Net::DHCP::Packet; use Socket; use strict; use Carp; use Net::DHCP::Options; use vars qw($VERSION); $VERSION=0.1; =pod =head1 NAME Net::DHCP::Packet - Object methods to create a DHCP packet. =head1 SYNOPSIS use Net::DHCP::Packet; use strict; my $p = new Net::DHCP::Packet('Chaddr' => '0??BCDEF', 'Xid' => hex(0x9F0FD), 'Ciaddr' => '0.0.0.0', 'Siaddr' => '0.0.0.0', 'Hops' => 0); =head1 DESCRIPTION Represents a DHCP packet as specified in RFC 1533, RFC 2132. =head2 CONSTRUCTORS =item new =item discover =item request =item decline =item release =head2 METHODS =cut # Opcode sub BOOTREQUEST() { 0x1 } sub BOOTREPLY() { 0x2 } sub randomstring($) { my $len = shift; my $c; while ($len--) { $c .= chr(int (rand(255))); } return $c; } sub mac2str($$) { my $nibbles = shift(@_) * 2; my $str = unpack("H$nibbles", shift); return $str; } sub str2mac($) { pack('H*', shift); } =pod =item op(BYTE opcode) Sets the opcode in the BOOTP type. Without argument, returns the BOOTP type. Argument is either: Net::DHCP::Packet::BOOTREQUEST() { pack('C',0x1) } Net::DHCP::Packet::BOOTREPLY() { pack('C',0x2) } =cut sub op { my ($self, $arg) = @_; $self->{op} = $arg unless (!defined ($arg)); return $self->{op}; } =pod =item htype(BYTE hardware address type) Ex. '1' = 10mb ethernet =cut sub htype { my ($self, $arg) = @_; $self->{htype} = $arg unless (!defined ($arg)); return $self->{htype}; } =pod =item hlen(BYTE hardware address length) For most NIC's, the MAC address has 6 bytes. =cut sub hlen { my ($self, $arg) = @_; $self->{hlen} = $arg unless (!defined ($arg)); return $self->{hlen}; } =pod =item hops(BYTE number of hops) This field is incremented by each encountered DHCP relay agent. =cut sub hops { my ($self, $arg) = @_; $self->{hops} = $arg unless (!defined ($arg)); return $self->{hops}; } =pod =item xid(C4 transaction id) 4 byte transaction id. =cut sub xid { my ($self, $arg) = @_; $self->{xid} = $arg unless (!defined ($arg)); return $self->{xid}; } =pod =item secs(SHORT elapsed boot time) 2 bytes for elapsed boot time. =cut sub secs { my ($self, $arg) = @_; $self->{secs} = $arg unless (!defined ($arg)); # unsigned Short. Exactly 16 bits. return $self->{secs}; } =pod =item flags(SHORT) 2 bytes. 0x0000 = No broadcasts. 0x1000 = Broadcasts. =cut sub flags { my ($self, $arg) = @_; $self->{flags} = $arg unless (!defined ($arg)); return $self->{flags}; } =pod =item ciaddr(IP address) IP address is an ascci string like '10.24.50.3'. =cut sub ciaddr { my ($self, $arg) = @_; $self->{ciaddr} = $arg unless (!defined($arg)); return $self->{ciaddr}; } =pod =item yiaddr(IP address) =cut sub yiaddr { my ($self, $arg) = @_; $self->{yiaddr} = $arg unless (!defined($arg)); return $self->{yiaddr}; } =pod =item siaddr(IP address) =cut sub siaddr { my ($self, $arg) = @_; $self->{siaddr} = $arg unless(!defined($arg)); return $self->{siaddr}; } =pod =item giaddr(IP address) =cut sub giaddr { my ($self, $arg) = @_; $self->{giaddr} = $arg unless (!defined($arg)); return $self->{giaddr}; } =pod =item chaddr(MAC address) Hexadecimal string represenatation. Example: "0010A706DFFF" for 6 bytes mac address. =cut sub chaddr { my ($self, $arg) = @_; $self->{chaddr} = $arg unless (!defined ($arg)); return $self->{chaddr}; } =pod =item sname(C64 servername) Optional 64 bytes null terminated string with server host name. =cut sub sname { my ($self, $arg) = @_; $self->{sname} = $arg unless (!defined ($arg)); return $self->{sname}; } =pod =item file(C128 bootfilename) Optional =cut sub file { my ($self, $arg) = @_; $self->{file} = $arg unless (!defined ($arg)); return $self->{file}; } =pod =item options(REF Net::DHCP::Options) Argument is reference to a Net::DHCP::Options object. Without argument, returns the Options object. =cut sub options { my ($self, $arg) = @_; $self->{options} = $arg unless (!defined ($arg)); return $self->{options}; } =pod =item addOption($type, $value) =cut sub addOption { my ($self, $type, $value) = @_; $self->{options}->setOption($type,$value); } =item getOption($type) =cut sub getOption { my ($self, $type) = @_; $self->{options}->getOption(chr($type)); } =pod =item new(%ARGS) The hash %ARGS can contain any of these keys: Op, Htype, Hlen, Hops, Xid, Secs, Flags, Ciaddr, Yiaddr, Siaddr, Giaddr, Chaddr, Sname, File =cut sub new { my ($class, %args) = @_; my $self = {}; bless $self, $class; $self->op($args{Op} || BOOTREQUEST()); $self->htype($args{Htype} || 1); # 10mb ethernet $self->hlen($args{Hlen} || 6); # Use 6 bytes MAC by default $self->hops($args{Hops} || 0); $self->xid($args{Xid} || randomstring(4)); $self->secs($args{Secs} || 0); $self->flags($args{Flags} || 0); $self->ciaddr($args{Ciaddr} || 0); $self->yiaddr($args{Yiaddr} || 0); $self->siaddr($args{Siaddr} || 0); $self->giaddr($args{Giaddr} || 0); $self->chaddr($args{Chaddr} || randomstring(ord($self->hlen()) ) ); $self->sname($args{Sname} || chr(0)); $self->file($args{File} || chr(0)); $self->{options} = new Net::DHCP::Options(); return $self; } =pod =item discover DHCP discover packet =cut sub discover { my ($class, %args) = @_; my $self =$class->new(%args); $self->addOption(Net::DHCP::Options::MESSAGE_TYPE(),Net::DHCP::Options::DISCOVER()); $self->addOption(Net::DHCP::Options::CLIENT_ID(), str2mac( '01' . $self->{chaddr} ) ); $self->addOption(Net::DHCP::Options::REQUEST_IP(), inet_aton($args{Request_ip}) ) if ( $args{Request_ip} ); $self->addOption(Net::DHCP::Options::HOSTNAME(), $args{Hostname}) if ( $args{Hostname} ); $self->addOption(Net::DHCP::Options::CLASS_ID(),'MSFT 5.0'); return $self; } =pod =item request DHCP request packet =cut sub request { my ($class, %args) = @_; my $self =$class->new(%args); $self->addOption(Net::DHCP::Options::MESSAGE_TYPE(),Net::DHCP::Options::REQUEST()); $self->addOption(Net::DHCP::Options::CLIENT_ID(), str2mac( '01' . $self->{chaddr} )); $self->addOption(Net::DHCP::Options::HOSTNAME(), $args{Hostname} ) if ( $args{Hostname} ); $self->addOption(Net::DHCP::Options::SERVER_IP(), inet_aton($args{Server_ip}) ) if ( $args{Server_ip} ); $self->addOption(Net::DHCP::Options::CLASS_ID(),'MSFT 5.0'); return $self; } =pod =item decline DHCP decline packet =cut sub decline { my ($class, %args) = @_; my $self =$class->new(%args); $self->addOption(Net::DHCP::Options::MESSAGE_TYPE(),Net::DHCP::Options::DECLINE()); $self->addOption(Net::DHCP::Options::CLASS_ID(),'MSFT 5.0'); return $self; } =pod =item release DHCP release packet =cut sub release { my ($class, %args) = @_; my $self =$class->new(%args); $self->addOption(Net::DHCP::Options::MESSAGE_TYPE(),Net::DHCP::Options::RELEASE()); $self->addOption(Net::DHCP::Options::CLIENT_ID(), str2mac( '01' . $self->{chaddr})) ; $self->addOption(Net::DHCP::Options::SERVER_IP(), inet_aton($args{Server_ip}) ) if ( $args{Server_ip} ); $self->addOption(Net::DHCP::Options::HOSTNAME(), $args{Hostname} ) if ( $args{Hostname} ); $self->addOption(Net::DHCP::Options::CLASS_ID(),'MSFT 5.0'); return $self; } =pod =item inform DHCP inform packet =cut sub inform { my ($class, %args) = @_; my $self =$class->new(%args); $self->addOption(Net::DHCP::Options::MESSAGE_TYPE(),Net::DHCP::Options::INFORM()); $self->addOption(Net::DHCP::Options::CLIENT_ID(), str2mac( '01' . $self->{chaddr})) ; $self->addOption(Net::DHCP::Options::SERVER_IP(), inet_aton($args{Server_ip}) ) if ( $args{Server_ip} ); $self->addOption(Net::DHCP::Options::HOSTNAME(), $args{Hostname} ) if ( $args{Hostname} ); $self->addOption(Net::DHCP::Options::CLASS_ID(),'MSFT 5.0'); return $self; } =pod =item serialize Converts a Net::DHCP::Packet to a string, ready to put on the network. =cut sub serialize { my ($self) = @_; my $bytes = undef; $bytes .= pack('C',$self->op()); $bytes .= pack('C',$self->htype()); $bytes .= pack('C',$self->hlen()); $bytes .= pack('C',$self->hops()); $bytes .= $self->xid(); $bytes .= pack('S',$self->secs()); $bytes .= pack('S',$self->flags()); $bytes .= inet_aton($self->ciaddr()); $bytes .= inet_aton($self->yiaddr()); $bytes .= inet_aton($self->siaddr()); $bytes .= inet_aton($self->giaddr()); $bytes .= pack('C16', unpack('C16', str2mac($self->chaddr()) ) ); $bytes .= pack('C64', unpack('C64',$self->sname())); $bytes .= pack('C128', unpack('C128',$self->file())); $bytes .= $self->{options}->serialize(); return $bytes; } =pod =item marshall(string) The inverse of serialize. Converts a string, presumably a received UDP packet, into a Net::DHCP::Packet. =cut sub marshall { use bytes; my ($self,$bytes) = @_; my $pos = 0; $self->{op} = unpack('C',substr($bytes,$pos++,1)); $self->{htype} = unpack('C',substr($bytes,$pos++,1)); $self->{hlen} = unpack('C',substr($bytes,$pos++,1)); $self->{hops} = unpack('C',substr($bytes,$pos++,1)); $self->{xid} = substr($bytes,$pos,4); $pos+=4; $self->{secs} = substr($bytes,$pos,2); $pos+=2; $self->{flags} = substr($bytes,$pos,2); $pos+=2; $self->{ciaddr} = inet_ntoa(substr($bytes,$pos,4)); $pos+=4; $self->{yiaddr} = inet_ntoa(substr($bytes,$pos,4)); $pos+=4; $self->{siaddr} = inet_ntoa(substr($bytes,$pos,4)); $pos+=4; $self->{giaddr} = inet_ntoa(substr($bytes,$pos,4)); $pos+=4; $self->{chaddr} = mac2str($self->{hlen},substr($bytes,$pos,16)); $pos+=16; $self->{sname} = substr($bytes,$pos,64); $pos+=64; $self->{sname} = substr($bytes,$pos,128); $pos+=128; $self->{options} = new Net::DHCP::Options()->marshall(substr($bytes,$pos)); return $self; } =pod =item toString() Returns a textual representation of the packet, for debugging. =cut sub toString { my ($self) = @_; my $s = ""; while ( my ($key, $value) = each (%$self) ) { next if ($key eq 'options'); $s .= sprintf("%s = %s\n",$key,$value); } $s .= sprintf("options =\n %s\n", $self->{options}->toString()); return $s; } =pod =head1 AUTHOR F. van Dun =head1 BUGS I only ran some simple tests on Windows 2000 with a W2K DHCP server and a USR DHCP server. Not yet tested on Unix platform. =head1 COPYRIGHT This is free software. It can be distributed and/or modified under the same terms as Perl itself. =head1 SEE ALSO perl(1), Net::DHCP::Options. =cut 1;