NAME

        Net::DHCPClientLive - stateful DHCP client object

SYNOPSIS

   use Net::DHCPClientLive;
   my $client = new Net::DHCPClientLive( interface => "eth0", state => 'BOUND')
                      or die "failed to move to BOUND state\n";
   print "DHCP client $client->{cltmac} is created and assigned $client->{requestip} from server\n";
 

DESCRIPTION

Net::DHCPClientLive allows you to create and manipulate DHCP client(s) so that you can test the behavior of your DHCP server upon client state transition.

DHCP client is a stateful host. It reaches "BOUND" state after the successful discover process, and will renew and/or rebind when T1/T2 timer expire. The state will be changed accordingly depending on the behavior of the server.

With this module you can move client's state, make transition, and even let it go freely. At each attempt of operation, it can tell whether it success or fail, so that you know if your server works as expected.

You can create many DHCP clients at the same time. In this way you can easily execute scalability test. Image you create 100 live DHCP clients, they are alive as though there were 100 hosts there, doing renew, rebind, or release interacting with your DHCP server for a few days, just like they do in real scenario.

I also provide some code showing how to do this in EXAMPLES section.

Client identifier - mac address is the identifier of a client, it's assigned when created and kept in the whole life cycle - xid is kept in the client life cycle until back to INIT, when xid is initialized

The following is the detail description of state transition. INIT->SELECT send DISCOVER, receive OFFER, check and report, return true if receiving DHCP OFFER from server, or false if no OFFER received. The state of client moves to SELECT anyway.

   INIT->REQUEST 
   SELECT->REQUEST 
      send DISCOVER, receive OFFER, and send REQUEST, check ACK and report,
      return true if receiving both DHCP OFFER and ACK from server, or false otherwise
      The state of client moves to SELECT if no OFFER received or REQUEST if OFFER received.

   SELECT->SELECT
      same as INIT->SELECT, 
      this allows you to test intensively the server response to DISCOVER

   REQUEST->REQUEST
      same as SELECT->REQUEST, 
      this allows you to test intensively the server response to DISCOVER/REQUEST


   INIT->BOUND 
      send DISCOVER, receive OFFER, and send REQUEST, receive ACK and update.
      return true only if the whole process correct.
      The state of client moves accordingly to SELECT, REQUEST, or BOUND
      SELECT if client sends DISCOVER
      REQUEST if client receives OFFER, then sends REQUEST
      BOUND if client receives ACK after sending REQUEST

   SELECT->BOUND
      back to INIT, then same to above

   REQUEST->BOUND
      send REQUEST, receive ACK and update (because client already has server offer info in obj)
      return true only if the client receives ACK.
      The state of client moves to BOUND if receives ACK, or stay at REQUEST if no ACK

   BOUND->BOUND 
      Simulates T1 expire. Sends unicast REQUEST to server, and move to RENEW
      move to BOUND and refresh lease after receiving ACK from server
      return true if receives ACK
      Note: Linux server sends ARP request to client ip, client has to replay this ARP
         before Linux server sends ACK

   RENEW->BOUND
   REBIND->BOUND
      Simulates T2 expire. sends broadcase REQUEST, move to BOUND and refresh lease if receives ACK
      return true if receives ACK


   BOUND->RENEW
      Simulates T1 expire. Sends unicast REQUEST to server, and move to RENEW
      return true if receives ACK 
      Note: Linux server sends ARP request to client ip, client also replay this ARP
         before Linux server sends ACK

   BOUND->REBIND
      Does BOUND->RENEW first
      Then ignore ACK and does RENEW->REBIND

   RENEW->REBIND
      Simulates T2 expire. sends broadcase REQUEST
      return true if receives ACK to this broadcase REQUEST


   REBIND->INIT
   RENEW->INIT
      Sends RELEASE and move to INIT

 
   RENEW->SELECT  
   RENEW->REQUEST
   REBIND->SELECT
   REBIND->REQUEST

      1. ->INIT
      2. INIT->REQUEST


   undef->INIT
      do nothing

   SELECT->INIT
      clear XID

   REQUEST->INIT 
      send DECLINE, clear XID

METHODS

new - create a new Net::DHCPClientLive object

   $clt = new Net::DHCPClientLive(interface => eth0,  # interface name of your host
                                  state => 'BOUND',   # state to be moved for this client, one of
                                                      # INIT, SELECT,REQUEST,BOUND,RENEW,REBIND
                                  mac => '00:01:02:03:04:05', # default is auto-created randomly
                                  options => {key => value, ...}, # refer to rfc2131 for options
                                  verb => $verb);     # print more pkt exchange info

   You don't need to specify options unless you have special interest, in which case, the dhcp packet
   exchangewill contain those options. If there is no "mac", the client is assgined one automatically,
   and it becomes the identifier of the client.

goState($state)

  $clt->goState('RENEW');

  The only argument to goState method is the state name you are driving the client to move to. 
  It returns true if the client successfully move the state, otherwise returns false.
  Refer to "DESCRIPTION" section for detail of state transition
  Legal state name includes "INIT","SELECT","REQUEST","BOUND","RENEW", and "REBIND".

Other methods

 These methods are called by goState. You probably don't need them. Just in case, you can use them to send some kind of DHCP packets.

 $clt->discover()
 $clt->request()
 $clt->renew()
 $clt->rebind()
 $clt->decline()
 $clt->release()

EXAMPLES

Here is a subroutine used to do state transition. It creates a client and try to move its state to $middleState, then it moves to $finalState if it is provided, It returns the client object on success, or false on failure.

   sub stateTransition {
      my ($middleState,$finalState) = @_;
      my $clt;
      unless ($clt = new Net::DHCPClientLive( interface => "eth1", state => $middleState )) {
         print "server response abnormal to $clt->{cltmac}\n";
         return 0;
      }else{
         print "$clt->{cltmac} moved to $clt->{'state'}\n";
      }
      return $clt unless($finalState);
      unless ($clt->goState($finalState)) {
         print "server response abnormal to $clt->{cltmac}\n";
         return 0;
      }else{
         print "$clt->{cltmac} moved to $clt->{'state'}\n";
      }
      return $clt;
   }

Here is another example to simulate multiple live clients. Please note that each client exists as individual process in your host.

   $SIG{CHLD} = sub {while( waitpid(-1, WNOHANG) > 0 ) {} };
   $SIG{INT} = sub { kill 'KILL', 0 };
   $SIG{QUIT} = sub { kill 'KILL', 0 };
   my $clt;
   my @liveClt = ();
   
   # create $numClient clients 
   for (my $k = 1; $k <= $numClient; $k++) {
      if ($clt = new Net::DHCPClientLive( interface => "$hostint", state => 'BOUND', verb => $verb)) {
         print "created live a client No.$k: $clt->{cltmac}\n";
         my $W = gensym();
         my $R = gensym();
         my $pid;
         if (pipe($R,$W) && defined($pid = fork())) {
            if ($pid) {
               # keep the client
               close $W;
               $clt->{sock} = $R;
               push @liveClt, $clt;
            }else{
               # a client is created
               close $R;
               open(STDOUT, ">&$W");
               select $W; $| = 1;
               while (1) {
                  my $now = time;
                  while (time < $now + $clt->{t1}) {};
                  print "T1 expired, renewing ... ";
                  unless ($clt->goState('BOUND')) {
                     print "failed\n";
                     my $now = time;
                     while (time < $now + $clt->{t2} - $clt->{t1}) {};
                     print "T2 expired, rebinding ...";
                     unless ($clt->goState('BOUND')) {
                        print "rebinding failed, relasing the client\n";
                        $clt->goState('INIT');
                        exit 0;
                     }else{
                        print "done\n";
                     }
                  }else{
                     print "done\n";
                  }
               }
            }
         }else{
            print("max clients has been created\n");
            last;
         }
      }else{
         print "No.$k client failed to go to BOUND\n";
      }
   }
   
   # main process shows information printed by clients
   if (@liveClt) {
      print "Totally created ", scalar @liveClt, " client(s)\n";
      my $liveCltSock = new IO::Select();
      for (@liveClt) {
         $liveCltSock->add($_->{sock});
      }
      while ( my @cltCanSay = $liveCltSock->can_read() ) {
         for my $cltSock (@cltCanSay) {
            my ($client) = grep {$_->{sock} eq $cltSock} @liveClt;
            my $msg = <$cltSock>;
            next if ($msg =~ /^\s*$/);
            print "$client->{cltmac}: $msg";
         }
      }
   }

REQUIRES

This module need to use the following modules Net::RawIP; Net::ARP; Net::PcapUtils; NetPacket::ARP; NetPacket::Ethernet; NetPacket::IP; NetPacket::UDP;

AUTHOR

Ming Zhang, <ming2004@gmail.com

COPYRIGHT AND LICENSE

Copyright 2007 by Ming Zhang

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.