mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-05-18 04:07:16 +00:00
bb8dd525da
Previously, makedhcp warned but still created host entries without a static IP reservation when a node's address fell inside the dynamic range. The node would silently get a random IP from the pool instead of its configured address. Now errors and skips the node on all four DHCP paths (ISC v4/v6, Kea v4/v6) with a clear message telling the admin to move the IP outside the range or adjust the dynamic range. This makes ISC DHCP and Kea behavior consistent and aligns with xCAT's design: the dynamic range is for hardware discovery, known nodes should have static IPs outside it. Closes #6539
4319 lines
151 KiB
Perl
4319 lines
151 KiB
Perl
# IBM(c) 2010 EPL license http://www.eclipse.org/legal/epl-v10.html
|
|
package xCAT_plugin::dhcp;
|
|
|
|
BEGIN
|
|
{
|
|
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
|
|
}
|
|
use lib "$::XCATROOT/lib/perl";
|
|
|
|
use strict;
|
|
use IPC::Open2;
|
|
use IPC::Open3;
|
|
use IO::Select;
|
|
use Symbol qw/gensym/;
|
|
use POSIX qw/WNOHANG/;
|
|
use xCAT::Table;
|
|
|
|
#use Data::Dumper;
|
|
use MIME::Base64;
|
|
use JSON ();
|
|
use Getopt::Long;
|
|
Getopt::Long::Configure("bundling");
|
|
Getopt::Long::Configure("pass_through");
|
|
use Socket;
|
|
my $candoipv6 = eval {
|
|
require Socket6;
|
|
1;
|
|
};
|
|
use Sys::Syslog;
|
|
use IPC::Open2;
|
|
use xCAT::Utils;
|
|
use xCAT::DHCP::BootPolicy;
|
|
use xCAT::DHCP::Backend;
|
|
use xCAT::DHCP::Range;
|
|
use xCAT::TableUtils;
|
|
use xCAT::NetworkUtils qw/getipaddr/;
|
|
use xCAT::ServiceNodeUtils;
|
|
use xCAT::NodeRange;
|
|
use Fcntl ':flock';
|
|
|
|
my @aixcfg; # hold AIX entries created by NIM
|
|
my @dhcpconf; #Hold DHCP config file contents to be written back.
|
|
my @dhcp6conf; #ipv6 equivalent
|
|
my @nrn; # To hold output of networks table to be consulted throughout process
|
|
my @nrn6; #holds ip -6 route output on Linux, yeah, name doesn't make much sense now..
|
|
my $site_domain;
|
|
my @alldomains;
|
|
my $omshell;
|
|
my $omshell6; #separate session to DHCPv6 instance of dhcp
|
|
my $statements; #Hold custom statements to be slipped into host declarations
|
|
my $localonly; # flag for running only on local server - needs to be global
|
|
my $callback;
|
|
my $restartdhcp;
|
|
my $restartdhcp6;
|
|
my $sitenameservers;
|
|
my $sitentpservers;
|
|
my $sitelogservers;
|
|
my $nrhash;
|
|
my $machash;
|
|
my $vpdhash;
|
|
my $iscsients;
|
|
my $nodetypeents;
|
|
my $chainents;
|
|
my $tftpdir = xCAT::TableUtils->getTftpDir();
|
|
my $dhcpconffile = $^O eq 'aix' ? '/etc/dhcpsd.cnf' : '/etc/dhcpd.conf';
|
|
my %dynamicranges; #track dynamic ranges defined to see if a host that resolves is actually a dynamic address
|
|
my %netcfgs;
|
|
my $distro = xCAT::Utils->osver();
|
|
my $checkdomain=0;
|
|
|
|
# dhcp 4.x will use /etc/dhcp/dhcpd.conf as the config file
|
|
my $dhcp6conffile;
|
|
if ($^O ne 'aix' and not -e $dhcpconffile and -d "/etc/dhcp") {
|
|
$dhcpconffile = '/etc/dhcp/dhcpd.conf';
|
|
$dhcp6conffile = '/etc/dhcp/dhcpd6.conf';
|
|
}
|
|
my $usingipv6;
|
|
|
|
# define usage statement
|
|
my $usage = "Usage: makedhcp -n\n\tmakedhcp -a\n\tmakedhcp -a -d\n\tmakedhcp -d noderange\n\tmakedhcp <noderange> [-s statements]\n\tmakedhcp -q\n\tmakedhcp [-h|--help]";
|
|
|
|
|
|
# is this ubuntu ?
|
|
if ($distro =~ /ubuntu.*/) {
|
|
if (-e '/etc/dhcp/') {
|
|
$dhcpconffile = '/etc/dhcp/dhcpd.conf';
|
|
}
|
|
else {
|
|
$dhcpconffile = '/etc/dhcp3/dhcpd.conf';
|
|
}
|
|
}
|
|
|
|
sub check_uefi_support {
|
|
my $ntent = shift;
|
|
my %blacklist = (
|
|
"win2k3.*" => 1,
|
|
"winxp.*" => 1,
|
|
"SL5.*" => 1,
|
|
"rhels5.*" => 1,
|
|
"centos5.*" => 1,
|
|
"sl5.*" => 1,
|
|
"sles10.*" => 1,
|
|
"esxi4.*" => 1);
|
|
if ($ntent and $ntent->{os}) {
|
|
foreach (keys %blacklist) {
|
|
if ($ntent->{os} =~ /$_/) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
if ($ntent->{os} =~ /^win/ or $ntent->{os} =~ /^hyperv/) { #UEFI support is a tad different, need to punt..
|
|
return 2;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
# check whether the proxydhcp has been enabled.
|
|
sub proxydhcp {
|
|
my $nrent = shift;
|
|
|
|
if ($nrent && defined $nrent->{'proxydhcp'} && $nrent->{'proxydhcp'} =~ /0|no|n/i) {
|
|
return 0;
|
|
}
|
|
my @output = xCAT::Utils->runcmd("ps -C proxydhcp-xcat", -1);
|
|
if (@output) {
|
|
if (grep /proxydhcp-xcat/, @output) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
sub ipIsDynamic {
|
|
|
|
#meant to be v4/v6 agnostic. DHCPv6 however takes some care to allow a dynamic range to overlap static reservations
|
|
#xCAT will for now continue to advise people to keep their nodes out of the dynamic range
|
|
my $ip = shift;
|
|
my $number = getipaddr($ip, GetNumber => 1);
|
|
unless ($number) { # shouldn't be possible, but pessimistically presume it dynamically if so
|
|
return 1;
|
|
}
|
|
foreach (values %dynamicranges) {
|
|
if ($_->[0] <= $number and $_->[1] >= $number) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0; #it isn't in any of the dynamic ranges we are aware of
|
|
}
|
|
|
|
sub handled_commands
|
|
{
|
|
return { makedhcp => "dhcp", };
|
|
}
|
|
|
|
######################################################
|
|
# Run omshell to query a host and parse the output.
|
|
# Uses a heredoc pipe instead of open2 to avoid deadlock
|
|
# when omshell produces output before all input is consumed.
|
|
######################################################
|
|
sub _omshell_query_host
|
|
{
|
|
my $node = shift;
|
|
my $omapiuser = shift;
|
|
my $omapikey = shift;
|
|
my $port = shift;
|
|
|
|
my $port_cmd = defined($port) ? "port $port\n" : "";
|
|
my $omcmds = "${port_cmd}key $omapiuser \"$omapikey\"\nconnect\nnew host\nset name = \"$node\"\nopen\nclose\n";
|
|
|
|
my @output = _run_omshell($omcmds);
|
|
|
|
return _parse_omshell_host_output($node, @output);
|
|
}
|
|
|
|
sub _run_omshell
|
|
{
|
|
my $omcmds = shift;
|
|
|
|
my ($in, $out);
|
|
my $err = gensym;
|
|
my $pid = eval { open3($in, $out, $err, '/usr/bin/omshell') };
|
|
return () if $@ || !$pid;
|
|
|
|
print $in $omcmds;
|
|
close($in);
|
|
|
|
my @output;
|
|
my $selector = IO::Select->new($out, $err);
|
|
my $deadline = time + 10;
|
|
while ($selector->count) {
|
|
my $remaining = $deadline - time;
|
|
if ($remaining <= 0) {
|
|
kill 'TERM', $pid;
|
|
last;
|
|
}
|
|
|
|
foreach my $fh ($selector->can_read($remaining)) {
|
|
my $line = <$fh>;
|
|
if (defined $line) {
|
|
push @output, $line if fileno($fh) == fileno($out);
|
|
} else {
|
|
$selector->remove($fh);
|
|
close($fh);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (1 .. 10) {
|
|
last if waitpid($pid, WNOHANG) == $pid;
|
|
select(undef, undef, undef, 0.1);
|
|
}
|
|
if (waitpid($pid, WNOHANG) == 0) {
|
|
kill 'KILL', $pid;
|
|
waitpid($pid, 0);
|
|
}
|
|
|
|
return @output;
|
|
}
|
|
|
|
sub _parse_omshell_host_output
|
|
{
|
|
my ($node, @output) = @_;
|
|
|
|
my ($nname, $ipaddr, $hwaddr);
|
|
foreach my $line (@output) {
|
|
chomp $line;
|
|
if ($line =~ /^\s*name\s*=\s*"?(.*?)"?\s*$/) {
|
|
$nname = $1 if $1 eq $node;
|
|
} elsif ($line =~ /^\s*hardware-address\s*=/) {
|
|
$hwaddr = $line;
|
|
} elsif ($line =~ /^\s*ip-address\s*=\s*(.+?)\s*$/) {
|
|
my $ip = $1;
|
|
if ($ip =~ /^(?:[[:xdigit:]]{1,2}:){3}[[:xdigit:]]{1,2}$/) {
|
|
my @parts = split(/\:/, $ip);
|
|
$ipaddr = "ip-address = " . join(".", map { hex($_) } @parts);
|
|
} else {
|
|
$ipaddr = "ip-address = $ip";
|
|
}
|
|
}
|
|
}
|
|
|
|
$nname = $node if !$nname && $ipaddr;
|
|
return ($nname, $ipaddr, $hwaddr);
|
|
}
|
|
|
|
######################################################
|
|
# List nodes in DHCP for both IPv4 and IPv6
|
|
######################################################
|
|
sub listnode
|
|
{
|
|
my $node = shift;
|
|
my $callback = shift;
|
|
my $rsp;
|
|
|
|
my $omapiuser;
|
|
my $omapikey;
|
|
|
|
my $pwtab = xCAT::Table->new("passwd");
|
|
my @pws = $pwtab->getAllAttribs('key', 'username', 'password', 'cryptmethod', 'authdomain', 'comments', 'disable');
|
|
foreach (@pws) {
|
|
if ($_->{key} =~ "omapi") {
|
|
$omapiuser = $_->{username};
|
|
$omapikey = $_->{password};
|
|
}
|
|
}
|
|
|
|
my $usingipv6;
|
|
my $nettab = xCAT::Table->new("networks");
|
|
my @vnets = $nettab->getAllAttribs('net', 'mgtifname', 'mask', 'dynamicrange', 'nameservers', 'ddnsdomain', 'domain');
|
|
foreach (@vnets) {
|
|
if ($_->{net} =~ /:/) {
|
|
$usingipv6 = 1;
|
|
}
|
|
}
|
|
|
|
my ($nname, $ipaddr, $hwaddr) = _omshell_query_host($node, $omapiuser, $omapikey, undef);
|
|
if ($ipaddr) {
|
|
push @{ $rsp->{data} }, "$nname: $ipaddr, $hwaddr";
|
|
xCAT::MsgUtils->message("I", $rsp, $callback);
|
|
}
|
|
|
|
if ($usingipv6) {
|
|
my ($nname6, $ipaddr6, $hwaddr6) = _omshell_query_host($node, $omapiuser, $omapikey, 7912);
|
|
if ($ipaddr6) {
|
|
push @{ $rsp->{data} }, "$nname6: $ipaddr6, $hwaddr6";
|
|
xCAT::MsgUtils->message("I", $rsp, $callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
sub delnode
|
|
{
|
|
my $node = shift;
|
|
my $inetn = inet_aton($node);
|
|
|
|
my $mactab = xCAT::Table->new('mac');
|
|
my $ent;
|
|
if ($machash) { $ent = $machash->{$node}->[0]; }
|
|
if ($ent and $ent->{mac})
|
|
{
|
|
my @macs = split(/\|/, $ent->{mac});
|
|
my $mace;
|
|
my $count = 0;
|
|
foreach $mace (@macs)
|
|
{
|
|
my $mac;
|
|
my $hname;
|
|
($mac, $hname) = split(/!/, $mace);
|
|
|
|
unless ($hname)
|
|
{
|
|
$hname = $node;
|
|
} #Default to hostname equal to nodename
|
|
unless ($mac) { next; } #Skip corrupt format
|
|
|
|
if (!grep /:/, $mac) {
|
|
$mac = lc($mac);
|
|
$mac =~ s/(\w{2})/$1:/g;
|
|
$mac =~ s/:$//;
|
|
}
|
|
my $hostname = $hname;
|
|
my %client_nethash = xCAT::DBobjUtils->getNetwkInfo([$node]);
|
|
if ($client_nethash{$node}{mgtifname} =~ /hf/)
|
|
{
|
|
if (scalar(@macs) > 1) {
|
|
if ($hname !~ /^(.*)-hf(.*)$/) {
|
|
$hostname = $hname . "-hf" . $count;
|
|
} else {
|
|
$hostname = $1 . "-hf" . $count;
|
|
}
|
|
}
|
|
}
|
|
$count = $count + 2;
|
|
|
|
unless ($hostname) { $hostname = $node; }
|
|
print $omshell "new host\n";
|
|
print $omshell
|
|
"set name = \"$hostname\"\n"; #Find and destroy conflict name
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
|
|
if ($mac)
|
|
{
|
|
print $omshell "new host\n";
|
|
print $omshell "set hardware-address = " . $mac
|
|
. "\n"; #find and destroy mac conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
}
|
|
if ($inetn)
|
|
{
|
|
my $ip;
|
|
if (inet_aton($hostname))
|
|
{
|
|
$ip = inet_ntoa(inet_aton($hostname));
|
|
}
|
|
if ($ip)
|
|
{
|
|
print $omshell "new host\n";
|
|
print $omshell
|
|
"set ip-address = $ip\n"; #find and destroy ip conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
print $omshell "new host\n";
|
|
print $omshell "set name = \"$node\"\n"; #Find and destroy conflict name
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
if ($inetn)
|
|
{
|
|
my $ip = inet_ntoa(inet_aton($node));
|
|
unless ($ip) { return; }
|
|
print $omshell "new host\n";
|
|
print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
}
|
|
}
|
|
|
|
sub addnode6 {
|
|
|
|
#omshell to add host dynamically
|
|
my $node = shift;
|
|
unless ($vpdhash) {
|
|
$callback->({ node => [ { name => [$node], warning => ["Skipping DHCPv6 setup due to missing vpd.uuid information."] } ] });
|
|
return;
|
|
}
|
|
my $ent = $vpdhash->{$node}->[0]; #tab->getNodeAttribs($node, [qw(mac)]);
|
|
unless ($ent and $ent->{uuid})
|
|
{
|
|
$callback->({ node => [ { name => [$node], warning => ["Skipping DHCPv6 setup due to missing vpd.uuid information."] } ] });
|
|
return;
|
|
}
|
|
|
|
#phase 1, dynamic and static addresses, hopefully ddns-hostname works, may be tricky to do 'send hostname'
|
|
#since FQDN is the only thing to be sent down, and that RFC clearly suggests that the client
|
|
#assembles that data, not host
|
|
#tricky for us since the client wouldn't know it's hostname/fqdn in advance
|
|
#unless acquired via IPv4 first
|
|
#don't think dhclient is smart enough to assemble advertised domain with it's own name and then
|
|
#request FQDN update
|
|
#goal is simple enough, we want `hostname` to look sane *and* we want DNS to look right
|
|
my $uuid = $ent->{uuid};
|
|
$uuid =~ s/-//g;
|
|
$uuid =~ s/(..)/$1:/g;
|
|
$uuid =~ s/:\z//;
|
|
$uuid =~ s/^/00:04:/;
|
|
my $ip = getipaddr($node);
|
|
if ($ip and $ip =~ /:/ and ipIsDynamic($ip)) {
|
|
$callback->({ error => ["Node $node has IPv6 address $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table."], errorcode => [1] });
|
|
return;
|
|
}
|
|
if ($ip and $ip =~ /:/) {
|
|
$ip = getipaddr($ip, GetNumber => 1);
|
|
$ip = $ip->as_hex;
|
|
$ip =~ s/^0x//;
|
|
$ip =~ s/(..)/$1:/g;
|
|
$ip =~ s/:\z//;
|
|
print $omshell6 "set ip-address = $ip\n";
|
|
} else {
|
|
$ip = 0;
|
|
}
|
|
print $omshell6 "new host\n";
|
|
print $omshell6 "set name = \"$node\"\n"; #Find and destroy conflict name
|
|
print $omshell6 "open\n";
|
|
print $omshell6 "remove\n";
|
|
print $omshell6 "close\n";
|
|
if ($ip) {
|
|
print $omshell6 "new host\n";
|
|
print $omshell6 "set ip-address = $ip\n"; #find and destroy ip conflict
|
|
print $omshell6 "open\n";
|
|
print $omshell6 "remove\n";
|
|
print $omshell6 "close\n";
|
|
}
|
|
print $omshell6 "new host\n";
|
|
print $omshell6 "set dhcp-client-identifier = " . $uuid . "\n"; #find and destroy DUID-UUID conflict
|
|
print $omshell6 "open\n";
|
|
print $omshell6 "remove\n";
|
|
print $omshell6 "close\n";
|
|
print $omshell6 "new host\n";
|
|
print $omshell6 "set name = \"$node\"\n";
|
|
print $omshell6 "set dhcp-client-identifier = $uuid\n";
|
|
print $omshell6 'set statements = "ddns-hostname \"' . $node . '\";";' . "\n";
|
|
|
|
if ($ip) {
|
|
print $omshell6 "set ip-address = $ip\n";
|
|
}
|
|
print $omshell6 "create\n";
|
|
print $omshell6 "close\n";
|
|
|
|
}
|
|
|
|
sub addnode
|
|
{
|
|
|
|
#Use omshell to add the node.
|
|
#the process used is blind typing commands that should work
|
|
#it tries to delet any conflicting entries matched by name and
|
|
#hardware address and ip address before creating a brand now one
|
|
#unfortunate side effect: dhcpd.leases can look ugly over time, when
|
|
#doing updates would keep it cleaner, good news, dhcpd restart cleans
|
|
#up the lease file the way we would want anyway.
|
|
my $node = shift;
|
|
my $ent;
|
|
my $nrent;
|
|
my $chainent;
|
|
my $ient;
|
|
my $ntent;
|
|
my $tftpserver;
|
|
|
|
my $httpport="80";
|
|
my @hports=xCAT::TableUtils->get_site_attribute("httpport");
|
|
if ($hports[0]){
|
|
$httpport=$hports[0];
|
|
}
|
|
|
|
if ($chainents and $chainents->{$node}) {
|
|
$chainent = $chainents->{$node}->[0];
|
|
}
|
|
if ($iscsients and $iscsients->{$node}) {
|
|
$ient = $iscsients->{$node}->[0];
|
|
}
|
|
if ($nodetypeents and $nodetypeents->{$node}) {
|
|
$ntent = $nodetypeents->{$node}->[0];
|
|
}
|
|
my $lstatements = $statements;
|
|
my $guess_next_server = 0;
|
|
my $nxtsrv;
|
|
if ($nrhash)
|
|
{
|
|
$nrent = $nrhash->{$node}->[0];
|
|
if ($nrent and $nrent->{tftpserver} and $nrent->{tftpserver} ne '<xcatmaster>')
|
|
{
|
|
#check the value of inet_ntoa(inet_aton("")),if the hostname cannot be resolved,
|
|
#the value of inet_ntoa() will be "undef", which will cause fatal error
|
|
my $tmp_name = inet_aton($nrent->{tftpserver});
|
|
unless ($tmp_name) {
|
|
|
|
#tell the reason to the user
|
|
$callback->(
|
|
{ error => ["Unable to resolve the tftpserver for node"], errorcode => [1] }
|
|
);
|
|
return;
|
|
}
|
|
$tftpserver = inet_ntoa($tmp_name);
|
|
$nxtsrv = $tftpserver;
|
|
$lstatements =
|
|
'next-server '
|
|
. $tftpserver . ';'
|
|
. $statements;
|
|
}
|
|
else
|
|
{
|
|
$guess_next_server = 1;
|
|
}
|
|
if ($nrent->{netboot} and ($nrent->{netboot} eq 'petitboot' or $nrent->{netboot} eq 'onie' )) {
|
|
if ($guess_next_server) {
|
|
my $node_server = undef;
|
|
if ($nrent->{xcatmaster}) {
|
|
$node_server = $nrent->{xcatmaster};
|
|
}
|
|
unless ($node_server) {
|
|
my @nxtsrvd = xCAT::NetworkUtils->my_ip_facing($node);
|
|
unless ($nxtsrvd[0]) { $nxtsrv = $nxtsrvd[1]; }
|
|
elsif ($nxtsrvd[0] == 1) { $callback->({ error => [ $nxtsrvd[1] ] }); }
|
|
else {
|
|
$callback->({ error => ["Unable to determine the tftpserver for $node, verify \"xcatmaster\" is set correctly"], errorcode => [1] });
|
|
return;
|
|
}
|
|
} else {
|
|
my $tmp_server = inet_aton($node_server);
|
|
unless ($tmp_server) {
|
|
$callback->({ error => ["Unable to resolve the tftpserver for $node, verify \"xcatmaster\" is set correctly"], errorcode => [1] });
|
|
return;
|
|
}
|
|
$nxtsrv = inet_ntoa($tmp_server);
|
|
}
|
|
unless ($nxtsrv) {
|
|
$callback->({ error => ["Unable to determine the tftpserver for $node, verify \"xcatmaster\" is set correctly"], errorcode => [1] });
|
|
return;
|
|
}
|
|
$guess_next_server = 0;
|
|
}
|
|
}
|
|
|
|
#else {
|
|
# $nrent = $nrtab->getNodeAttribs($node,['servicenode']);
|
|
# if ($nrent and $nrent->{servicenode}) {
|
|
# $statements = 'next-server = \"'.inet_ntoa(inet_aton($nrent->{servicenode})).'\";'.$statements;
|
|
# }
|
|
#}
|
|
}
|
|
else
|
|
{
|
|
$guess_next_server = 1;
|
|
}
|
|
unless ($machash)
|
|
{
|
|
$callback->(
|
|
{
|
|
warning => ["Unable to open mac table, it may not exist yet"]
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
$ent = $machash->{$node}->[0]; #tab->getNodeAttribs($node, [qw(mac)]);
|
|
unless ($ent and $ent->{mac})
|
|
{
|
|
$callback->(
|
|
{
|
|
warning => ["Unable to find mac address for $node"]
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
|
|
my @macs = split(/\|/, $ent->{mac});
|
|
my $mace;
|
|
my $deflstaments = $lstatements;
|
|
my $count = 0;
|
|
foreach $mace (@macs)
|
|
{
|
|
$lstatements = $deflstaments; #force recalc on every entry
|
|
my $mac;
|
|
my $hname;
|
|
$hname = "";
|
|
($mac, $hname) = split(/!/, $mace);
|
|
unless ($hname)
|
|
{
|
|
$hname = $node;
|
|
} #Default to hostname equal to nodename
|
|
unless ($mac) { next; } #Skip corrupt format
|
|
if ($mac !~ /^[0-9a-fA-F]{2}(-[0-9a-fA-F]{2}){5,8}$|^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5,8}$/)
|
|
{
|
|
$callback->(
|
|
{
|
|
error => ["Invalid mac address $mac for $node"],
|
|
errorcode => [1]
|
|
}
|
|
);
|
|
next;
|
|
}
|
|
my $ip = getipaddr($hname, OnlyV4 => 1);
|
|
if ($hname eq '*NOIP*') {
|
|
$hname = $node . "-noip" . $mac;
|
|
$hname =~ s/://g;
|
|
$ip = 'DENIED';
|
|
|
|
# } #if 'guess_next_server', inherit from the network provided value... see how this pans out
|
|
# if ($guess_next_server and $ip and $ip ne "DENIED")
|
|
# {
|
|
# $nxtsrv = xCAT::NetworkUtils->my_ip_facing($hname);
|
|
# if ($nxtsrv)
|
|
# {
|
|
# $tftpserver = $nxtsrv;
|
|
# $lstatements = "next-server $nxtsrv;$statements";
|
|
# } #of course, we set the xNBA variable to let that propogation carry forward into filename uri interpolation
|
|
} elsif ($guess_next_server) {
|
|
$nxtsrv = '${next-server}'; #if floating IP support, cause gPXE command-line expansion patch to drive inheritence from network
|
|
}
|
|
|
|
# The hostname could not be resolved, print a warning message
|
|
if (!$ip)
|
|
{
|
|
$callback->(
|
|
{
|
|
warning => [
|
|
"The hostname $hname of node $node could not be resolved."
|
|
]
|
|
}
|
|
);
|
|
}
|
|
|
|
if ($ip and $ip ne 'DENIED' and ipIsDynamic($ip))
|
|
{
|
|
$callback->(
|
|
{
|
|
error => [
|
|
"Node $node has IP $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table."
|
|
],
|
|
errorcode => [1]
|
|
}
|
|
);
|
|
next;
|
|
}
|
|
my $doiscsi = 0;
|
|
if ($ient and $ient->{server} and $ient->{target}) {
|
|
$doiscsi = 1;
|
|
unless (defined($ient->{lun})) { #Some firmware fails to properly implement the spec, so we must explicitly say zero for such firmware
|
|
$ient->{lun} = 0;
|
|
}
|
|
my $iscsirootpath = 'iscsi:' . $ient->{server} . ':6:3260:' . $ient->{lun} . ':' . $ient->{target};
|
|
if (defined($ient->{iname})) { #Attempt to use gPXE or IBM iSCSI formats to specify the initiator
|
|
#This all goes on one line, but will break it out to at least be readable in here
|
|
$lstatements = 'if option vendor-class-identifier = \"ISAN\" { ' #This is declared by IBM iSCSI initiators, will call it 'ISAN' mode
|
|
. 'option isan.iqn \"' . $ient->{iname} . '\"; ' #Use vendor-spcefic option to declare the expected Initiator name
|
|
. 'option isan.root-path \"' . $iscsirootpath . '\"; ' #We must *not* use standard root-path if using ISAN style options
|
|
. '} else { '
|
|
. 'option root-path \"' . $iscsirootpath . '\"; ' #For everything but ISAN, use standard, RFC defined behavior for root
|
|
. 'if exists gpxe.bus-id { ' #Since our iscsi-initiator-iqn is in no way a standardized thing, only use it for gPXE
|
|
. ' option iscsi-initiator-iqn \"' . $ient->{iname} . '\";' #gPXE will consider option 203 for initiator IQN
|
|
. '}'
|
|
. '}'
|
|
. $lstatements;
|
|
print $lstatements;
|
|
} else { #We stick to the good old RFC defined behavior, ISAN, gPXE, everyone should be content with this so long as no initiator name need be specified
|
|
$lstatements = 'option root-path \"' . $iscsirootpath . '\";' . $lstatements;
|
|
}
|
|
}
|
|
my $douefi = check_uefi_support($ntent);
|
|
if ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'xnba' and $lstatements !~ /filename/) {
|
|
if (-f "$tftpdir/xcat/xnba.kpxe") {
|
|
if ($doiscsi and $chainent and $chainent->{currstate} and ($chainent->{currstate} eq 'iscsiboot' or $chainent->{currstate} eq 'boot')) {
|
|
$lstatements = 'if option client-architecture = 00:00 and not exists gpxe.bus-id { filename = \"xcat/xnba.kpxe\"; } else { filename = \"\"; } ' . $lstatements;
|
|
} else {
|
|
|
|
# If proxydhcp daemon is enabled for windows deployment, do vendor-class-identifier of "PXEClient" to bump it over to proxydhcp.c
|
|
if (($douefi == 2 and $chainent->{currstate} =~ /^install/) or $chainent->{currstate} =~ /^winshell/) {
|
|
if (proxydhcp($nrent)) { #proxy dhcp required in uefi invocation
|
|
$lstatements = 'if option client-architecture = 00:00 or option client-architecture = 00:07 or option client-architecture = 00:09 { filename = \"\"; option vendor-class-identifier \"PXEClient\"; } else { filename = \"\"; }' . $lstatements; #If proxydhcp daemon is enable, use it.
|
|
} else {
|
|
$lstatements = 'if option user-class-identifier = \"xNBA\" and option client-architecture = 00:00 { always-broadcast on; filename = \"http://' . $nxtsrv . ':' . $httpport . '/tftpboot/xcat/xnba/nodes/' . $node . '\"; } else if option client-architecture = 00:07 or option client-architecture = 00:09 { filename = \"\"; option vendor-class-identifier \"PXEClient\"; } else if option client-architecture = 00:00 { filename = \"xcat/xnba.kpxe\"; } else { filename = \"\"; }' . $lstatements; #Only PXE compliant clients should ever receive xNBA
|
|
}
|
|
} elsif ($douefi and $chainent->{currstate} ne "boot" and $chainent->{currstate} ne "iscsiboot") {
|
|
$lstatements = 'if option user-class-identifier = \"xNBA\" and option client-architecture = 00:00 { always-broadcast on; filename = \"http://' . $nxtsrv . ':' . $httpport . '/tftpboot/xcat/xnba/nodes/' . $node . '\"; } else if option user-class-identifier = \"xNBA\" and option client-architecture = 00:09 { filename = \"http://' . $nxtsrv .':' . $httpport . '/tftpboot/xcat/xnba/nodes/' . $node . '.uefi\"; } else if option client-architecture = 00:07 { filename = \"xcat/xnba.efi\"; } else if option client-architecture = 00:00 { filename = \"xcat/xnba.kpxe\"; } else { filename = \"\"; }' . $lstatements; #Only PXE compliant clients should ever receive xNBA
|
|
} else {
|
|
$lstatements = 'if option user-class-identifier = \"xNBA\" and option client-architecture = 00:00 { filename = \"http://' . $nxtsrv .':' . $httpport . '/tftpboot/xcat/xnba/nodes/' . $node . '\"; } else if option client-architecture = 00:00 { filename = \"xcat/xnba.kpxe\"; } else { filename = \"\"; }' . $lstatements; #Only PXE compliant clients should ever receive xNBA
|
|
}
|
|
}
|
|
} #TODO: warn when windows
|
|
} elsif ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'pxe' and $lstatements !~ /filename/) {
|
|
if (-f "$tftpdir/xcat/xnba.kpxe") {
|
|
if ($doiscsi and $chainent and $chainent->{currstate} and ($chainent->{currstate} eq 'iscsiboot' or $chainent->{currstate} eq 'boot')) {
|
|
$lstatements = 'if exists gpxe.bus-id { filename = \"\"; } else if exists client-architecture { filename = \"xcat/xnba.kpxe\"; } ' . $lstatements;
|
|
} else {
|
|
$lstatements = 'if option vendor-class-identifier = \"ScaleMP\" { filename = \"vsmp/pxelinux.0\"; } else { filename = \"pxelinux.0\"; }' . $lstatements;
|
|
}
|
|
}
|
|
} elsif ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'yaboot') {
|
|
$lstatements = 'filename = \"/yb/node/yaboot-' . $node . '\";' . $lstatements;
|
|
} elsif ($nrent and $nrent->{netboot} and $nrent->{netboot} =~ /^grub2[-]?.*$/) {
|
|
$lstatements = 'filename = \"/boot/grub2/grub2-' . $node . '\";' . $lstatements;
|
|
} elsif ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'petitboot') {
|
|
$lstatements = 'option conf-file \"http://' . $nxtsrv .':' . $httpport . '/tftpboot/petitboot/' . $node . '\";' . $lstatements;
|
|
} elsif ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'onie') {
|
|
my $provmethod = $ntent->{provmethod};
|
|
if ($provmethod) {
|
|
my $linuximagetab = xCAT::Table->new('linuximage');
|
|
my $imagetab = $linuximagetab->getAttribs({ imagename => $provmethod }, 'pkgdir');
|
|
if ($imagetab) {
|
|
my $image_pkgdir = $imagetab->{'pkgdir'};
|
|
my @pkgdirs = split(/,/,$image_pkgdir);
|
|
my $validpkgdir;
|
|
foreach my $mypkgdir (@pkgdirs){
|
|
if (-f $mypkgdir) {
|
|
$lstatements = 'if substring (option vendor-class-identifier,0,11) = \"onie_vendor\" { option www-server = \"http://' . $nxtsrv .':' . $httpport . $mypkgdir . '\";}' . $lstatements;
|
|
$validpkgdir = 1;
|
|
last;
|
|
}
|
|
}
|
|
unless ($validpkgdir) {
|
|
$callback->({ warning => ["osimage $provmethod pkgdir doesn't exists"]});
|
|
}
|
|
} else {
|
|
$callback->({ warning => ["osimage $provmethod is not defined in the osimage table"]});
|
|
}
|
|
} else {
|
|
$callback->({ warning => ["provmethod is not defined for $node"]});
|
|
}
|
|
|
|
} elsif ($nrent and $nrent->{netboot} and $nrent->{netboot} eq 'nimol') {
|
|
$lstatements = 'supersede server.filename=\"/vios/nodes/' . $node . '\";' . $lstatements;
|
|
}
|
|
|
|
|
|
if ($^O eq 'aix')
|
|
{
|
|
addnode_aix($ip, $mac, $hname, $tftpserver);
|
|
}
|
|
else
|
|
{
|
|
if (!grep /:/, $mac) {
|
|
$mac = lc($mac);
|
|
$mac =~ s/(\w{2})/$1:/g;
|
|
$mac =~ s/:$//;
|
|
}
|
|
my $hostname = $hname;
|
|
my $hardwaretype = 1;
|
|
my %client_nethash = xCAT::DBobjUtils->getNetwkInfo([$node]);
|
|
if ($client_nethash{$node}{mgtifname} =~ /hf/)
|
|
{
|
|
$hardwaretype = 37;
|
|
if (scalar(@macs) > 1) {
|
|
if ($hname !~ /^(.*)-hf(.*)$/) {
|
|
$hostname = $hname . "-hf" . $count;
|
|
} else {
|
|
$hostname = $1 . "-hf" . $count;
|
|
}
|
|
}
|
|
} elsif (length($mac) == 23 || length($mac) == 26) { # 8 or 9 bytes of mac address
|
|
# Currently the only thing that has 8 or 9 bytes is an infiniband
|
|
# or infiniband like device, which is type 32 (0x20).
|
|
$hardwaretype = 32;
|
|
}
|
|
|
|
#syslog("local4|err", "Setting $node ($hname|$ip) to " . $mac);
|
|
print $omshell "new host\n";
|
|
print $omshell
|
|
"set name = \"$hostname\"\n"; #Find and destroy conflict name
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
if ($ip and $ip ne 'DENIED') {
|
|
print $omshell "new host\n";
|
|
print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
}
|
|
print $omshell "new host\n";
|
|
print $omshell "set hardware-address = " . $mac
|
|
. "\n"; #find and destroy mac conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
print $omshell "new host\n";
|
|
print $omshell "set name = \"$hostname\"\n";
|
|
print $omshell "set hardware-address = " . $mac . "\n";
|
|
print $omshell "set dhcp-client-identifier = " . $mac . "\n";
|
|
print $omshell "set hardware-type = $hardwaretype\n";
|
|
|
|
if ($ip eq "DENIED")
|
|
{ #Blacklist this mac to preclude confusion, give best shot at things working
|
|
print $omshell "set statements = \"deny booting;\"\n";
|
|
}
|
|
else
|
|
{
|
|
if ($ip) {
|
|
print $omshell "set ip-address = $ip\n";
|
|
}
|
|
if ($lstatements)
|
|
{
|
|
$lstatements = 'ddns-hostname \"' . $node . '\"; send host-name \"' . $node . '\";' . $lstatements;
|
|
|
|
} else {
|
|
$lstatements = 'ddns-hostname \"' . $node . '\"; send host-name \"' . $node . '\";';
|
|
}
|
|
print $omshell "set statements = \"$lstatements\"\n";
|
|
}
|
|
|
|
print $omshell "create\n";
|
|
print $omshell "close\n";
|
|
unless ($::XCATSITEVALS{externaldhcpservers}) {
|
|
unless (grep /#definition for host $node aka host $hostname/, @dhcpconf)
|
|
{
|
|
push @dhcpconf,
|
|
"#definition for host $node aka host $hostname can be found in the dhcpd.leases file (typically /var/lib/dhcpd/dhcpd.leases)\n";
|
|
}
|
|
}
|
|
}
|
|
$count = $count + 2;
|
|
}
|
|
}
|
|
|
|
sub addrangedetection {
|
|
my $net = shift;
|
|
my $tranges = $net->{dynamicrange}; #temp range, the dollar sign makes it look strange
|
|
my $myip;
|
|
my @myipd = xCAT::NetworkUtils->my_ip_facing($net->{net});
|
|
unless ($myipd[0]) { $myip = $myipd[1]; }
|
|
|
|
# convert <xcatmaster> to nameserver IP
|
|
if ($net->{nameservers} eq '<xcatmaster>')
|
|
{
|
|
$netcfgs{ $net->{net} }->{nameservers} = $myip;
|
|
}
|
|
else
|
|
{
|
|
$netcfgs{ $net->{net} }->{nameservers} = $net->{nameservers};
|
|
}
|
|
$netcfgs{ $net->{net} }->{ddnsdomain} = $net->{ddnsdomain};
|
|
$netcfgs{ $net->{net} }->{domain} = $net->{domain};
|
|
|
|
unless ($netcfgs{ $net->{net} }->{nameservers}) {
|
|
|
|
# convert <xcatmaster> to nameserver IP
|
|
if ($::XCATSITEVALS{nameservers} eq '<xcatmaster>')
|
|
{
|
|
$netcfgs{ $net->{net} }->{nameservers} = $myip;
|
|
}
|
|
else
|
|
{
|
|
$netcfgs{ $net->{net} }->{nameservers} = $::XCATSITEVALS{nameservers};
|
|
}
|
|
}
|
|
foreach my $range_entry ( @{ xCAT::DHCP::Range->parse_dynamic_ranges($tranges) } ) {
|
|
my $isc_range = xCAT::DHCP::Range->isc_range($range_entry);
|
|
$netcfgs{ $net->{net} }->{range} = $isc_range;
|
|
|
|
my ( $begin_number, $end_number ) = xCAT::DHCP::Range->bounds($range_entry);
|
|
$dynamicranges{$isc_range} = [ $begin_number, $end_number ] if defined($begin_number) && defined($end_number);
|
|
}
|
|
}
|
|
######################################################
|
|
# Add nodes into dhcpsd.cnf. For AIX only
|
|
######################################################
|
|
sub addnode_aix
|
|
{
|
|
my $ip = shift;
|
|
my $mac = shift;
|
|
my $hname = shift;
|
|
my $tftpserver = shift;
|
|
|
|
$restartdhcp = 1;
|
|
|
|
# Format the mac address to aix
|
|
$mac =~ s/://g;
|
|
$mac = lc($mac);
|
|
|
|
delnode_aix($hname);
|
|
|
|
#Find the location to insert node
|
|
my $isSubnetFound = 0;
|
|
my $i;
|
|
my $netmask;
|
|
for ($i = 0 ; $i < scalar(@dhcpconf) ; $i++)
|
|
{
|
|
if ($dhcpconf[$i] =~ /([\d\.]+)\/(\d+) ip configuration end/)
|
|
{
|
|
if (xCAT::NetworkUtils::isInSameSubnet($ip, $1, $2, 1))
|
|
{
|
|
$isSubnetFound = 1;
|
|
$netmask = $2;
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Format the netmask from AIX format (24) to Linux format (255.255.255.0)
|
|
my $netmask_linux = xCAT::NetworkUtils::formatNetmask($netmask, 1, 0);
|
|
|
|
# Create node section
|
|
my @node_section = ();
|
|
push @node_section, " client 1 $mac $ip #node $hname start\n";
|
|
push @node_section, " {\n";
|
|
push @node_section, " option 1 $netmask_linux\n";
|
|
push @node_section, " option 12 $hname\n";
|
|
|
|
# push @node_section, " option sa $tftpserver\n";
|
|
# push @node_section, " option bf \"/tftpboot/$hname\"\n";
|
|
push @node_section, " } # node $hname end\n";
|
|
|
|
|
|
if ($isSubnetFound)
|
|
{
|
|
splice @dhcpconf, $i, 0, @node_section;
|
|
}
|
|
}
|
|
|
|
###################################################
|
|
# Delete nodes in dhcpsd.cnf. For AIX only
|
|
###################################################
|
|
sub delnode_aix
|
|
{
|
|
my $hname = shift;
|
|
my $i;
|
|
my $node_start = 0;
|
|
my $node_end = 0;
|
|
for ($i = 0 ; $i < scalar(@dhcpconf) ; $i++)
|
|
{
|
|
if ($dhcpconf[$i] =~ /node $hname start/)
|
|
{
|
|
$node_start = $i;
|
|
}
|
|
elsif ($dhcpconf[$i] =~ /node $hname end/)
|
|
{
|
|
$node_end = $i;
|
|
last;
|
|
}
|
|
}
|
|
if ($node_start && $node_end)
|
|
{
|
|
$restartdhcp = 1;
|
|
splice @dhcpconf, $node_start, ($node_end - $node_start + 1);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
############################################################
|
|
# check_options will process the options for makedhcp and
|
|
# give a usage error for any invalid options
|
|
############################################################
|
|
sub check_options
|
|
{
|
|
my $req = shift;
|
|
my $opt = shift;
|
|
my $callback = shift;
|
|
my $rc = 0;
|
|
|
|
# Exit if the packet has been preprocessed
|
|
# Comment this line to make sure check_options can be processed on service node.
|
|
if ($req->{_xcatpreprocessed}->[0] == 1) { return 0; }
|
|
|
|
# display the usage if -h
|
|
if ($opt->{h})
|
|
{
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = $usage;
|
|
xCAT::MsgUtils->message("I", $rsp, $callback, 0);
|
|
return 0;
|
|
}
|
|
|
|
# check to see if -q is listed with any other options which is not allowed
|
|
if ($opt->{q} and ($opt->{a} || $opt->{d} || $opt->{n} || $opt->{r} || $opt->{l} || $statements)) {
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = "The -q option cannot be used with other options.";
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return 1;
|
|
}
|
|
|
|
# check to see if -n is listed with any other options which is not allowed
|
|
if ($opt->{n} and ($opt->{a} || $opt->{d} || $opt->{q} || $opt->{r} || $opt->{l} || $statements)) {
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = "The -n option cannot be used with other options.";
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return 1;
|
|
}
|
|
|
|
|
|
unless (($req->{arg} and (@{ $req->{arg} } > 0)) or $req->{node})
|
|
{
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = $usage;
|
|
xCAT::MsgUtils->message("I", $rsp, $callback, 1);
|
|
return;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
############################################################
|
|
# preprocess_request will perform syntax checking and do basic precess checking
|
|
############################################################
|
|
sub preprocess_request
|
|
{
|
|
my $req = shift;
|
|
my $callback = shift;
|
|
my $rc = 0;
|
|
|
|
#>>>>>>>used for trace log>>>>>>>
|
|
my $verbose_on_off = 0;
|
|
|
|
Getopt::Long::Configure("bundling");
|
|
$Getopt::Long::ignorecase = 0;
|
|
Getopt::Long::Configure("no_pass_through");
|
|
|
|
# Exit if the packet has been preprocessed
|
|
if ($req->{_xcatpreprocessed}->[0] == 1) { return [$req]; }
|
|
|
|
# Save the arguements in ARGV for GetOptions
|
|
if ($req && $req->{arg}) { @ARGV = @{ $req->{arg} }; }
|
|
else { @ARGV = (); }
|
|
|
|
my %opt;
|
|
|
|
# Parse the options for makedhcp
|
|
if (!GetOptions(
|
|
'h|help' => \$opt{h},
|
|
'a' => \$opt{a},
|
|
'd' => \$opt{d},
|
|
'l|localonly' => \$localonly,
|
|
'n' => \$opt{n},
|
|
'r' => \$opt{r},
|
|
's=s' => \$statements, # $statements is declared globally
|
|
'q' => \$opt{q},
|
|
'V' => \$opt{V} #>>>>>>>used for trace log>>>>>>>
|
|
))
|
|
{
|
|
# If the arguements do not pass GetOptions then issue error message and return
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = $usage;
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return 1;
|
|
}
|
|
|
|
#>>>>>>>used for trace log>>>>>>>
|
|
if ($opt{V}) { $verbose_on_off = 1; }
|
|
|
|
# check the syntax
|
|
$rc = check_options($req, \%opt, $callback);
|
|
if ($rc) {
|
|
xCAT::MsgUtils->trace($verbose_on_off, "e", "dhcp: command syntax error");
|
|
return [];
|
|
}
|
|
|
|
my $snonly = 0;
|
|
my @entries = xCAT::TableUtils->get_site_attribute("disjointdhcps");
|
|
my $t_entry = $entries[0];
|
|
if (defined($t_entry)) {
|
|
$snonly = $t_entry;
|
|
}
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: disjointdhcps=$t_entry");
|
|
my @requests = ();
|
|
my $hasHierarchy = 0;
|
|
my @snlist = xCAT::ServiceNodeUtils->getSNList('dhcpserver');
|
|
if (@snlist > 0) { $hasHierarchy = 1; }
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: hasHierarchy=$hasHierarchy");
|
|
|
|
my @dhcpsvrs = ();
|
|
if ($hasHierarchy) {
|
|
#hierarchy detected, enforce more rigorous sanity
|
|
my $ntab = xCAT::Table->new('networks');
|
|
if ($ntab) {
|
|
foreach (@{ $ntab->getAllEntries() }) {
|
|
next unless ($_->{dynamicrange});
|
|
# if dynamicrange specified but dhcpserver was not - issue error message
|
|
unless ($_->{dhcpserver}) {
|
|
$callback->({ error => [ "Hierarchy requested, therefore networks.dhcpserver must be set for net=" . $_->{net} . "" ], errorcode => [1] });
|
|
return [];
|
|
}
|
|
push @dhcpsvrs, $_->{dhcpserver} if (xCAT::NetworkUtils->nodeonmynet($_->{dhcpserver}));
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: dhcp server on $_->{net}: $_->{dhcpserver}");
|
|
}
|
|
}
|
|
} elsif ($snonly == 1) {
|
|
$snonly = 0;
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: disjointdhcps mode is disabled as no service nodes are running dhcp service.");
|
|
}
|
|
|
|
my @nodes = ();
|
|
|
|
# if the new option is not specified
|
|
if (!$opt{n}) {
|
|
|
|
# save the node names specified
|
|
if ($req->{node}) {
|
|
@nodes = @{ $req->{node} };
|
|
}
|
|
|
|
# if option all
|
|
elsif ($opt{a}) {
|
|
|
|
# if option delete - Delete all node entries, that were added by xCAT, from the DHCP server configuration.
|
|
if ($opt{d})
|
|
{
|
|
my $nodelist = xCAT::Table->new('nodelist');
|
|
my @entries = ($nodelist->getAllNodeAttribs([qw(node)]));
|
|
foreach (@entries)
|
|
{
|
|
push @nodes, $_->{node};
|
|
}
|
|
}
|
|
|
|
# Delete not specified so only add - Define all nodes to the DHCP server
|
|
else
|
|
{
|
|
my $mactab = xCAT::Table->new('mac');
|
|
my @entries = ();
|
|
if ($mactab) {
|
|
@entries = ($mactab->getAllNodeAttribs([qw(mac)]));
|
|
}
|
|
foreach (@entries)
|
|
{
|
|
push @nodes, $_->{node};
|
|
}
|
|
}
|
|
} # end - if -a
|
|
|
|
# don't put compute node entries in for AIX nodes
|
|
# this is handled by NIM - duplicate entires will cause
|
|
# an error
|
|
if ($^O eq 'aix') {
|
|
my @tmplist;
|
|
my $Imsg;
|
|
foreach my $n (@nodes)
|
|
{
|
|
# get the nodetype for each node
|
|
my $ntable = xCAT::Table->new('nodetype');
|
|
if ($ntable) {
|
|
my $mytype = $ntable->getNodeAttribs($n, ['nodetype']);
|
|
if ($mytype->{nodetype} =~ /osi/) {
|
|
$Imsg++;
|
|
}
|
|
|
|
# if its aix and not "osi" then add it to the list of nodes
|
|
unless ($mytype->{nodetype} =~ /osi/) {
|
|
push @tmplist, $n;
|
|
}
|
|
}
|
|
}
|
|
|
|
# replace nodes with the tmplist of nodes that are not osi nodetype
|
|
@nodes = @tmplist;
|
|
|
|
# if any nodes were found with a ndoetype of osi - issue message that they are handled by NIM
|
|
if ($Imsg) {
|
|
my $rsp;
|
|
push @{ $rsp->{data} }, "AIX nodes with a nodetype of \'osi\' will not be added to the dhcp configuration file. This is handled by NIM.\n";
|
|
xCAT::MsgUtils->message("I", $rsp, $callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
#>>>>>>>used for trace log>>>>>>>
|
|
my $str_node = join(" ", @nodes);
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: nodes are $str_node");
|
|
|
|
my $issn = xCAT::Utils->isServiceNode();
|
|
#check if this node is the service node for any input node
|
|
my @hostinfo = xCAT::NetworkUtils->determinehostname();
|
|
my %iphash = ();
|
|
|
|
# flag the hostnames in iphash
|
|
foreach (@hostinfo) { $iphash{$_} = 1; }
|
|
|
|
# If service node and not -n option
|
|
if (($snonly == 1) && (!$opt{n})) {
|
|
|
|
# if a list of nodes are specified
|
|
if (@nodes > 0) {
|
|
|
|
# get the hash of service nodes
|
|
my $sn_hash = xCAT::ServiceNodeUtils->getSNformattedhash(\@nodes, "xcat", "MN");
|
|
|
|
# if processing only on the local host
|
|
if ($localonly) {
|
|
|
|
# compare the service node hash with the iphash - a match adds this service node
|
|
foreach (keys %$sn_hash) {
|
|
if (exists($iphash{$_})) {
|
|
my $reqcopy = {%$req};
|
|
$reqcopy->{'node'} = $sn_hash->{$_};
|
|
$reqcopy->{'_xcatdest'} = $_;
|
|
$reqcopy->{_xcatpreprocessed}->[0] = 1;
|
|
push @requests, $reqcopy;
|
|
last;
|
|
}
|
|
}
|
|
} else {
|
|
my $handled4me = 0; # indicate myself is already handled.
|
|
my %prehandledhash = ();# the servers which is already handled.
|
|
foreach (@dhcpsvrs) {
|
|
# It is required to handle the whole noderange for the dhcp server which serving dynamic range
|
|
if (! exists($iphash{$_})) {
|
|
my $reqcopy = {%$req};
|
|
$reqcopy->{'node'} = \@nodes;
|
|
$reqcopy->{'_xcatdest'} = $_;
|
|
$reqcopy->{_xcatpreprocessed}->[0] = 1;
|
|
push @requests, $reqcopy;
|
|
$prehandledhash{$_} = 1;
|
|
} elsif ($handled4me == 0) {
|
|
my $reqcopy = {%$req};
|
|
$reqcopy->{'node'} = \@nodes;
|
|
$reqcopy->{_xcatpreprocessed}->[0] = 1;
|
|
push @requests, $reqcopy;
|
|
$handled4me = 1;
|
|
}
|
|
}
|
|
|
|
# create a request for each service node
|
|
foreach (keys %$sn_hash) {
|
|
# to check if the SN already handled
|
|
next if (exists($prehandledhash{$_}));
|
|
if (! exists($iphash{$_})) {
|
|
my $reqcopy = {%$req};
|
|
$reqcopy->{'node'} = $sn_hash->{$_};
|
|
$reqcopy->{'_xcatdest'} = $_;
|
|
$reqcopy->{_xcatpreprocessed}->[0] = 1;
|
|
push @requests, $reqcopy;
|
|
} elsif ($handled4me == 0) {
|
|
my $reqcopy = {%$req};
|
|
$reqcopy->{'node'} = $sn_hash->{$_};
|
|
$reqcopy->{_xcatpreprocessed}->[0] = 1;
|
|
push @requests, $reqcopy;
|
|
$handled4me = 1;
|
|
}
|
|
}
|
|
}
|
|
} # list of nodes specified
|
|
# if new specified or there are nodes
|
|
} # end if service node only and NOT -n option
|
|
# if -n option or nodes were specified
|
|
elsif (@nodes > 0 or $opt{n}) { #send the request to every dhservers
|
|
$req->{'node'} = \@nodes;
|
|
$req->{_xcatpreprocessed}->[0] = 1;
|
|
@requests = ({%$req}); #Start with a straight copy to reflect local instance
|
|
|
|
# if not localonly - get list of service nodes and create requests
|
|
unless ($localonly || $hasHierarchy == 0) {
|
|
|
|
# If running on SN, get site.master from DB and dispatch to it as MN will not be in SN list.
|
|
if ( $issn ) {
|
|
my @entries = xCAT::TableUtils->get_site_attribute("master");
|
|
my $reqcopy = {%$req};
|
|
$reqcopy->{'_xcatdest'} = $entries[0];
|
|
push @requests, $reqcopy;
|
|
}
|
|
|
|
foreach my $s (@snlist)
|
|
{
|
|
if (scalar @nodes == 1 and $nodes[0] eq $s) { next; }
|
|
next if ($issn && exists($iphash{$s}));
|
|
|
|
my $reqcopy = {%$req};
|
|
$reqcopy->{'_xcatdest'} = $s;
|
|
push @requests, $reqcopy;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#print Dumper(@requests);
|
|
return \@requests;
|
|
|
|
}
|
|
|
|
#############################################################################
|
|
# process_request will perform syntax checking and do basic process checkingi
|
|
# and call other functions to complete the request to add or delete entries
|
|
#############################################################################
|
|
sub process_request
|
|
{
|
|
my $req = shift;
|
|
$callback = shift;
|
|
my $oldmask = umask 0077;
|
|
$restartdhcp = 0;
|
|
my $rsp;
|
|
|
|
#print Dumper($req);
|
|
|
|
#>>>>>>>used for trace log>>>>>>>
|
|
my $verbose_on_off = 0;
|
|
|
|
Getopt::Long::Configure("bundling");
|
|
$Getopt::Long::ignorecase = 0;
|
|
Getopt::Long::Configure("no_pass_through");
|
|
|
|
# Save the arguements in ARGV for GetOptions
|
|
if ($req && $req->{arg}) { @ARGV = @{ $req->{arg} }; }
|
|
else { @ARGV = (); }
|
|
|
|
my %opt;
|
|
|
|
# Parse the options for makedhcp
|
|
if (!GetOptions(
|
|
'h|help' => \$opt{h},
|
|
'a' => \$opt{a},
|
|
'd' => \$opt{d},
|
|
'l|localonly' => \$localonly,
|
|
'n' => \$opt{n},
|
|
'r' => \$opt{r},
|
|
's=s' => \$statements, # $statements is declared globally
|
|
'q' => \$opt{q},
|
|
'V' => \$opt{V} #>>>>>>>used for trace log>>>>>>>
|
|
))
|
|
{
|
|
# If the arguements do not pass GetOptions then issue error message and return
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = $usage;
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return 1;
|
|
}
|
|
|
|
#>>>>>>>used for trace log>>>>>>>
|
|
if ($opt{V}) { $verbose_on_off = 1; }
|
|
|
|
# Check options again in case we are called from plugin and options have not been processed
|
|
my $rc = 0;
|
|
$rc = check_options($req, \%opt, $callback);
|
|
|
|
if ($rc) {
|
|
xCAT::MsgUtils->trace($verbose_on_off, "e", "dhcp: there is invalid option in command");
|
|
return [];
|
|
}
|
|
|
|
my $backend = xCAT::DHCP::Backend->new_backend(check_available => 1);
|
|
if ( ref($backend) eq 'HASH' && $backend->{error} ) {
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = $backend->{error};
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return;
|
|
}
|
|
if ( $backend->name eq 'kea' && $statements ) {
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = "The -s option contains ISC DHCP statement text and is not supported with the Kea DHCP backend.";
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return;
|
|
}
|
|
|
|
# if current node is a servicenode, make sure that it is also a dhcpserver
|
|
my $isok = 1;
|
|
if (xCAT::Utils->isServiceNode()) {
|
|
$isok = 0;
|
|
my @hostinfo = xCAT::NetworkUtils->determinehostname();
|
|
my %iphash = ();
|
|
foreach (@hostinfo) { $iphash{$_} = 1; }
|
|
my @sn = xCAT::ServiceNodeUtils->getSNList('dhcpserver');
|
|
foreach my $s (@sn) {
|
|
if (exists($iphash{$s})) {
|
|
$isok = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($isok == 0) { #do nothing if it is a service node, but not dhcpserver
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: it is a service node, but not dhcpserver. Do nothing");
|
|
print "Do nothing\n";
|
|
return;
|
|
}
|
|
|
|
# if not -n, dhcp service needs to be running
|
|
if (!($opt{n})) {
|
|
if (xCAT::Utils->isLinux()) {
|
|
my $ret = 0;
|
|
my $service_error = "dhcp server is not running. please start the dhcp server.";
|
|
if ( $backend->name eq 'kea' ) {
|
|
my $status = $backend->check_services();
|
|
$ret = $status->{error} ? 1 : 0;
|
|
$service_error = $status->{error} if $status->{error};
|
|
} else {
|
|
$ret = xCAT::Utils->checkservicestatus("dhcp");
|
|
}
|
|
if ($ret != 0) {
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = $service_error;
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return;
|
|
}
|
|
} else { # AIX
|
|
my @output = xCAT::Utils->runcmd("lssrc -s dhcpsd ", -1);
|
|
if ($::RUNCMD_RC != 0) { # not running
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = "dhcpsd is not running. Run startsrc -s dhcpsd and rerun your command.";
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return;
|
|
} else { # check the status
|
|
# the return output varies, sometime status is the third sometimes the 4th col
|
|
if (grep /inoperative/, @output)
|
|
{
|
|
my $rsp = {};
|
|
$rsp->{data}->[0] = "dhcpsd is not running. Run startsrc -s dhcpsd and rerun your command.";
|
|
xCAT::MsgUtils->message("E", $rsp, $callback, 1);
|
|
return;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# if option is query then call listnode for each node and return
|
|
if ($opt{q})
|
|
{
|
|
if ( $backend->name eq 'kea' ) {
|
|
kea_process_request($backend, $req, \%opt, {}, $verbose_on_off);
|
|
return;
|
|
}
|
|
|
|
# call listnode for each node requested
|
|
foreach my $node (@{ $req->{node} }) {
|
|
listnode($node, $callback);
|
|
}
|
|
return;
|
|
}
|
|
|
|
my $servicenodetab = xCAT::Table->new('servicenode');
|
|
my @nodeinfo = xCAT::NetworkUtils->determinehostname;
|
|
my $nodename = pop @nodeinfo; # get hostname
|
|
my $dhcpinterfaces = $servicenodetab->getNodeAttribs($nodename, ['dhcpinterfaces']);
|
|
|
|
my %activenics;
|
|
my $querynics = 1;
|
|
|
|
if (xCAT::Utils->isServiceNode() and $dhcpinterfaces and $dhcpinterfaces->{dhcpinterfaces}) {
|
|
# The keyword 'noboot' was appended to the NICs that doesn't need to reply DHCP configuration file, only used for mknb at present.
|
|
$dhcpinterfaces->{dhcpinterfaces} =~ s/:noboot//g;
|
|
my @dhcpifs = split ',', $dhcpinterfaces->{dhcpinterfaces};
|
|
foreach my $nic (@dhcpifs) {
|
|
$activenics{$nic} = 1;
|
|
$querynics = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
my @entries = xCAT::TableUtils->get_site_attribute("dhcpinterfaces");
|
|
my $t_entry = $entries[0];
|
|
unless (defined($t_entry))
|
|
{ #LEGACY: singular keyname for old style site value
|
|
@entries = xCAT::TableUtils->get_site_attribute("dhcpinterface");
|
|
$t_entry = $entries[0];
|
|
}
|
|
if (defined($t_entry))
|
|
|
|
#syntax should be like host|ifname1,ifname2;host2|ifname3,ifname2 etc or simply ifname,ifname2
|
|
#depending on complexity of network wished to be described
|
|
{
|
|
my $dhcpinterfaces = $t_entry;
|
|
# The keyword 'noboot' was appended to the NICs that doesn't need to reply DHCP configuration file, only used for mknb at present.
|
|
$dhcpinterfaces =~ s/:noboot//g;
|
|
|
|
my $dhcpif;
|
|
INTF: foreach $dhcpif (split /;/, $dhcpinterfaces) {
|
|
my $host;
|
|
my $savehost;
|
|
my $foundself = 1;
|
|
if ($dhcpif =~ /\|/) {
|
|
$foundself = 0;
|
|
|
|
(my $ngroup, $dhcpif) = split /\|/, $dhcpif;
|
|
foreach $host (noderange($ngroup)) {
|
|
$savehost = $host;
|
|
unless (xCAT::NetworkUtils->thishostisnot($host)) {
|
|
$foundself = 1;
|
|
last;
|
|
}
|
|
}
|
|
if (!defined($savehost)) { # host not defined in db,
|
|
# probably management node
|
|
unless (xCAT::NetworkUtils->thishostisnot($ngroup)) {
|
|
$foundself = 1;
|
|
}
|
|
}
|
|
}
|
|
unless ($foundself) {
|
|
next INTF;
|
|
}
|
|
foreach (split /[,\s]+/, $dhcpif)
|
|
{
|
|
$activenics{$_} = 1;
|
|
$querynics = 0;
|
|
}
|
|
}
|
|
}
|
|
@entries = xCAT::TableUtils->get_site_attribute("nameservers");
|
|
$t_entry = $entries[0];
|
|
if (defined($t_entry)) {
|
|
$sitenameservers = $t_entry;
|
|
}
|
|
@entries = xCAT::TableUtils->get_site_attribute("ntpservers");
|
|
$t_entry = $entries[0];
|
|
if (defined($t_entry)) {
|
|
$sitentpservers = $t_entry;
|
|
}
|
|
@entries = xCAT::TableUtils->get_site_attribute("logservers");
|
|
$t_entry = $entries[0];
|
|
if (defined($t_entry)) {
|
|
$sitelogservers = $t_entry;
|
|
}
|
|
@entries = xCAT::TableUtils->get_site_attribute("domain");
|
|
$t_entry = $entries[0];
|
|
|
|
unless (defined($t_entry))
|
|
{
|
|
# this may not be an error
|
|
# $callback->(
|
|
# {error => ["No domain defined in site tabe"], errorcode => [1]}
|
|
# );
|
|
# return;
|
|
} else {
|
|
$site_domain = $t_entry;
|
|
}
|
|
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: sitelogservers=$sitelogservers sitentpservers=$sitentpservers sitenameservers=$sitenameservers site_domain=$site_domain");
|
|
}
|
|
|
|
if ( $backend->name eq 'kea' ) {
|
|
kea_process_request($backend, $req, \%opt, \%activenics, $verbose_on_off);
|
|
return;
|
|
}
|
|
|
|
@dhcpconf = ();
|
|
@dhcp6conf = ();
|
|
|
|
my $dhcplockfd;
|
|
open($dhcplockfd, ">", "/tmp/xcat/dhcplock");
|
|
flock($dhcplockfd, LOCK_EX);
|
|
if ($::XCATSITEVALS{externaldhcpservers}) {
|
|
|
|
# do nothing if remote dhcpservers at this point
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: remote dhcpservers at this point, do nothing");
|
|
} elsif ($opt{n}) {
|
|
if (-e $dhcpconffile) {
|
|
if ($^O eq 'aix') {
|
|
|
|
# save NIM aix entries - to be restored later
|
|
my $aixconf;
|
|
open($aixconf, $dhcpconffile);
|
|
if ($aixconf) {
|
|
my $save = 0;
|
|
while (<$aixconf>) {
|
|
if ($save) {
|
|
push @aixcfg, $_;
|
|
}
|
|
|
|
if ($_ =~ /#Network configuration end\n/) {
|
|
$save++;
|
|
}
|
|
}
|
|
close($aixconf);
|
|
}
|
|
$restartdhcp = 1;
|
|
@dhcpconf = ();
|
|
}
|
|
|
|
my $rsp;
|
|
push @{ $rsp->{data} }, "Renamed existing dhcp configuration file to $dhcpconffile.xcatbak\n";
|
|
xCAT::MsgUtils->message("I", $rsp, $callback);
|
|
|
|
my $bakname = "$dhcpconffile.xcatbak";
|
|
rename("$dhcpconffile", $bakname);
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: Renamed existing dhcp configuration file to $dhcpconffile.xcatbak");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: load dhcp config file $dhcpconffile");
|
|
my $rconf;
|
|
open($rconf, $dhcpconffile); # Read file into memory
|
|
if ($rconf)
|
|
{
|
|
while (<$rconf>)
|
|
{
|
|
push @dhcpconf, $_;
|
|
}
|
|
close($rconf);
|
|
}
|
|
unless ($dhcpconf[0] =~ /^#xCAT/)
|
|
{ #Discard file if not xCAT originated, like 1.x did
|
|
$restartdhcp = 1;
|
|
@dhcpconf = ();
|
|
}
|
|
if ($dhcp6conffile and -e $dhcp6conffile) {
|
|
open($rconf, $dhcp6conffile);
|
|
while (<$rconf>) { push @dhcp6conf, $_; }
|
|
close($rconf);
|
|
}
|
|
unless ($dhcp6conf[0] =~ /^#xCAT/)
|
|
{ #Discard file if not xCAT originated
|
|
$restartdhcp6 = 1;
|
|
@dhcp6conf = ();
|
|
}
|
|
}
|
|
my $nettab = xCAT::Table->new("networks");
|
|
my @vnets = $nettab->getAllAttribs('net', 'mgtifname', 'mask', 'dynamicrange', 'nameservers', 'ddnsdomain', 'domain');
|
|
|
|
# get a list of all domains listed in xCAT network defs
|
|
# - include the site domain - if any
|
|
my $nettab = xCAT::Table->new("networks");
|
|
my @doms = $nettab->getAllAttribs('domain');
|
|
foreach my $netdom (@doms) {
|
|
if ($netdom->{domain}) {
|
|
push(@alldomains, $netdom->{domain}) unless grep(/^$netdom->{domain}$/, @alldomains);
|
|
}
|
|
}
|
|
$nettab->close;
|
|
|
|
# add the site domain
|
|
if ($site_domain) {
|
|
if (!grep(/^$site_domain$/, @alldomains)) {
|
|
push(@alldomains, $site_domain);
|
|
}
|
|
}
|
|
|
|
foreach (@vnets) {
|
|
if ($_->{net} =~ /:/) { #IPv6 detected
|
|
$usingipv6 = 1;
|
|
}
|
|
addrangedetection($_); #add to hash for remembering whether a node has a static address or just happens to live dynamically
|
|
}
|
|
if ($^O eq 'aix')
|
|
{
|
|
@nrn = xCAT::NetworkUtils::get_subnet_aix();
|
|
}
|
|
else
|
|
{
|
|
foreach my $route (local_ipv4_routes()) {
|
|
push @nrn, join(':', @{$route});
|
|
}
|
|
my @ip6routes = `ip -6 route`;
|
|
foreach (@ip6routes) {
|
|
|
|
#TODO: filter out multicast? Don't know if multicast groups *can* appear in ip -6 route...
|
|
#ignore link-local, global-local, junk, and routed networks
|
|
if (/^default/ or /^fe80::\/64/ or /^2002::\/64/ or /^unreachable/ or /^[^ ]+ via/) {
|
|
next;
|
|
}
|
|
my @parts = split /\s+/;
|
|
push @nrn6, { net => $parts[0], iface => $parts[2] };
|
|
}
|
|
}
|
|
|
|
foreach (@vnets) {
|
|
|
|
#TODO: v6 relayed networks?
|
|
my $n = $_->{net};
|
|
my $if = $_->{mgtifname};
|
|
my $nm = $_->{mask};
|
|
if ($if =~ /!remote!/ and $n !~ /:/) { #only take in networks with special interface, but only v4 for now
|
|
push @nrn, "$n:$if:$nm";
|
|
}
|
|
}
|
|
if ($querynics)
|
|
{
|
|
# Use netstat to determine activenics only when no site ent.
|
|
# TODO: IPv6 auto-detect, or just really really insist people define dhcpinterfaces or suffer doom?
|
|
foreach (@nrn)
|
|
{
|
|
my @ent = split /:/;
|
|
my $firstoctet = $ent[0];
|
|
$firstoctet =~ s/^(\d+)\..*/$1/;
|
|
if ($ent[0] eq "169.254.0.0" or ($firstoctet >= 224 and $firstoctet <= 239) or $ent[0] eq "127.0.0.0" or $ent[0] eq '127')
|
|
{
|
|
next;
|
|
}
|
|
my $netif = $ent[1];
|
|
if ($netif =~ /!remote!\S+/) {
|
|
$netif =~ s/!remote!\s*(.*)$/$1/;
|
|
}
|
|
|
|
# Bridge nics
|
|
if ((-f "/usr/sbin/brctl") || (-f "/sbin/brctl"))
|
|
{
|
|
#system "brctl showmacs $ent[1] 2>&1 1>/dev/null";
|
|
system "brctl showmacs $netif 2>&1 1>/dev/null";
|
|
if ($? == 0)
|
|
{
|
|
#$activenics{$ent[1]} = 1;
|
|
$activenics{$netif} = 1;
|
|
next;
|
|
}
|
|
}
|
|
|
|
#if ($ent[1] =~ m/(remote|ipoib|ib|vlan|bond|eth|myri|man|wlan|en\S*\d+|em\S*\d+)/)
|
|
if ($netif =~ m/(remote|ipoib|ib|vlan|bond|eth|myri|man|wlan|en\S*\d+|em\S*\d+)/)
|
|
{ #Mask out many types of interfaces, like xCAT 1.x
|
|
#$activenics{$ent[1]} = 1;
|
|
$activenics{$netif} = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($^O ne 'aix')
|
|
{
|
|
my $os = xCAT::Utils->osver();
|
|
|
|
#add the active nics to /etc/sysconfig/dhcpd or /etc/default/dhcp3-server(ubuntu)
|
|
my $dhcpver;
|
|
my %missingfiles = ("dhcpd" => 1, "dhcpd6" => 1, "dhcp3-server" => 1);
|
|
foreach $dhcpver ("dhcpd", "dhcpd6", "dhcp3-server", "isc-dhcp-server") {
|
|
|
|
# if ipv6 is not present, no need to look at dhcpd6 files
|
|
if (!$usingipv6 and $dhcpver eq "dhcpd6") {
|
|
delete($missingfiles{"dhcpd6"});
|
|
next;
|
|
}
|
|
|
|
# check the possible system config paths for the various Linux O/S
|
|
my $syspath;
|
|
foreach $syspath ("/etc/sysconfig", "/etc/default") {
|
|
|
|
my $generatedpath = "$syspath/$dhcpver";
|
|
my $dhcpd_key = "DHCPDARGS";
|
|
|
|
# For SLES11+ and RHEL7+ Operating system releases, the
|
|
# dhcpd/dhcpd6 configuration is stored in the same file
|
|
if (dhcpd_sysconfig_uses_interface_key($os)) {
|
|
|
|
$dhcpd_key = "DHCPD_INTERFACE";
|
|
if ($usingipv6 and $dhcpver eq "dhcpd6") {
|
|
$dhcpd_key = "DHCPD6_INTERFACE";
|
|
$generatedpath = "$syspath/dhcpd";
|
|
}
|
|
}
|
|
|
|
if ($generatedpath and -e "$generatedpath") {
|
|
|
|
# remove the file from the hash because it will be processed
|
|
if ($dhcpver eq "dhcpd") {
|
|
|
|
# If dhcpd is found, then not necessary to find dhcp3-server
|
|
delete($missingfiles{"dhcp3-server"});
|
|
}
|
|
|
|
# UBUNTU/DEBIAN specific
|
|
if ($dhcpver eq "isc-dhcp-server") {
|
|
|
|
# UBUNTU/DEBIAN configuration ipv6 & ipv4 uses the isc-dhcp-server
|
|
# remove all other from the missingfiles hash
|
|
delete($missingfiles{"dhcpd"});
|
|
delete($missingfiles{"dhcpd6"});
|
|
delete($missingfiles{"dhcp3-server"});
|
|
|
|
$dhcpd_key = "INTERFACES";
|
|
}
|
|
delete($missingfiles{$dhcpver});
|
|
|
|
open DHCPD_FD, "$generatedpath";
|
|
my $syscfg_dhcpd = "";
|
|
my $found = 0;
|
|
|
|
my $ifarg = "$dhcpd_key=\"";
|
|
foreach (keys %activenics) {
|
|
if (/!remote!/) { next; }
|
|
$ifarg .= " $_";
|
|
}
|
|
$ifarg =~ s/\=\" /\=\"/;
|
|
$ifarg .= "\"\n";
|
|
|
|
while (<DHCPD_FD>) {
|
|
if ($_ =~ m/^$dhcpd_key/) {
|
|
$found = 1;
|
|
$syscfg_dhcpd .= $ifarg;
|
|
} else {
|
|
$syscfg_dhcpd .= $_;
|
|
}
|
|
}
|
|
|
|
if ($found eq 0) {
|
|
$syscfg_dhcpd .= $ifarg;
|
|
}
|
|
close DHCPD_FD;
|
|
|
|
# write out the new file with the interfaces defined
|
|
open DBG_FD, '>', "$generatedpath";
|
|
print DBG_FD $syscfg_dhcpd;
|
|
close DBG_FD;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($usingipv6) {
|
|
|
|
# sles11.3 and rhels7 has dhcpd and dhcpd6 config in the dhcp file
|
|
if (dhcpd_sysconfig_uses_interface_key($os)) {
|
|
if ($missingfiles{dhcpd}) {
|
|
$callback->({ error => ["The file /etc/sysconfig/dhcpd doesn't exist, check the dhcp server"] });
|
|
}
|
|
} else {
|
|
if ($missingfiles{dhcpd6}) {
|
|
$callback->({ error => ["The file /etc/sysconfig/dhcpd6 doesn't exist, check the dhcp server"] });
|
|
}
|
|
}
|
|
}
|
|
if ($missingfiles{dhcpd}) {
|
|
$callback->({ error => ["The file /etc/sysconfig/dhcpd doesn't exist, check the dhcp server"] });
|
|
}
|
|
}
|
|
|
|
unless ($dhcpconf[0])
|
|
{ #populate an empty config with some starter data...
|
|
$restartdhcp = 1;
|
|
newconfig();
|
|
}
|
|
if ($usingipv6 and not $dhcp6conf[0]) {
|
|
$restartdhcp6 = 1;
|
|
newconfig6();
|
|
}
|
|
if ($^O ne 'aix')
|
|
{
|
|
foreach (keys %activenics)
|
|
{
|
|
addnic($_, \@dhcpconf);
|
|
if ($usingipv6) {
|
|
addnic($_, \@dhcp6conf);
|
|
}
|
|
}
|
|
}
|
|
|
|
#need to transfer CEC/Frame to FSPs/BPAs
|
|
my @inodes = ();
|
|
my @validnodes = ();
|
|
my $pnode;
|
|
my $cnode;
|
|
if ($req->{node})
|
|
{
|
|
#@inodes = split /,/,${$req->{noderange}};
|
|
my $typehash = xCAT::DBobjUtils->getnodetype(\@{ $req->{node} });
|
|
foreach $pnode (@{ $req->{node} })
|
|
{
|
|
my $ntype = $$typehash{$pnode};
|
|
if ($ntype =~ /^(cec|frame)$/)
|
|
{
|
|
$cnode = xCAT::DBobjUtils->getchildren($pnode);
|
|
foreach (@$cnode)
|
|
{
|
|
push @validnodes, $_;
|
|
}
|
|
} else
|
|
{
|
|
push @validnodes, $pnode;
|
|
}
|
|
}
|
|
$req->{node} = \@validnodes;
|
|
}
|
|
|
|
if ((!$req->{node}) && ($opt{a}))
|
|
{
|
|
if ($opt{d}) #delete all entries
|
|
{
|
|
$req->{node} = [];
|
|
my $nodelist = xCAT::Table->new('nodelist');
|
|
my @entries = ($nodelist->getAllNodeAttribs([qw(node)]));
|
|
my @nodeentries;
|
|
foreach (@entries) {
|
|
push @nodeentries, $_->{node};
|
|
}
|
|
my $typehash = xCAT::DBobjUtils->getnodetype(\@nodeentries);
|
|
foreach (@entries)
|
|
{
|
|
#delete the CEC and Frame node
|
|
my $ntype = $$typehash{ $_->{node} };
|
|
unless ($ntype =~ /^(cec|frame)$/)
|
|
{
|
|
push @{ $req->{node} }, $_->{node};
|
|
}
|
|
}
|
|
}
|
|
else #add all entries
|
|
{
|
|
$req->{node} = [];
|
|
my $mactab = xCAT::Table->new('mac');
|
|
|
|
my @entries = ();
|
|
if ($mactab) {
|
|
@entries = ($mactab->getAllNodeAttribs([qw(mac)]));
|
|
}
|
|
|
|
foreach (@entries)
|
|
{
|
|
push @{ $req->{node} }, $_->{node};
|
|
}
|
|
|
|
# don't put compute node entries in for AIX nodes
|
|
# this is handled by NIM - duplicate entires will cause
|
|
# an error
|
|
if ($^O eq 'aix') {
|
|
my @tmplist;
|
|
foreach my $n (@{ $req->{node} })
|
|
{
|
|
# get the nodetype for each node
|
|
my $ntable = xCAT::Table->new('nodetype');
|
|
if ($ntable) {
|
|
my $ntype = $ntable->getNodeAttribs($n, ['nodetype']);
|
|
|
|
# don't add if it is type "osi"
|
|
unless ($ntype->{nodetype} =~ /osi/) {
|
|
push @tmplist, $n;
|
|
}
|
|
}
|
|
}
|
|
@{ $req->{node} } = @tmplist;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (@nrn)
|
|
{
|
|
my @line = split /:/;
|
|
my $firstoctet = $line[0];
|
|
$firstoctet =~ s/^(\d+)\..*/$1/;
|
|
if ($line[0] eq "169.254.0.0" or ($firstoctet >= 224 and $firstoctet <= 239))
|
|
{
|
|
next;
|
|
}
|
|
my $netif = $line[1];
|
|
if ($netif =~ /!remote!\S+/) {
|
|
$netif =~ s/!remote!\s*(.*)$/$1/;
|
|
if (!defined($activenics{"!remote!"})) {
|
|
next;
|
|
} elsif (!defined($activenics{$netif})) {
|
|
addnic($netif, \@dhcpconf);
|
|
$activenics{$netif} = 1;
|
|
}
|
|
}
|
|
|
|
#if ($activenics{$line[1]} and $line[3] !~ /G/)
|
|
if ($activenics{$netif} and $line[3] !~ /G/)
|
|
{
|
|
addnet($line[0], $line[2]);
|
|
}
|
|
}
|
|
if ($checkdomain)
|
|
{
|
|
$callback->({ error => [ "above error fail to generate new dhcp configuration file, restore dhcp configuration file $dhcpconffile" ], errorcode => [1] });
|
|
my $backupfile = $dhcpconffile.".xcatbak";
|
|
rename("$backupfile", $dhcpconffile);
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: Restore dhcp configuration file to $dhcpconffile");
|
|
exit 1;
|
|
}
|
|
foreach (@nrn6) { #do the ipv6 networks
|
|
addnet6($_); #already did all the filtering before putting into nrn6
|
|
}
|
|
|
|
if ($req->{node})
|
|
{
|
|
my $ip_hash;
|
|
foreach my $node (@{ $req->{node} }) {
|
|
|
|
#need to change the way of finding IP for nodes
|
|
my $ifip = xCAT::NetworkUtils->isIpaddr($node);
|
|
if ($ifip)
|
|
{
|
|
$ip_hash->{$node} = $node;
|
|
}
|
|
else
|
|
{
|
|
my $hoststab = xCAT::Table->new('hosts');
|
|
my $ent = $hoststab->getNodeAttribs($node, ['ip']);
|
|
if ($ent->{ip}) {
|
|
if ($ip_hash->{ $ent->{ip} }) {
|
|
$callback->({ error => [ "Duplicated IP addresses in hosts table for following nodes: $node," . $ip_hash->{ $ent->{ip} } ], errorcode => [1] });
|
|
return;
|
|
}
|
|
$ip_hash->{ $ent->{ip} } = $node;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($^O ne 'aix')
|
|
{
|
|
my $passtab = xCAT::Table->new('passwd');
|
|
my $ent;
|
|
($ent) = $passtab->getAttribs({ key => "omapi" }, qw(username password));
|
|
unless ($ent->{username} and $ent->{password})
|
|
{
|
|
$callback->({ error => ["Unable to access omapi key from passwd table, add the key from dhcpd.conf or makedhcp -n to create a new one"], errorcode => [1] });
|
|
syslog("local4|err", "Unable to access omapi key from passwd table, unable to update DHCP configuration");
|
|
return;
|
|
} # TODO sane err
|
|
|
|
#Have nodes to update
|
|
#open2($omshellout,$omshell,"/usr/bin/omshell");
|
|
open($omshell, "|/usr/bin/omshell > /dev/null");
|
|
print $omshell "key "
|
|
. $ent->{username} . " \""
|
|
. $ent->{password} . "\"\n";
|
|
if ($::XCATSITEVALS{externaldhcpservers}) {
|
|
print $omshell "server $::XCATSITEVALS{externaldhcpservers}\n";
|
|
}
|
|
print $omshell "connect\n";
|
|
if ($usingipv6) {
|
|
open($omshell6, "|/usr/bin/omshell > /dev/null");
|
|
if ($::XCATSITEVALS{externaldhcpservers}) {
|
|
print $omshell "server $::XCATSITEVALS{externaldhcpservers}\n";
|
|
}
|
|
print $omshell6 "port 7912\n";
|
|
print $omshell6 "key "
|
|
. $ent->{username} . " \""
|
|
. $ent->{password} . "\"\n";
|
|
print $omshell6 "connect\n";
|
|
}
|
|
}
|
|
|
|
my $nrtab = xCAT::Table->new('noderes');
|
|
my $chaintab = xCAT::Table->new('chain');
|
|
if ($chaintab) {
|
|
$chainents = $chaintab->getNodesAttribs($req->{node}, ['currstate']);
|
|
} else {
|
|
$chainents = undef;
|
|
}
|
|
$nrhash = $nrtab->getNodesAttribs($req->{node}, [ 'tftpserver', 'netboot', 'proxydhcp', 'xcatmaster', 'servicenode']);
|
|
my $nodetypetab;
|
|
$nodetypetab = xCAT::Table->new('nodetype', -create => 0);
|
|
if ($nodetypetab) {
|
|
$nodetypeents = $nodetypetab->getNodesAttribs($req->{node}, [qw(os provmethod)]);
|
|
}
|
|
my $iscsitab = xCAT::Table->new('iscsi', -create => 0);
|
|
if ($iscsitab) {
|
|
$iscsients = $iscsitab->getNodesAttribs($req->{node}, [qw(server target lun iname)]);
|
|
}
|
|
my $mactab = xCAT::Table->new('mac');
|
|
$machash = $mactab->getNodesAttribs($req->{node}, ['mac']);
|
|
unless ($opt{n} or $machash)
|
|
{
|
|
$callback->(
|
|
{
|
|
error => ["Unable to get mac address in mac table for specified nodes"],
|
|
errorcode => [1]
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
my $vpdtab = xCAT::Table->new('vpd');
|
|
$vpdhash = $vpdtab->getNodesAttribs($req->{node}, ['uuid']);
|
|
foreach (@{ $req->{node} })
|
|
{
|
|
if ($opt{d})
|
|
{
|
|
if ($^O eq 'aix')
|
|
{
|
|
delnode_aix $_;
|
|
}
|
|
else
|
|
{
|
|
delnode $_;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (xCAT::NetworkUtils->getipaddr($_) and not xCAT::NetworkUtils->nodeonmynet($_))
|
|
{
|
|
next;
|
|
}
|
|
addnode $_;
|
|
if ($usingipv6) {
|
|
addnode6 $_;
|
|
}
|
|
}
|
|
}
|
|
close($omshell) if ($^O ne 'aix');
|
|
close($omshell6) if ($omshell6 and $^O ne 'aix');
|
|
}
|
|
writeout();
|
|
if (not $::XCATSITEVALS{externaldhcpservers} and $restartdhcp) {
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: restart dhcp service");
|
|
if ($^O eq 'aix')
|
|
{
|
|
restart_dhcpd_aix();
|
|
}
|
|
else {
|
|
if ($distro =~ /ubuntu.*/ || $distro =~ /debian.*/i)
|
|
{
|
|
if (-e '/etc/dhcp/dhcpd.conf') {
|
|
system("chmod a+r /etc/dhcp/dhcpd.conf");
|
|
|
|
#system("/etc/init.d/isc-dhcp-server restart");
|
|
}
|
|
else {
|
|
#ubuntu config
|
|
system("chmod a+r /etc/dhcp3/dhcpd.conf");
|
|
|
|
#system("/etc/init.d/dhcp3-server restart");
|
|
}
|
|
}
|
|
|
|
#else
|
|
#{
|
|
# system("/etc/init.d/dhcpd restart");
|
|
# # should not chkconfig dhcpd on every makedhcp invoation
|
|
# # it is not appropriate and will cause problem for HAMN
|
|
# # do it in xcatconfig instead
|
|
# #system("chkconfig dhcpd on");
|
|
#}
|
|
xCAT::Utils->restartservice("dhcp");
|
|
print "xx";
|
|
}
|
|
}
|
|
flock($dhcplockfd, LOCK_UN);
|
|
umask $oldmask;
|
|
}
|
|
|
|
sub kea_process_request
|
|
{
|
|
my ( $backend, $req, $opt, $activenics, $verbose_on_off ) = @_;
|
|
|
|
if ($::XCATSITEVALS{externaldhcpservers}) {
|
|
xCAT::MsgUtils->trace($verbose_on_off, "d", "dhcp: remote dhcpservers configured, Kea backend has no local work");
|
|
return;
|
|
}
|
|
|
|
my $dhcplockfd;
|
|
mkdir "/tmp/xcat" unless -d "/tmp/xcat";
|
|
open($dhcplockfd, ">", "/tmp/xcat/dhcplock");
|
|
flock($dhcplockfd, LOCK_EX);
|
|
|
|
if ($opt->{q}) {
|
|
my $loaded4 = $backend->load_dhcp4_config();
|
|
if ($loaded4->{error}) {
|
|
$callback->({ error => [ $loaded4->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
my $loaded6 = $backend->load_dhcp6_config();
|
|
$loaded6 = undef if $loaded6->{error};
|
|
foreach my $node (@{ $req->{node} || [] }) {
|
|
kea_query_node($backend, $loaded4, $node);
|
|
kea_query_node($backend, $loaded6, $node) if $loaded6;
|
|
}
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
|
|
my $intent4 = kea_build_dhcp4_intent($backend, $activenics);
|
|
if ($intent4->{error}) {
|
|
$callback->({ error => [ $intent4->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
my $intent6 = kea_build_dhcp6_intent($backend, $activenics);
|
|
if ($intent6->{error}) {
|
|
$callback->({ error => [ $intent6->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
my $using_dhcp6 = @{ $intent6->{subnets} || [] } ? 1 : 0;
|
|
my $ddns_intent = kea_build_ddns_intent();
|
|
my $using_ddns = $ddns_intent && !$ddns_intent->{error} ? 1 : 0;
|
|
if ($ddns_intent && $ddns_intent->{error}) {
|
|
$callback->({ error => [ $ddns_intent->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
if ($using_ddns) {
|
|
my $dhcp_ddns = kea_dhcp_ddns_section();
|
|
$intent4->{'dhcp-ddns'} = $dhcp_ddns;
|
|
kea_apply_ddns_behavior($intent4);
|
|
$intent6->{'dhcp-ddns'} = $dhcp_ddns if $using_dhcp6;
|
|
kea_apply_ddns_behavior($intent6) if $using_dhcp6;
|
|
}
|
|
|
|
if ($opt->{n}) {
|
|
my $result = $backend->write_dhcp4_config($intent4, backup_existing => 1);
|
|
if ($result->{error}) {
|
|
$callback->({ error => [ $result->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
if ($using_dhcp6) {
|
|
my $dhcp6_result = $backend->write_dhcp6_config($intent6, backup_existing => 1);
|
|
if ($dhcp6_result->{error}) {
|
|
$callback->({ error => [ $dhcp6_result->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
}
|
|
if ($using_ddns) {
|
|
my $ddns_result = $backend->write_ddns_config($ddns_intent, backup_existing => 1);
|
|
if ($ddns_result->{error}) {
|
|
$callback->({ error => [ $ddns_result->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
}
|
|
if (kea_control_agent_enabled()) {
|
|
my $ca_result = $backend->write_ctrl_agent_config({ dhcp6 => $using_dhcp6, ddns => $using_ddns });
|
|
if ($ca_result->{error}) {
|
|
$callback->({ error => [ $ca_result->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
}
|
|
my $restart = $backend->restart_services(ipv6 => $using_dhcp6, ctrl_agent => kea_control_agent_enabled(), ddns => $using_ddns, enable => 1);
|
|
if ($restart->{error}) {
|
|
$callback->({ error => [ $restart->{error} ], errorcode => [1] });
|
|
}
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
|
|
my $loaded4 = $backend->load_dhcp4_config();
|
|
if ($loaded4->{error}) {
|
|
$callback->({ error => [ $loaded4->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
if (!@{ $loaded4->{Dhcp4}{subnet4} || [] }) {
|
|
my $result = $backend->write_dhcp4_config($intent4);
|
|
if ($result->{error}) {
|
|
$callback->({ error => [ $result->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
$loaded4 = $backend->load_dhcp4_config();
|
|
}
|
|
if ($loaded4->{error}) {
|
|
$callback->({ error => [ $loaded4->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
my $loaded6;
|
|
if ($using_dhcp6) {
|
|
$loaded6 = $backend->load_dhcp6_config();
|
|
if ($loaded6->{error}) {
|
|
$callback->({ error => [ $loaded6->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
if (!@{ $loaded6->{Dhcp6}{subnet6} || [] }) {
|
|
my $result = $backend->write_dhcp6_config($intent6);
|
|
if ($result->{error}) {
|
|
$callback->({ error => [ $result->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
$loaded6 = $backend->load_dhcp6_config();
|
|
}
|
|
}
|
|
|
|
my $nodes = kea_expand_request_nodes($req, $opt);
|
|
unless ($nodes) {
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
|
|
my @deleted4;
|
|
my @deleted6;
|
|
my $reservations4 = [];
|
|
my $reservations6 = [];
|
|
my $client_classes_changed = 0;
|
|
if ($opt->{d}) {
|
|
foreach my $match (@{ kea_reservation_matches_for_nodes($nodes) }) {
|
|
push @deleted4, @{ $backend->delete_reservations($loaded4, $match) };
|
|
push @deleted6, @{ $backend->delete_reservations($loaded6, $match) } if $loaded6;
|
|
}
|
|
$client_classes_changed = kea_remove_xnba_client_classes($loaded4, $nodes);
|
|
} else {
|
|
$reservations4 = kea_build_node_reservations($backend, $loaded4, $nodes);
|
|
$backend->upsert_reservations($loaded4, $reservations4);
|
|
$client_classes_changed = kea_sync_xnba_client_classes($loaded4, $nodes);
|
|
if ($loaded6) {
|
|
$reservations6 = kea_build_node_reservations6($backend, $loaded6, $nodes);
|
|
$backend->upsert_reservations($loaded6, $reservations6);
|
|
}
|
|
}
|
|
|
|
my $result = $backend->write_dhcp4_json( $backend->encode_config($loaded4) );
|
|
if ($result->{error}) {
|
|
$callback->({ error => [ $result->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
if ($loaded6) {
|
|
my $dhcp6_result = $backend->write_dhcp6_json( $backend->encode_config($loaded6) );
|
|
if ($dhcp6_result->{error}) {
|
|
$callback->({ error => [ $dhcp6_result->{error} ], errorcode => [1] });
|
|
flock($dhcplockfd, LOCK_UN);
|
|
return;
|
|
}
|
|
}
|
|
|
|
my $live_ok = 0;
|
|
if (kea_control_agent_live_enabled($backend)) {
|
|
my $live4 = $opt->{d}
|
|
? $backend->live_delete_reservations(\@deleted4, service => ['dhcp4'])
|
|
: $backend->live_upsert_reservations($reservations4, service => ['dhcp4']);
|
|
my $live6 = { ok => 1 };
|
|
if ($loaded6) {
|
|
$live6 = $opt->{d}
|
|
? $backend->live_delete_reservations(\@deleted6, service => ['dhcp6'])
|
|
: $backend->live_upsert_reservations($reservations6, service => ['dhcp6']);
|
|
}
|
|
$live_ok = !$live4->{error} && !$live6->{error};
|
|
if (!$live_ok) {
|
|
my $why = $live4->{error} || $live6->{error};
|
|
$callback->({ warning => ["Kea Control Agent host update failed, restarting Kea services instead: $why"] });
|
|
}
|
|
}
|
|
|
|
unless ($live_ok && !$client_classes_changed) {
|
|
my $restart = $backend->restart_services(ipv6 => $using_dhcp6, ctrl_agent => kea_control_agent_enabled(), ddns => $using_ddns);
|
|
if ($restart->{error}) {
|
|
$callback->({ error => [ $restart->{error} ], errorcode => [1] });
|
|
}
|
|
}
|
|
|
|
flock($dhcplockfd, LOCK_UN);
|
|
}
|
|
|
|
sub kea_build_dhcp4_intent
|
|
{
|
|
my ($backend, $activenics) = @_;
|
|
|
|
%dynamicranges = ();
|
|
%netcfgs = ();
|
|
@alldomains = ();
|
|
|
|
my $httpport = "80";
|
|
my @hports = xCAT::TableUtils->get_site_attribute("httpport");
|
|
if ($hports[0]) {
|
|
$httpport = $hports[0];
|
|
}
|
|
|
|
my $nettab = xCAT::Table->new("networks");
|
|
return { error => "Unable to open networks table, please run makenetworks" } unless $nettab;
|
|
|
|
my @vnets = $nettab->getAllAttribs('net', 'mgtifname', 'mask', 'dynamicrange', 'nameservers', 'ddnsdomain', 'domain');
|
|
my @doms = $nettab->getAllAttribs('domain');
|
|
foreach my $netdom (@doms) {
|
|
if ($netdom->{domain}) {
|
|
push(@alldomains, $netdom->{domain}) unless grep(/^$netdom->{domain}$/, @alldomains);
|
|
}
|
|
}
|
|
if ($site_domain) {
|
|
push(@alldomains, $site_domain) unless grep(/^$site_domain$/, @alldomains);
|
|
}
|
|
|
|
foreach (@vnets) {
|
|
addrangedetection($_);
|
|
}
|
|
|
|
my @routes = kea_ipv4_routes(@vnets);
|
|
my %dhcp_interfaces = %$activenics;
|
|
if (!keys %dhcp_interfaces) {
|
|
my %configured_local_interfaces = map { $_->{mgtifname} => 1 } grep { $_->{mgtifname} && $_->{mgtifname} !~ /!remote!/ } @vnets;
|
|
foreach my $route (@routes) {
|
|
my ( $net, $netif, undef, $flags ) = @$route;
|
|
next if kea_skip_ipv4_network($net);
|
|
next if defined($flags) && $flags =~ /G/;
|
|
next if $netif =~ /!remote!/;
|
|
$dhcp_interfaces{$netif} = 1 if $configured_local_interfaces{$netif};
|
|
}
|
|
}
|
|
|
|
my @interfaces = grep { $_ ne '!remote!' && $_ !~ /!remote!/ } sort keys %dhcp_interfaces;
|
|
if (!@interfaces && !$dhcp_interfaces{'!remote!'}) {
|
|
return { error => "Unable to infer local Kea DHCP interfaces. Set site.dhcpinterfaces to the intended provisioning interface." };
|
|
}
|
|
|
|
my @subnets;
|
|
my @opal_classes;
|
|
my $id = 1;
|
|
foreach my $route (@routes) {
|
|
my ( $net, $netif, $mask, $flags ) = @$route;
|
|
next if kea_skip_ipv4_network($net);
|
|
next if defined($flags) && $flags =~ /G/;
|
|
|
|
my $interface = $netif;
|
|
my $remote = 0;
|
|
if ($interface =~ /!remote!\S*/) {
|
|
$remote = 1;
|
|
$interface =~ s/!remote!\s*(.*)$/$1/;
|
|
next unless $dhcp_interfaces{'!remote!'};
|
|
} else {
|
|
next unless $dhcp_interfaces{$interface};
|
|
}
|
|
|
|
my $subnet = kea_subnet4_intent($nettab, $net, $mask, $interface, $remote, $id, $httpport);
|
|
return $subnet if $subnet->{error};
|
|
push @opal_classes, @{ delete $subnet->{client_classes} || [] };
|
|
push @subnets, $subnet;
|
|
$id++;
|
|
}
|
|
$nettab->close;
|
|
|
|
my $intent = {
|
|
interfaces => \@interfaces,
|
|
valid_lifetime => kea_dhcp_lease_time(),
|
|
'option-def' => kea_option_defs(),
|
|
'option-data' => kea_global_option_data(),
|
|
'client-classes' => [ @{ kea_boot_client_classes() }, @opal_classes ],
|
|
subnets => \@subnets,
|
|
};
|
|
|
|
if (kea_control_agent_enabled()) {
|
|
$intent->{'control-socket'} = {
|
|
'socket-type' => 'unix',
|
|
'socket-name' => '/var/run/kea/kea4-ctrl-socket',
|
|
};
|
|
my $hook = $backend->host_cmds_hook_path();
|
|
if ($hook) {
|
|
$intent->{'hooks-libraries'} = [ { library => $hook } ];
|
|
} else {
|
|
$callback->({ warning => ["Kea Control Agent was requested, but libdhcp_host_cmds.so was not found. Host reservations will use JSON render and reload."] });
|
|
}
|
|
}
|
|
|
|
return $intent;
|
|
}
|
|
|
|
sub kea_build_dhcp6_intent
|
|
{
|
|
my ($backend, $activenics) = @_;
|
|
|
|
my @interfaces = grep { $_ ne '!remote!' && $_ !~ /!remote!/ } sort keys %$activenics;
|
|
@interfaces = ('*') unless @interfaces;
|
|
|
|
my $nettab = xCAT::Table->new("networks");
|
|
return { error => "Unable to open networks table, please run makenetworks" } unless $nettab;
|
|
|
|
my @vnets = $nettab->getAllAttribs('net', 'mgtifname', 'mask', 'dynamicrange', 'nameservers', 'ddnsdomain', 'domain');
|
|
my @subnets;
|
|
my $id = 10001;
|
|
foreach my $entry (@vnets) {
|
|
next unless $entry->{net} && $entry->{net} =~ /:/;
|
|
my $interface = $entry->{mgtifname} || '*';
|
|
my $remote = 0;
|
|
if ($interface =~ /!remote!\S*/) {
|
|
$remote = 1;
|
|
$interface =~ s/!remote!\s*(.*)$/$1/;
|
|
next if %$activenics && !$activenics->{'!remote!'};
|
|
} elsif (%$activenics && !$activenics->{$interface} && $interfaces[0] ne '*') {
|
|
next;
|
|
}
|
|
|
|
my $subnet = kea_subnet6_intent($entry, $interface, $remote, $id);
|
|
return $subnet if $subnet->{error};
|
|
push @subnets, $subnet;
|
|
$id++;
|
|
}
|
|
$nettab->close;
|
|
|
|
my $intent = {
|
|
interfaces => \@interfaces,
|
|
preferred_lifetime => kea_dhcp_lease_time(),
|
|
valid_lifetime => kea_dhcp_lease_time(),
|
|
subnets => \@subnets,
|
|
};
|
|
|
|
if (kea_control_agent_enabled()) {
|
|
$intent->{'control-socket'} = {
|
|
'socket-type' => 'unix',
|
|
'socket-name' => '/var/run/kea/kea6-ctrl-socket',
|
|
};
|
|
my $hook = $backend->host_cmds_hook_path();
|
|
if ($hook) {
|
|
$intent->{'hooks-libraries'} = [ { library => $hook } ];
|
|
}
|
|
}
|
|
|
|
return $intent;
|
|
}
|
|
|
|
sub kea_subnet6_intent
|
|
{
|
|
my ( $entry, $interface, $remote, $id ) = @_;
|
|
|
|
my $net = $entry->{net};
|
|
my $prefix;
|
|
if ($net =~ m{^(.+)/(\d+)$}) {
|
|
$prefix = $2;
|
|
} elsif (defined $entry->{mask} && $entry->{mask} =~ /^(\d+)$/) {
|
|
$prefix = $1;
|
|
$net = "$net/$prefix";
|
|
} else {
|
|
return { error => "IPv6 network $net must include a prefix length for Kea DHCPv6." };
|
|
}
|
|
|
|
my $domain = $entry->{domain} || $site_domain;
|
|
my @option_data;
|
|
if ($domain) {
|
|
push @option_data, { name => 'domain-search', data => $domain };
|
|
}
|
|
my $nameservers = $entry->{nameservers} || $sitenameservers;
|
|
if ($nameservers && $nameservers =~ /:/) {
|
|
push @option_data, { name => 'dns-servers', data => $nameservers };
|
|
}
|
|
|
|
my %subnet = (
|
|
id => $id,
|
|
subnet => $net,
|
|
dynamicrange => $entry->{dynamicrange},
|
|
option_data => \@option_data,
|
|
);
|
|
$subnet{interface} = $interface unless $remote || $interface eq '*';
|
|
|
|
return \%subnet;
|
|
}
|
|
|
|
sub kea_build_ddns_intent
|
|
{
|
|
return unless kea_ddns_enabled();
|
|
|
|
my $nettab = xCAT::Table->new("networks");
|
|
return { error => "Unable to open networks table, please run makenetworks" } unless $nettab;
|
|
|
|
my @vnets = $nettab->getAllAttribs('net', 'mask', 'nameservers', 'ddnsdomain', 'domain');
|
|
$nettab->close;
|
|
|
|
my ( $key_algorithm, $key_secret ) = kea_ddns_key();
|
|
return { error => "Unable to find DDNS key material for Kea D2. Run makedns with dnshandler=ddns first." } unless $key_secret;
|
|
|
|
my @tsig_keys = (
|
|
{
|
|
name => 'xcat_key',
|
|
algorithm => $key_algorithm,
|
|
secret => $key_secret,
|
|
}
|
|
);
|
|
|
|
my ( %forward_seen, %reverse_seen, @forward, @reverse );
|
|
foreach my $entry (@vnets) {
|
|
my $dns = $entry->{nameservers} || $sitenameservers || '';
|
|
$dns =~ s/,.*//;
|
|
next unless $dns;
|
|
|
|
my $domain = $entry->{ddnsdomain} || $entry->{domain} || $site_domain;
|
|
if ($domain) {
|
|
$domain .= '.' unless $domain =~ /\.$/;
|
|
if (!$forward_seen{$domain}++) {
|
|
push @forward, kea_ddns_domain($domain, $dns);
|
|
}
|
|
}
|
|
|
|
my $zone_mask = $entry->{net} =~ /:/ ? undef : $entry->{mask};
|
|
foreach my $zone (getzonesfornet($entry->{net}, $zone_mask)) {
|
|
$zone .= '.' unless $zone =~ /\.$/;
|
|
if (!$reverse_seen{$zone}++) {
|
|
push @reverse, kea_ddns_domain($zone, $dns);
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
'tsig-keys' => \@tsig_keys,
|
|
forward_domains => \@forward,
|
|
reverse_domains => \@reverse,
|
|
};
|
|
}
|
|
|
|
sub kea_ddns_domain
|
|
{
|
|
my ( $name, $server ) = @_;
|
|
|
|
return {
|
|
name => $name,
|
|
'key-name' => 'xcat_key',
|
|
'dns-servers' => [
|
|
{
|
|
'ip-address' => $server,
|
|
port => 53,
|
|
}
|
|
],
|
|
};
|
|
}
|
|
|
|
sub kea_dhcp_ddns_section
|
|
{
|
|
return {
|
|
'enable-updates' => JSON::true,
|
|
'server-ip' => '127.0.0.1',
|
|
'server-port' => 53001,
|
|
'ncr-protocol' => 'UDP',
|
|
'ncr-format' => 'JSON',
|
|
};
|
|
}
|
|
|
|
sub kea_apply_ddns_behavior
|
|
{
|
|
my ($intent) = @_;
|
|
|
|
$intent->{'ddns-send-updates'} = JSON::true;
|
|
$intent->{'ddns-override-no-update'} = JSON::true;
|
|
$intent->{'ddns-override-client-update'} = JSON::true;
|
|
$intent->{'ddns-qualifying-suffix'} = $site_domain ? "$site_domain." : 'xcat.local.';
|
|
$intent->{'ddns-update-on-renew'} = JSON::true;
|
|
}
|
|
|
|
sub dhcpd_sysconfig_uses_interface_key
|
|
{
|
|
my $os = shift || "";
|
|
my $os_ver = $os;
|
|
$os_ver =~ s/[^0-9.^0-9]//g;
|
|
|
|
return 1 if $os =~ /(sles|opensuse[-_]?leap|leap)/i && $os_ver >= 11;
|
|
return 1 if $os =~ /rhels?/i && $os_ver >= 7;
|
|
return 0;
|
|
}
|
|
|
|
sub kea_ddns_enabled
|
|
{
|
|
return defined($::XCATSITEVALS{dnshandler}) && $::XCATSITEVALS{dnshandler} =~ /ddns/ ? 1 : 0;
|
|
}
|
|
|
|
sub kea_ddns_key
|
|
{
|
|
my $key_path = "/etc/xcat/ddns.key";
|
|
if (open(my $fh, '<', $key_path)) {
|
|
local $/;
|
|
my $contents = <$fh>;
|
|
close($fh);
|
|
my ($algorithm) = $contents =~ /algorithm\s+([A-Za-z0-9-]+)\s*;/;
|
|
my ($secret) = $contents =~ /secret\s+"([^"]+)"/;
|
|
$algorithm ||= 'HMAC-SHA256';
|
|
$algorithm = uc($algorithm);
|
|
$algorithm =~ s/^HMAC-/HMAC-/;
|
|
return ($algorithm, $secret) if $secret;
|
|
}
|
|
|
|
my $passtab = xCAT::Table->new('passwd');
|
|
my $pent = $passtab ? $passtab->getAttribs({ key => 'omapi', username => 'xcat_key' }, ['password']) : undef;
|
|
return ('HMAC-SHA256', $pent->{password}) if $pent && $pent->{password};
|
|
return;
|
|
}
|
|
|
|
sub kea_ipv4_routes
|
|
{
|
|
my @vnets = @_;
|
|
my @routes;
|
|
|
|
push @routes, local_ipv4_routes();
|
|
|
|
foreach (@vnets) {
|
|
my $n = $_->{net};
|
|
my $if = $_->{mgtifname};
|
|
my $nm = $_->{mask};
|
|
if ($if =~ /!remote!/ and $n !~ /:/) {
|
|
push @routes, [ $n, $if, $nm, '' ];
|
|
}
|
|
}
|
|
|
|
return @routes;
|
|
}
|
|
|
|
sub local_ipv4_routes
|
|
{
|
|
my @routes;
|
|
|
|
my $ipcmd = kea_command_path('ip');
|
|
if ($ipcmd) {
|
|
my @route_output = split /\n/, `$ipcmd -4 route show 2>/dev/null`;
|
|
foreach my $line (@route_output) {
|
|
if ($line =~ /^default\b/ && $line =~ /\bdev\s+(\S+)/) {
|
|
push @routes, [ '0.0.0.0', $1, '0.0.0.0', 'G' ];
|
|
next;
|
|
}
|
|
next unless $line =~ /^(\d+(?:\.\d+){3})\/(\d+)\b.*\bdev\s+(\S+)/;
|
|
push @routes, [ $1, $3, kea_prefix_to_mask($2), '' ];
|
|
}
|
|
} else {
|
|
my $netstat = kea_command_path('netstat');
|
|
if ($netstat) {
|
|
my @nsrnoutput = split /\n/, `$netstat -rn 2>/dev/null`;
|
|
splice @nsrnoutput, 0, 2;
|
|
foreach (@nsrnoutput) {
|
|
my @parts = split /\s+/;
|
|
next unless $parts[0] && $parts[2] && $parts[7];
|
|
push @routes, [ $parts[0], $parts[7], $parts[2], $parts[3] ];
|
|
}
|
|
}
|
|
}
|
|
|
|
return @routes;
|
|
}
|
|
|
|
sub kea_command_path
|
|
{
|
|
my ($command) = @_;
|
|
|
|
foreach my $dir (split /:/, $ENV{PATH} || '') {
|
|
next unless $dir;
|
|
my $path = "$dir/$command";
|
|
return $path if -x $path;
|
|
}
|
|
|
|
foreach my $path ( "/usr/sbin/$command", "/usr/bin/$command", "/sbin/$command", "/bin/$command" ) {
|
|
return $path if -x $path;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub kea_subnet4_intent
|
|
{
|
|
my ( $nettab, $net, $mask, $interface, $remote, $id, $httpport ) = @_;
|
|
|
|
my @myipd = xCAT::NetworkUtils->my_ip_facing($net);
|
|
my $myip;
|
|
unless ($myipd[0]) { $myip = $myipd[1]; }
|
|
|
|
my ($ent) = $nettab->getAttribs(
|
|
{ net => $net, mask => $mask },
|
|
qw(tftpserver nameservers ntpservers logservers gateway dynamicrange dhcpserver domain mtu)
|
|
);
|
|
|
|
my $ntpservers = $ent && $ent->{ntpservers} ? $ent->{ntpservers} : $sitentpservers;
|
|
my $logservers = $ent && $ent->{logservers} ? $ent->{logservers} : $sitelogservers;
|
|
my $domain = $ent && $ent->{domain} ? $ent->{domain} : $site_domain;
|
|
return { error => "No domain defined for $net entry in networks table, and no domain defined in site table." } unless $domain;
|
|
|
|
my $nameservers;
|
|
if ($ent and $ent->{nameservers}) {
|
|
$nameservers = $ent->{nameservers};
|
|
} elsif ($sitenameservers) {
|
|
$nameservers = $sitenameservers;
|
|
}
|
|
$nameservers =~ s/<xcatmaster>/$myip/g if $nameservers;
|
|
|
|
if (!$ntpservers || ($ntpservers eq '<xcatmaster>')) {
|
|
$ntpservers = $myip;
|
|
}
|
|
|
|
$nameservers = putmyselffirst($nameservers) if $nameservers;
|
|
$ntpservers = putmyselffirst($ntpservers) if $ntpservers;
|
|
$logservers = putmyselffirst($logservers) if $logservers;
|
|
|
|
my $tftp = $ent && $ent->{tftpserver} ? $ent->{tftpserver} : undef;
|
|
if (!$tftp || ($tftp eq '<xcatmaster>')) {
|
|
$tftp = $myip;
|
|
}
|
|
|
|
my $gateway = $ent && $ent->{gateway} ? $ent->{gateway} : undef;
|
|
if ($gateway && $gateway eq '<xcatmaster>') {
|
|
$gateway = xCAT::NetworkUtils->ip_forwarding_enabled() ? $myip : '';
|
|
}
|
|
if ($gateway) {
|
|
my $maskn = unpack("N", inet_aton($mask));
|
|
my $netn = unpack("N", inet_aton($net));
|
|
my $gaten = unpack("N", inet_aton($gateway));
|
|
if (($gaten & $maskn) != ($maskn & $netn)) {
|
|
return { error => "Specified gateway $gateway is not valid for $net/$mask, must be on same network" };
|
|
}
|
|
}
|
|
|
|
my @option_data;
|
|
push @option_data, { name => 'routers', data => $gateway } if $gateway;
|
|
push @option_data, { name => 'log-servers', data => $logservers || $myip } if $logservers || $myip;
|
|
push @option_data, { name => 'ntp-servers', data => $ntpservers } if $ntpservers;
|
|
if ($nameservers) {
|
|
push @option_data, { name => 'domain-name', data => $domain };
|
|
push @option_data, { name => 'domain-name-servers', data => $nameservers };
|
|
}
|
|
push @option_data, { name => 'interface-mtu', data => $ent->{mtu} } if $ent && $ent->{mtu};
|
|
push @option_data, { name => 'cumulus-provision-url', data => "http://$tftp:$httpport/install/postscripts/cumulusztp" } if $tftp;
|
|
|
|
my $domainstring = join(', ', map { $_ eq $domain ? $_ : $_ } grep { $_ } @alldomains);
|
|
push @option_data, { name => 'domain-search', data => $domainstring } if $domainstring;
|
|
|
|
my $prefix = kea_mask_to_prefix($mask);
|
|
my $dynamicrange = $ent ? $ent->{dynamicrange} : undef;
|
|
if ( $dynamicrange && $ent->{dhcpserver} && xCAT::NetworkUtils->thishostisnot( $ent->{dhcpserver} ) ) {
|
|
$dynamicrange = undef;
|
|
}
|
|
|
|
my $opal_class = kea_opal_client_class($net, $prefix, $tftp, $httpport);
|
|
my %subnet = (
|
|
id => $id,
|
|
subnet => "$net/$prefix",
|
|
dynamicrange => $dynamicrange,
|
|
option_data => \@option_data,
|
|
next_server => $tftp,
|
|
);
|
|
$subnet{interface} = $interface unless $remote;
|
|
if ($opal_class) {
|
|
$subnet{additional_client_classes} = [ $opal_class->{name} ];
|
|
$subnet{client_classes} = [$opal_class];
|
|
}
|
|
|
|
return \%subnet;
|
|
}
|
|
|
|
sub kea_opal_client_class
|
|
{
|
|
my ( $net, $prefix, $tftp, $httpport ) = @_;
|
|
|
|
return unless $net && defined($prefix) && $tftp;
|
|
|
|
my $class_name = "xcat-opal-v3-$net-$prefix";
|
|
$class_name =~ s/[^A-Za-z0-9_.-]/_/g;
|
|
|
|
return {
|
|
name => $class_name,
|
|
test => 'option[93].hex == 0x000e',
|
|
additional_only => JSON::true,
|
|
'option-data' => [
|
|
{
|
|
name => 'conf-file',
|
|
data => "http://$tftp:$httpport/tftpboot/pxelinux.cfg/p/" . $net . "_" . $prefix,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
sub kea_expand_request_nodes
|
|
{
|
|
my ( $req, $opt ) = @_;
|
|
|
|
my @nodes;
|
|
if ($req->{node}) {
|
|
my $typehash = xCAT::DBobjUtils->getnodetype(\@{ $req->{node} });
|
|
foreach my $node (@{ $req->{node} }) {
|
|
my $ntype = $$typehash{$node};
|
|
if ($ntype =~ /^(cec|frame)$/) {
|
|
my $children = xCAT::DBobjUtils->getchildren($node);
|
|
push @nodes, @$children;
|
|
} else {
|
|
push @nodes, $node;
|
|
}
|
|
}
|
|
return \@nodes;
|
|
}
|
|
|
|
return unless $opt->{a};
|
|
if ($opt->{d}) {
|
|
my $nodelist = xCAT::Table->new('nodelist');
|
|
my @entries = ($nodelist->getAllNodeAttribs([qw(node)]));
|
|
my @nodeentries = map { $_->{node} } @entries;
|
|
my $typehash = xCAT::DBobjUtils->getnodetype(\@nodeentries);
|
|
foreach (@entries) {
|
|
my $ntype = $$typehash{ $_->{node} };
|
|
push @nodes, $_->{node} unless $ntype =~ /^(cec|frame)$/;
|
|
}
|
|
} else {
|
|
my $mactab = xCAT::Table->new('mac');
|
|
my @entries = $mactab ? ($mactab->getAllNodeAttribs([qw(mac)])) : ();
|
|
foreach (@entries) {
|
|
push @nodes, $_->{node};
|
|
}
|
|
}
|
|
|
|
return \@nodes;
|
|
}
|
|
|
|
sub kea_build_node_reservations
|
|
{
|
|
my ( $backend, $config, $nodes ) = @_;
|
|
|
|
my $nrtab = xCAT::Table->new('noderes');
|
|
my $chaintab = xCAT::Table->new('chain');
|
|
my $nodetypetab = xCAT::Table->new('nodetype', -create => 0);
|
|
my $iscsitab = xCAT::Table->new('iscsi', -create => 0);
|
|
my $mactab = xCAT::Table->new('mac');
|
|
|
|
$chainents = $chaintab ? $chaintab->getNodesAttribs($nodes, ['currstate']) : undef;
|
|
$nrhash = $nrtab->getNodesAttribs($nodes, [ 'tftpserver', 'netboot', 'proxydhcp', 'xcatmaster', 'servicenode' ]);
|
|
$nodetypeents = $nodetypetab ? $nodetypetab->getNodesAttribs($nodes, [qw(os provmethod arch)]) : undef;
|
|
$iscsients = $iscsitab ? $iscsitab->getNodesAttribs($nodes, [qw(server target lun iname)]) : undef;
|
|
$machash = $mactab ? $mactab->getNodesAttribs($nodes, ['mac']) : undef;
|
|
|
|
my @reservations;
|
|
foreach my $node (@$nodes) {
|
|
push @reservations, @{ kea_node_reservations($backend, $config, $node) };
|
|
}
|
|
|
|
return \@reservations;
|
|
}
|
|
|
|
sub kea_build_node_reservations6
|
|
{
|
|
my ( $backend, $config, $nodes ) = @_;
|
|
|
|
my $nrtab = xCAT::Table->new('noderes');
|
|
my $mactab = xCAT::Table->new('mac');
|
|
my $vpdtab = xCAT::Table->new('vpd', -create => 0);
|
|
|
|
$nrhash = $nrtab ? $nrtab->getNodesAttribs($nodes, [ 'tftpserver', 'xcatmaster', 'servicenode' ]) : undef;
|
|
$machash = $mactab ? $mactab->getNodesAttribs($nodes, ['mac']) : undef;
|
|
$vpdhash = $vpdtab ? $vpdtab->getNodesAttribs($nodes, ['uuid']) : undef;
|
|
|
|
my @reservations;
|
|
foreach my $node (@$nodes) {
|
|
push @reservations, @{ kea_node_reservations6($backend, $config, $node) };
|
|
}
|
|
|
|
return \@reservations;
|
|
}
|
|
|
|
sub kea_node_reservations
|
|
{
|
|
my ( $backend, $config, $node ) = @_;
|
|
|
|
my $nrent = $nrhash && $nrhash->{$node} ? $nrhash->{$node}->[0] : undef;
|
|
my $chainent = $chainents && $chainents->{$node} ? $chainents->{$node}->[0] : undef;
|
|
my $ntent = $nodetypeents && $nodetypeents->{$node} ? $nodetypeents->{$node}->[0] : undef;
|
|
my $ient = $iscsients && $iscsients->{$node} ? $iscsients->{$node}->[0] : undef;
|
|
my $macent = $machash && $machash->{$node} ? $machash->{$node}->[0] : undef;
|
|
|
|
unless ($macent and $macent->{mac}) {
|
|
$callback->({ warning => ["Unable to find mac address for $node"] });
|
|
return [];
|
|
}
|
|
|
|
my ( $nxtsrv, $tftpserver ) = kea_next_server_for_node($node, $nrent);
|
|
my @reservations;
|
|
my @macs = split(/\|/, $macent->{mac});
|
|
foreach my $mace (@macs) {
|
|
my ( $mac, $hname ) = split(/!/, $mace);
|
|
$hname ||= $node;
|
|
next unless $mac;
|
|
if ($mac !~ /^[0-9a-fA-F]{2}(-[0-9a-fA-F]{2}){5,8}$|^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5,8}$/) {
|
|
$callback->({ error => ["Invalid mac address $mac for $node"], errorcode => [1] });
|
|
next;
|
|
}
|
|
if (!grep /:/, $mac) {
|
|
$mac = lc($mac);
|
|
$mac =~ s/(\w{2})/$1:/g;
|
|
$mac =~ s/:$//;
|
|
}
|
|
|
|
my $ip = getipaddr($hname, OnlyV4 => 1);
|
|
next unless $ip;
|
|
|
|
if (ipIsDynamic($ip)) {
|
|
$callback->({ error => ["Node $node has IP $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table."], errorcode => [1] });
|
|
next;
|
|
}
|
|
|
|
my $subnet_id = $backend->subnet_id_for_ip($config, $ip);
|
|
unless ($subnet_id) {
|
|
$callback->({ warning => ["Unable to find a Kea subnet for $node ($ip), skipping DHCP reservation"] });
|
|
next;
|
|
}
|
|
|
|
my %reservation = (
|
|
'subnet-id' => $subnet_id,
|
|
'hw-address' => lc($mac),
|
|
hostname => $hname,
|
|
'ip-address' => $ip,
|
|
);
|
|
$reservation{'next-server'} = $nxtsrv if $nxtsrv && $nxtsrv !~ /\$\{/;
|
|
|
|
my $boot = kea_boot_for_node($node, $nrent, $chainent, $ntent, $ient, $nxtsrv);
|
|
$reservation{'boot-file-name'} = $boot->{'boot-file-name'} if defined $boot->{'boot-file-name'};
|
|
$reservation{'option-data'} = $boot->{'option-data'} if @{ $boot->{'option-data'} || [] };
|
|
|
|
push @reservations, \%reservation;
|
|
}
|
|
|
|
return \@reservations;
|
|
}
|
|
|
|
sub kea_sync_xnba_client_classes
|
|
{
|
|
my ( $config, $nodes ) = @_;
|
|
|
|
my $changed = kea_remove_xnba_client_classes($config, $nodes);
|
|
my $classes = kea_xnba_client_classes_for_nodes($nodes);
|
|
return $changed unless @$classes;
|
|
|
|
$config->{Dhcp4} ||= {};
|
|
my @existing = @{ $config->{Dhcp4}{'client-classes'} || [] };
|
|
$config->{Dhcp4}{'client-classes'} = [ @$classes, @existing ];
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub kea_remove_xnba_client_classes
|
|
{
|
|
my ( $config, $nodes ) = @_;
|
|
|
|
return 0 unless $config && $config->{Dhcp4};
|
|
my $classes = $config->{Dhcp4}{'client-classes'} || [];
|
|
my %nodes = map { $_ => 1 } @$nodes;
|
|
my @kept;
|
|
my $changed = 0;
|
|
|
|
foreach my $class (@$classes) {
|
|
my $context = $class->{'user-context'} || {};
|
|
if ( ( $context->{'xcat-purpose'} || '' ) eq 'xnba-second-stage' && $nodes{ $context->{'xcat-node'} || '' } ) {
|
|
$changed = 1;
|
|
next;
|
|
}
|
|
push @kept, $class;
|
|
}
|
|
|
|
$config->{Dhcp4}{'client-classes'} = \@kept if $changed;
|
|
return $changed;
|
|
}
|
|
|
|
sub kea_xnba_client_classes_for_nodes
|
|
{
|
|
my ($nodes) = @_;
|
|
|
|
my $nrtab = xCAT::Table->new('noderes');
|
|
my $mactab = xCAT::Table->new('mac');
|
|
return [] unless $nrtab && $mactab;
|
|
|
|
my $nrents = $nrtab->getNodesAttribs($nodes, [ 'tftpserver', 'netboot', 'proxydhcp', 'xcatmaster', 'servicenode' ]);
|
|
my $macents = $mactab->getNodesAttribs($nodes, ['mac']);
|
|
my $httpport = "80";
|
|
my @hports = xCAT::TableUtils->get_site_attribute("httpport");
|
|
if ($hports[0]) {
|
|
$httpport = $hports[0];
|
|
}
|
|
|
|
my @records;
|
|
foreach my $node (@$nodes) {
|
|
my $nrent = $nrents && $nrents->{$node} ? $nrents->{$node}->[0] : undef;
|
|
next unless $nrent && $nrent->{netboot} && $nrent->{netboot} eq 'xnba';
|
|
|
|
my $macent = $macents && $macents->{$node} ? $macents->{$node}->[0] : undef;
|
|
next unless $macent && $macent->{mac};
|
|
|
|
my ( $nxtsrv ) = kea_next_server_for_node($node, $nrent);
|
|
next unless $nxtsrv;
|
|
|
|
foreach my $mace (split(/\|/, $macent->{mac})) {
|
|
my ($mac) = split(/!/, $mace);
|
|
$mac = kea_normalize_mac($mac);
|
|
next unless $mac;
|
|
push @records, {
|
|
node => $node,
|
|
mac => $mac,
|
|
next_server => $nxtsrv,
|
|
httpport => $httpport,
|
|
};
|
|
}
|
|
}
|
|
|
|
return xCAT::DHCP::BootPolicy->kea_xnba_node_classes(
|
|
nodes => \@records,
|
|
xnba_efi => -f "$tftpdir/xcat/xnba.efi" ? 1 : 0,
|
|
);
|
|
}
|
|
|
|
sub kea_normalize_mac
|
|
{
|
|
my ($mac) = @_;
|
|
|
|
return unless $mac;
|
|
return unless $mac =~ /^[0-9a-fA-F]{2}(-[0-9a-fA-F]{2}){5,8}$|^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5,8}$/;
|
|
if (index($mac, ':') == -1) {
|
|
$mac = lc($mac);
|
|
$mac =~ s/(\w{2})/$1:/g;
|
|
$mac =~ s/:$//;
|
|
}
|
|
return lc($mac);
|
|
}
|
|
|
|
sub kea_node_reservations6
|
|
{
|
|
my ( $backend, $config, $node ) = @_;
|
|
|
|
my $macent = $machash && $machash->{$node} ? $machash->{$node}->[0] : undef;
|
|
my $vpdent = $vpdhash && $vpdhash->{$node} ? $vpdhash->{$node}->[0] : undef;
|
|
unless ($macent and $macent->{mac}) {
|
|
$callback->({ warning => ["Unable to find mac address for $node"] });
|
|
return [];
|
|
}
|
|
|
|
my $duid = kea_duid_from_uuid($vpdent ? $vpdent->{uuid} : undef);
|
|
my @reservations;
|
|
foreach my $mace (split(/\|/, $macent->{mac})) {
|
|
my ( $mac, $hname ) = split(/!/, $mace);
|
|
$hname ||= $node;
|
|
next unless $mac;
|
|
if (!grep /:/, $mac) {
|
|
$mac = lc($mac);
|
|
$mac =~ s/(\w{2})/$1:/g;
|
|
$mac =~ s/:$//;
|
|
}
|
|
|
|
my $ip = getipaddr($hname, OnlyV6 => 1);
|
|
next unless $ip;
|
|
|
|
if (ipIsDynamic($ip)) {
|
|
$callback->({ error => ["Node $node has IPv6 address $ip which is inside the DHCP dynamic range. Move the node IP outside the dynamic range or adjust the range in the networks table."], errorcode => [1] });
|
|
next;
|
|
}
|
|
|
|
my $subnet_id = $backend->subnet_id_for_ip($config, $ip);
|
|
unless ($subnet_id) {
|
|
$callback->({ warning => ["Unable to find a Kea DHCPv6 subnet for $node ($ip), skipping DHCPv6 reservation"] });
|
|
next;
|
|
}
|
|
|
|
my %reservation = (
|
|
'subnet-id' => $subnet_id,
|
|
hostname => $hname,
|
|
'ip-addresses' => [$ip],
|
|
);
|
|
if ($duid) {
|
|
$reservation{duid} = $duid;
|
|
} else {
|
|
$reservation{'hw-address'} = lc($mac);
|
|
}
|
|
|
|
push @reservations, \%reservation;
|
|
}
|
|
|
|
return \@reservations;
|
|
}
|
|
|
|
sub kea_duid_from_uuid
|
|
{
|
|
my ($uuid) = @_;
|
|
|
|
return unless $uuid;
|
|
$uuid =~ s/[^0-9a-fA-F]//g;
|
|
return unless length($uuid) == 32;
|
|
$uuid =~ s/(..)/$1:/g;
|
|
$uuid =~ s/:$//;
|
|
return "00:04:$uuid";
|
|
}
|
|
|
|
sub kea_reservation_matches_for_nodes
|
|
{
|
|
my ($nodes) = @_;
|
|
|
|
my $mactab = xCAT::Table->new('mac');
|
|
my $machash_local = $mactab ? $mactab->getNodesAttribs($nodes, ['mac']) : undef;
|
|
my $vpdtab = xCAT::Table->new('vpd', -create => 0);
|
|
my $vpdhash_local = $vpdtab ? $vpdtab->getNodesAttribs($nodes, ['uuid']) : undef;
|
|
my @matches;
|
|
|
|
foreach my $node (@$nodes) {
|
|
push @matches, { hostname => $node };
|
|
my $ip = getipaddr($node, OnlyV4 => 1);
|
|
push @matches, { 'ip-address' => $ip } if $ip;
|
|
my $ip6 = getipaddr($node, OnlyV6 => 1);
|
|
push @matches, { 'ip-address' => $ip6 } if $ip6;
|
|
my $vpdent = $vpdhash_local && $vpdhash_local->{$node} ? $vpdhash_local->{$node}->[0] : undef;
|
|
my $duid = kea_duid_from_uuid($vpdent ? $vpdent->{uuid} : undef);
|
|
push @matches, { duid => $duid } if $duid;
|
|
my $macent = $machash_local && $machash_local->{$node} ? $machash_local->{$node}->[0] : undef;
|
|
next unless $macent && $macent->{mac};
|
|
foreach my $mace (split(/\|/, $macent->{mac})) {
|
|
my ( $mac, $hname ) = split(/!/, $mace);
|
|
if ($hname) {
|
|
push @matches, { hostname => $hname };
|
|
my $host_ip = getipaddr($hname, OnlyV4 => 1);
|
|
push @matches, { 'ip-address' => $host_ip } if $host_ip;
|
|
my $host_ip6 = getipaddr($hname, OnlyV6 => 1);
|
|
push @matches, { 'ip-address' => $host_ip6 } if $host_ip6;
|
|
}
|
|
next unless $mac;
|
|
if (!grep /:/, $mac) {
|
|
$mac = lc($mac);
|
|
$mac =~ s/(\w{2})/$1:/g;
|
|
$mac =~ s/:$//;
|
|
}
|
|
push @matches, { 'hw-address' => lc($mac) };
|
|
}
|
|
}
|
|
|
|
return \@matches;
|
|
}
|
|
|
|
sub kea_query_node
|
|
{
|
|
my ( $backend, $config, $node ) = @_;
|
|
|
|
my @matches = @{ kea_reservation_matches_for_nodes([$node]) };
|
|
my @found;
|
|
foreach my $match (@matches) {
|
|
push @found, @{ $backend->query_reservations($config, $match) };
|
|
}
|
|
|
|
my %seen;
|
|
foreach my $reservation (@found) {
|
|
my $key = join('|', map { $reservation->{$_} || '' } qw(subnet-id hw-address ip-address hostname));
|
|
next if $seen{$key}++;
|
|
my $msg = "$node:";
|
|
$msg .= " ip-address = $reservation->{'ip-address'}" if $reservation->{'ip-address'};
|
|
$msg .= " hardware-address = $reservation->{'hw-address'}" if $reservation->{'hw-address'};
|
|
$msg .= " hostname = $reservation->{hostname}" if $reservation->{hostname};
|
|
$callback->({ data => [$msg] });
|
|
}
|
|
}
|
|
|
|
sub kea_next_server_for_node
|
|
{
|
|
my ( $node, $nrent ) = @_;
|
|
|
|
if ($nrent and $nrent->{tftpserver} and $nrent->{tftpserver} ne '<xcatmaster>') {
|
|
my $tmp_name = inet_aton($nrent->{tftpserver});
|
|
unless ($tmp_name) {
|
|
$callback->({ error => ["Unable to resolve the tftpserver for node"], errorcode => [1] });
|
|
return;
|
|
}
|
|
my $server = inet_ntoa($tmp_name);
|
|
return ($server, $server);
|
|
}
|
|
|
|
my $node_server = $nrent && $nrent->{xcatmaster} ? $nrent->{xcatmaster} : undef;
|
|
if ($node_server) {
|
|
my $tmp_server = inet_aton($node_server);
|
|
if ($tmp_server) {
|
|
my $server = inet_ntoa($tmp_server);
|
|
return ($server, $server);
|
|
}
|
|
}
|
|
|
|
my @nxtsrvd = xCAT::NetworkUtils->my_ip_facing($node);
|
|
unless ($nxtsrvd[0]) {
|
|
return ($nxtsrvd[1], $nxtsrvd[1]);
|
|
}
|
|
|
|
return ('${next-server}', undef);
|
|
}
|
|
|
|
sub kea_boot_for_node
|
|
{
|
|
my ( $node, $nrent, $chainent, $ntent, $ient, $nxtsrv ) = @_;
|
|
|
|
my $httpport = "80";
|
|
my @hports = xCAT::TableUtils->get_site_attribute("httpport");
|
|
if ($hports[0]) {
|
|
$httpport = $hports[0];
|
|
}
|
|
|
|
my %boot = ( 'option-data' => [] );
|
|
my $netboot = $nrent ? $nrent->{netboot} : undef;
|
|
|
|
if ($ient and $ient->{server} and $ient->{target}) {
|
|
$ient->{lun} = 0 unless defined($ient->{lun});
|
|
my $rootpath = 'iscsi:' . $ient->{server} . ':6:3260:' . $ient->{lun} . ':' . $ient->{target};
|
|
push @{ $boot{'option-data'} }, { name => 'root-path', data => $rootpath };
|
|
push @{ $boot{'option-data'} }, { name => 'iscsi-initiator-iqn', data => $ient->{iname} } if defined($ient->{iname});
|
|
}
|
|
|
|
if ($netboot and $netboot eq 'pxe') {
|
|
$boot{'boot-file-name'} = 'pxelinux.0';
|
|
} elsif ($netboot and $netboot eq 'yaboot') {
|
|
$boot{'boot-file-name'} = "/yb/node/yaboot-$node";
|
|
} elsif ($netboot and $netboot =~ /^grub2[-]?.*$/) {
|
|
$boot{'boot-file-name'} = "/boot/grub2/grub2-$node";
|
|
} elsif ($netboot and $netboot eq 'petitboot') {
|
|
if ($nxtsrv) {
|
|
my $petitboot_conf = "http://$nxtsrv:$httpport/tftpboot/petitboot/$node";
|
|
$boot{'boot-file-name'} = $petitboot_conf;
|
|
push @{ $boot{'option-data'} }, { name => 'conf-file', data => $petitboot_conf };
|
|
}
|
|
} elsif ($netboot and $netboot eq 'onie') {
|
|
my $onie_url = kea_onie_url_for_node($node, $ntent, $nxtsrv, $httpport);
|
|
push @{ $boot{'option-data'} }, { name => 'www-server', data => $onie_url } if $onie_url;
|
|
}
|
|
|
|
push @{ $boot{'option-data'} }, { name => 'host-name', data => $node };
|
|
return \%boot;
|
|
}
|
|
|
|
sub kea_onie_url_for_node
|
|
{
|
|
my ( $node, $ntent, $nxtsrv, $httpport ) = @_;
|
|
|
|
return unless $nxtsrv;
|
|
my $provmethod = $ntent ? $ntent->{provmethod} : undef;
|
|
return "http://$nxtsrv:$httpport/install/onie/onie-installer" unless $provmethod;
|
|
|
|
my $linuximagetab = xCAT::Table->new('linuximage');
|
|
my $imagetab = $linuximagetab ? $linuximagetab->getAttribs({ imagename => $provmethod }, 'pkgdir') : undef;
|
|
return "http://$nxtsrv:$httpport/install/onie/onie-installer" unless $imagetab;
|
|
|
|
foreach my $pkgdir (split(/,/, $imagetab->{pkgdir} || '')) {
|
|
return "http://$nxtsrv:$httpport$pkgdir" if -f $pkgdir;
|
|
}
|
|
|
|
$callback->({ warning => ["osimage $provmethod pkgdir doesn't exists"]});
|
|
return;
|
|
}
|
|
|
|
sub kea_boot_client_classes
|
|
{
|
|
return xCAT::DHCP::BootPolicy->kea_client_classes(
|
|
xnba_kpxe => -f "$tftpdir/xcat/xnba.kpxe" ? 1 : 0,
|
|
xnba_efi => -f "$tftpdir/xcat/xnba.efi" ? 1 : 0,
|
|
);
|
|
}
|
|
|
|
sub kea_option_defs
|
|
{
|
|
return [
|
|
{ name => 'conf-file', code => 209, type => 'string', space => 'dhcp4' },
|
|
{ name => 'iscsi-initiator-iqn', code => 203, type => 'string', space => 'dhcp4' },
|
|
{ name => 'cumulus-provision-url', code => 239, type => 'string', space => 'dhcp4' },
|
|
];
|
|
}
|
|
|
|
sub kea_global_option_data
|
|
{
|
|
my @options;
|
|
if ($::XCATSITEVALS{timezone}) {
|
|
push @options, { name => 'tcode', data => $::XCATSITEVALS{timezone} };
|
|
}
|
|
return \@options;
|
|
}
|
|
|
|
sub kea_dhcp_lease_time
|
|
{
|
|
return $::XCATSITEVALS{'dhcplease'} if defined $::XCATSITEVALS{'dhcplease'} && $::XCATSITEVALS{'dhcplease'} ne "";
|
|
return 43200;
|
|
}
|
|
|
|
sub kea_control_agent_enabled
|
|
{
|
|
my @entries = xCAT::TableUtils->get_site_attribute("keacontrolagent");
|
|
my $value = $entries[0];
|
|
return 0 unless defined($value);
|
|
return $value =~ /^(1|yes|y|true|enabled)$/i ? 1 : 0;
|
|
}
|
|
|
|
sub kea_control_agent_live_enabled
|
|
{
|
|
my ($backend) = @_;
|
|
|
|
return 0 unless kea_control_agent_enabled();
|
|
return $backend->host_cmds_hook_path() ? 1 : 0;
|
|
}
|
|
|
|
sub kea_mask_to_prefix
|
|
{
|
|
my ($mask) = @_;
|
|
|
|
my $maskn = unpack("N", inet_aton($mask));
|
|
my $bits = 0;
|
|
for my $idx (0 .. 31) {
|
|
$bits++ if $maskn & (1 << (31 - $idx));
|
|
}
|
|
|
|
return $bits;
|
|
}
|
|
|
|
sub kea_prefix_to_mask
|
|
{
|
|
my ($prefix) = @_;
|
|
|
|
return '0.0.0.0' unless defined($prefix) && $prefix =~ /^\d+$/ && $prefix > 0 && $prefix <= 32;
|
|
my $maskn = (0xffffffff << (32 - $prefix)) & 0xffffffff;
|
|
return inet_ntoa(pack("N", $maskn));
|
|
}
|
|
|
|
sub kea_skip_ipv4_network
|
|
{
|
|
my ($net) = @_;
|
|
|
|
my $firstoctet = $net;
|
|
$firstoctet =~ s/^(\d+)\..*/$1/;
|
|
return 1 if $net eq "169.254.0.0";
|
|
return 1 if $net eq "127.0.0.0" || $net eq '127';
|
|
return 1 if ($firstoctet >= 224 and $firstoctet <= 239);
|
|
return 0;
|
|
}
|
|
|
|
# Restart dhcpd on aix
|
|
sub restart_dhcpd_aix
|
|
{
|
|
#Check if dhcpd is running
|
|
my @res = xCAT::Utils->runcmd('lssrc -s dhcpsd', 0);
|
|
if ($::RUNCMD_RC != 0)
|
|
{
|
|
xCAT::MsgUtils->message("E", "Failed to check dhcpsd status\n");
|
|
}
|
|
if (grep /\sactive/, @res)
|
|
{
|
|
xCAT::Utils->runcmd('refresh -s dhcpsd', 0);
|
|
xCAT::MsgUtils->message("E", "Failed to refresh dhcpsd configuration\n") if ($::RUNCMD_RC);
|
|
}
|
|
else
|
|
{
|
|
xCAT::Utils->runcmd('startsrc -s dhcpsd', 0);
|
|
xCAT::MsgUtils->message("E", "Failed to start dhcpsd\n") if ($::RUNCMD_RC);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
sub getzonesfornet {
|
|
my $net = shift;
|
|
my $mask = shift;
|
|
my @zones = ();
|
|
if ($net =~ /:/) { #ipv6, for now do the simple stuff under the assumption we won't have a mask indivisible by 4
|
|
$net =~ s/\/(.*)//;
|
|
my $maskbits = $1;
|
|
if ($mask) {
|
|
die "Not supporting having a mask like $mask on an ipv6 network like $net";
|
|
}
|
|
my $netnum = getipaddr($net, GetNumber => 1);
|
|
unless ($netnum) { return (); }
|
|
$netnum->brsft(128 - $maskbits);
|
|
my $prefix = $netnum->as_hex();
|
|
my $nibbs = $maskbits / 4;
|
|
$prefix =~ s/^0x//;
|
|
my $rev;
|
|
|
|
foreach (reverse(split //, $prefix)) {
|
|
$rev .= $_ . ".";
|
|
$nibbs--;
|
|
}
|
|
while ($nibbs) {
|
|
$rev .= "0.";
|
|
$nibbs--;
|
|
}
|
|
$rev .= "ip6.arpa.";
|
|
return ($rev);
|
|
}
|
|
|
|
#return all in-addr reverse zones for a given mask and net
|
|
#for class a,b,c, the answer is easy
|
|
#for classless, identify the partial byte, do $netbyte | (0xff&~$maskbyte) to get the highest value
|
|
#return sequence from $net to value calculated above
|
|
#since old bind.pm only went as far as class c, we will carry that over for now (more people with smaller than class c complained
|
|
#and none hit the theoretical conflict. FYI, the 'official' method in RFC 2317 seems cumbersome, but maybe one day it makes sense
|
|
#since this is dhcpv4 for now, we'll use the inet_aton, ntop functions to generate the answers (dhcpv6 omapi would be nice...)
|
|
my $netn = inet_aton($net);
|
|
my $maskn = inet_aton($mask);
|
|
unless ($netn and $mask) { return (); }
|
|
my $netnum = unpack('N', $netn);
|
|
my $masknum = unpack('N', $maskn);
|
|
if ($masknum >= 0xffffff00) { #treat all netmasks higher than 255.255.255.0 as class C
|
|
$netnum = $netnum & 0xffffff00;
|
|
$netn = pack('N', $netnum);
|
|
$net = inet_ntoa($netn);
|
|
$net =~ s/\.[^\.]*$//;
|
|
return (join('.', reverse(split('\.', $net))) . '.IN-ADDR.ARPA.');
|
|
} elsif ($masknum > 0xffff0000) { #class b (/16) to /23
|
|
my $tempnumber = ($netnum >> 8);
|
|
$masknum = $masknum >> 8;
|
|
my $highnet = $tempnumber | (0xffffff & ~$masknum);
|
|
foreach ($tempnumber .. $highnet) {
|
|
$netnum = $_ << 8;
|
|
$net = inet_ntoa(pack('N', $netnum));
|
|
$net =~ s/\.[^\.]*$//;
|
|
push @zones, join('.', reverse(split('\.', $net))) . '.IN-ADDR.ARPA.';
|
|
}
|
|
return @zones;
|
|
} elsif ($masknum > 0xff000000) { #class a (/8) to /15, could have made it more flexible, for for only two cases, not worth in
|
|
my $tempnumber = ($netnum >> 16); #the last two bytes are insignificant, shift them off to make math easier
|
|
$masknum = $masknum >> 16;
|
|
my $highnet = $tempnumber | (0xffff & ~$masknum);
|
|
foreach ($tempnumber .. $highnet) {
|
|
$netnum = $_ << 16; #convert back to the real network value
|
|
$net = inet_ntoa(pack('N', $netnum));
|
|
$net =~ s/\.[^\.]*$//;
|
|
$net =~ s/\.[^\.]*$//;
|
|
push @zones, join('.', reverse(split('\.', $net))) . '.IN-ADDR.ARPA.';
|
|
}
|
|
return @zones;
|
|
} else { #class a (theoretically larger, but those shouldn't exist)
|
|
my $tempnumber = ($netnum >> 24); #the last two bytes are insignificant, shift them off to make math easier
|
|
$masknum = $masknum >> 24;
|
|
my $highnet = $tempnumber | (0xff & ~$masknum);
|
|
foreach ($tempnumber .. $highnet) {
|
|
$netnum = $_ << 24; #convert back to the real network value
|
|
$net = inet_ntoa(pack('N', $netnum));
|
|
$net =~ s/\.[^\.]*$//;
|
|
$net =~ s/\.[^\.]*$//;
|
|
$net =~ s/\.[^\.]*$//;
|
|
push @zones, join('.', reverse(split('\.', $net))) . '.IN-ADDR.ARPA.';
|
|
}
|
|
return @zones;
|
|
}
|
|
}
|
|
|
|
sub putmyselffirst {
|
|
my $srvlist = shift;
|
|
if ($srvlist =~ /,/) { #TODO: only reshuffle when requested, or allow opt out of reshuffle?
|
|
my @dnsrvs = split /,/, $srvlist;
|
|
my @reordered;
|
|
foreach (@dnsrvs) {
|
|
if (xCAT::NetworkUtils->thishostisnot($_)) {
|
|
push @reordered, $_;
|
|
} else {
|
|
unshift @reordered, $_;
|
|
}
|
|
}
|
|
$srvlist = join(', ', @reordered);
|
|
}
|
|
return $srvlist;
|
|
}
|
|
|
|
sub addnet6
|
|
{
|
|
if ($::XCATSITEVALS{externaldhcpservers}) { return; }
|
|
if (!$usingipv6) { return; }
|
|
my $netentry = shift;
|
|
my $net = $netentry->{net};
|
|
my $iface = $netentry->{iface};
|
|
my $idx = 0;
|
|
if (grep /\} # $net subnet_end/, @dhcp6conf) { #need to add to dhcp6conf
|
|
return;
|
|
} else { #need to add to dhcp6conf
|
|
$restartdhcp6 = 1;
|
|
while ($idx <= $#dhcp6conf)
|
|
{
|
|
if ($dhcp6conf[$idx] =~ /\} # $iface nic_end/) {
|
|
last;
|
|
}
|
|
$idx++;
|
|
}
|
|
unless ($dhcp6conf[$idx] =~ /\} # $iface nic_end\n/) {
|
|
$callback->(
|
|
{
|
|
error =>
|
|
["Could not add the subnet $net for interface $iface into $dhcpconffile.\nPlease verify the xCAT database matches networks defined on this system."],
|
|
errorcode => [1]
|
|
}
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
|
|
my $dhcplease = 43200;
|
|
if (defined $::XCATSITEVALS{'dhcplease'} && $::XCATSITEVALS{'dhcplease'} ne "") {
|
|
$dhcplease = $::XCATSITEVALS{'dhcplease'};
|
|
}
|
|
|
|
my @netent = (
|
|
" subnet6 $net {\n",
|
|
" authoritative;\n",
|
|
" max-lease-time $dhcplease;\n",
|
|
" min-lease-time $dhcplease;\n",
|
|
" default-lease-time $dhcplease;\n",
|
|
);
|
|
|
|
#for now, just do address allocatios (phase 1)
|
|
#phase 2 (by 2.6 presumably) will include the various things like DNS server and other options allowed by dhcpv6
|
|
#gateway is *not* currently allowed to be DHCP designated, router advertises its own self indpendent of dhcp. We'll just keep it that way
|
|
#domain search list is allowed (rfc 3646)
|
|
#nis domain is also an alloed option (rfc 3898)
|
|
#sntp server list (rfc 4075)
|
|
#ntp server rfc 5908
|
|
#fqdn rfc 4704
|
|
#posix timezone rfc 4833/tzdb timezone
|
|
#phase 3 will include whatever is required to do Netboot6. That might be in the october timeframe for lack of implementations to test
|
|
#boot url/param (rfc 59070)
|
|
my $netdomain = $netcfgs{$net}->{domain};
|
|
unless ($netdomain) { $netdomain = $site_domain; }
|
|
push @netent, " option domain-name \"" . $netdomain . "\";\n";
|
|
|
|
# add domain-search if not sles10 or rh5
|
|
my $osv = xCAT::Utils->osver();
|
|
unless (($osv =~ /^sle[sc]10/) || ($osv =~ /^rh.*5$/)) {
|
|
|
|
# We want something like "option domain-search "foo.com", "bar.com";"
|
|
my $domainstring = qq~"$netcfgs{$net}->{domain}"~;
|
|
foreach my $dom (@alldomains) {
|
|
chomp $dom;
|
|
if ($dom ne $netcfgs{$net}->{domain}) {
|
|
$domainstring .= qq~, "$dom"~;
|
|
}
|
|
}
|
|
|
|
if ($netcfgs{$net}->{domain}) {
|
|
push @netent, " option domain-search $domainstring;\n";
|
|
}
|
|
}
|
|
|
|
|
|
my $nameservers = $netcfgs{$net}->{nameservers};
|
|
if ($nameservers and $nameservers =~ /:/) {
|
|
push @netent, " nameservers " . $netcfgs{$net}->{nameservers} . ";\n";
|
|
}
|
|
my $ddnserver = $nameservers;
|
|
$ddnserver =~ s/,.*//;
|
|
my $ddnsdomain;
|
|
if ($netcfgs{$net}->{ddnsdomain}) {
|
|
$ddnsdomain = $netcfgs{$net}->{ddnsdomain};
|
|
}
|
|
if ($::XCATSITEVALS{dnshandler} =~ /ddns/) {
|
|
if ($ddnsdomain) {
|
|
push @netent, " ddns-domainname \"" . $ddnsdomain . "\";\n";
|
|
push @netent, " zone $ddnsdomain. {\n";
|
|
} else {
|
|
push @netent, " zone $netdomain. {\n";
|
|
}
|
|
push @netent, " primary $ddnserver; key xcat_key; \n";
|
|
push @netent, " }\n";
|
|
foreach (getzonesfornet($net)) {
|
|
push @netent, " zone $_ {\n";
|
|
push @netent, " primary $ddnserver; key xcat_key; \n";
|
|
push @netent, " }\n";
|
|
}
|
|
}
|
|
if ($netcfgs{$net}->{range}) {
|
|
push @netent, " range6 " . $netcfgs{$net}->{range} . ";\n";
|
|
} else {
|
|
$callback->({ warning => ["No dynamic range specified for $net. Hosts with no static address will receive no addresses on this subnet."] });
|
|
}
|
|
push @netent, " } # $net subnet_end\n";
|
|
splice(@dhcp6conf, $idx, 0, @netent);
|
|
}
|
|
|
|
sub addnet
|
|
{
|
|
if ($::XCATSITEVALS{externaldhcpservers}) { return; }
|
|
my $net = shift;
|
|
my $mask = shift;
|
|
my $nic;
|
|
my $domain;
|
|
my $httpport="80";
|
|
my @hports=xCAT::TableUtils->get_site_attribute("httpport");
|
|
if ($hports[0]){
|
|
$httpport=$hports[0];
|
|
}
|
|
my $firstoctet = $net;
|
|
$firstoctet =~ s/^(\d+)\..*/$1/;
|
|
if ($net eq "169.254.0.0" or ($firstoctet >= 224 and $firstoctet <= 239)) {
|
|
return;
|
|
}
|
|
unless (grep /\} # $net\/$mask subnet_end/, @dhcpconf)
|
|
{
|
|
$restartdhcp = 1;
|
|
foreach (@nrn)
|
|
{ # search for relevant NIC
|
|
my @ent = split /:/;
|
|
$firstoctet = $ent[0];
|
|
$firstoctet =~ s/^(\d+)\..*/$1/;
|
|
if ($ent[0] eq "169.254.0.0" or ($firstoctet >= 224 and $firstoctet <= 239))
|
|
{
|
|
next;
|
|
}
|
|
if ($ent[0] eq $net and $ent[2] eq $mask)
|
|
{
|
|
$nic = $ent[1];
|
|
if ($nic =~ /!remote!\S+/) {
|
|
$nic =~ s/!remote!\s*(.*)$/$1/;
|
|
}
|
|
|
|
# The first nic that matches the network,
|
|
# what will happen if there are more than one nics in the same subnet,
|
|
# and we want to use the second nic as the dhcp interfaces?
|
|
# this is a TODO
|
|
last;
|
|
}
|
|
}
|
|
|
|
#print " add $net $mask under $nic\n";
|
|
my $idx = 0;
|
|
if ($^O ne 'aix')
|
|
{
|
|
while ($idx <= $#dhcpconf)
|
|
{
|
|
if ($dhcpconf[$idx] =~ /\} # $nic nic_end\n/)
|
|
{
|
|
last;
|
|
}
|
|
$idx++;
|
|
}
|
|
unless ($dhcpconf[$idx] =~ /\} # $nic nic_end\n/)
|
|
{
|
|
$callback->(
|
|
{
|
|
error =>
|
|
["Could not add the subnet $net for interface $nic into $dhcpconffile.\nPlease verify the xCAT database matches networks defined on this system."],
|
|
errorcode => [1]
|
|
}
|
|
);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
# if here, means we found the idx before which to insert
|
|
my $nettab = xCAT::Table->new("networks");
|
|
my $nameservers;
|
|
my $ntpservers;
|
|
my $logservers;
|
|
my $gateway;
|
|
my $tftp;
|
|
my $range;
|
|
my $myip;
|
|
my $mtu;
|
|
my @myipd = xCAT::NetworkUtils->my_ip_facing($net);
|
|
unless ($myipd[0]) { $myip = $myipd[1]; }
|
|
|
|
if ($nettab)
|
|
{
|
|
my $mask_formated = $mask;
|
|
if ($^O eq 'aix')
|
|
{
|
|
my $mask_shift = 32 - $mask;
|
|
$mask_formated = inet_ntoa(pack("N", 2**$mask - 1 << $mask_shift));
|
|
|
|
# $mask_formated = inet_ntoa(pack("N", 2**$mask - 1 << (32 - $mask)));
|
|
}
|
|
|
|
my ($ent) =
|
|
$nettab->getAttribs({ net => $net, mask => $mask_formated },
|
|
qw(tftpserver nameservers ntpservers logservers gateway dynamicrange dhcpserver domain mtu));
|
|
if ($ent and $ent->{ntpservers}) {
|
|
$ntpservers = $ent->{ntpservers};
|
|
} elsif ($sitentpservers) {
|
|
$ntpservers = $sitentpservers;
|
|
}
|
|
if ($ent and $ent->{logservers}) {
|
|
$logservers = $ent->{logservers};
|
|
} elsif ($sitelogservers) {
|
|
$logservers = $sitelogservers;
|
|
}
|
|
if ($ent and $ent->{domain}) {
|
|
$domain = $ent->{domain};
|
|
} elsif ($site_domain) {
|
|
$domain = $site_domain;
|
|
} else {
|
|
$callback->(
|
|
{
|
|
error => [
|
|
"No domain defined for $net entry in networks table, and no domain defined in site table."
|
|
],
|
|
errorcode => [1]
|
|
});
|
|
$checkdomain=1;
|
|
}
|
|
|
|
if ($ent and $ent->{nameservers})
|
|
{
|
|
$nameservers = $ent->{nameservers};
|
|
}
|
|
else
|
|
{
|
|
if ($sitenameservers) {
|
|
$nameservers = $sitenameservers;
|
|
} else {
|
|
$callback->(
|
|
{
|
|
warning => [
|
|
"No $net specific entry for nameservers, and no nameservers defined in site table."
|
|
]
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
# convert <xcatmaster> to nameserver IP
|
|
$nameservers =~ s/<xcatmaster>/$myip/g;
|
|
|
|
if (!$ntpservers || ($ntpservers eq '<xcatmaster>'))
|
|
{
|
|
$ntpservers = $myip;
|
|
}
|
|
|
|
$nameservers = putmyselffirst($nameservers);
|
|
$ntpservers = putmyselffirst($ntpservers);
|
|
$logservers = putmyselffirst($logservers);
|
|
|
|
|
|
if ($ent and $ent->{tftpserver})
|
|
{
|
|
$tftp = $ent->{tftpserver};
|
|
}
|
|
if (!$tftp || ($tftp eq '<xcatmaster>'))
|
|
{
|
|
$tftp = $myip;
|
|
}
|
|
|
|
if ($ent and $ent->{gateway})
|
|
{
|
|
$gateway = $ent->{gateway};
|
|
|
|
if ($gateway eq '<xcatmaster>')
|
|
{
|
|
if (xCAT::NetworkUtils->ip_forwarding_enabled())
|
|
{
|
|
$gateway = $myip;
|
|
}
|
|
else
|
|
{
|
|
$gateway = '';
|
|
}
|
|
}
|
|
}
|
|
if ($ent and $ent->{dynamicrange})
|
|
{
|
|
unless ($ent->{dhcpserver}
|
|
and xCAT::NetworkUtils->thishostisnot($ent->{dhcpserver}))
|
|
{ #If specific, only one dhcp server gets a dynamic range
|
|
$range = join ';', xCAT::DHCP::Range->isc_ranges($ent->{dynamicrange});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$callback->(
|
|
{
|
|
warning => [
|
|
"No dynamic range specified for $net. If hardware discovery is being used, a dynamic range is required."
|
|
]
|
|
}
|
|
);
|
|
}
|
|
if ($ent and $ent->{mtu})
|
|
{
|
|
$mtu = $ent->{mtu};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$callback->(
|
|
{
|
|
error =>
|
|
["Unable to open networks table, please run makenetworks"],
|
|
errorcode => [1]
|
|
}
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
if ($^O eq 'aix')
|
|
{
|
|
return gen_aix_net($myip, $net, $mask, $gateway, $tftp,
|
|
$logservers, $ntpservers, $domain,
|
|
$nameservers, $range, $mtu);
|
|
}
|
|
my @netent;
|
|
|
|
my $maskn = unpack("N", inet_aton($mask));
|
|
my $netn = unpack("N", inet_aton($net));
|
|
my $dhcplease = 43200;
|
|
if (defined $::XCATSITEVALS{'dhcplease'} && $::XCATSITEVALS{'dhcplease'} ne "") {
|
|
$dhcplease = $::XCATSITEVALS{'dhcplease'};
|
|
}
|
|
@netent = (
|
|
" subnet $net netmask $mask {\n",
|
|
" authoritative;\n",
|
|
" max-lease-time $dhcplease;\n",
|
|
" min-lease-time $dhcplease;\n",
|
|
" default-lease-time $dhcplease;\n"
|
|
);
|
|
if ($gateway)
|
|
{
|
|
my $gaten = unpack("N", inet_aton($gateway));
|
|
if (($gaten & $maskn) == ($maskn & $netn))
|
|
{
|
|
push @netent, " option routers $gateway;\n";
|
|
}
|
|
else
|
|
{
|
|
$callback->(
|
|
{
|
|
error => [
|
|
"Specified gateway $gateway is not valid for $net/$mask, must be on same network"
|
|
],
|
|
errorcode => [1]
|
|
}
|
|
);
|
|
}
|
|
}
|
|
if ($tftp)
|
|
{
|
|
push @netent, " next-server $tftp;\n";
|
|
}
|
|
if ($logservers) {
|
|
push @netent, " option log-servers $logservers;\n";
|
|
} elsif ($myip) {
|
|
push @netent, " option log-servers $myip;\n";
|
|
}
|
|
if ($ntpservers) {
|
|
push @netent, " option ntp-servers $ntpservers;\n";
|
|
}
|
|
if ($nameservers)
|
|
{
|
|
push @netent, " option domain-name \"$domain\";\n";
|
|
push @netent, " option domain-name-servers $nameservers;\n";
|
|
}
|
|
if ($mtu)
|
|
{
|
|
push @netent, " option interface-mtu $mtu;\n";
|
|
}
|
|
|
|
|
|
# add domain-search if not sles10 or rh5
|
|
my $osv = xCAT::Utils->osver();
|
|
unless (($osv =~ /^sle[sc]10/) || ($osv =~ /^rh.*5$/)) {
|
|
|
|
# want something like "option domain-search "foo.com", "bar.com";"
|
|
my $domainstring = qq~"$domain"~;
|
|
foreach my $dom (@alldomains) {
|
|
chomp $dom;
|
|
if ($dom ne $domain) {
|
|
$domainstring .= qq~, "$dom"~;
|
|
}
|
|
}
|
|
|
|
if ($domain) {
|
|
push @netent, " option domain-search $domainstring;\n";
|
|
}
|
|
}
|
|
#for cumulus ZTP process
|
|
push @netent, " option cumulus-provision-url \"http://$tftp:$httpport/install/postscripts/cumulusztp\";\n";
|
|
|
|
my $ddnserver = $nameservers;
|
|
$ddnserver =~ s/,.*//;
|
|
my $ddnsdomain;
|
|
if ($netcfgs{$net}->{ddnsdomain}) {
|
|
$ddnsdomain = $netcfgs{$net}->{ddnsdomain};
|
|
}
|
|
if ($::XCATSITEVALS{dnshandler} =~ /ddns/) {
|
|
if ($ddnsdomain) {
|
|
push @netent, " ddns-domainname \"" . $ddnsdomain . "\";\n";
|
|
push @netent, " zone $ddnsdomain. {\n";
|
|
} else {
|
|
push @netent, " zone $domain. {\n";
|
|
}
|
|
if ($ddnserver)
|
|
{
|
|
push @netent, " primary $ddnserver; key xcat_key; \n";
|
|
}
|
|
push @netent, " }\n";
|
|
foreach (getzonesfornet($net, $mask)) {
|
|
push @netent, " zone $_ {\n";
|
|
if ($ddnserver)
|
|
{
|
|
push @netent, " primary $ddnserver; key xcat_key; \n";
|
|
}
|
|
push @netent, " }\n";
|
|
}
|
|
}
|
|
|
|
my $tmpmaskn = unpack("N", inet_aton($mask));
|
|
my $maskbits = 32;
|
|
while (not($tmpmaskn & 1)) {
|
|
$maskbits--;
|
|
$tmpmaskn = $tmpmaskn >> 1;
|
|
}
|
|
|
|
# $lstatements = 'if exists gpxe.bus-id { filename = \"\"; } else if exists client-architecture { filename = \"xcat/xnba.kpxe\"; } '.$lstatements;
|
|
push @netent, " if option user-class-identifier = \"xNBA\" and option client-architecture = 00:00 { #x86, xCAT Network Boot Agent\n";
|
|
push @netent, " always-broadcast on;\n";
|
|
push @netent, " filename = \"http://$tftp:$httpport/tftpboot/xcat/xnba/nets/" . $net . "_" . $maskbits . "\";\n";
|
|
push @netent, " } else if option user-class-identifier = \"xNBA\" and option client-architecture = 00:09 { #x86, xCAT Network Boot Agent\n";
|
|
push @netent, " filename = \"http://$tftp:$httpport/tftpboot/xcat/xnba/nets/" . $net . "_" . $maskbits . ".uefi\";\n";
|
|
push @netent, " } else if option client-architecture = 00:00 { #x86\n";
|
|
push @netent, " filename \"xcat/xnba.kpxe\";\n";
|
|
push @netent, " } else if option vendor-class-identifier = \"Etherboot-5.4\" { #x86\n";
|
|
push @netent, " filename \"xcat/xnba.kpxe\";\n";
|
|
push @netent,
|
|
" } else if option client-architecture = 00:07 { #x86_64 uefi\n ";
|
|
push @netent, " filename \"xcat/xnba.efi\";\n";
|
|
push @netent,
|
|
" } else if option client-architecture = 00:09 { #x86_64 uefi alternative id\n ";
|
|
push @netent, " filename \"xcat/xnba.efi\";\n";
|
|
push @netent,
|
|
" } else if option client-architecture = 00:02 { #ia64\n ";
|
|
push @netent, " filename \"elilo.efi\";\n";
|
|
push @netent,
|
|
" } else if option client-architecture = 00:0b { #aaarch64\n ";
|
|
push @netent, " filename \"boot/grub2/grub2.aarch64\";\n";
|
|
push @netent,
|
|
" } else if option client-architecture = 00:0e { #OPAL-v3\n ";
|
|
push @netent, " option conf-file = \"http://$tftp:$httpport/tftpboot/pxelinux.cfg/p/" . $net . "_" . $maskbits . "\";\n";
|
|
push @netent,
|
|
" } else if substring (option vendor-class-identifier,0,11) = \"onie_vendor\" { #for onie on cumulus switch\n";
|
|
push @netent, " option www-server = \"http://$tftp:$httpport/install/onie/onie-installer\";\n";
|
|
push @netent,
|
|
" } else if substring(filename,0,1) = null { #otherwise, provide yaboot if the client isn't specific\n ";
|
|
push @netent, " filename \"/yaboot\";\n";
|
|
push @netent, " }\n";
|
|
|
|
if ($range) {
|
|
foreach my $singlerange (split /;/, $range) {
|
|
push @netent, " range dynamic-bootp $singlerange;\n"
|
|
}
|
|
}
|
|
push @netent, " } # $net\/$mask subnet_end\n";
|
|
splice(@dhcpconf, $idx, 0, @netent);
|
|
}
|
|
}
|
|
|
|
######################################################
|
|
# Generate network configuration for aix
|
|
######################################################
|
|
sub gen_aix_net
|
|
{
|
|
my $myip = shift;
|
|
my $net = shift;
|
|
my $mask = shift;
|
|
my $gateway = shift;
|
|
my $tftp = shift;
|
|
my $logservers = shift;
|
|
my $ntpservers = shift;
|
|
my $domain = shift;
|
|
my $nameservers = shift;
|
|
my $range = shift;
|
|
my $mtu = shift;
|
|
|
|
my $idx = 0;
|
|
while ($idx <= $#dhcpconf)
|
|
{
|
|
if ($dhcpconf[$idx] =~ /#Network configuration end\n/)
|
|
{
|
|
last;
|
|
}
|
|
$idx++;
|
|
}
|
|
|
|
unless ($dhcpconf[$idx] =~ /#Network configuration end\n/)
|
|
{
|
|
return 1; #TODO: this is an error condition
|
|
}
|
|
|
|
$range =~ s/ /-/;
|
|
my @netent = ("network $net $mask\n{\n");
|
|
if ($gateway)
|
|
{
|
|
if ($gateway eq '<xcatmaster>')
|
|
{
|
|
if (xCAT::NetworkUtils->ip_forwarding_enabled())
|
|
{
|
|
$gateway = $myip;
|
|
}
|
|
else
|
|
{
|
|
$gateway = '';
|
|
}
|
|
}
|
|
if (xCAT::NetworkUtils::isInSameSubnet($gateway, $net, $mask, 1))
|
|
{
|
|
push @netent, " option 3 $gateway\n";
|
|
}
|
|
else
|
|
{
|
|
$callback->(
|
|
{
|
|
error => [
|
|
"Specified gateway $gateway is not valid for $net/$mask, must be on same network"
|
|
],
|
|
errorcode => [1]
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
# if ($tftp)
|
|
# {
|
|
# push @netent, " option 66 $tftp\n";
|
|
# }
|
|
if ($logservers) {
|
|
$logservers =~ s/,/ /g;
|
|
push @netent, " option 7 $logservers\n";
|
|
} elsif ($myip) {
|
|
push @netent, " option 7 $myip\n";
|
|
}
|
|
if ($mtu) {
|
|
push @netent, " option 26 $mtu\n";
|
|
}
|
|
if ($ntpservers) {
|
|
$ntpservers =~ s/,/ /g;
|
|
push @netent, " option 42 $ntpservers\n";
|
|
} elsif ($myip) {
|
|
push @netent, " option 42 $myip\n";
|
|
}
|
|
push @netent, " option 15 \"$domain\"\n";
|
|
if ($nameservers)
|
|
{
|
|
$nameservers =~ s/,/ /g;
|
|
push @netent, " option 6 $nameservers\n";
|
|
}
|
|
push @netent, " subnet $net $range\n {\n";
|
|
push @netent, " } # $net/$mask ip configuration end\n";
|
|
push @netent, "} # $net/$mask subnet_end\n\n";
|
|
|
|
splice(@dhcpconf, $idx, 0, @netent);
|
|
}
|
|
|
|
sub addnic
|
|
{
|
|
if ($::XCATSITEVALS{externaldhcpservers}) { return; }
|
|
my $nic = shift;
|
|
my $conf = shift;
|
|
my $firstindex = 0;
|
|
my $lastindex = 0;
|
|
unless (grep /} # $nic nic_end/, @$conf)
|
|
{ #add a section if not there
|
|
#$restartdhcp=1;
|
|
#print "Adding NIC $nic\n";
|
|
if ($nic eq '!remote!') {
|
|
push @$conf, "#shared-network $nic {\n";
|
|
push @$conf, "#\} # $nic nic_end\n";
|
|
} else {
|
|
push @$conf, "shared-network $nic {\n";
|
|
push @$conf, "\} # $nic nic_end\n";
|
|
}
|
|
|
|
}
|
|
|
|
#return; #Don't touch it, it should already be fine..
|
|
#my $idx=0;
|
|
#while ($idx <= $#dhcpconf) {
|
|
# if ($dhcpconf[$idx] =~ /^shared-network $nic {/) {
|
|
# $firstindex = $idx; # found the first place to chop...
|
|
# } elsif ($dhcpconf[$idx] =~ /} # $nic network_end/) {
|
|
# $lastindex=$idx;
|
|
# }
|
|
# $idx++;
|
|
#}
|
|
#print Dumper(\@dhcpconf);
|
|
#if ($firstindex and $lastindex) {
|
|
# splice @dhcpconf,$firstindex,($lastindex-$firstindex+1);
|
|
#}
|
|
#print Dumper(\@dhcpconf);
|
|
}
|
|
|
|
sub writeout
|
|
{
|
|
if ($::XCATSITEVALS{externaldhcpservers}) { return; }
|
|
|
|
# add the new entries to the dhcp config file
|
|
my $targ;
|
|
open($targ, '>', $dhcpconffile);
|
|
my $idx;
|
|
my $skipone;
|
|
foreach $idx (0 .. $#dhcpconf)
|
|
{
|
|
#avoid writing out empty shared network declarations
|
|
if ($dhcpconf[$idx] =~ /^shared-network/ and $dhcpconf[ $idx + 1 ] =~ /^} .* nic_end/) {
|
|
$skipone = 1;
|
|
next;
|
|
} elsif ($skipone) {
|
|
$skipone = 0;
|
|
next;
|
|
}
|
|
print $targ $dhcpconf[$idx];
|
|
}
|
|
|
|
if ($^O eq 'aix')
|
|
{
|
|
# add back any NIM entries that were saved earlier
|
|
if (@aixcfg) {
|
|
foreach $idx (0 .. $#aixcfg)
|
|
{
|
|
print $targ $aixcfg[$idx];
|
|
}
|
|
}
|
|
}
|
|
close($targ);
|
|
@dhcpconf = (); #dispose of the file contents in memory, no longer needed
|
|
@aixcfg = ();
|
|
|
|
|
|
if (@dhcp6conf) {
|
|
open($targ, '>', $dhcp6conffile);
|
|
foreach $idx (0 .. $#dhcp6conf)
|
|
{
|
|
if ($dhcp6conf[$idx] =~ /^shared-network/ and $dhcp6conf[ $idx + 1 ] =~ /^} .* nic_end/) {
|
|
$skipone = 1;
|
|
next;
|
|
} elsif ($skipone) {
|
|
$skipone = 0;
|
|
next;
|
|
}
|
|
print $targ $dhcp6conf[$idx];
|
|
}
|
|
close($targ);
|
|
@dhcp6conf = ();
|
|
}
|
|
}
|
|
|
|
sub newconfig6 {
|
|
if ($::XCATSITEVALS{externaldhcpservers}) { return; }
|
|
|
|
#phase 1, basic working
|
|
#phase 2, ddns too, evaluate other stuff from dhcpv4 as applicable
|
|
push @dhcp6conf, "#xCAT generated dhcp configuration\n";
|
|
push @dhcp6conf, "\n";
|
|
push @dhcp6conf, "ddns-update-style interim;\n";
|
|
push @dhcp6conf, "ignore client-updates;\n";
|
|
|
|
# push @dhcp6conf, "update-static-leases on;\n";
|
|
push @dhcp6conf, "omapi-port 7912;\n"; #Enable omapi...
|
|
push @dhcp6conf, "key xcat_key {\n";
|
|
push @dhcp6conf, " algorithm hmac-md5;\n";
|
|
my $passtab = xCAT::Table->new('passwd', -create => 1);
|
|
(my $passent) =
|
|
$passtab->getAttribs({ key => 'omapi', username => 'xcat_key' }, 'password');
|
|
my $secret = encode_base64(genpassword(32)); #Random from set of 62^32
|
|
chomp $secret;
|
|
if ($passent->{password}) { $secret = $passent->{password}; }
|
|
else
|
|
{
|
|
$callback->(
|
|
{
|
|
data =>
|
|
["The dhcp server must be restarted for OMAPI function to work"]
|
|
}
|
|
);
|
|
$passtab->setAttribs({ key => 'omapi' },
|
|
{ username => 'xcat_key', password => $secret });
|
|
}
|
|
|
|
push @dhcp6conf, " secret \"" . $secret . "\";\n";
|
|
push @dhcp6conf, "};\n";
|
|
push @dhcp6conf, "omapi-key xcat_key;\n";
|
|
|
|
#that is all for pristine ipv6 config
|
|
}
|
|
|
|
sub newconfig
|
|
{
|
|
if ($::XCATSITEVALS{externaldhcpservers}) { return; }
|
|
return newconfig_aix() if ($^O eq 'aix');
|
|
|
|
# This function puts a standard header in and enough to make omapi work.
|
|
my $passtab = xCAT::Table->new('passwd', -create => 1);
|
|
push @dhcpconf, "#xCAT generated dhcp configuration\n";
|
|
push @dhcpconf, "\n";
|
|
push @dhcpconf, "option conf-file code 209 = text;\n";
|
|
push @dhcpconf, "option space isan;\n";
|
|
push @dhcpconf, "option isan-encap-opts code 43 = encapsulate isan;\n";
|
|
push @dhcpconf, "option isan.iqn code 203 = string;\n";
|
|
push @dhcpconf, "option isan.root-path code 201 = string;\n";
|
|
push @dhcpconf, "option space gpxe;\n";
|
|
push @dhcpconf, "option gpxe-encap-opts code 175 = encapsulate gpxe;\n";
|
|
push @dhcpconf, "option gpxe.bus-id code 177 = string;\n";
|
|
push @dhcpconf, "option user-class-identifier code 77 = string;\n";
|
|
push @dhcpconf, "option gpxe.no-pxedhcp code 176 = unsigned integer 8;\n";
|
|
push @dhcpconf, "option tcode code 101 = text;\n";
|
|
|
|
push @dhcpconf, "option iscsi-initiator-iqn code 203 = string;\n"; #Only via gPXE, not a standard
|
|
push @dhcpconf, "ddns-update-style interim;\n";
|
|
push @dhcpconf, "ignore client-updates;\n"; #Windows clients like to do all caps, very un xCAT-like
|
|
|
|
# push @dhcpconf, "update-static-leases on;\n"; #makedns rendered optional
|
|
push @dhcpconf,
|
|
"option client-architecture code 93 = unsigned integer 16;\n";
|
|
if ($::XCATSITEVALS{timezone}) {
|
|
push @dhcpconf, "option tcode \"" . $::XCATSITEVALS{timezone} . "\";\n";
|
|
}
|
|
push @dhcpconf, "option gpxe.no-pxedhcp 1;\n";
|
|
push @dhcpconf, "option www-server code 114 = string;\n";
|
|
push @dhcpconf, "option cumulus-provision-url code 239 = text;\n";
|
|
push @dhcpconf, "\n";
|
|
push @dhcpconf, "omapi-port 7911;\n"; #Enable omapi...
|
|
push @dhcpconf, "key xcat_key {\n";
|
|
push @dhcpconf, " algorithm hmac-md5;\n";
|
|
(my $passent) =
|
|
$passtab->getAttribs({ key => 'omapi', username => 'xcat_key' }, 'password');
|
|
my $secret = encode_base64(genpassword(32)); #Random from set of 62^32
|
|
chomp $secret;
|
|
if ($passent->{password}) { $secret = $passent->{password}; }
|
|
else
|
|
{
|
|
$callback->(
|
|
{
|
|
data =>
|
|
["The dhcp server must be restarted for OMAPI function to work"]
|
|
}
|
|
);
|
|
$passtab->setAttribs({ key => 'omapi' },
|
|
{ username => 'xcat_key', password => $secret });
|
|
}
|
|
|
|
push @dhcpconf, " secret \"" . $secret . "\";\n";
|
|
push @dhcpconf, "};\n";
|
|
push @dhcpconf, "omapi-key xcat_key;\n";
|
|
push @dhcpconf, ('class "pxe" {' . "\n", " match if substring (option vendor-class-identifier, 0, 9) = \"PXEClient\";\n", " ddns-updates off;\n", " max-lease-time 600;\n", "}\n");
|
|
}
|
|
|
|
sub newconfig_aix
|
|
{
|
|
push @dhcpconf, "#xCAT generated dhcp configuration\n";
|
|
push @dhcpconf, "\n";
|
|
|
|
#push @dhcpconf, "numLogFiles 4\n";
|
|
#push @dhcpconf, "logFileSize 100\n";
|
|
#push @dhcpconf, "logFileName /var/log/dhcpsd.log\n";
|
|
#push @dhcpconf, "logItem SYSERR\n";
|
|
#push @dhcpconf, "logItem OBJERR\n";
|
|
#push @dhcpconf, "logItem PROTERR\n";
|
|
#push @dhcpconf, "logItem WARNING\n";
|
|
#push @dhcpconf, "logItem EVENT\n";
|
|
#push @dhcpconf, "logItem ACTION\n";
|
|
#push @dhcpconf, "logItem INFO\n";
|
|
#push @dhcpconf, "logItem ACNTING\n";
|
|
#push @dhcpconf, "logItem TRACE\n";
|
|
|
|
push @dhcpconf, "leaseTimeDefault 43200 seconds\n";
|
|
push @dhcpconf, "#Network configuration begin\n";
|
|
push @dhcpconf, "#Network configuration end\n";
|
|
}
|
|
|
|
|
|
sub genpassword
|
|
{
|
|
|
|
#Generate a pseudo-random password of specified length
|
|
my $length = shift;
|
|
my $password = '';
|
|
my $characters =
|
|
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
|
|
srand; #have to reseed, rand is not rand otherwise
|
|
while (length($password) < $length)
|
|
{
|
|
$password .= substr($characters, int(rand 63), 1);
|
|
}
|
|
return $password;
|
|
}
|
|
|
|
1;
|