Net-ISC-DHCPClient
view release on metacpan or search on metacpan
lib/Net/ISC/DHCPClient.pm view on Meta::CPAN
package Net::ISC::DHCPClient;
use 5.014;
use strict;
use warnings;
=head1 NAME
Net::ISC::DHCPClient - ISC dhclient lease reader
=head1 VERSION
Version 0.14
=cut
our $VERSION = '0.14';
use Net::ISC::DHCPClient::InetLease;
use Net::ISC::DHCPClient::Inet6Lease;
use Time::Local;
sub new {
my ($class, %opts) = @_;
die "Missing leases_path!" if (!defined($opts{leases_path}));
my $self = {};
# Incoming arguments:
$self->{INTERFACE} = defined($opts{interface}) ? $opts{interface} : undef;
$self->{leases_path} = $opts{leases_path};
# Internal storage:
$self->{leases_af_inet} = undef;
$self->{leases_af_inet6} = undef;
bless ($self, $class);
return $self;
}
sub is_dhcp($$;$)
{
my ($self, $af, $inteface_to_query) = @_;
die "Address family is: inet or inet6!" if (!($af eq 'inet' || $af eq 'inet6'));
if (defined($inteface_to_query) &&
defined($self->{INTERFACE}) &&
$self->{INTERFACE} ne $inteface_to_query) {
die sprintf("Cannot query interface %s, has %s.", $inteface_to_query, $self->{INTERFACE});
}
if (defined($self->{INTERFACE})) {
if ($af eq 'inet') {
$self->leases_af_inet();
return scalar(@{$self->{leases_af_inet}}) > 0;
}
if ($af eq 'inet6') {
$self->leases_af_inet6();
return scalar(@{$self->{leases_af_inet6}}) > 0;
}
return 0;
}
die "Need interface!" if (!defined($inteface_to_query));
# Iterate all found leases and look for given interface
my $leases_to_check;
$leases_to_check = $self->{leases_af_inet} if ($af eq 'inet');
$leases_to_check = $self->{leases_af_inet6} if ($af eq 'inet6');
for my $lease (@$leases_to_check) {
return 1 if ($lease->{INTERFACE} eq $inteface_to_query);
}
return 0;
}
sub leases_af_inet($)
{
my ($self) = @_;
return $self->{leases_af_inet} if ($self->{leases_af_inet});
$self->{leases_af_inet} = $self->_read_lease_file($self->{leases_path},
$self->{INTERFACE},
'inet');
return $self->{leases_af_inet};
}
sub leases_af_inet6($)
{
my ($self) = @_;
return $self->{leases_af_inet6} if ($self->{leases_af_inet6});
$self->{leases_af_inet6} = $self->_read_lease_file($self->{leases_path},
$self->{INTERFACE},
'inet6');
return $self->{leases_af_inet6};
}
sub _read_lease_file($$$$)
{
my ($self, $path, $interface, $af) = @_;
my @isc_lease_files;
my @netplan_lease_files;
my $leases = [];
# Search for matching .lease files
my $leasefile_re1;
my $leasefile_re2;
my $leasefile_re3;
if ($af eq 'inet') {
if ($interface) {
$leasefile_re1 = qr/^dhclient-(.*)?-($interface)\.lease$/;
$leasefile_re2 = qr/^dhclient\.($interface)\.leases$/;
$leasefile_re3 = qr/^internal-(.*)?-($interface)\.lease$/;
} else {
$leasefile_re1 = qr/^dhclient-(.*)?-(.+)\.lease$/;
$leasefile_re2 = qr/^dhclient\.(.+)\.leases$/;
$leasefile_re3 = qr/^internal-(.*)?-($interface)\.lease$/;
}
} elsif ($af eq 'inet6') {
if ($interface) {
$leasefile_re1 = qr/^dhclient6-(.*)?-($interface)\.lease$/;
$leasefile_re2 = qr/^dhclient6\.($interface)\.leases$/;
} else {
$leasefile_re1 = qr/^dhclient6-(.*)?-(.+)\.lease$/;
$leasefile_re2 = qr/^dhclient6\.(.+)\.leases$/;
}
} else {
die "Unknown AF! '$af'";
}
my @paths_to_attempt;
if (ref($path) eq "ARRAY") {
@paths_to_attempt = @$path;
} else {
@paths_to_attempt = ($path);
}
foreach my $lease_path (@paths_to_attempt) {
next if (! -d $lease_path || ! -X $lease_path);
opendir(my $dh, $lease_path) or
die "Cannot read lease directory $lease_path. Error: $!";
my @all_files = readdir($dh);
@isc_lease_files = grep { /$leasefile_re1/ && -f "$lease_path/$_" } @all_files;
@isc_lease_files = grep { /$leasefile_re2/ && -f "$lease_path/$_" } @all_files if (!@isc_lease_files);
@netplan_lease_files = grep { /$leasefile_re3/ && -f "$lease_path/$_" } @all_files if ($leasefile_re3);
closedir($dh);
if (@isc_lease_files) {
@isc_lease_files = map("$lease_path/$_", @isc_lease_files);
}
if (@netplan_lease_files) {
@netplan_lease_files = map("$lease_path/$_", @netplan_lease_files);
}
last if (@isc_lease_files || @netplan_lease_files);
}
for my $leaseFile (@isc_lease_files) {
open (LEASEFILE, $leaseFile) or
die "Cannot open leasefile $leaseFile. Error: $!";
my $currentLease;
my $hasIscLeaseData = 0;
my $ia_type = [];
while (<LEASEFILE>) {
chomp();
if (/^lease? \{/) {
$hasIscLeaseData = 1;
$currentLease = Net::ISC::DHCPClient::InetLease->new();
next;
}
if (/^lease6 \{/) {
$hasIscLeaseData = 1;
$currentLease = Net::ISC::DHCPClient::Inet6Lease->new();
next;
}
if (/^\}/) {
# dhclient will append lease information, newest is last.
# unshift() will place newest first.
unshift(@$leases, $currentLease) if ($hasIscLeaseData);
$hasIscLeaseData = 0;
next;
}
if (!$hasIscLeaseData) {
next;
}
s/^\s+//; # Eat starting whitespace
$self->_isc_af_inet_lease_parser($currentLease, $_) if ($af eq 'inet');
$self->_isc_af_inet6_lease_parser($currentLease, $ia_type, $_) if ($af eq 'inet6');
} # end while (<LEASEFILE>)
close (LEASEFILE);
}
for my $leaseFile (@netplan_lease_files) {
open (LEASEFILE, $leaseFile) or
die "Cannot open leasefile $leaseFile. Error: $!";
my $currentLease;
my $ia_type = [];
while (<LEASEFILE>) {
chomp();
next if (!/^([^=]+)=(.*)$/);
my $freshLease = 0;
if (!$currentLease) {
$currentLease = Net::ISC::DHCPClient::InetLease->new();
$freshLease = 1;
}
$self->_netplan_af_inet_lease_parser($currentLease, $1, $2) if ($af eq 'inet');
if ($freshLease) {
# Netplan lease file doesn't have interface information in
# the file. Parse the filename for interface.
if ($leaseFile =~ /-([^-.]+)\.lease$/) {
$currentLease->{INTERFACE} = $1;
}
}
} # end while (<LEASEFILE>)
close (LEASEFILE);
# There will be only 1 lease in the netplan lease file
unshift(@$leases, $currentLease) if ($currentLease);
}
return $leases;
}
sub _isc_af_inet_lease_parser($$$)
{
my ($self, $currentLease, $line) = @_;
SWITCH: {
# interface "eth1";
/^interface\s+"(.+)";/ && do {
$currentLease->{INTERFACE} = $1;
last SWITCH;
};
# fixed-address 213.28.228.27;
/^fixed-address\s+(.+);/ && do {
$currentLease->{FIXED_ADDRESS} = $1;
last SWITCH;
};
# option subnet-mask 255.255.255.0;
(/^option\s+(\S+)\s*(.+);/) && do {
$currentLease->{OPTION}{$1} = $2;
last SWITCH;
};
# renew 5 2002/12/27 06:25:31;
(m#^renew\s+(\d+)\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);#) && do {
my $leaseTime = timegm($7, $6, $5, $4, $3-1, $2);
$currentLease->{RENEW} = $leaseTime;
last SWITCH;
};
# rebind 5 2002/12/27 06:25:31;
(m#^rebind\s+(\d+)\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);#) && do {
my $leaseTime = timegm($7, $6, $5, $4, $3-1, $2);
$currentLease->{REBIND} = $leaseTime;
last SWITCH;
};
# renew 5 2002/12/27 06:25:31;
(m#^expire\s+(\d+)\s+(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+);#) && do {
my $leaseTime = timegm($7, $6, $5, $4, $3-1, $2);
$currentLease->{EXPIRE} = $leaseTime;
last SWITCH;
};
}
}
sub _isc_af_inet6_lease_parser($$$$)
{
my ($self, $currentLease, $ia_type, $line) = @_;
my $context = '';
my $addr = '';
$context = $ia_type->[0] if (defined($ia_type->[0]));
$addr = $ia_type->[1] if (defined($ia_type->[1]));
SWITCH: {
# interface "eth1";
/^interface\s+"(.+)";/ && do {
$currentLease->{INTERFACE} = $1;
last SWITCH;
};
/^ia-na\s+(\S+)\s*\{/ && do {
# Identity Association: Non-temporary Address
push(@$ia_type, 'non-temporary');
$currentLease->{IA}->{'non-temporary'} = {};
last SWITCH;
};
/^ia-pd\s+(\S+)\s*\{/ && do {
# Identity Association: Prefix Delegation
push(@$ia_type, 'prefix');
$currentLease->{IA}->{'prefix'} = {};
last SWITCH;
};
/^\}/ && do {
pop(@$ia_type);
last SWITCH;
};
# starts 1517742816;
# Note: either IA or address
/^(starts)\s+(\d+);/ && do {
if (defined($ia_type->[1])) {
$currentLease->{IA}->{$context}->{$addr}->{$1} = $2;
} else {
$currentLease->{IA}->{$context}->{$1} = $2;
}
last SWITCH;
};
# renew 302400;
/^(renew)\s+(\d+);/ && do {
$currentLease->{IA}->{$context}->{$1} = $2;
last SWITCH;
};
# rebind 483840;
/^(rebind)\s+(\d+);/ && do {
$currentLease->{IA}->{$context}->{$1} = $2;
last SWITCH;
};
# preferred-life 604800;
/^(preferred-life)\s+(\d+);/ && do {
$currentLease->{IA}->{$context}->{$addr}->{$1} = $2;
last SWITCH;
};
# max-life 2592000;
/^(max-life)\s+(\d+);/ && do {
$currentLease->{IA}->{$context}->{$addr}->{$1} = $2;
last SWITCH;
};
( run in 0.777 second using v1.01-cache-2.11-cpan-39bf76dae61 )