1 package PVE
::DHCPServer
;
14 use Fcntl
qw(:DEFAULT :flock SEEK_END);
15 use POSIX
qw(EINTR setsid strftime);
19 use Net
::DHCP
::Packet
;
20 use Net
::DHCP
::Constants
;
36 our @ISA = qw(Exporter);
49 our $VERSION = '0.01';
50 our $NAME = 'PVE::DHCPServer';
51 our $DEFAULT_LEASE = 7200;
52 our $DEFAULT_LEASE_RENEW = 5400;
55 #########################################################################
57 #########################################################################
60 my ($self, $message, $level) = @_;
63 return unless ($level >= $self->{LOG_LEVEL
});
65 $level = "debug" if $level eq DEBUG
;
66 $level = "info" if $level eq INFO
;
67 $level = "notice" if $level eq NOTICE
;
68 $level = "warning" if $level eq WARNING
;
69 $level = "err" if $level eq ERROR
;
70 $level = "crit" if $level eq CRITICAL
;
71 $level = "alert" if $level eq ALERT
;
72 $level = "emerg" if $level eq EMERGENCY
;
75 print STDOUT strftime
"[%d/%b/%Y:%H:%M:%S] ", localtime;
76 print STDOUT
"$level: " . ($message ?
$message : 'No message') . "\n";
77 } elsif ($self->{log_file
} eq 'syslog') {
78 openlog
($NAME, 'ndelay,pid', 'user');
79 syslog
($level, $message);
82 my $fh = new IO
::File
;
83 if (! $fh->open("> $self->{log_file}")) {
84 croak
"$self->{log_file}: $!";
86 print $fh strftime
"[%d/%b/%Y:%H:%M:%S] ", localtime;
87 print $fh "$level: $message\n";
92 my $run_with_timeout = sub {
93 my ($self, $code, @param) = @_;
95 die "got timeout" if $self->{timeout
} <= 0;
103 local $SIG{ALRM
} = sub { $sigcount++; }; # catch alarm outside eval
106 local $SIG{ALRM
} = sub { $sigcount++; die "got timeout"; };
107 local $SIG{PIPE
} = sub { $sigcount++; die "broken pipe" };
108 local $SIG{__DIE__
}; # see SA bug 4631
110 $prev_alarm = alarm($self->{timeout
});
112 $res = &$code(@param);
114 alarm(0); # avoid race conditions
119 alarm($prev_alarm) if defined($prev_alarm);
121 die "unknown error" if $sigcount && !$err; # seems to happen sometimes
129 my ($self, $file, $shared) = @_;
131 my $mode = $shared ? LOCK_SH
: LOCK_EX
;
133 my $lock_func = sub {
134 if ($mode == LOCK_SH
) {
135 $self->{file_handle
} = new IO
::File
("<$file") ||
136 die "can't open file '$file' for read - $!";
138 $self->{file_handle
} = new IO
::File
(">$file") ||
139 die "can't open file '$file' for write - $!";
141 $self->$logger("trying to aquire lock on '$file'...");
142 if (!flock ($self->{file_handle
}, $mode|LOCK_NB
)) {
145 $success = flock($self->{file_handle
}, $mode);
146 # try again on EINTR (see bug #273)
147 if ($success || ($! != EINTR
)) {
151 if ($mode == LOCK_SH
) {
152 seek($self->{file_handle
}, 0, SEEK_END
) or $success = 0;
155 $self->$logger(" failed");
156 die "can't aquire lock - $!";
159 $self->$logger(" OK");
166 $res = $self->$run_with_timeout($lock_func);
169 $self->$logger("can't lock file '$file' - $@", ERROR
);
170 $self->{file_handle
} = undef;
178 my ($self, $file) = @_;
180 return '' unless($self->{file_handle
});
181 my $unlock_func = sub {
182 $self->$logger("trying to unlock '$file'...");
183 if (!flock($self->{file_handle
}, LOCK_UN
)) {
187 $success = flock($self->{file_handle
}, LOCK_UN
);
188 # try again on EINTR (see bug #273)
189 if ($success || ($! != EINTR
)) {
194 $self->$logger(" failed");
195 die "can't unlock - $!";
198 $self->$logger(" OK");
205 $res = $self->$run_with_timeout($unlock_func);
208 $self->$logger("can't lock file '$file' - $@", ERROR
);
209 $self->{file_handle
} = undef;
216 my $convert_timestamp = sub {
217 my ($self, $timestamp, $strtotime) = @_;
218 my ($res, $mday, $mon, $year, $hour, $min, $sec);
220 $self->$logger("Timestamp: $timestamp");
222 if ($timestamp !~ /^\d{4}\/\d
{2}\
/\d{2}\s+\d{2}:\d{2}:\d{2}$/) {
223 $self->$logger("$timestamp: (strtotime) Bad format", ERROR
);
226 ($year,$mon,$mday,$hour,$min,$sec) = split(/[\s\/:]+/, $timestamp);
227 $res = timelocal
($sec,$min,$hour,$mday,$mon-1,$year);
230 $self->$logger($timestamp);
231 if ($timestamp !~ /^\d+$/) {
232 $self->$logger("$timestamp: (timetostr) Bad format", ERROR
);
235 ($sec,$min,$hour,$mday,$mon,$year) = localtime($timestamp);
236 $self->$logger("Timestamp: $sec,$min,$hour,$mday,$mon,$year");
237 $res = sprintf("%d/%02d/%02d %02d:%02d:%02d", ($year+1900),($mon+1),$mday,$hour,$min,$sec);
238 $self->$logger("Timestamp: $res");
246 my $add_lease = sub {
247 my ($self, $ip, $lease) = @_;
250 my $mac = $lease->{'hardware ethernet'};
252 $lease->{'hardware ethernet'} = $mac;
253 $ts = $self->$convert_timestamp($lease->{starts
}, 1);
255 $lease->{starts
} = $ts;
256 $ts = $self->$convert_timestamp($lease->{ends
}, 1);
258 $lease->{ends
} = $ts;
260 $self->{_leases
}->{$ip} = $lease;
261 $self->$logger(Dumper
($self->{_leases
}->{$ip}));
262 $self->{_reverse
}->{$mac} = $ip;
263 $self->$logger("$mac => $self->{_reverse}->{$mac}");
266 #lease vvv.xxx.yyy.zzz {
267 # starts yyyy/mm/dd hh:mm:ss;
268 # ends yyyy/mm/dd hh:mm:ss;
269 # binding state active|free;
270 # hardware ethernet MAC;
271 # client-hostname "name"
273 my $read_lease_file = sub {
275 my ($res, $key, $lease);
278 # Start with empty leases file?
279 if (! -e
$self->{lease_file
}) {
283 $self->$lock($self->{lease_file
}, 1);
284 if ($self->{file_handle
}) {
285 my $fh = $self->{file_handle
};
288 $self->$logger("Read: $_");
289 if ($_ =~ /^\s*lease\s+([\d\.]+)\s+{\s*/) {
290 $self->$add_lease($key, $lease) if $lease;
294 $self->$logger("Key: $key");
297 next if ($_ =~ /^\s*}\s*/ || $_ =~ /^\s*$/ || $_ =~ /^\s*#.*/);
298 if ($_ =~ /^\s*(starts|ends|binding state|hardware ethernet|client-hostname)\s+(.+)\s*;/) {
300 $self->$logger("Key: $1 Value: $2");
302 $key = 'UNDEF' unless $key;
303 $self->$logger("$key: Bad format", ERROR
);
310 if ($lease && !$error) {
311 $self->$logger("Key: $key");
312 $self->$add_lease($key, $lease);
314 $self->$logger("Leases data structure: \n" . Dumper
($self->{_leases
}));
315 $self->$unlock($self->{lease_file
});
318 $self->$logger("Could not read leases file", INFO
);
325 my $write_lease_file = sub {
329 $res = $self->$lock($self->{lease_file
}, 0);
330 if ($self->{file_handle
}) {
331 if ($self->{_leases
}) {
332 my $fh = $self->{file_handle
};
333 my $leases = $self->{_leases
};
334 while ((my $lease, my $elems) = each (%$leases)) {
335 $self->$logger("Writing: $lease");
336 print $fh "lease $lease {\n";
337 while ((my $key, my $val) = each %$elems) {
338 if ($key =~ /^(starts|ends)$/) {
339 $val = $self->$convert_timestamp($val, 0);
341 $self->$logger("Writing: $key $val");
342 print $fh "\t$key $val;\n";
347 $self->$unlock($self->{lease_file
});
350 $self->$logger("$self->{lease_file}: Could not write leases file", ERROR
);
357 #subnet 192.168.9.0 netmask 255.255.255.0 {
358 # range 192.168.9.2 192.168.9.100;
361 # router 192.168.9.254;
362 # dns-servers 192.168.2.201;
363 # ntp-servers 192.168.9.254;
364 # broadcast 192.168.9.255;
365 # domain-name "foo.bar";
367 # allow 001cc0c33317,001cc0c33318,001cc0c33319,001cc0c33320;
368 # static 001cc0c33317 192.168.9.100,001cc0c33318 192.168.9.200;
371 my $read_config = sub {
373 my ($res, $key, $netmask, $config, $subopt);
375 $self->$lock($self->{conf_file
}, 1);
376 if ($self->{file_handle
}) {
377 my $fh = $self->{file_handle
};
381 $self->$logger("Read: $_");
382 if ($_ =~ /^\s*subnet\s+([\d\.]+)\s+netmask\s+([\d\.]+)\s+{\s*/) {
383 $self->{_config
}->{$key} = $config if $config;
386 $config->{netmask
} = $2;
387 $self->$logger("Key: $key Netmask: $config->{netmask}");
389 next if (($_ =~ /^\s*}\s*/ && ! $subopt) || $_ =~ /^\s*$/ || $_ =~ /^\s*#.*/);
390 if (! $subopt && $_ =~ /^\s*(range|ttl|rttl|router|dns-servers|ntp-servers|broadcast|domain-name)\s+(.+)\s*;/) {
392 $self->$logger("Key: $1 Value: $2");
393 } elsif ($subopt &&$_ =~ /^\s*}\s*/) {
395 } elsif ($subopt || $_ =~ /^\s*{\s*/) {
397 if ($_ =~ /^\s*(allow|static)\s+(.+)\s*;/) {
398 my @vals = split(/\s*,\s*/, $2);
399 $config->{$1} = [@vals];
400 $self->$logger("Key: $1 Value: $2");
402 $key = 'UNDEF' unless $key;
403 my $err = "$key: 'suboptions' Bad format";
404 $self->$logger($err, ERROR
);
413 $key = 'UNDEF' unless $key;
414 my $err = "$key: Bad format";
415 $self->$logger($err, ERROR
);
423 $self->{_config
}->{$key} = $config;
425 $self->$logger("Config data structure: \n" . Dumper
($self->{_config
}));
426 $self->$unlock($self->{conf_file
});
427 if (!$self->{_config
}) {
428 die "Empty config file";
431 die "Could not read config file";
435 #########################################################################
436 # Private methods which handle DHCP4 requests
437 #########################################################################
439 my $get_mac_ip4 = sub {
440 my ($self, $req) = @_;
443 $mac = $req->chaddr();
449 my $can_client_use_net_ip4 = sub {
450 my ($self, $req, $network) = @_;
453 # Is client allowed to request IP?
455 if ($self->{_config
}->{$network}->{allow
}) {
456 $self->$logger("Allow: " . Dumper
($self->{_config
}->{$network}->{allow
}));
457 foreach (@
{$self->{_config
}->{$network}->{allow
}}) {
458 if ($_ eq $self->$get_mac_ip4($req)) {
470 my $add_options_ip4 = sub {
471 my ($self, $resp) = @_;
473 if ($self->{lease_time
}) {
474 $resp->addOptionValue(DHO_DHCP_LEASE_TIME
, $self->{lease_time
});
476 if ($self->{lease_time_renew
}) {
477 $resp->addOptionValue(DHO_DHCP_RENEWAL_TIME
, $self->{lease_time_renew
});
479 if ($self->{subnet_mask
}) {
480 $resp->addOptionValue(DHO_SUBNET_MASK
, $self->{subnet_mask
});
482 if ($self->{routers
}) {
483 $resp->addOptionValue(DHO_ROUTERS
, $self->{routers
});
485 if ($self->{broadcast_addr
}) {
486 $resp->addOptionValue(DHO_BROADCAST_ADDRESS
, $self->{broadcast_addr
});
488 if ($self->{domain_name
}) {
489 $resp->addOptionValue(DHO_DOMAIN_NAME
, $self->{domain_name
});
491 if ($self->{ntp_servers
}) {
492 $resp->addOptionValue(DHO_NTP_SERVERS
, $self->{ntp_servers
});
494 if ($self->{dns_servers
}) {
495 $resp->addOptionValue(DHO_DOMAIN_NAME_SERVERS
, $self->{dns_servers
});
500 my ($self, $req, $message) = @_;
502 $message = 'Bad request' unless $message;
503 my $peeraddr = ($req->ciaddr() && $req->ciaddr() ne inet_ntoa
(INADDR_ANY
)) ?
504 $req->ciaddr() : inet_ntoa
(INADDR_BROADCAST
);
506 my $sock = IO
::Socket
::IP
->new(
509 PeerAddr
=> $peeraddr,
513 $self->$logger("[discover_ip4] Socket creation error: $err", ERROR
);
514 die "[discover_ip4] Socket creation error: $err\n";
517 my $resp = new Net
::DHCP
::Packet
(
518 Comment
=> $req->comment(),
520 Hops
=> $req->hops(),
522 Flags
=> $req->flags(),
523 Ciaddr
=> $req->ciaddr(),
525 Siaddr
=> $req->siaddr(),
526 Giaddr
=> $req->giaddr(),
527 Chaddr
=> $req->chaddr(),
528 DHO_DHCP_MESSAGE_TYPE
() => DHCPNAK
(),
529 DHO_DHCP_MESSAGE
(), $message,
532 $self->$logger("Sending NAK to " . $sock->peerhost . ':' . $sock->peerport .
533 "\nReason: $message", INFO
);
534 $self->$logger($resp->toString());
536 my $xid = $req->xid() ?
$req->xid() : 'Missing';
537 $self->$logger("Sending OFFER tr=$xid", INFO
);
539 $sock->send($resp->serialize()) || die "Error sending OFFER: $!\n";
543 my $send_accept = sub {
544 my ($self, $req, $calc_ip, $reply) = @_;
547 my $peeraddr = ($req->ciaddr() && $req->ciaddr() ne inet_ntoa
(INADDR_ANY
)) ?
548 $req->ciaddr() : inet_ntoa
(INADDR_BROADCAST
);
550 if ($reply == DHCP_OFFER
) {
551 $reply = DHCPOFFER
();
553 } elsif ($reply == DHCP_ACK
) {
557 my $err = "$reply: Unknown reply";
558 $self->$logger($err, ERROR
);
562 my $sock = IO
::Socket
::IP
->new(
565 PeerAddr
=> $peeraddr,
569 $self->$logger("[discover_ip4] Socket creation error: $err", ERROR
);
570 die "[discover_ip4] Socket creation error: $err\n";
573 my $resp = new Net
::DHCP
::Packet
(
574 Comment
=> $req->comment(),
576 Hops
=> $req->hops(),
578 Flags
=> $req->flags(),
579 Ciaddr
=> $req->ciaddr(),
581 Siaddr
=> $req->siaddr(),
582 Giaddr
=> $req->giaddr(),
583 Chaddr
=> $req->chaddr(),
584 DHO_DHCP_MESSAGE_TYPE
() => $reply,
585 DHO_DHCP_SERVER_IDENTIFIER
() => $sock->sockhost
587 $self->$add_options_ip4($resp);
588 my $xid = $req->xid();
589 $self->{_transaction
}->{$xid}->{me
} = $sock->sockhost;
591 $self->$logger("Sending $msg to " . $sock->peerhost . ':' . $sock->peerport, INFO
);
592 $self->$logger($resp->toString());
594 $self->$logger("Sending OFFER tr=".$req->xid(), INFO
);
596 $sock->send($resp->serialize()) || die "Error sending $msg: $!\n";
600 my $update_transaction = sub {
601 my ($self, $req, $tx) = @_;
602 my ($res, $xid, $offer);
605 return -1 unless $xid;
608 $self->{_transaction
}->{$xid} = $tx;
611 if ($self->{_transaction
}->{$xid} && $self->{_transaction
}->{$xid}->{me
}) {
612 my $me = $req->getOptionValue(DHO_DHCP_SERVER_IDENTIFIER
());
613 $me = $req->ciaddr() unless $me;
614 $offer = $self->{_transaction
}->{$xid}->{offer_ip
};
616 if ($me ne $self->{_transaction
}->{$xid}->{me
}) {
617 # Another DHCP server is chosen by client
618 $self->$logger("$me: Offer '".($offer?
$offer : 'None')."' refused by client xid=$xid", INFO
);
619 delete($self->{_transaction
}->{$xid});
620 delete($self->{_leases
}->{$offer}) if $offer;
621 $self->$write_lease_file();
624 $self->$logger("Offer '$offer' accepted by client xid=$xid", INFO
);
628 # Caught request for other DHCP server
631 if ($self->{_transaction
}->{$xid}) {
632 $offer = $self->{_transaction
}->{$xid}->{offer_ip
};
633 $self->$logger("Offer '$offer' wait approval from client xid=$xid", INFO
);
642 my $create_new_lease_ip4 = sub {
643 my ($self, $req, $network) = @_;
646 $lease->{'hardware ethernet'} = $self->$get_mac_ip4($req);
647 my $client = $req->getOptionValue(DHO_HOST_NAME
());
648 $lease->{'client-hostname'} = $client ?
$client : $self->$get_mac_ip4($req);
649 $lease->{'binding state'} = 'active';
651 my $end = $start + $self->{_config
}->{$network}->{ttl
};
652 $lease->{starts
} = $self->$convert_timestamp($start, 0);
653 $lease->{ends
} = $self->$convert_timestamp($end, 0);
658 my $add_lease_ip4 = sub {
659 my ($self, $req, $network, $ip) = @_;
661 my $lease = $self->$create_new_lease_ip4($req, $network);
662 $self->$add_lease($ip, $lease);
663 $self->{lease_time
} = $DEFAULT_LEASE;
664 if ($self->{_config
}->{$network}->{ttl
}) {
665 $self->{lease_time
} = $self->{_config
}->{$network}->{ttl
};
667 $self->{lease_time_renew
} = $DEFAULT_LEASE_RENEW;
668 if ($self->{_config
}->{$network}->{rttl
}) {
669 $self->{lease_time_renew
} = $self->{_config
}->{$network}->{rttl
};
671 if ($self->{_config
}->{$network}->{netmask
}) {
672 $self->{subnet_mask
} = $self->{_config
}->{$network}->{netmask
};
674 if ($self->{_config
}->{$network}->{router
}) {
675 $self->{routers
} = $self->{_config
}->{$network}->{router
};
677 if ($self->{_config
}->{$network}->{broadcast
}) {
678 $self->{broadcast_addr
} = $self->{_config
}->{$network}->{broadcast
};
680 if ($self->{_config
}->{$network}->{'domain-name'}) {
681 $self->{domain_name
} = $self->{_config
}->{$network}->{'domain-name'};
683 if ($self->{_config
}->{$network}->{'dns-servers'}) {
684 $self->{dns_servers
} = $self->{_config
}->{$network}->{'dns-servers'};
686 if ($self->{_config
}->{$network}->{'ntp-servers'}) {
687 $self->{ntp_servers
} = $self->{_config
}->{$network}->{'ntp-servers'};
691 my $find_ip_ip4 = sub {
692 my ($self, $req, $network, $reqaddr) = @_;
693 my ($start, $end, $ip);
695 my @range_str = split(/\s+/, $self->{_config
}->{$network}->{range
});
696 $self->$logger("Range: " . $range_str[0] . " - " . $range_str[1], INFO
);
697 $start = NetAddr
::IP
->new($range_str[0].'/'.$self->{_config
}->{$network}->{netmask
});
698 $end = NetAddr
::IP
->new($range_str[1].'/'.$self->{_config
}->{$network}->{netmask
});
699 $self->$logger(Dumper
($start) . Dumper
($end));
702 my $request = NetAddr
::IP
->new($reqaddr);
703 if ($start->numeric() <= $request->numeric() && $request->numeric() <= $start->numeric()) {
704 my $cip = $request->addr();
705 $self->$logger("[find_ip_ip4] reqaddr: $reqaddr IP: $cip", INFO
);
706 if ($self->{_leases
}->{$cip}) {
707 my $lease = $self->{_leases
}->{$cip};
708 my $mac = $self->$get_mac_ip4($req);
709 if ($lease->{'hardware ethernet'} eq $mac) {
718 for (; $start <= $end; $start = $start + 1) {
719 my $cip = $start->addr();
720 $self->$logger("[find_ip_ip4] IP: $cip");
721 if ($self->{_leases
}->{$cip} && ! $free) {
722 my $lease = $self->{_leases
}->{$cip};
723 my $mac = $self->$get_mac_ip4($req);
724 if ($lease->{'hardware ethernet'} eq $mac) {
726 } elsif ($lease->{'binding state'} eq 'free') {
734 if (! $ip && $free) {
739 $self->$logger("[find_ip_ip4] IP: " . ($ip ?
$ip : 'None'), INFO
);
744 my $calculate_net_ip4 = sub {
745 my ($self, $req, $req_addr) = @_;
746 my ($network, $net, $ip);
748 $self->$logger("Req IP: " . ($req_addr ?
$req_addr : 'None'), INFO
);
749 foreach $net (keys %{$self->{_config
}}) {
750 my $opt = $self->{_config
}->{$net};
751 $self->$logger("Network: $net/$opt->{netmask}\n" . Dumper
($opt), INFO
);
752 $network = $net if ($self->$can_client_use_net_ip4($req, $net));
755 $ip = $self->$find_ip_ip4($req, $network, $req_addr);
757 $ip = $self->$find_ip_ip4($req, $network);
763 $self->$logger("Network: " . ($network ?
$network : 'None') . " IP: " . ($ip ?
$ip : 'None'), INFO
);
765 return ($network, $ip);
768 my $calculate_ip_ip4 = sub {
769 my ($self, $req, $state, $reqaddr) = @_;
772 if ($state == DHCP_OFFER
) {
774 ($network, $ip) = $self->$calculate_net_ip4($req, $reqaddr);
776 ($network, $ip) = $self->$calculate_net_ip4($req);
778 } elsif ($state == DHCP_ACK
) {
779 # If no $reqaddr then client fail
781 my $xid = $req->xid();
782 if ($self->{_transaction
}->{$xid}) {
783 my $offer = $self->{_transaction
}->{$xid}->{offer_ip
};
784 if ($offer eq $reqaddr) {
785 $network = $self->{_transaction
}->{$xid}->{network
};
786 $ip = $self->{_transaction
}->{$xid}->{offer_ip
}
788 delete($self->{_transaction
}->{$xid});
790 # No prior discovery. We maintain transaction
791 ($network, $ip) = $self->$calculate_net_ip4($req, $reqaddr);
797 return ($network, $ip);
800 my $discover_ip4 = sub {
801 my ($self, $req) = @_;
802 my ($tx, $res, $resp, $network, $calc_ip, $req_addr);
804 $self->$logger("Got ip4 discover request: \n" . $req->toString(), INFO
);
806 $res = $self->$update_transaction($req);
808 my $err = "Missing transaction ID";
809 $self->$send_nak($req, $err);
810 $self->$logger($err, ERROR
);
814 $req_addr = $req->getOptionValue(DHO_DHCP_REQUESTED_ADDRESS
());
815 $res = $self->$read_lease_file();
816 $self->$logger("Starting with empty lease file", INFO
) unless $res;
819 if ($self->{LOG_LEVEL
} <= INFO
) {
821 $self->$logger("[D] Requested IP: $req_addr", INFO
);
823 $self->$logger("[D] Requested IP: None", INFO
);
827 $tx->{req_ip
} = $req_addr ?
$req_addr : 'None';
829 ($network, $calc_ip) = $self->$calculate_ip_ip4($req, DHCP_OFFER
, $req_addr);
830 $tx->{offer_ip
} = $calc_ip ?
$calc_ip : 'None';
831 $tx->{network
} = $network ?
$network : 'None';
833 $self->$logger("Offer: $tx->{offer_ip}");
835 if ($network && $calc_ip) {
836 $self->$logger("Creating lease for $calc_ip", INFO
);
837 $res = $self->$update_transaction($req, $tx);
839 my $err = "Could not create transaction";
840 $self->$logger($err, ERROR
);
841 $self->$send_nak($req, $err);
843 $self->$add_lease_ip4($req, $network, $calc_ip);
844 $res = $self->$write_lease_file();
846 my $err = "Could not write lease file. Bailing";
847 $self->$logger($err, ERROR
);
848 my $xid = $req->xid();
849 delete($self->{_transaction
}->{$xid});
850 $self->$send_nak($req, $err);
852 $self->$send_accept($req, $calc_ip, DHCP_OFFER
);
856 # bad request, we send a NAK
857 my $err = "$req_addr: Not available";
858 $self->$logger($err, INFO
);
859 $self->$send_nak($req, $err);
862 $self->$logger("Transaction:\n".Dumper
($self->{_transaction
}), INFO
);
865 my $request_ip4 = sub {
866 my ($self, $req) = @_;
867 my ($calc_ip, $network, $res);
869 $self->$logger("Got request\n".$req->toString());
871 $res = $self->$update_transaction($req);
874 my $err = "Missing transaction ID";
875 $self->$send_nak($req, $err);
876 $self->$logger($err, ERROR
);
883 my $req_addr = $req->getOptionValue(DHO_DHCP_REQUESTED_ADDRESS
());
884 if ($self->{LOG_LEVEL
} <= INFO
) {
886 $self->$logger("[R] Requested IP: $req_addr", INFO
);
888 $self->$logger("[R] Requested IP: None", INFO
);
892 ($network, $calc_ip) = $self->$calculate_ip_ip4($req, DHCP_ACK
, $req_addr);
893 if ($network && $calc_ip) {
894 $self->$logger("Creating lease for $calc_ip", INFO
);
895 $self->$add_lease_ip4($req, $network, $calc_ip);
896 $res = $self->$write_lease_file();
898 my $err = "Could not write lease file. Bailing";
899 $self->$logger($err, ERROR
);
900 $self->$send_nak($req, $err);
902 $self->$send_accept($req, $calc_ip, DHCP_ACK
);
905 # bad request, we send a NAK
906 $self->$send_nak($req);
909 # This transaction is finished with either NAK or ACK
910 my $xid = $req->xid();
911 delete($self->{_transaction
}->{$xid});
913 $self->$logger("Transaction:\n".Dumper
($self->{_transaction
}), INFO
);
916 my $release_ip4 = sub {
917 my ($self, $req) = @_;
920 $self->$logger($req->toString());
921 $ip = $req->ciaddr();
922 $mac = $self->$get_mac_ip4($req);
923 $self->$logger("Release request for IP: $ip MAC: $mac", INFO
);
925 if ($self->{_leases
}->{$ip}) {
926 my $lease = $self->{_leases
}->{$ip};
927 if ($lease->{'hardware ethernet'} eq $mac) {
928 $self->$logger("Set binding state free IP: $ip MAC: $mac", INFO
);
929 $lease->{'binding state'} = 'free';
930 $self->$write_lease_file();
933 $self->$logger("Transaction:\n".Dumper
($self->{_transaction
}), INFO
);
936 #########################################################################
937 # Private methods which handle DHCP6 requests
938 #########################################################################
940 my $excuse_me_ip6 = sub {
941 my ($self, $addr, $req) = @_;
943 $self->$logger("IPv6 request from [$addr]: $req", INFO
);
944 my $sock = IO
::Socket
::IP
->new(
953 $self->$logger("[excuse_me_ip6] Socket creation error: $err", ERROR
);
954 die "[excuse_me_ip6] Socket creation error: $err\n";
956 $self->$logger("$addr: Not implemented here", INFO
);
957 $sock->send("Not implemented here") || die "Error sending excuse: $!\n";
961 #########################################################################
963 #########################################################################
965 # generic signal handler to cause daemon to stop
969 $SIG{INT
} = $SIG{TERM
} = $SIG{HUP
} = \
&signal_handler
;
971 # ignore any PIPE signal: standard behaviour is to quit process
972 $SIG{PIPE
} = 'IGNORE';
975 my ($class, %self) = @_;
978 $class = ref($class) || $class;
983 $self->{_sock_in_ip4
} = undef;
984 $self->{_sock_in_ip6
} = undef;
985 $self->{_leases
} = undef;
986 $self->{_reverse
} = undef;
987 $self->{_config
} = undef;
988 $self->{_dhpcp_ip4
} = undef;
989 $self->{_transaction
} = ();
992 $self->{log_file
} ||= 'syslog';
993 $self->{lease_time
} ||= $DEFAULT_LEASE;
994 $self->{lease_time_renew
} ||= $DEFAULT_LEASE_RENEW;
995 $self->{subnet_mask
} ||= undef;
996 $self->{routers
} ||= undef;
997 $self->{broadcast_addr
} ||= undef;
998 $self->{domain_name
} ||= undef;
999 $self->{dns_servers
} ||= undef;
1000 $self->{ntp_servers
} ||= undef;
1001 $self->{LOG_LEVEL
} = ERROR
unless defined $self->{LOG_LEVEL
};
1002 $self->{NODAEMON
} ||= 0;
1003 $self->{DEBUG
} ||= 0;
1004 $self->{timeout
} ||= 10;
1005 $self->{lease_file
} ||= '/tmp/dhcpd.leases';
1006 $self->{conf_file
} ||= '/tmp/dhcpd.cfg';
1013 my ($sel, @ready, $socket, $res);
1016 $self->$read_config();
1020 $self->$logger($err, ERROR
);
1023 $self->$logger("Starting dhcpd", INFO
);
1024 if ($self->{NODAEMON
} < 1) {
1025 $self->$logger("Entering Daemon mode");
1026 chdir '/' or die "Can't chdir to /: $!";
1029 open STDIN
, '/dev/null' or die "Can't read /dev/null: $!";
1030 open STDOUT
, '>/dev/null' or die "Can't write to /dev/null: $!";
1031 open STDERR
, '>/dev/null' or die "Can't write to /dev/null: $!";
1037 $self->$logger("Couldn't fork: $err", ERROR
);
1038 die "Couldn't fork: $err";
1039 } unless defined($pid);
1041 POSIX
::setsid
() || do {
1043 $self->$logger("Can't start a new session: $err", ERROR
);
1044 die "Can't start a new session: $err";
1046 $self->$logger("Now in Daemon mode", INFO
);
1049 $res = $self->$read_lease_file();
1050 $self->$logger("Starting with empty leases file '$self->{lease_file}'", INFO
)
1051 if (! $res || ! $self->{_leases
});
1053 $self->$logger("Initialization complete", INFO
);
1055 # open listening socket
1056 $self->{_sock_in_ip4
} = IO
::Socket
::IP
->new(
1059 LocalAddr
=> inet_ntoa
(INADDR_ANY
),
1063 $self->$logger("IP4 Socket creation error: $err", ERROR
);
1064 die "IP4 Socket creation error: $err\n";
1066 $self->{_sock_in_ip6
} = IO
::Socket
::IP
->new(
1074 $self->$logger("IP6 Socket creation error: $err", ERROR
);
1075 die "IP6 Socket creation error: $err\n";
1078 $sel = IO
::Select
->new($self->{_sock_in_ip4
});
1079 $sel->add($self->{_sock_in_ip6
});
1081 until ($time_to_die) {
1086 eval { # catch fatal errors
1087 while (@ready = $sel->can_read) {
1088 $self->$logger("Waiting for incoming packet", INFO
);
1089 foreach $socket (@ready) {
1090 if ($socket == $self->{_sock_in_ip4
}) {
1091 # receive ipv4 packet
1092 $fromaddr = $socket->recv($buf, 4096)
1093 || $self->$logger("recv: $!", ERROR
);
1094 next if ($!); # continue loop if an error occured
1096 $req = new Net
::DHCP
::Packet
($buf);
1100 my $xid = $req->xid();
1101 $xid = $xid ?
$xid : 'None';
1102 my ($port,$addr) = unpack_sockaddr_in
($fromaddr);
1103 my $ipaddr = inet_ntoa
($addr);
1104 $self->$logger("Got a packet tr=$xid src=$ipaddr:$port length=".length($buf), INFO
);
1107 my $messagetype = $req->getOptionValue(DHO_DHCP_MESSAGE_TYPE
());
1109 if ($messagetype eq DHCPDISCOVER
()) {
1110 $self->$discover_ip4($req);
1111 } elsif ($messagetype eq DHCPREQUEST
()) {
1112 $self->$request_ip4($req);
1113 } elsif ($messagetype eq DHCPINFORM
()) {
1114 $self->$logger("Not implemented: DHCPINFORM", WARNING
);
1115 } elsif ($messagetype eq DHCPRELEASE
()) {
1116 $self->$release_ip4($req);
1118 $self->$logger("$messagetype: Packet dropped since unknown message type", WARNING
);
1119 # bad messagetype, we drop it
1122 # Receive ipv6 packet
1123 my $myaddr = $socket->sockhost;
1125 $fromaddr = $socket->recv($buf, 4096)
1126 || $self->$logger("recv: $!", ERROR
);
1127 next if ($!); # continue loop if an error occured
1128 $self->$logger("recv: $buf", INFO
);
1131 my ($port,$addr) = unpack_sockaddr_in6
($fromaddr);
1132 my $ipaddr = inet_ntop
(AF_INET6
, $addr);
1133 $self->$logger("Got a packet tr=$self->{_transaction_ip6} src=$ipaddr:$port length=".length($buf), INFO
);
1135 $self->$excuse_me_ip6($myaddr, $buf);
1139 }; # end of 'eval' blocks
1141 $self->$logger("Caught error in main loop: $@", ERROR
);
1144 $self->{_sock_in_ip4
}->close;
1145 $self->{_sock_in_ip6
}->close;
1146 $self->$logger("Exiting dhcpd", INFO
);