+};
+
+#########################################################################
+# Public methods
+#########################################################################
+
+# generic signal handler to cause daemon to stop
+sub signal_handler {
+ $time_to_die = 1;
+}
+$SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signal_handler;
+
+# ignore any PIPE signal: standard behaviour is to quit process
+$SIG{PIPE} = 'IGNORE';
+
+sub new {
+ my ($class, %self) = @_;
+
+ # OOP stuff
+ $class = ref($class) || $class;
+ my $self = \%self;
+ bless $self, $class;
+
+ # private
+ $self->{_sock_in_ip4} = undef;
+ $self->{_sock_out_ip4} = undef;
+ $self->{_sock_in_ip6} = undef;
+ $self->{_sock_out_ip6} = undef;
+ $self->{_leases} = undef;
+ $self->{_reverse} = undef;
+ $self->{_config} = undef;
+ $self->{_transaction_ip4} = 0;
+ $self->{_transaction_ip6} = 0;
+ $self->{_dhpcp_ip4} = undef;
+
+ # public
+ $self->{log_file} ||= 'syslog';
+ $self->{lease_time} ||= $DEFAULT_LEASE;
+ $self->{lease_time_renew} ||= $DEFAULT_LEASE_RENEW;
+ $self->{subnet_mask} ||= undef;
+ $self->{routers} ||= undef;
+ $self->{broadcast_addr} ||= undef;
+ $self->{domain_name} ||= undef;
+ $self->{dns_servers} ||= undef;
+ $self->{ntp_servers} ||= undef;
+ $self->{LOG_LEVEL} = ERROR unless defined $self->{LOG_LEVEL};
+ $self->{NODAEMON} ||= 0;
+ $self->{DEBUG} ||= 0;
+ $self->{timeout} ||= 10;
+ $self->{lease_file} ||= '/tmp/dhcpd.leases';
+ $self->{conf_file} ||= '/tmp/dhcpd.cfg';
+
+ return $self;
+}
+
+sub run {
+ my ($self) = @_;
+ my ($sel, @ready, $socket, $res);
+
+ eval {
+ $self->$read_config();
+ };
+ if ($@) {
+ my $err = $@;
+ $self->$logger($err, ERROR);
+ die $err;
+ }
+ $self->$logger("Starting dhcpd", INFO);
+ if ($self->{NODAEMON} < 1) {
+ $self->$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;
+ do {
+ my $err = $!;
+ $self->$logger("Couldn't fork: $err", ERROR);
+ die "Couldn't fork: $err";
+ } unless defined($pid);
+
+ POSIX::setsid() || do {
+ my $err = $!;
+ $self->$logger("Can't start a new session: $err", ERROR);
+ die "Can't start a new session: $err";
+ };
+ $self->$logger("Now in Daemon mode", INFO);
+ }
+
+ $res = $self->$read_lease_file();
+ $self->$logger("Starting with empty leases file '$self->{lease_file}'", INFO)
+ if (! $res || ! $self->{_leases});
+
+ $self->$logger("Initialization complete", INFO);
+
+ # open listening socket
+ $self->{_sock_in_ip4} = IO::Socket::IP->new(
+ Domain => PF_INET,
+ LocalPort => 67,
+ LocalAddr => inet_ntoa(INADDR_ANY),
+ Proto => 'udp'
+ ) || do {
+ my $err = $@;
+ $self->$logger("IP4 Socket creation error: $err", ERROR);
+ die "IP4 Socket creation error: $err\n";
+ };
+ $self->{_sock_in_ip6} = IO::Socket::IP->new(
+ Domain => PF_INET6,
+ V6Only => 1,
+ LocalPort => 547,
+ LocalAddr => '::',
+ Proto => 'udp'
+ ) || do {
+ my $err = $@;
+ $self->$logger("IP6 Socket creation error: $err", ERROR);
+ die "IP6 Socket creation error: $err\n";
+ };
+
+ $sel = IO::Select->new($self->{_sock_in_ip4});
+ $sel->add($self->{_sock_in_ip6});
+
+ until ($time_to_die) {
+ my $buf = undef;
+ my $fromaddr;
+ my $dhcpreq;
+
+ eval { # catch fatal errors
+ while (@ready = $sel->can_read) {
+ $self->$logger("Waiting for incoming packet", INFO);
+ foreach $socket (@ready) {
+ if ($socket == $self->{_sock_in_ip4}) {
+ # receive ipv4 packet
+ $fromaddr = $self->{_sock_in_ip4}->recv($buf, 4096)
+ || $self->$logger("recv: $!", ERROR);
+ next if ($!); # continue loop if an error occured
+ $self->{_transaction_ip4}++; # transaction counter
+
+ {
+ use bytes;
+ my ($port,$addr) = unpack_sockaddr_in($fromaddr);
+ my $ipaddr = inet_ntoa($addr);
+ $self->$logger("Got a packet tr=$self->{_transaction_ip4} src=$ipaddr:$port length=".length($buf), INFO);
+ }
+
+ $dhcpreq = new Net::DHCP::Packet($buf);
+ $dhcpreq->comment($self->{_transaction_ip4});
+
+ my $messagetype = $dhcpreq->getOptionValue(DHO_DHCP_MESSAGE_TYPE());
+
+ if ($messagetype eq DHCPDISCOVER()) {
+ $self->$discover_ip4($dhcpreq);
+ } elsif ($messagetype eq DHCPREQUEST()) {
+ $self->$request_ip4($dhcpreq);
+ } elsif ($messagetype eq DHCPINFORM()) {
+ $self->$logger("Not implemented: DHCPINFORM", WARNING);
+ } elsif ($messagetype eq DHCPRELEASE()) {
+ $self->$release_ip4($dhcpreq);
+ } else {
+ $self->$logger("Packet dropped", WARNING);
+ # bad messagetype, we drop it
+ }
+ } else {
+ # Receive ipv6 packet
+ my $myaddr = $self->{_sock_in_ip6}->sockhost;
+
+ $fromaddr = $self->{_sock_in_ip6}->recv($buf, 4096)
+ || $self->$logger("recv: $!", ERROR);
+ next if ($!); # continue loop if an error occured
+ $self->{_transaction_ip6}++; # transaction counter
+ $self->$logger("recv: $buf", INFO);
+ {
+ use bytes;
+ my ($port,$addr) = unpack_sockaddr_in6($fromaddr);
+ my $ipaddr = inet_ntop(AF_INET6, $addr);
+ $self->$logger("Got a packet tr=$self->{_transaction_ip6} src=$ipaddr:$port length=".length($buf), INFO);
+ }
+ $self->$excuse_me_ip6($myaddr, $buf);
+ }
+ }
+ }
+ }; # end of 'eval' blocks
+ if ($@) {
+ $self->$logger("Caught error in main loop: $@", ERROR);
+ }
+ }
+ $self->{_sock_in_ip4}->close;
+ $self->{_sock_in_ip6}->close;
+ $self->$logger("Exiting dhcpd", INFO);