+ my $resp = new Net::DHCP::Packet(
+ Comment => $req->comment(),
+ Op => BOOTREPLY(),
+ Hops => $req->hops(),
+ Xid => $req->xid(),
+ Flags => $req->flags(),
+ Ciaddr => $req->ciaddr(),
+ Yiaddr => $calc_ip,
+ Siaddr => $req->siaddr(),
+ Giaddr => $req->giaddr(),
+ Chaddr => $req->chaddr(),
+ DHO_DHCP_MESSAGE_TYPE() => $reply,
+ DHO_DHCP_SERVER_IDENTIFIER() => $sock->sockhost
+ );
+ $self->$add_options_ip4($resp);
+ my $xid = $req->xid();
+ $self->{_transaction}->{$xid}->{me} = $sock->sockhost;
+
+ $self->$logger("Sending $msg to " . $sock->peerhost . ':' . $sock->peerport, INFO);
+ $self->$logger($resp->toString());
+
+ $self->$logger("Sending OFFER tr=".$req->xid(), INFO);
+
+ $sock->send($resp->serialize()) || die "Error sending $msg: $!\n";
+ $sock->close;
+};
+
+my $update_transaction = sub {
+ my ($self, $req, $tx) = @_;
+ my ($res, $xid, $offer);
+
+ $xid = $req->xid();
+ return -1 unless $xid;
+
+ if ($tx) {
+ $self->{_transaction}->{$xid} = $tx;
+ $res = 0;
+ } else {
+ if ($self->{_transaction}->{$xid} && $self->{_transaction}->{$xid}->{me}) {
+ my $me = $req->getOptionValue(DHO_DHCP_SERVER_IDENTIFIER());
+ $me = $req->ciaddr() unless $me;
+ $offer = $self->{_transaction}->{$xid}->{offer_ip};
+ if ($me) {
+ if ($me ne $self->{_transaction}->{$xid}->{me}) {
+ # Another DHCP server is chosen by client
+ $self->$logger("$me: Offer '".($offer? $offer : 'None')."' refused by client xid=$xid", INFO);
+ delete($self->{_transaction}->{$xid});
+ delete($self->{_leases}->{$offer}) if $offer;
+ $self->$write_lease_file();
+ $res = 1;
+ } else {
+ $self->$logger("Offer '$offer' accepted by client xid=$xid", INFO);
+ $res = 0;
+ }
+ } else {
+ # Caught request for other DHCP server
+ }
+ } else {
+ if ($self->{_transaction}->{$xid}) {
+ $offer = $self->{_transaction}->{$xid}->{offer_ip};
+ $self->$logger("Offer '$offer' wait approval from client xid=$xid", INFO);
+ $res = 0;
+ }
+ }
+ }
+
+ return $res;
+};
+
+my $create_new_lease_ip4 = sub {
+ my ($self, $req, $network) = @_;
+ my $lease;
+
+ $lease->{'hardware ethernet'} = $self->$get_mac_ip4($req);
+ my $client = $req->getOptionValue(DHO_HOST_NAME());
+ $lease->{'client-hostname'} = $client ? $client : $self->$get_mac_ip4($req);
+ $lease->{'binding state'} = 'active';
+ my $start = time;
+ my $end = $start + $self->{_config}->{$network}->{ttl};
+ $lease->{starts} = $self->$convert_timestamp($start, 0);
+ $lease->{ends} = $self->$convert_timestamp($end, 0);
+
+ return $lease;
+};
+
+my $add_lease_ip4 = sub {
+ my ($self, $req, $network, $ip) = @_;
+
+ my $lease = $self->$create_new_lease_ip4($req, $network);
+ $self->$add_lease($ip, $lease);
+ $self->{lease_time} = $DEFAULT_LEASE;
+ if ($self->{_config}->{$network}->{ttl}) {
+ $self->{lease_time} = $self->{_config}->{$network}->{ttl};
+ }
+ $self->{lease_time_renew} = $DEFAULT_LEASE_RENEW;
+ if ($self->{_config}->{$network}->{rttl}) {
+ $self->{lease_time_renew} = $self->{_config}->{$network}->{rttl};
+ }
+ if ($self->{_config}->{$network}->{netmask}) {
+ $self->{subnet_mask} = $self->{_config}->{$network}->{netmask};
+ }
+ if ($self->{_config}->{$network}->{router}) {
+ $self->{routers} = $self->{_config}->{$network}->{router};
+ }
+ if ($self->{_config}->{$network}->{broadcast}) {
+ $self->{broadcast_addr} = $self->{_config}->{$network}->{broadcast};
+ }
+ if ($self->{_config}->{$network}->{'domain-name'}) {
+ $self->{domain_name} = $self->{_config}->{$network}->{'domain-name'};
+ }
+ if ($self->{_config}->{$network}->{'dns-servers'}) {
+ $self->{dns_servers} = $self->{_config}->{$network}->{'dns-servers'};
+ }
+ if ($self->{_config}->{$network}->{'ntp-servers'}) {
+ $self->{ntp_servers} = $self->{_config}->{$network}->{'ntp-servers'};
+ }
+};
+
+my $find_ip_ip4 = sub {
+ my ($self, $req, $network, $reqaddr) = @_;
+ my ($start, $end, $ip);
+
+ my @range_str = split(/\s+/, $self->{_config}->{$network}->{range});
+ $self->$logger("Range: " . $range_str[0] . " - " . $range_str[1], INFO);
+ $start = NetAddr::IP->new($range_str[0].'/'.$self->{_config}->{$network}->{netmask});
+ $end = NetAddr::IP->new($range_str[1].'/'.$self->{_config}->{$network}->{netmask});
+ $self->$logger(Dumper($start) . Dumper($end));
+
+ if ($reqaddr) {
+ my $request = NetAddr::IP->new($reqaddr);
+ if ($start->numeric() <= $request->numeric() && $request->numeric() <= $start->numeric()) {
+ my $cip = $request->addr();
+ $self->$logger("[find_ip_ip4] reqaddr: $reqaddr IP: $cip", INFO);
+ if ($self->{_leases}->{$cip}) {
+ my $lease = $self->{_leases}->{$cip};
+ my $mac = $self->$get_mac_ip4($req);
+ if ($lease->{'hardware ethernet'} eq $mac) {
+ $ip = $cip;
+ }
+ } else {
+ $ip = $cip;
+ }
+ }
+ } else {
+ my $free = undef;
+ for (; $start <= $end; $start = $start + 1) {
+ my $cip = $start->addr();
+ $self->$logger("[find_ip_ip4] IP: $cip");
+ if ($self->{_leases}->{$cip} && ! $free) {
+ my $lease = $self->{_leases}->{$cip};
+ my $mac = $self->$get_mac_ip4($req);
+ if ($lease->{'hardware ethernet'} eq $mac) {
+ $ip = $cip;
+ } elsif ($lease->{'binding state'} eq 'free') {
+ $free = $cip;
+ }
+ } else {
+ $ip = $cip;
+ }
+ last if $ip;
+ }
+ if (! $ip && $free) {
+ $ip = $free;
+ }
+ }
+
+ $self->$logger("[find_ip_ip4] IP: " . ($ip ? $ip : 'None'), INFO);
+
+ return $ip;
+};
+
+my $calculate_net_ip4 = sub {
+ my ($self, $req, $req_addr) = @_;
+ my ($network, $net, $ip);
+
+ $self->$logger("Req IP: " . ($req_addr ? $req_addr : 'None'), INFO);
+ foreach $net (keys %{$self->{_config}}) {
+ my $opt = $self->{_config}->{$net};
+ $self->$logger("Network: $net/$opt->{netmask}\n" . Dumper($opt), INFO);
+ $network = $net if ($self->$can_client_use_net_ip4($req, $net));
+ if ($network) {
+ if ($req_addr) {
+ $ip = $self->$find_ip_ip4($req, $network, $req_addr);
+ } else {
+ $ip = $self->$find_ip_ip4($req, $network);
+ }
+ last if $ip;
+ $network = undef;
+ }
+ }
+ $self->$logger("Network: " . ($network ? $network : 'None') . " IP: " . ($ip ? $ip : 'None'), INFO);
+
+ return ($network, $ip);
+};
+
+my $calculate_ip_ip4 = sub {
+ my ($self, $req, $state, $reqaddr) = @_;
+ my ($network, $ip);
+
+ if ($state == DHCP_OFFER) {
+ if ($reqaddr) {
+ ($network, $ip) = $self->$calculate_net_ip4($req, $reqaddr);
+ } else {
+ ($network, $ip) = $self->$calculate_net_ip4($req);
+ }
+ } elsif ($state == DHCP_ACK) {
+ # If no $reqaddr then client fail
+ if ($reqaddr) {
+ my $xid = $req->xid();
+ if ($self->{_transaction}->{$xid}) {
+ my $offer = $self->{_transaction}->{$xid}->{offer_ip};
+ if ($offer eq $reqaddr) {
+ $network = $self->{_transaction}->{$xid}->{network};
+ $ip = $self->{_transaction}->{$xid}->{offer_ip}
+ }
+ delete($self->{_transaction}->{$xid});
+ } else {
+ # No prior discovery. We maintain transaction
+ ($network, $ip) = $self->$calculate_net_ip4($req, $reqaddr);
+ }
+ }
+ } else {
+ }
+
+ return ($network, $ip);
+};
+
+my $discover_ip4 = sub {
+ my ($self, $req) = @_;
+ my ($tx, $res, $resp, $network, $calc_ip, $req_addr);
+
+ $self->$logger("Got ip4 discover request: \n" . $req->toString(), INFO);
+
+ $res = $self->$update_transaction($req);
+ if ($res) {
+ my $err = "Missing transaction ID";
+ $self->$send_nak($req, $err);
+ $self->$logger($err, ERROR);
+ die $err;
+ }
+
+ $req_addr = $req->getOptionValue(DHO_DHCP_REQUESTED_ADDRESS());
+ $res = $self->$read_lease_file();
+ $self->$logger("Starting with empty lease file", INFO) unless $res;
+
+
+ if ($self->{LOG_LEVEL} <= INFO) {
+ if ($req_addr) {
+ $self->$logger("[D] Requested IP: $req_addr", INFO);
+ } else {
+ $self->$logger("[D] Requested IP: None", INFO);
+ }
+ }
+
+ $tx->{req_ip} = $req_addr ? $req_addr : 'None';
+
+ ($network, $calc_ip) = $self->$calculate_ip_ip4($req, DHCP_OFFER, $req_addr);
+ $tx->{offer_ip} = $calc_ip ? $calc_ip : 'None';
+ $tx->{network} = $network ? $network : 'None';
+
+ $self->$logger("Offer: $tx->{offer_ip}");
+
+ if ($network && $calc_ip) {
+ $self->$logger("Creating lease for $calc_ip", INFO);
+ $res = $self->$update_transaction($req, $tx);
+ if ($res) {
+ my $err = "Could not create transaction";
+ $self->$logger($err, ERROR);
+ $self->$send_nak($req, $err);
+ } else {
+ $self->$add_lease_ip4($req, $network, $calc_ip);
+ $res = $self->$write_lease_file();
+ if (! $res) {
+ my $err = "Could not write lease file. Bailing";
+ $self->$logger($err, ERROR);
+ my $xid = $req->xid();
+ delete($self->{_transaction}->{$xid});
+ $self->$send_nak($req, $err);
+ } else {
+ $self->$send_accept($req, $calc_ip, DHCP_OFFER);
+ }
+ }