#!/usr/bin/perl ## >>--------------------------------------------------------------------------------- use strict; use Sys::Hostname; use Socket; use Net::DHCP::Packet; use Net::DHCP::Constants; use POSIX qw(setsid strftime); use IO::Socket::INET; # sample logger sub logger{ my $str = shift; print STDOUT strftime "[%d/%b/%Y:%H:%M:%S] ", localtime; print STDOUT "$str\n"; } logger("Starting dhcpd"); my $DAEMON = 0; # run as daemon ? # accept only from selected VENDOR classes (avoids messing existing networks) my $VENDOR_ACCEPTED = "foo|bar"; # broadcast address my $bcastaddr = sockaddr_in("68",INADDR_BROADCAST); # get a flag to force daemon to stop my $time_to_die = 0; # generic signal handler to cause daemon to stop sub signal_handler { $time_to_die = 1; } $SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signal_handler; # trap or ignore $SIG{PIPE} # Daemon behaviour # ignore any PIPE signal: standard behaviour is to quit process $SIG{PIPE} = 'IGNORE'; # open listening socket my $sock_in = IO::Socket::INET->new( LocalPort => 67, LocalAddr => "127.0.0.1", Proto => 'udp') || die "Socket creation error: $@\n"; if ($DAEMON) { # doesn't seem to work very well on cygwin logger("Entering Daemon mode"); chdir '/' or die "Can't chdir to /: $!"; umask 0; open STDIN, '/dev/null' or die "Can't read /dev/null: $!"; open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!"; open STDERR, '>/dev/null' or die "Can't write to /dev/null: $!"; my $pid = fork; exit if $pid; die "Couldn't fork: $!" unless defined($pid); POSIX::setsid() or die "Can't start a new session: $!"; logger("Now in Daemon mode"); } logger("Initialization complete"); # main loop # # process incoming packets my $transaction = 0; # report transaction number until ($time_to_die) { my $buf = undef; my $fromaddr; # address & port from which packet was received my $dhcpreq; eval { # catch fatal errors logger("Waiting for incoming packet"); # receive packet $fromaddr = $sock_in->recv($buf,4096) || logger("recv:$!"); next if ($!); # continue loop if an error occured $transaction++; # transaction counter { use bytes; my ($port,$addr) = unpack_sockaddr_in($fromaddr); my $ipaddr = inet_ntoa($addr); logger("Got a packet tr=$transaction src=$ipaddr:$port length=".length($buf)); } my $dhcpreq = new Net::DHCP::Packet($buf); $dhcpreq->comment($transaction); my $messagetype = $dhcpreq->getOptionValue(DHO_DHCP_MESSAGE_TYPE()); if ($messagetype eq DHCPDISCOVER()) { do_discover($dhcpreq); } elsif ($messagetype eq DHCPREQUEST()) { do_request($dhcpreq); } elsif ($messagetype eq DHCPINFORM()) { } else { logger("Packet dropped"); # bad messagetype, we drop it } }; # end of 'eval' blocks if ($@) { logger("Caught error in main loop:$@"); } } logger("Exiting dhcpd"); #======================================================================= sub do_discover($) { my ($dhcpreq) = @_; my $sock_out; my ($calc_ip, $calc_router, $calc_mask); # calculate address $calc_ip = "12.34.56.78"; my $vendor = $dhcpreq->getOptionValue(DHO_VENDOR_CLASS_IDENTIFIER()); if ($vendor !~ $VENDOR_ACCEPTED) { logger("DISCOVER rejected, unsupported VENDOR class"); return; # dropping packet } my $dhcpresp = new Net::DHCP::Packet( Comment => $dhcpreq->comment(), Op => BOOTREPLY(), Hops => $dhcpreq->hops(), Xid => $dhcpreq->xid(), Flags => $dhcpreq->flags(), Ciaddr => $dhcpreq->ciaddr(), Yiaddr => $calc_ip, Siaddr => $dhcpreq->siaddr(), Giaddr => $dhcpreq->giaddr(), Chaddr => $dhcpreq->chaddr(), DHO_DHCP_MESSAGE_TYPE() => DHCPOFFER(), ); logger("Sending response"); # Socket object keeps track of whom sent last packet # so we don't need to specify target address logger("Sending OFFER tr=".$dhcpresp->comment()); $sock_in->send($dhcpresp->serialize()) || die "Error sending OFFER:$!\n"; # TODO: you have to choose between sending back to sender or broadcasting to network } #======================================================================= sub do_request($) { my ($dhcpreq) = @_; my $sock_out; my $calc_ip; my $dhcpresp; $calc_ip = "12.34.56.78"; my $vendor = $dhcpreq->getOptionValue(DHO_VENDOR_CLASS_IDENTIFIER()); if ($vendor !~ $VENDOR_ACCEPTED) { logger("REQUEST rejected, unsupported VENDOR class"); return; # dropping packet } # compare calculated address with requested address if ($calc_ip eq $dhcpreq->getOptionValue(DHO_DHCP_REQUESTED_ADDRESS())) { # address is correct, we send an ACK $dhcpresp = new Net::DHCP::Packet( Comment => $dhcpreq->comment(), Op => BOOTREPLY(), Hops => $dhcpreq->hops(), Xid => $dhcpreq->xid(), Flags => $dhcpreq->flags(), Ciaddr => $dhcpreq->ciaddr(), Yiaddr => $calc_ip, Siaddr => $dhcpreq->siaddr(), Giaddr => $dhcpreq->giaddr(), Chaddr => $dhcpreq->chaddr(), DHO_DHCP_MESSAGE_TYPE() => DHCPACK(), ); } else { # bad request, we send a NAK $dhcpresp = new Net::DHCP::Packet( Comment => $dhcpreq->comment(), Op => BOOTREPLY(), Hops => $dhcpreq->hops(), Xid => $dhcpreq->xid(), Flags => $dhcpreq->flags(), Ciaddr => $dhcpreq->ciaddr(), Yiaddr => "0.0.0.0", Siaddr => $dhcpreq->siaddr(), Giaddr => $dhcpreq->giaddr(), Chaddr => $dhcpreq->chaddr(), DHO_DHCP_MESSAGE_TYPE() => DHCPNAK(), DHO_DHCP_MESSAGE(), "Bad request...", ); } # Socket object keeps track of whom sent last packet # so we don't need to specify target address logger("Sending ACK/NAK tr=".$dhcpresp->comment()); $sock_in->send($dhcpresp->serialize()) || die "Error sending ACK/NAK:$!\n"; # TODO: you have to choose between sending back to sender or broadcasting to network }