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

1972 lines
52 KiB
Perl
Raw Normal View History

#!/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) {
unshift(@INC, qw(/usr/opt/perl5/lib/5.8.2/aix-thread-multi /usr/opt/perl5/lib/5.8.2 /usr/opt/perl5/lib/site_perl/5.8.2/aix-thread-multi /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);
my $utildata; #data to persist locally
#--------------------------------------------------------------------------------
=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) {
if ($mask =~ /\//) {
$mask =~ s/^\///;
$mask=Math::BigInt->new("0b".("1"x$mask).("0"x($numbits-$mask)));
} else {
$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::NetworkUtils::formatNetmask($mask, 0, 1);
my $localnets = xCAT::NetworkUtils->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;
}
#-------------------------------------------------------------------------------
=head3 classful_networks_for_net_and_mask
Arguments:
network and mask
Returns:
a list of classful subnets that constitute the entire potentially classless arguments
Globals:
none
Error:
none
Example:
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub classful_networks_for_net_and_mask
{
my $network = shift;
my $mask = shift;
my $given_mask = 0;
if ($mask =~ /\./)
{
$given_mask = 1;
my $masknumber = unpack("N", inet_aton($mask));
$mask = 32;
until ($masknumber % 2)
{
$masknumber = $masknumber >> 1;
$mask--;
}
}
my @results;
my $bitstoeven = (8 - ($mask % 8));
if ($bitstoeven eq 8) { $bitstoeven = 0; }
my $resultmask = $mask + $bitstoeven;
if ($given_mask)
{
$resultmask =
inet_ntoa(pack("N", (2**$resultmask - 1) << (32 - $resultmask)));
}
push @results, $resultmask;
my $padbits = (32 - ($bitstoeven + $mask));
my $numchars = int(($mask + $bitstoeven) / 4);
my $curmask = 2**$mask - 1 << (32 - $mask);
my $nown = unpack("N", inet_aton($network));
$nown = $nown & $curmask;
my $highn = $nown + ((2**$bitstoeven - 1) << (32 - $mask - $bitstoeven));
while ($nown <= $highn)
{
push @results, inet_ntoa(pack("N", $nown));
#$rethash->{substr($nowhex, 0, $numchars)} = $network;
$nown += 1 << (32 - $mask - $bitstoeven);
}
return @results;
}
#-------------------------------------------------------------------------------
=head3 my_hexnets
Arguments:
none
Returns:
Globals:
none
Error:
none
Example:
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub my_hexnets
{
my $rethash;
my @nets = split /\n/, `/sbin/ip addr`;
foreach (@nets)
{
my @elems = split /\s+/;
unless (/^\s*inet\s/)
{
next;
}
(my $curnet, my $maskbits) = split /\//, $elems[2];
my $bitstoeven = (4 - ($maskbits % 4));
if ($bitstoeven eq 4) { $bitstoeven = 0; }
my $padbits = (32 - ($bitstoeven + $maskbits));
my $numchars = int(($maskbits + $bitstoeven) / 4);
my $curmask = 2**$maskbits - 1 << (32 - $maskbits);
my $nown = unpack("N", inet_aton($curnet));
$nown = $nown & $curmask;
my $highn =
$nown + ((2**$bitstoeven - 1) << (32 - $maskbits - $bitstoeven));
while ($nown <= $highn)
{
my $nowhex = sprintf("%08x", $nown);
$rethash->{substr($nowhex, 0, $numchars)} = $curnet;
$nown += 1 << (32 - $maskbits - $bitstoeven);
}
}
return $rethash;
}
#-------------------------------------------------------------------------------
=head3 get_host_from_ip
Description:
Get the hostname of an IP addresses. First from hosts table, and then try system resultion.
If there is a shortname, it will be returned. Otherwise it will return long name. If the IP cannot be resolved, return undef;
Arguments:
$ip: the IP to get;
Returns:
Return: the hostname.
For an example
Globals:
none
Error:
none
Example:
xCAT::NetworkUtils::get_host_from_ip('192.168.200.1')
Comments:
=cut
#-----------------------------------------------------------------------
sub get_host_from_ip
{
my $ip = shift;
}
#-------------------------------------------------------------------------------
=head3 isPingable
Description:
Check if an IP address can be pinged
Arguments:
$ip: the IP to ping;
Returns:
Return: 1 indicates yes; 0 indicates no.
For an example
Globals:
none
Error:
none
Example:
xCAT::NetworkUtils::isPingable('192.168.200.1')
Comments:
none
=cut
#-----------------------------------------------------------------------
my %PING_CACHE;
sub isPingable
{
my $ip = shift;
my $rc;
if ( exists $PING_CACHE{ $ip})
{
$rc = $PING_CACHE{ $ip};
}
else
{
my $res = `LANG=C ping -c 1 -w 5 $ip 2>&1`;
if ( $res =~ /100% packet loss/g)
{
$rc = 1;
}
else
{
$rc = 0;
}
$PING_CACHE{ $ip} = $rc;
}
return ! $rc;
}
#-------------------------------------------------------------------------------
=head3 my_nets
Description:
Return a hash ref that contains all subnet and netmask on the mn (or sn). This subroutine can be invoked on both Linux and AIX.
Arguments:
none.
Returns:
Return a hash ref. Each entry will be: <subnet/mask>=><existing ip>;
For an example:
'192.168.200.0/255.255.255.0' => '192.168.200.246';
For an example
Globals:
none
Error:
none
Example:
xCAT::NetworkUtils::my_nets().
Comments:
none
=cut
#-----------------------------------------------------------------------
sub my_nets
{
require xCAT::Table;
my $rethash;
my @nets;
my $v6net;
my $v6ip;
if ( $^O eq 'aix')
{
@nets = split /\n/, `/usr/sbin/ifconfig -a`;
}
else
{
@nets = split /\n/, `/sbin/ip addr`; #could use ip route, but to match hexnets...
}
foreach (@nets)
{
$v6net = '';
my @elems = split /\s+/;
unless (/^\s*inet/)
{
next;
}
my $curnet; my $maskbits;
if ( $^O eq 'aix')
{
if ($elems[1] eq 'inet6')
{
$v6net=$elems[2];
$v6ip=$elems[2];
$v6ip =~ s/\/.*//; # ipv6 address 4000::99/64
$v6ip =~ s/\%.*//; # ipv6 address ::1%1/128
}
else
{
$curnet = $elems[2];
$maskbits = formatNetmask( $elems[4], 2, 1);
}
}
else
{
if ($elems[1] eq 'inet6')
{
next; #Linux IPv6 TODO, do not return IPv6 networks on Linux for now
}
($curnet, $maskbits) = split /\//, $elems[2];
}
if (!$v6net)
{
my $curmask = 2**$maskbits - 1 << (32 - $maskbits);
my $nown = unpack("N", inet_aton($curnet));
$nown = $nown & $curmask;
my $textnet=inet_ntoa(pack("N",$nown));
$textnet.="/$maskbits";
$rethash->{$textnet} = $curnet;
}
else
{
$rethash->{$v6net} = $v6ip;
}
}
# now get remote nets
my $nettab = xCAT::Table->new("networks");
#my $sitetab = xCAT::Table->new("site");
#my $master = $sitetab->getAttribs({key=>'master'},'value');
#$master = $master->{value};
my @masters = xCAT::TableUtils->get_site_attribute("master");
my $master = $masters[0];
my @vnets = $nettab->getAllAttribs('net','mgtifname','mask');
foreach(@vnets){
my $n = $_->{net};
my $if = $_->{mgtifname};
my $nm = $_->{mask};
if (!$n || !$if || $nm)
{
next; #incomplete network
}
if ($if =~ /!remote!/) { #only take in networks with special interface
$nm = formatNetmask($nm, 0 , 1);
$n .="/$nm";
#$rethash->{$n} = $if;
$rethash->{$n} = $master;
}
}
return $rethash;
}
#-------------------------------------------------------------------------------
=head3 my_if_netmap
Arguments:
none
Returns:
hash of networks to interface names
Globals:
none
Error:
none
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub my_if_netmap
{
my $net;
if (scalar(@_))
{ #called with the other syntax
$net = shift;
}
my @rtable = split /\n/, `netstat -rn`;
if ($?)
{
return "Unable to run netstat, $?";
}
my %retmap;
foreach (@rtable)
{
if (/^\D/) { next; } #skip headers
if (/^\S+\s+\S+\s+\S+\s+\S*G/)
{
next;
} #Skip networks that require gateways to get to
/^(\S+)\s.*\s(\S+)$/;
$retmap{$1} = $2;
}
return \%retmap;
}
#-------------------------------------------------------------------------------
=head3 my_ip_facing
Returns my ip address
Linux only
Arguments:
nodename
Returns:
Globals:
none
Error:
none
Example:
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub my_ip_facing
{
my $peer = shift;
if (@_)
{
$peer = shift;
}
return my_ip_facing_aix( $peer) if ( $^O eq 'aix');
my $peernumber = inet_aton($peer); #TODO: IPv6 support
unless ($peernumber) { return undef; }
my $noden = unpack("N", inet_aton($peer));
my @nets = split /\n/, `/sbin/ip addr`;
foreach (@nets)
{
my @elems = split /\s+/;
unless (/^\s*inet\s/)
{
next;
}
(my $curnet, my $maskbits) = split /\//, $elems[2];
my $curmask = 2**$maskbits - 1 << (32 - $maskbits);
my $curn = unpack("N", inet_aton($curnet));
if (($noden & $curmask) == ($curn & $curmask))
{
return $curnet;
}
}
return undef;
}
#-------------------------------------------------------------------------------
=head3 my_ip_facing_aix
Returns my ip address
AIX only
Arguments:
nodename
Returns:
Globals:
none
Error:
none
Example:
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub my_ip_facing_aix
{
my $peer = shift;
my @nets = `ifconfig -a`;
chomp @nets;
foreach my $net (@nets)
{
my ($curnet,$netmask);
if ( $net =~ /^\s*inet\s+([\d\.]+)\s+netmask\s+(\w+)\s+broadcast/)
{
($curnet,$netmask) = ($1,$2);
}
elsif ($net =~ /^\s*inet6\s+(.*)$/)
{
($curnet,$netmask) = split('/', $1);
}
else
{
next;
}
if (isInSameSubnet($peer, $curnet, $netmask, 2))
{
return $curnet;
}
}
return undef;
}
#-------------------------------------------------------------------------------
=head3 formatNetmask
Description:
Transform netmask to one of 3 formats (255.255.255.0, 24, 0xffffff00).
Arguments:
$netmask: the original netmask
$origType: the original netmask type. The valid value can be 0, 1, 2:
Type 0: 255.255.255.0
Type 1: 24
Type 2: 0xffffff00
$newType: the new netmask type, valid values can be 0,1,2, as above.
Returns:
Return undef if any error. Otherwise return the netmask in new format.
Globals:
none
Error:
none
Example:
xCAT::NetworkUtils::formatNetmask( '24', 1, 0); #return '255.255.255.0'.
Comments:
none
=cut
#-----------------------------------------------------------------------
sub formatNetmask
{
my $mask = shift;
my $origType = shift;
my $newType = shift;
my $maskn;
if ( $origType == 0)
{
$maskn = unpack("N", inet_aton($mask));
}
elsif ( $origType == 1)
{
$maskn = 2**$mask - 1 << (32 - $mask);
}
elsif( $origType == 2)
{
$maskn = hex $mask;
}
else
{
return undef;
}
if ( $newType == 0)
{
return inet_ntoa( pack('N', $maskn));
}
if ( $newType == 1)
{
my $bin = unpack ("B32", pack("N", $maskn));
my @dup = ( $bin =~ /(1{1})0*/g);
return scalar ( @dup);
}
if ( $newType == 2)
{
return sprintf "0x%1x", $maskn;
}
return undef;
}
#-------------------------------------------------------------------------------
=head3 isInSameSubnet
Description:
Check if 2 given IP addresses are in same subnet
Arguments:
$ip1: the first IP
$ip2: the second IP
$mask: the netmask, here are 3 possible netmask types, following are examples for these 3 types:
Type 0: 255.255.255.0
Type 1: 24
Type 2: 0xffffff00
$masktype: the netmask type, 3 possible values: 0,1,2, as indicated above
Returns:
1: they are in same subnet
2: not in same subnet
Globals:
none
Error:
none
Example:
xCAT::NetworkUtils::isInSameSubnet( '192.168.10.1', '192.168.10.2', '255.255.255.0', 0);
Comments:
none
=cut
#-----------------------------------------------------------------------
sub isInSameSubnet
{
my $ip1 = shift;
my $ip2 = shift;
my $mask = shift;
my $maskType = shift;
$ip1 = xCAT::NetworkUtils->getipaddr($ip1);
$ip2 = xCAT::NetworkUtils->getipaddr($ip2);
if (!defined($ip1) || !defined($ip2))
{
return undef;
}
if ((($ip1 =~ /\d+\.\d+\.\d+\.\d+/) && ($ip2 !~ /\d+\.\d+\.\d+\.\d+/))
||(($ip1 !~ /\d+\.\d+\.\d+\.\d+/) && ($ip2 =~ /\d+\.\d+\.\d+\.\d+/)))
{
#ipv4 and ipv6 can not be in the same subnet
return undef;
}
if (($ip1 =~ /\d+\.\d+\.\d+\.\d+/) && ($ip2 =~ /\d+\.\d+\.\d+\.\d+/))
{
my $maskn;
if ( $maskType == 0)
{
$maskn = unpack("N", inet_aton($mask));
}
elsif ( $maskType == 1)
{
$maskn = 2**$mask - 1 << (32 - $mask);
}
elsif( $maskType == 2)
{
$maskn = hex $mask;
}
else
{
return undef;
}
my $ip1n = unpack("N", inet_aton($ip1));
my $ip2n = unpack("N", inet_aton($ip2));
return ( ( $ip1n & $maskn) == ( $ip2n & $maskn) );
}
else
{
#ipv6
if (($ip1 =~ /\%/) || ($ip2 =~ /\%/))
{
return undef;
}
my $netipmodule = eval {require Net::IP;};
if ($netipmodule) {
my $eip1 = Net::IP::ip_expand_address ($ip1,6);
my $eip2 = Net::IP::ip_expand_address ($ip2,6);
my $bmask = Net::IP::ip_get_mask($mask,6);
my $bip1 = Net::IP::ip_iptobin($eip1,6);
my $bip2 = Net::IP::ip_iptobin($eip2,6);
if (($bip1 & $bmask) == ($bip2 & $bmask)) {
return 1;
}
} # else, can not check without Net::IP module
return undef;
}
}
#-------------------------------------------------------------------------------
=head3 nodeonmynet - checks to see if node is on any network this server is attached to or remote network potentially managed by this system
Arguments:
Node name
Returns: 1 if node is on the network
Globals:
none
Error:
none
Example:
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub nodeonmynet
{
require xCAT::Table;
my $nodetocheck = shift;
if (scalar(@_))
{
$nodetocheck = shift;
}
my $nodeip = getNodeIPaddress( $nodetocheck );
if (!$nodeip)
{
return 0;
}
unless ($nodeip =~ /\d+\.\d+\.\d+\.\d+/)
{
#IPv6
if ( $^O eq 'aix')
{
my @subnets = get_subnet_aix();
for my $net_ent (@subnets)
{
if ($net_ent !~ /-/)
{
#ipv4
next;
}
my ($net, $interface, $mask, $flag) = split/-/ , $net_ent;
if (xCAT::NetworkUtils->ishostinsubnet($nodeip, $mask, $net))
{
return 1;
}
}
} else {
my @v6routes = split /\n/,`ip -6 route`;
foreach (@v6routes) {
if (/via/ or /^unreachable/ or /^fe80::\/64/) {
#only count local ones, remote ones can be caught in next loop
#also, link-local would be a pitfall,
#since more context than address is
#needed to determine locality
next;
}
s/ .*//; #remove all after the space
if (xCAT::NetworkUtils->ishostinsubnet($nodeip,'',$_)) { #bank on CIDR support
return 1;
}
}
}
my $nettab=xCAT::Table->new("networks");
my @vnets = $nettab->getAllAttribs('net','mgtifname','mask');
foreach (@vnets) {
if ((defined $_->{mgtifname}) && ($_->{mgtifname} eq '!remote!'))
{
if (xCAT::NetworkUtils->ishostinsubnet($nodeip, $_->{mask}, $_->{net}))
{
return 1;
}
}
}
return 0;
}
my $noden = unpack("N", inet_aton($nodeip));
my @nets;
if ($utildata->{nodeonmynetdata} and $utildata->{nodeonmynetdata}->{pid} == $$) {
@nets = @{$utildata->{nodeonmynetdata}->{nets}};
} else {
if ( $^O eq 'aix')
{
my @subnets = get_subnet_aix();
for my $net_ent (@subnets)
{
if ($net_ent =~ /-/)
{
#ipv6
next;
}
my @ents = split /:/ , $net_ent;
push @nets, $ents[0] . '/' . $ents[2] . ' dev ' . $ents[1];
}
}
else
{
@nets = split /\n/, `/sbin/ip route`;
}
my $nettab=xCAT::Table->new("networks");
my @vnets = $nettab->getAllAttribs('net','mgtifname','mask');
foreach (@vnets) {
if ((defined $_->{mgtifname}) && ($_->{mgtifname} eq '!remote!'))
{ #global scoped network
my $curm = unpack("N", inet_aton($_->{mask}));
my $bits=32;
until ($curm & 1) {
$bits--;
$curm=$curm>>1;
}
push @nets,$_->{'net'}."/".$bits." dev remote";
}
}
$utildata->{nodeonmynetdata}->{pid}=$$;
$utildata->{nodeonmynetdata}->{nets} = \@nets;
}
foreach (@nets)
{
my @elems = split /\s+/;
unless ($elems[1] =~ /dev/)
{
next;
}
(my $curnet, my $maskbits) = split /\//, $elems[0];
my $curmask = 2**$maskbits - 1 << (32 - $maskbits);
my $curn = unpack("N", inet_aton($curnet));
if (($noden & $curmask) == $curn)
{
return 1;
}
}
return 0;
}
#-------------------------------------------------------------------------------
=head3 getNodeIPaddress
Arguments:
Node name only one at a time
Returns: ip address(s)
Globals:
none
Error:
none
Example: my $c1 = xCAT::NetworkUtils::getNodeIPaddress($nodetocheck);
=cut
#-------------------------------------------------------------------------------
sub getNodeIPaddress
{
require xCAT::Table;
my $nodetocheck = shift;
my $port = shift;
my $nodeip;
$nodeip = xCAT::NetworkUtils->getipaddr($nodetocheck);
if (!$nodeip)
{
my $hoststab = xCAT::Table->new( 'hosts');
my $ent = $hoststab->getNodeAttribs( $nodetocheck, ['ip'] );
if ( $ent->{'ip'} ) {
$nodeip = $ent->{'ip'};
}
}
if ( $nodeip ) {
return $nodeip;
} else {
return undef;
}
}
#-------------------------------------------------------------------------------
=head3 thishostisnot
returns 0 if host is not the same
Arguments:
hostname
Returns:
Globals:
none
Error:
none
Example:
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub thishostisnot
{
my $comparison = shift;
if (scalar(@_))
{
$comparison = shift;
}
my @ips;
if ( $^O eq 'aix')
{
@ips = split /\n/, `/usr/sbin/ifconfig -a`;
}
else
{
@ips = split /\n/, `/sbin/ip addr`;
}
my $comp = xCAT::NetworkUtils->getipaddr($comparison);
if ($comp)
{
foreach (@ips)
{
if (/^\s*inet.?\s+/)
{
my @ents = split(/\s+/);
my $ip = $ents[2];
$ip =~ s/\/.*//;
$ip =~ s/\%.*//;
if ($ip eq $comp)
{
return 0;
}
}
}
}
return 1;
}
#-----------------------------------------------------------------------------
=head3 gethost_ips (AIX and Linux)
Will use ifconfig to determine all possible ip addresses for the
host it is running on and then gethostbyaddr to get all possible hostnames
input:
output: array of ipaddress(s) and hostnames
example: @ips=xCAT::NetworkUtils->gethost_ips();
=cut
#-----------------------------------------------------------------------------
sub gethost_ips
{
my ($class) = @_;
my $cmd;
my @ipaddress;
$cmd = "ifconfig" . " -a";
$cmd = $cmd . "| grep \"inet\"";
my @result = xCAT::Utils->runcmd($cmd, 0);
if ($::RUNCMD_RC != 0)
{
xCAT::MsgUtils->message("S", "Error from $cmd\n");
exit $::RUNCMD_RC;
}
foreach my $addr (@result)
{
my @ip;
if (xCAT::Utils->isLinux())
{
if ($addr =~ /inet6/)
{
#TODO, Linux ipv6
}
else
{
my ($inet, $addr1, $Bcast, $Mask) = split(" ", $addr);
@ip = split(":", $addr1);
push @ipaddress, $ip[1];
}
}
else
{ #AIX
if ($addr =~ /inet6/)
{
$addr =~ /\s*inet6\s+([\da-fA-F:]+).*\/(\d+)/;
my $v6ip = $1;
my $v6mask = $2;
if ($v6ip)
{
push @ipaddress, $v6ip;
}
}
else
{
my ($inet, $addr1, $netmask, $mask1, $Bcast, $bcastaddr) =
split(" ", $addr);
push @ipaddress, $addr1;
}
}
}
my @names = @ipaddress;
foreach my $ipaddr (@names)
{
my $hostname = xCAT::NetworkUtils->gethostname($ipaddr);
if ($hostname)
{
my @shorthost = split(/\./, $hostname);
push @ipaddress, $shorthost[0];
}
}
return @ipaddress;
}
#-------------------------------------------------------------------------------
=head3 get_subnet_aix
Description:
To get present subnet configuration by parsing the output of 'netstat'. Only designed for AIX.
Arguments:
None
Returns:
@aix_nrn : An array with entries in format "net:nic:netmask:flag". Following is an example entry:
9.114.47.224:en0:27:U
Globals:
none
Error:
none
Example:
my @nrn =xCAT::NetworkUtils::get_subnet_aix
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub get_subnet_aix
{
my @netstat_res = `/usr/bin/netstat -rn`;
chomp @netstat_res;
my @aix_nrn;
for my $entry ( @netstat_res)
{
#We need to find entries like:
#Destination Gateway Flags Refs Use If Exp Groups
#9.114.47.192/27 9.114.47.205 U 1 1 en0
#4000::/64 link#4 UCX 1 0 en2 - -
my ( $net, $netmask, $flag, $nic);
if ( $entry =~ /^\s*([\d\.]+)\/(\d+)\s+[\d\.]+\s+(\w+)\s+\d+\s+\d+\s(\w+)/)
{
( $net, $netmask, $flag, $nic) = ($1,$2,$3,$4);
my @dotsec = split /\./, $net;
for ( my $i = 4; $i > scalar(@dotsec); $i--)
{
$net .= '.0';
}
push @aix_nrn, "$net:$nic:$netmask:$flag" if ($net ne '127.0.0.0');
}
elsif ($entry =~ /^\s*([\dA-Fa-f\:]+)\/(\d+)\s+.*?\s+(\w+)\s+\d+\s+\d+\s(\w+)/)
{
#print "=====$entry====\n";
( $net, $netmask, $flag, $nic) = ($1,$2,$3,$4);
# for ipv6, can not use : as the delimiter
push @aix_nrn, "$net-$nic-$netmask-$flag" if ($net ne '::')
}
}
return @aix_nrn;
}
#-----------------------------------------------------------------------------
=head3 determinehostname and ip address(s)
Used on the service node to figure out what hostname and ip address(s)
are valid names and addresses
Input: None
Output: ipaddress(s),nodename
=cut
#-----------------------------------------------------------------------------
sub determinehostname
{
my $hostname;
my $hostnamecmd = "/bin/hostname";
my @thostname = xCAT::Utils->runcmd($hostnamecmd, 0);
if ($::RUNCMD_RC != 0)
{ # could not get hostname
xCAT::MsgUtils->message("S",
"Error $::RUNCMD_RC from $hostnamecmd command\n");
exit $::RUNCMD_RC;
}
$hostname = $thostname[0];
#get all potentially valid abbreviations, and pick the one that is ok
#by 'noderange'
my @hostnamecandidates;
my $nodename;
while ($hostname =~ /\./) {
push @hostnamecandidates,$hostname;
$hostname =~ s/\.[^\.]*//;
}
push @hostnamecandidates,$hostname;
my $checkhostnames = join(',',@hostnamecandidates);
my @validnodenames = xCAT::NodeRange::noderange($checkhostnames);
unless (scalar @validnodenames) { #If the node in question is not in table, take output literrally.
push @validnodenames,$hostnamecandidates[0];
}
#now, noderange doesn't guarantee the order, so we search the preference order, most to least specific.
foreach my $host (@hostnamecandidates) {
if (grep /^$host$/,@validnodenames) {
$nodename = $host;
last;
}
}
my @ips = xCAT::NetworkUtils->gethost_ips;
my @hostinfo = (@ips, $nodename);
return @hostinfo;
}
#-----------------------------------------------------------------------------
=head3 toIP
IPv4 function to convert hostname to IP address
=cut
#-----------------------------------------------------------------------------
sub toIP
{
if (($_[0] =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) || ($_[0] =~ /:/))
{
return ([0, $_[0]]);
}
my $ip = xCAT::NetworkUtils->getipaddr($_[0]);
if (!$ip)
{
return ([1, "Cannot Resolve: $_[0]\n"]);
}
return ([0, $ip]);
}
#-------------------------------------------------------------------------------
=head3 validate_ip
Validate list of IPs
Arguments:
List of IPs
Returns:
1 - Invalid IP address in the list
0 - IP addresses are all valid
Globals:
none
Error:
none
Example:
if (xCAT::NetworkUtils->validate_ip($IP)) {}
Comments:
none
=cut
#-------------------------------------------------------------------------------
sub validate_ip
{
my ($class, @IPs) = @_;
foreach (@IPs) {
my $ip = $_;
#TODO need more check for IPv6 address
if ($ip =~ /:/)
{
return([0]);
}
###################################
# Length is 4 for IPv4 addresses
###################################
my (@octets) = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
if ( scalar(@octets) != 4 ) {
return( [1,"Invalid IP address1: $ip"] );
}
foreach my $octet ( @octets ) {
if (( $octet < 0 ) or ( $octet > 255 )) {
return( [1,"Invalid IP address2: $ip"] );
}
}
}
return([0]);
}
#-------------------------------------------------------------------------------
=head3 getFacingIP
Gets the ip address of the adapter of the localhost that is facing the
the given node.
Assume it is the same as my_ip_facing...
Arguments:
The name of the node that is facing the localhost.
Returns:
The ip address of the adapter that faces the node.
=cut
#-------------------------------------------------------------------------------
sub getFacingIP
{
my ($class, $node) = @_;
my $ip;
my $cmd;
my @ipaddress;
my $nodeip = inet_ntoa(inet_aton($node));
unless ($nodeip =~ /\d+\.\d+\.\d+\.\d+/)
{
return 0; #Not supporting IPv6 here IPV6TODO
}
$cmd = "ifconfig" . " -a";
$cmd = $cmd . "| grep \"inet \"";
my @result = xCAT::Utils->runcmd($cmd, 0);
if ($::RUNCMD_RC != 0)
{
xCAT::MsgUtils->message("S", "Error from $cmd\n");
exit $::RUNCMD_RC;
}
# split node address
my ($n1, $n2, $n3, $n4) = split('\.', $nodeip);
foreach my $addr (@result)
{
my $ip;
my $mask;
if (xCAT::Utils->isLinux())
{
my ($inet, $addr1, $Bcast, $Mask) = split(" ", $addr);
if ((!$addr1) || (!$Mask)) { next; }
my @ips = split(":", $addr1);
my @masks = split(":", $Mask);
$ip = $ips[1];
$mask = $masks[1];
}
else
{ #AIX
my ($inet, $addr1, $netmask, $mask1, $Bcast, $bcastaddr) =
split(" ", $addr);
if ((!$addr1) && (!$mask1)) { next; }
$ip = $addr1;
$mask1 =~ s/0x//;
$mask =
`printf "%d.%d.%d.%d" \$(echo "$mask1" | sed 's/../0x& /g')`;
}
if ($ip && $mask)
{
# split interface IP
my ($h1, $h2, $h3, $h4) = split('\.', $ip);
# split mask
my ($m1, $m2, $m3, $m4) = split('\.', $mask);
# AND this interface IP with the netmask of the network
my $a1 = ((int $h1) & (int $m1));
my $a2 = ((int $h2) & (int $m2));
my $a3 = ((int $h3) & (int $m3));
my $a4 = ((int $h4) & (int $m4));
# AND node IP with the netmask of the network
my $b1 = ((int $n1) & (int $m1));
my $b2 = ((int $n2) & (int $m2));
my $b3 = ((int $n3) & (int $m3));
my $b4 = ((int $n4) & (int $m4));
if (($b1 == $a1) && ($b2 == $a2) && ($b3 == $a3) && ($b4 == $a4))
{
return $ip;
}
}
}
xCAT::MsgUtils->message("S", "Cannot find master for the node $node\n");
return 0;
}
#-------------------------------------------------------------------------------
=head3 isIpaddr
returns 1 if parameter is has a valid IP address form.
Arguments:
dot qulaified IP address: e.g. 1.2.3.4
Returns:
1 - if legal IP address
0 - if not legal IP address.
Globals:
none
Error:
none
Example:
if ($ipAddr) { blah; }
Comments:
Doesn't test if the IP address is on the network,
just tests its form.
=cut
#-------------------------------------------------------------------------------
sub isIpaddr
{
my $addr = shift;
if (($addr) && ($addr =~ /xCAT::NetworkUtils/))
{
$addr = shift;
}
unless ( $addr )
{
return 0;
}
#print "addr=$addr\n";
if ($addr !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/)
{
return 0;
}
if ($1 > 255 || $1 == 0 || $2 > 255 || $3 > 255 || $4 > 255)
{
return 0;
}
else
{
return 1;
}
}
#-------------------------------------------------------------------------------
=head3 getNodeNetworkCfg
Description:
Get node network configuration, including "IP, hostname(the nodename),and netmask" by this node's name.
Arguments:
node: the nodename
Returns:
Return an array, which contains (IP,hostname,gateway,netmask').
undef - Failed to get the network configuration info
Globals:
none
Error:
none
Example:
my ($ip,$host,undef,$mask) = xCAT::NetworkUtils::getNodeNetworkCfg('node1');
Comments:
Presently gateway is always blank. Need to be improved.
=cut
#-------------------------------------------------------------------------------
sub getNodeNetworkCfg
{
my $node = shift;
my $nets = xCAT::NetworkUtils::my_nets();
my $ip = xCAT::NetworkUtils->getipaddr($node);
my $mask = undef;
for my $net (keys %$nets)
{
my $netname;
($netname,$mask) = split /\//, $net;
last if ( xCAT::NetworkUtils::isInSameSubnet( $netname, $ip, $mask, 1));
}
return ($ip, $node, undef, xCAT::NetworkUtils::formatNetmask($mask,1,0));
}
#-------------------------------------------------------------------------------
=head3 get_hdwr_ip
Description:
Get hardware(CEC, BPA) IP from the hosts table, and then /etc/hosts.
Arguments:
node: the nodename(cec, or bpa)
Returns:
Return the node IP
-1 - Failed to get the IP.
Globals:
none
Error:
none
Example:
my $ip = xCAT::NetworkUtils::get_hdwr_ip('node1');
Comments:
Used in FSPpower FSPflash, FSPinv.
=cut
#-------------------------------------------------------------------------------
sub get_hdwr_ip
{
require xCAT::Table;
my $node = shift;
my $ip = undef;
my $Rc = undef;
my $ip_tmp_res = xCAT::NetworkUtils::toIP($node);
($Rc, $ip) = @$ip_tmp_res;
if ( $Rc ) {
my $hosttab = xCAT::Table->new( 'hosts' );
if ( $hosttab) {
my $node_ip_hash = $hosttab->getNodeAttribs( $node,[qw(ip)]);
$ip = $node_ip_hash->{ip};
}
}
if (!$ip) {
return undef;
}
return $ip;
}
1;