#!/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 xCAT::GlobalDef; 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 getNodeDomains Gets the network domain for a list of nodes The domain value comes from the network definition associated with the node ip address. If the network domain is not set then the default is to use the site.domain value Arguments: list of nodes Returns: error - undef success - hash ref of domains for each node Globals: $::VERBOSE Error: Example: my $nodedomains = xCAT::NetworkUtils->getNodeDomains(\@nodes, $callback); Comments: none =cut #------------------------------------------------------------------------------- sub getNodeDomains() { my $class = shift; my $nodes = shift; my $callback = shift; my @nodelist = @$nodes; my %nodedomains; # Get the network info for each node my %nethash = xCAT::DBobjUtils->getNetwkInfo(\@nodelist, $callback); if (!(%nethash) && $::VERBOSE) { my $rsp; push @{$rsp->{data}}, "Could not get xCAT network definitions for one or more nodes.\n"; xCAT::MsgUtils->message("W", $rsp, $callback); } # get the site domain value my @domains = xCAT::TableUtils->get_site_attribute("domain"); my $sitedomain = $domains[0]; if (!$sitedomain && $::VERBOSE) { my $rsp; push @{$rsp->{data}}, "Cannot get a domain value from the cluster site definition.\n"; xCAT::MsgUtils->message("W", $rsp, $callback); } # for each node - set hash value to network domain or default # to site domain foreach my $node (@nodelist) { if ($nethash{$node}{domain}) { $nodedomains{$node} = $nethash{$node}{domain}; } else { $nodedomains{$node} = $sitedomain; } } return \%nodedomains; } #------------------------------------------------------------------------------- =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 # 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: xCAT::NetworkUtils->my_hexnets 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: =>; 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: xCAT::NetworkUtils->my_ip_facing 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: xCAT::NetworkUtils->nodeonmynet 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: xCAT::NetworkUtils->thishostisnot 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; } #-------------------------------------------------------------------------------- =head3 pingNodeStatus This function takes an array of nodes and returns their status using fping. Arguments: nodes-- an array of nodes. Returns: a hash that has the node status. The format is: {alive=>[node1, node3,...], unreachable=>[node4, node2...]} =cut #-------------------------------------------------------------------------------- sub pingNodeStatus { my ($class, @mon_nodes)=@_; my %status=(); my @active_nodes=(); my @inactive_nodes=(); if ((@mon_nodes)&& (@mon_nodes > 0)) { #get all the active nodes my $nodes= join(' ', @mon_nodes); my $temp=`fping -a $nodes 2> /dev/null`; chomp($temp); @active_nodes=split(/\n/, $temp); #get all the inactive nodes by substracting the active nodes from all. my %temp2; if ((@active_nodes) && ( @active_nodes > 0)) { foreach(@active_nodes) { $temp2{$_}=1}; foreach(@mon_nodes) { if (!$temp2{$_}) { push(@inactive_nodes, $_);} } } else {@inactive_nodes=@mon_nodes;} } $status{$::STATUS_ACTIVE}=\@active_nodes; $status{$::STATUS_INACTIVE}=\@inactive_nodes; return %status; } 1;