xcat-core/perl-xCAT/xCAT/NetworkUtils.pm

625 lines
18 KiB
Perl
Executable File

#!/usr/bin/env perl
# IBM(c) 2010 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::NetworkUtils;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
# if AIX - make sure we include perl 5.8.2 in INC path.
# Needed to find perl dependencies shipped in deps tarball.
if ($^O =~ /^aix/i) {
use lib "/usr/opt/perl5/lib/5.8.2/aix-thread-multi";
use lib "/usr/opt/perl5/lib/5.8.2";
use lib "/usr/opt/perl5/lib/site_perl/5.8.2/aix-thread-multi";
use lib "/usr/opt/perl5/lib/site_perl/5.8.2";
}
use lib "$::XCATROOT/lib/perl";
use POSIX qw(ceil);
use File::Path;
use Math::BigInt;
use Socket;
use strict;
use warnings "all";
my $socket6support = eval { require Socket6 };
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(getipaddr);
#--------------------------------------------------------------------------------
=head1 xCAT::NetworkUtils
=head2 Package Description
This program module file, is a set of network utilities used by xCAT commands.
=cut
#-------------------------------------------------------------
#-------------------------------------------------------------------------------
=head3 gethostnameandip
Works for both IPv4 and IPv6.
Takes either a host name or an IP address string
and performs a lookup on that name,
returns an array with two elements: the hostname, the ip address
if the host name or ip address can not be resolved,
the corresponding element in the array will be undef
Arguments:
hostname or ip address
Returns: the hostname and the ip address
Globals:
Error:
none
Example:
my ($host, $ip) = xCAT::NetworkUtils->gethostnameandip($iporhost);
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub gethostnameandip()
{
my ($class, $iporhost) = @_;
if (($iporhost =~ /\d+\.\d+\.\d+\.\d+/) || ($iporhost =~ /:/)) #ip address
{
return (xCAT::NetworkUtils->gethostname($iporhost), $iporhost);
}
else #hostname
{
return ($iporhost, xCAT::NetworkUtils->getipaddr($iporhost));
}
}
#-------------------------------------------------------------------------------
=head3 gethostname
Works for both IPv4 and IPv6.
Takes an IP address string and performs a lookup on that name,
returns the hostname of the ip address
if the ip address can not be resolved, returns undef
Arguments:
ip address
Returns: the hostname
Globals:
cache: %::iphosthash
Error:
none
Example:
my $host = xCAT::NetworkUtils->gethostname($ip);
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub gethostname()
{
my ($class, $iporhost) = @_;
if (!defined($iporhost))
{
return undef;
}
if (ref($iporhost) eq 'ARRAY')
{
$iporhost = @{$iporhost}[0];
if (!$iporhost)
{
return undef;
}
}
if (($iporhost !~ /\d+\.\d+\.\d+\.\d+/) && ($iporhost !~ /:/))
{
#why you do so? pass in a hostname and only want a hostname??
return $iporhost;
}
#cache, do not lookup DNS each time
if (defined($::iphosthash{$iporhost}) && $::iphosthash{$iporhost})
{
return $::iphosthash{$iporhost};
}
else
{
if ($socket6support) # the getaddrinfo and getnameinfo supports both IPv4 and IPv6
{
my ($family, $socket, $protocol, $ip, $name) = Socket6::getaddrinfo($iporhost,0,AF_UNSPEC,SOCK_STREAM,6); #specifically ask for TCP capable records, maximizing chance of no more than one return per v4/v6
my $host = (Socket6::getnameinfo($ip))[0];
if ($host eq $iporhost) # can not resolve
{
return undef;
}
if ($host)
{
$host =~ s/\..*//; #short hostname
}
return $host;
}
else
{
#it is possible that no Socket6 available,
#but passing in IPv6 address, such as ::1 on loopback
if ($iporhost =~ /:/)
{
return undef;
}
my $hostname = gethostbyaddr(inet_aton($iporhost), AF_INET);
if ( $hostname ) {
$hostname =~ s/\..*//; #short hostname
}
return $hostname;
}
}
}
#-------------------------------------------------------------------------------
=head3 getipaddr
Works for both IPv4 and IPv6.
Takes a hostname string and performs a lookup on that name,
returns the the ip address of the hostname
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
Error:
none
Example:
my $ip = xCAT::NetworkUtils->getipaddr($hostname);
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub getipaddr
{
my $iporhost = shift;
if ($iporhost eq 'xCAT::NetworkUtils') { #was called with -> syntax
$iporhost = shift;
}
my %extraarguments = @_;
if (!defined($iporhost))
{
return undef;
}
if (ref($iporhost) eq 'ARRAY')
{
$iporhost = @{$iporhost}[0];
if (!$iporhost)
{
return undef;
}
}
#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})
{
return $::hostiphash{$iporhost};
}
else
{
if ($socket6support) # the getaddrinfo and getnameinfo supports both IPv4 and IPv6
{
my @returns;
my $reqfamily=AF_UNSPEC;
if ($extraarguments{OnlyV6}) {
$reqfamily=AF_INET6;
} elsif ($extraarguments{OnlyV4}) {
$reqfamily=AF_INET;
}
my @addrinfo = Socket6::getaddrinfo($iporhost,0,$reqfamily,SOCK_STREAM,6);
my ($family, $socket, $protocol, $ip, $name) = splice(@addrinfo,0,5);
while ($ip)
{
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($_);
}
push(@returns,$bignumber);
} else {
push @returns,(Socket6::getnameinfo($ip, Socket6::NI_NUMERICHOST()))[0];
}
if (scalar @addrinfo and $extraarguments{GetAllAddresses}) {
($family, $socket, $protocol, $ip, $name) = splice(@addrinfo,0,5);
} else {
$ip=0;
}
}
unless ($extraarguments{GetAllAddresses}) {
return $returns[0];
}
return @returns;
}
else
{
#return inet_ntoa(inet_aton($iporhost))
#TODO, what if no scoket6 support, but passing in a IPv6 hostname?
if ($iporhost =~ /:/) { #ipv6
return undef;
#die "Attempt to process IPv6 address, but system does not have requisite IPv6 perl support";
}
my $packed_ip;
$iporhost and $packed_ip = inet_aton($iporhost);
if (!$packed_ip)
{
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);
}
}
}
#-------------------------------------------------------------------------------
=head3 linklocaladdr
Only for IPv6.
Takes a mac address, calculate the IPv6 link local address
Arguments:
mac address
Returns:
ipv6 link local address. returns undef if passed in a invalid mac address
Globals:
Error:
none
Example:
my $linklocaladdr = xCAT::NetworkUtils->linklocaladdr($mac);
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub linklocaladdr {
my ($class, $mac) = @_;
$mac = lc($mac);
my $localprefix = "fe80";
my ($m1, $m2, $m3, $m6, $m7, $m8);
# mac address can be 00215EA376B0 or 00:21:5E:A3:76:B0
if($mac =~ /^([0-9A-Fa-f]{2}).*?([0-9A-Fa-f]{2}).*?([0-9A-Fa-f]{2}).*?([0-9A-Fa-f]{2}).*?([0-9A-Fa-f]{2}).*?([0-9A-Fa-f]{2})$/)
{
($m1, $m2, $m3, $m6, $m7, $m8) = ($1, $2, $3, $4, $5, $6);
}
else
{
#not a valid mac address
return undef;
}
my ($m4, $m5) = ("ff","fe");
#my $bit = (int $m1) & 2;
#if ($bit) {
# $m1 = $m1 - 2;
#} else {
# $m1 = $m1 + 2;
#}
$m1 = hex($m1);
$m1 = $m1 ^ 2;
$m1 = sprintf("%x", $m1);
$m1 = $m1 . $m2;
$m3 = $m3 . $m4;
$m5 = $m5 . $m6;
$m7 = $m7 . $m8;
my $laddr = join ":", $m1, $m3, $m5, $m7;
$laddr = join "::", $localprefix, $laddr;
return $laddr;
}
#-------------------------------------------------------------------------------
=head3 ishostinsubnet
Works for both IPv4 and IPv6.
Takes an ip address, the netmask and a subnet,
chcek if the ip address is in the subnet
Arguments:
ip address, netmask, subnet
Returns:
1 - if the ip address is in the subnet
0 - if the ip address is NOT in the subnet
Globals:
Error:
none
Example:
if(xCAT::NetworkUtils->ishostinsubnet($ip, $netmask, $subnet);
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub ishostinsubnet {
my ($class, $ip, $mask, $subnet) = @_;
my $numbits=32;
if ($ip =~ /:/) {#ipv6
$numbits=128;
}
if ($mask) {
$mask=getipaddr($mask,GetNumber=>1);
} else { #CIDR notation supported
if ($subnet =~ /\//) {
($subnet,$mask) = split /\//,$subnet,2;
$mask=Math::BigInt->new("0b".("1"x$mask).("0"x($numbits-$mask)));
} else {
die "ishostinsubnet must either be called with a netmask or CIDR /bits notation";
}
}
$ip = getipaddr($ip,GetNumber=>1);
$subnet = getipaddr($subnet,GetNumber=>1);
$ip &= $mask;
if ($ip && $subnet && ($ip == $subnet)) {
return 1;
} else {
return 0;
}
}
#-----------------------------------------------------------------------------
=head3 setup_ip_forwarding
Sets up ip forwarding on localhost
=cut
#-----------------------------------------------------------------------------
sub setup_ip_forwarding
{
my ($class, $enable)=@_;
if (xCAT::Utils->isLinux()) {
my $conf_file="/etc/sysctl.conf";
`grep "net.ipv4.ip_forward" $conf_file`;
if ($? == 0) {
`sed -i "s/^net.ipv4.ip_forward = .*/net.ipv4.ip_forward = $enable/" $conf_file`;
} else {
`echo "net.ipv4.ip_forward = $enable" >> $conf_file`;
}
`sysctl -e -p $conf_file`; # workaround for redhat bug 639821
}
else
{
`no -o ipforwarding=$enable`;
}
return 0;
}
#-------------------------------------------------------------------------------
=head3 prefixtomask
Convert the IPv6 prefix length(e.g. 64) to the netmask(e.g. ffff:ffff:ffff:ffff:0000:0000:0000:0000).
Till now, the netmask format ffff:ffff:ffff:: only works for AIX NIM
Arguments:
prefix length
Returns:
netmask - netmask like ffff:ffff:ffff:ffff:0000:0000:0000:0000
0 - if the prefix length is not correct
Globals:
Error:
none
Example:
my #netmask = xCAT::NetworkUtils->prefixtomask($prefixlength);
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub prefixtomask {
my ($class, $prefixlength) = @_;
if (($prefixlength < 1) || ($prefixlength > 128))
{
return 0;
}
my $number=Math::BigInt->new("0b".("1"x$prefixlength).("0"x(128-$prefixlength)));
my $mask = $number->as_hex();
$mask =~ s/^0x//;
$mask =~ s/(....)/$1/g;
return $mask;
}
#-------------------------------------------------------------------------------
=head3 my_ip_in_subnet
Get the facing ip for some specific network
Arguments:
net - subnet, such as 192.168.0.01
mask - netmask, such as 255.255.255.0
Returns:
facing_ip - The local ip address in the subnet,
returns undef if no local ip address is in the subnet
Globals:
Error:
none
Example:
my $facingip = xCAT::NetworkUtils->my_ip_in_subnet($net, $mask);
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub my_ip_in_subnet
{
my ($class, $net, $mask) = @_;
if (!$net || !$mask)
{
return undef;
}
my $fmask = xCAT::Utils::formatNetmask($mask, 0, 1);
my $localnets = xCAT::Utils->my_nets();
return $localnets->{"$net\/$fmask"};
}
#-------------------------------------------------------------------------------
=head3 ip_forwarding_enabled
Check if the ip_forward enabled on the system
Arguments:
Returns:
1 - The ip_forwarding is eanbled
0 - The ip_forwarding is not eanbled
Globals:
Error:
none
Example:
if(xCAT::NetworkUtils->ip_forwarding_enabled())
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub ip_forwarding_enabled
{
my $enabled;
if (xCAT::Utils->isLinux())
{
$enabled = `sysctl -n net.ipv4.ip_forward`;
chomp($enabled);
}
else
{
$enabled = `no -o ipforwarding`;
chomp($enabled);
$enabled =~ s/ipforwarding\s+=\s+//;
}
return $enabled;
}
#-------------------------------------------------------------------------------
=head3 get_nic_ip
Get the ip address for the node nics
Arguments:
Returns:
Hash of the mapping of the nic and the ip addresses
Globals:
Error:
none
Example:
xCAT::NetworkUtils->get_nic_ip()
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub get_nic_ip
{
my $nic;
my %iphash;
my $cmd = "ifconfig -a";
my $result = `$cmd`;
my $mode = "MULTICAST";
#############################################
# Error running command
#############################################
if ( !$result ) {
return undef;
}
if (xCAT::Utils->isAIX()) {
##############################################################
# Should look like this for AIX:
# en0: flags=4e080863,80<UP,BROADCAST,NOTRAILERS,RUNNING,
# SIMPLEX,MULTICAST,GROUPRT,64BIT,PSEG,CHAIN>
# inet 30.0.0.1 netmask 0xffffff00 broadcast 30.0.0.255
# inet 192.168.2.1 netmask 0xffffff00 broadcast 192.168.2.255
# en1: ...
#
##############################################################
my @adapter = split /(\w+\d+):\s+flags=/, $result;
foreach ( @adapter ) {
if ($_ =~ /^(en\d)/) {
$nic = $1;
next;
}
if ( !($_ =~ /LOOPBACK/ ) and
$_ =~ /UP(,|>)/ and
$_ =~ /$mode/ ) {
my @ip = split /\n/;
for my $ent ( @ip ) {
if ( $ent =~ /^\s*inet\s+(\d+\.\d+\.\d+\.\d+)/ ) {
$iphash{$nic} = $1;
next;
}
}
}
}
}
else {
##############################################################
# Should look like this for Linux:
# eth0 Link encap:Ethernet HWaddr 00:02:55:7B:06:30
# inet addr:9.114.154.193 Bcast:9.114.154.223
# inet6 addr: fe80::202:55ff:fe7b:630/64 Scope:Link
# UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
# RX packets:1280982 errors:0 dropped:0 overruns:0 frame:0
# TX packets:3535776 errors:0 dropped:0 overruns:0 carrier:0
# collisions:0 txqueuelen:1000
# RX bytes:343489371 (327.5 MiB) TX bytes:870969610 (830.6 MiB)
# Base address:0x2600 Memory:fbfe0000-fc0000080
#
# eth1 ...
#
##############################################################
my @adapter= split /\n{2,}/, $result;
foreach ( @adapter ) {
if ( !($_ =~ /LOOPBACK / ) and
$_ =~ /UP / and
$_ =~ /$mode / ) {
my @ip = split /\n/;
for my $ent ( @ip ) {
if ($ent =~ /^(eth\d|ib\d|hf\d)\s+/) {
$nic = $1;
}
if ( $ent =~ /^\s*inet addr:\s*(\d+\.\d+\.\d+\.\d+)/ ) {
$iphash{$nic} = $1;
next;
}
}
}
}
}
return \%iphash;
}
1;