-Implement phase 1 or IPv6 with DHCP (still needs Utils twek for nodeonmynet)

git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@8683 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
This commit is contained in:
jbjohnso 2011-01-17 19:17:38 +00:00
parent 6308de24e7
commit 95e5fbfa0f
2 changed files with 307 additions and 34 deletions
perl-xCAT/xCAT
xCAT-server/lib/xcat/plugins

@ -19,6 +19,7 @@ if ($^O =~ /^aix/i) {
use lib "$::XCATROOT/lib/perl";
use POSIX qw(ceil);
use File::Path;
use Math::BigInt;
use Socket;
use strict;
use warnings "all";
@ -26,6 +27,8 @@ my $netipmodule = eval {require Net::IP;};
my $socket6support = eval { require Socket6 };
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(getipaddr);
#--------------------------------------------------------------------------------
@ -168,6 +171,8 @@ sub gethostname()
if the hostname can not be resolved, returns undef
Arguments:
hostname
Optional:
GetNumber=>1 (return the address as a BigInt instead of readable string)
Returns: ip address
Globals:
cache: %::hostiphash
@ -180,9 +185,13 @@ sub gethostname()
=cut
#-------------------------------------------------------------------------------
sub getipaddr()
sub getipaddr
{
my ($class, $iporhost) = @_;
my $iporhost = shift;
if ($iporhost eq 'xCAT::NetworkUtils') { #was called with -> syntax
$iporhost = shift;
}
my %extraarguments = @_;
if (!defined($iporhost))
{
@ -198,11 +207,13 @@ sub getipaddr()
}
}
if ($iporhost and ($iporhost =~ /\d+\.\d+\.\d+\.\d+/) || ($iporhost =~ /:/))
{
#pass in an ip and only want an ip??
return $iporhost;
}
#go ahead and do the reverse lookup on ip, useful to 'frontend' aton/pton and also to
#spit out a common abbreviation if leading zeroes or using different ipv6 presentation rules
#if ($iporhost and ($iporhost =~ /\d+\.\d+\.\d+\.\d+/) || ($iporhost =~ /:/))
#{
# #pass in an ip and only want an ip??
# return $iporhost;
#}
#cache, do not lookup DNS each time
if ($::hostiphash and defined($::hostiphash{$iporhost}) && $::hostiphash{$iporhost})
@ -216,7 +227,17 @@ sub getipaddr()
my ($family, $socket, $protocol, $ip, $name) = Socket6::getaddrinfo($iporhost,0);
if ($ip)
{
return (Socket6::getnameinfo($ip, Socket6::NI_NUMERICHOST()))[0];
if ($extraarguments{GetNumber}) { #return a BigInt for compare, e.g. for comparing ip addresses for determining if they are in a common network or range
my $ip = (Socket6::getnameinfo($ip, Socket6::NI_NUMERICHOST()))[0];
my $bignumber = Math::BigInt->new(0);
foreach (unpack("N*",Socket6::inet_pton($family,$ip))) { #if ipv4, loop will iterate once, for v6, will go 4 times
$bignumber->blsft(32);
$bignumber->badd($_);
}
return $bignumber;
} else {
return (Socket6::getnameinfo($ip, Socket6::NI_NUMERICHOST()))[0];
}
}
return undef;
}
@ -230,6 +251,9 @@ sub getipaddr()
{
return undef;
}
if ($extraarguments{GetNumber}) { #only 32 bits, no for loop needed.
return Math::BigInt->new(unpack("N*",$packed_ip));
}
return inet_ntoa($packed_ip);
}
}

@ -14,43 +14,57 @@ 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::NetworkUtils;
use xCAT::NetworkUtils qw/getipaddr/;
use xCAT::Utils;
use xCAT::NodeRange;
use Fcntl ':flock';
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 $domain;
my $omshell;
my $omshell6; #separate session to DHCPv6 instance of dhcp
my $statements; #Hold custom statements to be slipped into host declarations
my $callback;
my $restartdhcp;
my $restartdhcp6;
my $sitenameservers;
my $sitentpservers;
my $sitelogservers;
my $nrhash;
my $machash;
my $vpdhash;
my $iscsients;
my $chainents;
my $tftpdir = xCAT::Utils->getTftpDir();
use Math::BigInt;
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;
# dhcp 4.x will use /etc/dhcp/dhcpd.conf as the config file
my $dhcp6conffile;
if ( $^O ne 'aix' and -d "/etc/dhcp" ) {
$dhcpconffile = '/etc/dhcp/dhcpd.conf';
$dhcp6conffile = '/etc/dhcp/dhcpd6.conf';
}
my $usingipv6;
sub ipIsDynamic {
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 = inet_aton($ip);
my $number = getipaddr($ip,GetNumber=>1);
unless ($number) { # shouldn't be possible, but pessimistically presume it dynamically if so
return 1;
}
$number = unpack("N*",$number);
foreach (values %dynamicranges) {
if ($_->[0] <= $number and $_->[1] >= $number) {
return 1;
@ -134,6 +148,72 @@ sub delnode
}
}
sub addnode6 {
#omshell to add host dynamically
my $node = shift;
unless ($vpdhash) {
$callback->({error => ["Unable to open vpd table, it may not exist yet"], errorcode => [1] });
return;
}
my $ent = $vpdhash->{$node}->[0]; #tab->getNodeAttribs($node, [qw(mac)]);
unless ($ent and $ent->{uuid})
{
print Dumper($vpdhash);
$callback->( { error => ["Unable to find UUID for $node"], errorcode => [1] });
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 not ipIsDynamic($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";
if ($ip) {
print $omshell6 "set ip-address = $ip\n";
}
print $omshell6 "create\n";
print $omshell6 "close\n";
}
sub addnode
{
@ -360,6 +440,44 @@ sub addnode
}
}
sub addrangedetection {
my $net = shift;
my $trange = $net->{dynamicrange}; #temp range, the dollar sign makes it look strange
my $begin;
my $end;
if ($trange =~ /[ ,-]/) { #a range of one number to another..
$trange =~ s/[,-]/ /g;
$netcfgs{$net->{net}}->{range}=$trange;
($begin,$end) = split / /,$trange;
$dynamicranges{$trange}=[getipaddr($begin,GetNumber=>1),getipaddr($end,GetNumber=>1)];
} elsif ($trange =~ /\//) { #a CIDR style specification for a range that could be described in subnet rules
#we are going to assume that this is a subset of the network (it really ought to be) and therefore all zeroes or all ones is good to include
my $prefix;
my $suffix;
($prefix,$suffix) = split /\//,$trange;
my $numbits;
if ($prefix =~ /:/) { #ipv6
$netcfgs{$net->{net}}->{range}=$trange; #we can put in dhcpv6 ranges verbatim as CIDR
$numbits=128;
} else {
$numbits=32;
}
my $number = getipaddr($prefix,GetNumber=>1);
my $highmask=Math::BigInt->new("0b".("1"x$suffix).("0"x($numbits-$suffix)));
my $lowmask=Math::BigInt->new("0b".("1"x($numbits-$suffix)));
$number &= $highmask; #remove any errant high bits beyond the mask.
$begin = $number->copy();
$number |= $lowmask; #get the highest number in the range,
$end=$number->copy();
$dynamicranges{$trange}=[$begin,$end];
if ($prefix !~ /:/) { #ipv4, must convert CIDR subset to range
my $lowip = inet_ntoa(pack("N*",$begin));
my $highip = inet_ntoa(pack("N*",$end));
$netcfgs{$net->{net}}->{range} = "$lowip $highip";
}
}
}
######################################################
# Add nodes into dhcpsd.cnf. For AIX only
######################################################
@ -717,16 +835,24 @@ sub process_request
$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');
foreach (@vnets) {
my $trange = $_->{dynamicrange}; #temp range, the dollar sign makes it look strange
$trange =~ s/[,-]/ /g;
my $begin;
my $end;
($begin,$end) = split / /,$trange;
$dynamicranges{$trange}=[unpack("N*",inet_aton($begin)),unpack("N*",inet_aton($end))];
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')
{
@ -740,19 +866,30 @@ sub process_request
my @parts = split /\s+/;
push @nrn,$parts[0].":".$parts[7].":".$parts[2].":".$parts[3];
}
my @ip6routes = `ip -6 route`;
foreach (@ip6routes) {
#TODO: filter out multicast? Don't know if multicast groups *can* appear in ip -6 route...
if (/^fe80::\/64/ or /^unreachable/ or /^[^ ]+ via/) { #ignore link-local, junk, and routed networks
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};
#$callback->({data => ["array of nets $n : $if : $nm"]});
if ($if =~ /!remote!/) { #only take in networks with special interface
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 /:/;
@ -772,8 +909,10 @@ sub process_request
if ( $^O ne 'aix')
{
#add the active nics to /etc/sysconfig/dhcpd
if (-e "/etc/sysconfig/dhcpd") {
open DHCPD_FD, "/etc/sysconfig/dhcpd";
my $dhcpver;
foreach $dhcpver ("dhcpd","dhcpd6") {
if (-e "/etc/sysconfig/$dhcpver") {
open DHCPD_FD, "/etc/sysconfig/$dhcpver";
my $syscfg_dhcpd = "";
my $found = 0;
my $dhcpd_key = "DHCPDARGS";
@ -804,13 +943,14 @@ sub process_request
}
close DHCPD_FD;
open DBG_FD, '>', "/etc/sysconfig/dhcpd";
open DBG_FD, '>', "/etc/sysconfig/$dhcpver";
print DBG_FD $syscfg_dhcpd;
close DBG_FD;
} else {
$callback->({error=>"The file /etc/sysconfig/dhcpd doesn't exist, check the dhcp server"});
} elsif ($_ eq "dhcpd" or $usingipv6) {
$callback->({error=>"The file /etc/sysconfig/$_ doesn't exist, check the dhcp server"});
# return;
}
}
}
unless ($dhcpconf[0])
@ -818,16 +958,23 @@ sub process_request
$restartdhcp=1;
newconfig();
}
if ($usingipv6 and not $dhcp6conf[0]) {
$restartdhcp6=1;
newconfig6();
}
if ( $^O ne 'aix')
{
foreach (keys %activenics)
{
addnic($_);
addnic($_,\@dhcpconf);
if ($usingipv6) {
addnic($_,\@dhcp6conf);
}
}
}
if ((!$req->{node}) && (grep /^-a$/, @{$req->{arg}}))
{
if (grep /-d$/, @{$req->{arg}})
if (grep /-d$/, @{$req->{arg}}) #delete all entries
{
$req->{node} = [];
my $nodelist = xCAT::Table->new('nodelist');
@ -837,7 +984,7 @@ sub process_request
push @{$req->{node}}, $_->{node};
}
}
else
else #add all entries
{
$req->{node} = [];
my $mactab = xCAT::Table->new('mac');
@ -867,6 +1014,9 @@ sub process_request
addnet($line[0], $line[2]);
}
}
foreach (@nrn6) { #do the ipv6 networks
addnet6($_); #already did all the filtering before putting into nrn6
}
if ($req->{node})
{
@ -901,11 +1051,18 @@ sub process_request
#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";
print $omshell "connect\n";
if ($usingipv6) {
open($omshell6, "|/usr/bin/omshell > /dev/null");
print $omshell6 "port 7912\n";
print $omshell6 "key "
. $ent->{username} . " \""
. $ent->{password} . "\"\n";
print $omshell6 "connect\n";
}
}
my $nrtab = xCAT::Table->new('noderes');
@ -922,6 +1079,8 @@ sub process_request
}
my $mactab = xCAT::Table->new('mac');
$machash = $mactab->getNodesAttribs($req->{node},['mac']);
my $vpdtab = xCAT::Table->new('vpd');
$vpdhash = $vpdtab->getNodesAttribs($req->{node},['uuid']);
foreach (@{$req->{node}})
{
if (grep /^-d$/, @{$req->{arg}})
@ -941,11 +1100,14 @@ sub process_request
{
next;
}
#print "addnode $_\n";
addnode $_;
if ($usingipv6) {
addnode6 $_;
}
}
}
close($omshell) if ($^O ne 'aix');
close($omshell6) if ($^O ne 'aix');
foreach my $node (@{$req->{node}})
{
unless ($machash)
@ -1117,6 +1279,52 @@ sub putmyselffirst {
}
return $srvlist;
}
sub addnet6
{
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
while ($idx <= $#dhcp6conf)
{
if ($dhcp6conf[$idx] =~ /\} # $iface nic_end/) {
last;
}
$idx++;
}
unless ($dhcp6conf[$idx] =~ /\} # $iface nic_end\n/) {
return 1; #TODO: this is an error condition
}
}
my @netent = (
" subnet6 $net {\n",
" max-lease-time 43200;\n",
" min-lease-time 43200;\n",
" default-lease-time 43200;\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)
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
{
my $net = shift;
@ -1434,18 +1642,19 @@ sub gen_aix_net
sub addnic
{
my $nic = shift;
my $conf = shift;
my $firstindex = 0;
my $lastindex = 0;
unless (grep /} # $nic nic_end/, @dhcpconf)
unless (grep /} # $nic nic_end/, @$conf)
{ #add a section if not there
$restartdhcp=1;
print "Adding NIC $nic\n";
if ($nic =~ /!remote!/) {
push @dhcpconf, "#shared-network $nic {\n";
push @dhcpconf, "#\} # $nic nic_end\n";
push @$conf, "#shared-network $nic {\n";
push @$conf, "#\} # $nic nic_end\n";
} else {
push @dhcpconf, "shared-network $nic {\n";
push @dhcpconf, "\} # $nic nic_end\n";
push @$conf, "shared-network $nic {\n";
push @$conf, "\} # $nic nic_end\n";
}
}
@ -1476,6 +1685,46 @@ sub writeout
print $targ $_;
}
close($targ);
if (@dhcp6conf) {
open($targ, '>', $dhcp6conffile);
foreach (@dhcp6conf)
{
print $targ $_;
}
close($targ);
}
}
sub newconfig6 {
#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, "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