#!/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 Socket; use strict; use warnings "all"; my $netipmodule = eval {require Net::IP;}; my $socket6support = eval { require Socket6 }; our @ISA = qw(Exporter); #-------------------------------------------------------------------------------- =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); 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); $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 Returns: ip address Globals: cache: %::hostiphash Error: none Example: my $ip = xCAT::NetworkUtils->getipaddr($hostname); Comments: none =cut #------------------------------------------------------------------------------- sub getipaddr() { my ($class, $iporhost) = @_; if (!defined($iporhost)) { return undef; } if (ref($iporhost) eq 'ARRAY') { $iporhost = @{$iporhost}[0]; if (!$iporhost) { return undef; } } 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 ($family, $socket, $protocol, $ip, $name) = Socket6::getaddrinfo($iporhost,0); if ($ip) { return (Socket6::getnameinfo($ip, Socket6::NI_NUMERICHOST()))[0]; } return undef; } else { #return inet_ntoa(inet_aton($iporhost)) #TODO, what if no scoket6 support, but passing in a IPv6 hostname? my $packed_ip; $iporhost and $packed_ip = inet_aton($iporhost); if (!$packed_ip) { return undef; } 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) = @_; if ($ip =~ /\d+\.\d+\.\d+\.\d+/) {# ipv4 address $ip =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/; my $ipnum = ($1<<24)+($2<<16)+($3<<8)+$4; $mask =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/; my $masknum = ($1<<24)+($2<<16)+($3<<8)+$4; $subnet =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/; my $netnum = ($1<<24)+($2<<16)+($3<<8)+$4; if (($ipnum & $masknum) == $netnum) { return 1; } else { return 0; } } else { # for ipv6 if ($netipmodule) { my $eip = Net::IP::ip_expand_address ($ip,6); my $enet = Net::IP::ip_expand_address ($subnet,6); my $bmask = Net::IP::ip_get_mask($mask,6); my $bip = Net::IP::ip_iptobin($eip,6); my $bipnet = $bip & $bmask; my $bnet = Net::IP::ip_iptobin($enet,6); if (!$bipnet || !$bnet) { return 0; } if ($bipnet == $bnet) { return 1; } } # else, can not check without Net::IP module 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 -p $conf_file`; } 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; } # can not do this without Net::IP module if (!$netipmodule) { return 0; } my $ip = new Net::IP ("fe80::/$prefixlength") or die (Net::IP::Error()); my $mask = $ip->mask(); return $mask; } 1;