#!/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 Sys::Hostname;
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 @nodelist = @$nodes;
    my %nodedomains;

    # Get the network info for each node
    my %nethash = xCAT::DBobjUtils->getNetwkInfo(\@nodelist);

    # get the site domain value
    my @domains    = xCAT::TableUtils->get_site_attribute("domain");
    my $sitedomain = $domains[0];

    # for each node - set hash value to network domain or default
    #		to site domain
    foreach my $node (@nodelist) {
        unless (defined($node)) { next; }
        if (defined($nethash{$node}) && $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)
        GetAllAddresses=>1 (return the )
        OnlyV6=>1 ()
        OnlyV4=> ()
    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;
    #}
    my $isip=0;
    if ($iporhost and ($iporhost =~ /\d+\.\d+\.\d+\.\d+/) || ($iporhost =~ /:/)){
        $isip=1;
    }


#print "============================\n";
#print Dumper(\%::hostiphash);
#print "\n";
#print Dumper(\%extraarguments);
#print "\n";
#print "iporhost=$iporhost";
#print "\n";
#print "============================\n";

    #cache, do not lookup DNS each time
    if (
        ((not $extraarguments{OnlyV6}) and (not $extraarguments{GetAllAddresses}))  and defined($::hostiphash{$iporhost}) and $::hostiphash{$iporhost})
    {

        if($extraarguments{GetNumber} ) {
            if($::hostiphash{$iporhost}{Number}){
        #print "YYYYYYYYYY GetNumber Cache Hit!!!YYYYYYYYY\n";
                return $::hostiphash{$iporhost}{Number};
            }
        } elsif($::hostiphash{$iporhost}{hostip}) {
        #print "YYYYYYYYYY dns  Cache Hit!!!YYYYYYYYY\n";
            return $::hostiphash{$iporhost}{hostip};
        }
    }

    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;
        if($isip) {
            @addrinfo=Socket6::getaddrinfo($iporhost, 0, $reqfamily, SOCK_STREAM, 6,Socket6::AI_NUMERICHOST());
        }else{
            @addrinfo=Socket6::getaddrinfo($iporhost, 0, $reqfamily, SOCK_STREAM, 6);
        }
        my ($family, $socket, $protocol, $ip, $name) = splice(@addrinfo, 0, 5);
        unless($reqfamily == AF_INET6){
            if($isip){
               if($name){
                   $::hostiphash{$iporhost}{hostip}=$name;
               }
            }elsif($ip){
                $::hostiphash{$iporhost}{hostip}=$ip;
            }
        }
        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);
                $::hostiphash{$iporhost}{Number}=$returns[0];
            } else {
                push @returns, (Socket6::getnameinfo($ip, Socket6::NI_NUMERICHOST()))[0];
                $::hostiphash{$iporhost}{hostip}=$returns[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;
        }

        my $myip=inet_ntoa($packed_ip);

        unless($isip) {
            $::hostiphash{$iporhost}{hostip}=$myip;
        }

        if ($extraarguments{GetNumber}) { #only 32 bits, no for loop needed.
            my $number=Math::BigInt->new(unpack("N*", $packed_ip));
            $::hostiphash{$iporhost}{Number}=$number;
            return $number;
        }

        return $myip;
    }
}

#-------------------------------------------------------------------------------

=head3  clearcache
    Workaround: Clear the IP address cache in case that the long running process
    (discovery/install monitor) could work as normal when the node's IP address
    is changed.
    Globals:
        cache: %::hostiphash
    Error:
        none
    Example:
        xCAT::NetworkUtils->clearcache();
=cut

#-------------------------------------------------------------------------------
sub clearcache
{
    undef %::hostiphash;
}

#-------------------------------------------------------------------------------

=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) = @_;

    #safe guard
    if (!defined($ip) || !defined($mask) || !defined($subnet))
    {
        return 0;
    }

    my $maskType=0;

    #CIDR notation supported
    if ($subnet && ($subnet =~ /\//)) {
        ($subnet, $mask) = split /\//, $subnet, 2;
        $subnet =~ s/\/.*$//;
        $maskType=1;
    }elsif ($mask) {
        if ($mask =~ /\//) {
            $mask =~ s/^\///;
            $maskType=1;
        } elsif($mask =~ /^0x/i ) {
            $maskType=2;
        }
    }

    my $ret=xCAT::NetworkUtils::isInSameSubnet( $ip, $subnet, $mask, $maskType);
    if(defined $ret and $ret==1){
        return 1;
    }else{
        return 0;
    }
}

sub ishostinsubnet_orig {
    my ($class, $ip, $mask, $subnet) = @_;

    #safe guard
    if (!defined($ip) || !defined($mask) || !defined($subnet))
    {
        return 0;
    }

    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 =~ /\//)) {
            ($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";
        }
    }
    if ($subnet && ($subnet =~ /\//))    #remove CIDR suffix from subnet
    {
        $subnet =~ s/\/.*$//;
    }
    $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`;
`sed -i "s/^#net.ipv4.ip_forward *= *.*/net.ipv4.ip_forward = $enable/" $conf_file`; #debian/ubuntu have different default format
        } 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 setup_ipv6_forwarding

    Sets up ipv6 forwarding on localhost

=cut

#-----------------------------------------------------------------------------
sub setup_ipv6_forwarding
{
    my ($class, $enable) = @_;
    if (xCAT::Utils->isLinux()) {
        my $conf_file = "/etc/sysctl.conf";
        `grep "net.ipv6.conf.all.forwarding" $conf_file`;
        if ($? == 0) {
`sed -i "s/^net.ipv6.conf.all.forwarding = .*/net.ipv6.conf.all.forwarding = $enable/" $conf_file`;
        } else {
            `echo "net.ipv6.conf.all.forwarding = $enable" >> $conf_file`;
        }
        `sysctl -e -p $conf_file`;
    }
    else
    {
        `no -o ip6forwarding=$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 $mode            = "MULTICAST";
    my $payingattention = 0;
    my $interface       = "";
    my $keepcurrentiface;


    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 $cmd    = "ifconfig -a";
        my $result = `$cmd`;
        #############################################
        # Error running command
        #############################################
        if (!$result) {
            return undef;
        }
        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 {    # linux
        my @ipoutput = `ip addr`;
        #############################################
        # Error running command
        #############################################
        if (!@ipoutput) {
            return undef;
        }
        foreach my $line (@ipoutput) {
            if ($line =~ /^\d/) {    # new interface, new context..
                if ($interface and not $keepcurrentiface) {

                    #don't bother reporting unusable nics
                    delete $iphash{$interface};
                }
                $keepcurrentiface = 0;
                $interface = "";
                if (!($line =~ /LOOPBACK/) and
                    $line =~ /UP( |,|>)/ and
                    $line =~ /$mode/) {

                    $payingattention = 1;
                    $line =~ /^([^:]*): ([^:]*):/;
                    $interface = $2;
                } else {
                    $payingattention = 0;
                    next;
                }
            }
            unless ($payingattention) { next; }
            if ($line =~ /inet/) {
                $keepcurrentiface = 1;
            }
            if ($line =~ /^\s*inet \s*(\d+\.\d+\.\d+\.\d+)/) {
                $iphash{$interface} = $1;
            }
        }
    }
    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 == 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 == 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 in the same network with the specified node
         Linux only
    Arguments:
        nodename
    Returns:
	result and error message or my ip address
	1. If node can not be resolved, the return info will be like this:
	[1, "The $node can not be resolved"].
	2. If no IP found that matching the giving node, the return info will be:
	[2, "The IP address of node $node is in an undefined subnet"].
	3. If IP found:
	[0,ip1,ip2,...]
    Globals:
        none
    Error:
        none
    Example:
        my @ip = xCAT::NetworkUtils->my_ip_facing($peerip)  # return multiple
    Comments:
        none
=cut

#-------------------------------------------------------------------------------
sub my_ip_facing
{
    my $peer = shift;
    if (@_)
    {
        $peer = shift;
    }

    return my_ip_facing_aix($peer) if ($^O eq 'aix');
    my @rst;
    my $peernumber = inet_aton($peer);    #TODO: IPv6 support
    unless ($peernumber) {
        $rst[0] = 1;
        $rst[1] = "The $peer can not be resolved";
        return @rst; }
    my $noden = unpack("N", $peernumber);
    my @nets = split /\n/, `/sbin/ip addr`;

    my @ips;
    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))
        {
            push @ips, $curnet;
        }
    }

    if (@ips) {
        $rst[0] = 0;
        push @rst, @ips;
    } else {
        $rst[0] = 2;
        $rst[1] = "The IP address of node $peer is in an undefined subnet";
    }
    return @rst;
}

#-------------------------------------------------------------------------------

=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;
    my @ips;
    my @rst;
    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))
        {
            push @ips, $curnet;
        }
    }
    if (@ips)
    {
        $rst[0] = 0;
        push @rst, @ips;
    }
    else
    {
        $rst[0] = 2;
        $rst[1] = "The IP address of node $peer is in an undefined subnet";
    }
    return @rst;
}

#-------------------------------------------------------------------------------

=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
        undef: 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} =~ /!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} =~ /!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;
    if ($nodetocheck eq 'xCAT::NetworkUtils') {    #was called with -> syntax
        $nodetocheck = shift;
    }

    # Quick return if pass in an IP
    return $nodetocheck if (xCAT::NetworkUtils->isIpaddr($nodetocheck));

    my $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   checkNodeIPaddress
    Arguments:
       Node name  only one at a time
    Returns: a hash object contains IP or Error
    Globals:
        none
    Example:   my $ipresult = xCAT::NetworkUtils::checkNodeIPaddress($nodetocheck);

=cut

#-------------------------------------------------------------------------------

sub checkNodeIPaddress
{
    require xCAT::Table;
    my $nodetocheck = shift;
    if ($nodetocheck eq 'xCAT::NetworkUtils') {    #was called with -> syntax
        $nodetocheck = shift;
    }
    my $ret;

    my $nodeip;
    my $hoststab = xCAT::Table->new('hosts');
    my $ent = $hoststab->getNodeAttribs($nodetocheck, ['ip']);
    if ($ent->{'ip'}) {
        $nodeip = $ent->{'ip'};
    }

    # Get the IP from DNS
    my $dnsip = xCAT::NetworkUtils->getipaddr($nodetocheck);
    if (!$dnsip)
    {
        $ret->{'error'} = "The $nodetocheck can not be resolved.";
        $ret->{'ip'} = $nodeip if ($nodeip);
    } elsif (!$nodeip) {
        $ret->{'ip'} = $dnsip;
    } else {
        $ret->{'ip'} = $nodeip;
        $ret->{'error'} = "Defined IP address of $nodetocheck is inconsistent with DNS." if ($nodeip ne $dnsip);
    }
    return $ret;
}


#-------------------------------------------------------------------------------

=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_ips1
#{
#    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];
#                $addr1 =~ s/.*://;
#                push @ipaddress, $addr1;
#            }
#        }
#        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;
#}


sub gethost_ips
{
    my ($class) = @_;
    my $cmd;
    my @ipaddress;
    if (xCAT::Utils->isLinux())
    {
        $cmd = "ip -4 --oneline addr show |awk -F ' ' '{print \$4}'|awk -F '/' '{print \$1}'";
        my @result = xCAT::Utils->runcmd($cmd);
        if ($::RUNCMD_RC != 0)
        {
            xCAT::MsgUtils->message("S", "Error from $cmd\n");
            exit $::RUNCMD_RC;
        }

        push @ipaddress, @result;
    }
    else
    {    #AIX

        $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)
        {
            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;
    eval {
        $hostname = hostname;
    };
    if($@){
        xCAT::MsgUtils->message("S","Fail to get hostname: $@\n");
        exit -1;
    }
    #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    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 || $2 =~ /^0\d/ || $3 > 255 || $3 =~ /^0\d/ || $4 > 255 || $4 =~ /^0\d/)
    {
        return 0;
    }
    else
    {
        return 1;
    }
}




#-------------------------------------------------------------------------------

=head3  getNodeNameservers
    Description:
        Get nameservers of  specified nodes.
        The priority: noderes.nameservers > networks.nameservers > site.nameservers
    Arguments:
        node: node name list
    Returns:
        Return a hash ref, of the $nameservers{$node}
        undef - Failed to get the nameservers
    Globals:
        none
    Error:
        none
    Example:
        my $nameservers = xCAT::NetworkUtils::getNodeNameservers(\@node);
    Comments:
        none

=cut

#-------------------------------------------------------------------------------
sub getNodeNameservers {
    my $nodes = shift;
    if ($nodes =~ /xCAT::NetworkUtils/)
    {
        $nodes = shift;
    }
    my @nodelist = @$nodes;
    my %nodenameservers;
    my $nrtab = xCAT::Table->new('noderes', -create => 0);
    my %nrhash = %{ $nrtab->getNodesAttribs(\@nodelist, ['nameservers']) };

    my $nettab  = xCAT::Table->new("networks");
    my %nethash = xCAT::DBobjUtils->getNetwkInfo(\@nodelist);

    my @nameservers     = xCAT::TableUtils->get_site_attribute("nameservers");
    my $sitenameservers = $nameservers[0];


    foreach my $node (@nodelist) {
        if ($nrhash{$node} and $nrhash{$node}->[0] and $nrhash{$node}->[0]->{nameservers})
        {
            $nodenameservers{$node} = $nrhash{$node}->[0]->{nameservers};
        } elsif ($nethash{$node}{nameservers})
        {
            $nodenameservers{$node} = $nethash{$node}{nameservers};
        } elsif ($sitenameservers)
        {
            $nodenameservers{$node} = $sitenameservers;
        }
    }

    return \%nodenameservers;
}


#-------------------------------------------------------------------------------

=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;
    if ($node =~ /xCAT::NetworkUtils/)
    {
        $node = shift;
    }

    my $ip      = xCAT::NetworkUtils->getipaddr($node);
    my $mask    = undef;
    my $gateway = undef;

    my $nettab = xCAT::Table->new("networks");
    if ($nettab) {
        my @nets = $nettab->getAllAttribs('net', 'mask', 'gateway');
        foreach my $net (@nets) {
            if (xCAT::NetworkUtils::isInSameSubnet($net->{'net'}, $ip, $net->{'mask'}, 0)) {
                $gateway = $net->{'gateway'};
                $mask    = $net->{'mask'};
            }
        }
    }

    return ($ip, $node, $gateway, xCAT::NetworkUtils::formatNetmask($mask, 0, 0));
}

#-------------------------------------------------------------------------------

=head3   getNodesNetworkCfg
    Description:
        Get network configuration (ip,netmask,gateway) for a group of nodes

    Arguments:
        nodes: the group of nodes
    Returns:
        If failed: (1, error_msg)
        If success: (0, the hash variable store network configuration info for nodes that get matching network entry)
    Error:
        none
    Example:
        my ($ret, $hash) = xCAT::NetworkUtils::getNodesNetworkCfg($noderange);
    Comments:

=cut

#-------------------------------------------------------------------------------

sub getNodesNetworkCfg
{
    my $nodes = shift;
    if ($nodes =~ /xCAT::NetworkUtils/) {
        $nodes = shift;
    }
    my @nets   = ();
    my $nettab = xCAT::Table->new("networks");
    if ($nettab) {
        my @error_net = ();
        my @all_nets = $nettab->getAllAttribs('net', 'mask', 'gateway');
        foreach my $net (@all_nets) {
            my $gateway = $net->{gateway};
            if (defined($gateway) and ($gateway eq '<xcatmaster>')) {
                my @gatewayd = xCAT::NetworkUtils->my_ip_facing($net->{'net'});
                unless ($gatewayd[0]) {
                    $gateway = $gatewayd[1];
                }
            }
            push @nets, { net => $net->{net}, mask => $net->{mask}, gateway => $gateway };
        }
        $nettab->close;
    }
    else {
        return (1, "Open \"networks\" table failed");
    }
    if (!scalar(@nets)) {
        return (1, "No entry find in \"networks\" table");
    }
    my %rethash = ();
    foreach my $node (@$nodes) {
        my $ip = xCAT::NetworkUtils->getipaddr($node);
        foreach my $net (@nets) {
            if (xCAT::NetworkUtils::isInSameSubnet($net->{'net'}, $ip, $net->{'mask'}, 0)) {
                $rethash{$node}->{ip}      = $ip;
                $rethash{$node}->{mask}    = $net->{'mask'};
                $rethash{$node}->{gateway} = $net->{'gateway'};
                last;
            }
        }
    }
    return (0, \%rethash);
}

#-------------------------------------------------------------------------------

=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 nmap or 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 = ();

    #print "NetworkUtils->pingNodeStatus called, nodes=@mon_nodes\n";
    if ((@mon_nodes) && (@mon_nodes > 0)) {

        #get all the active nodes
        my $nodes = join(' ', @mon_nodes);
        if (-x '/usr/bin/nmap' or -x '/usr/local/bin/nmap') {    #use nmap
                #print "use nmap\n";
            my %deadnodes;
            foreach (@mon_nodes) {
                $deadnodes{$_} = 1;
            }

            # get additional options from site table
            my @nmap_options = xCAT::TableUtils->get_site_attribute("nmapoptions");
            my $more_options = $nmap_options[0];

            #call namp
            open(NMAP, "nmap -PE --system-dns --send-ip -sP $more_options " . $nodes . " 2> /dev/null|") or die("Cannot open nmap pipe: $!");
            my $node;
            while (<NMAP>) {
                if (/Host (.*) \(.*\) appears to be up/) {
                    $node = $1;
                    unless ($deadnodes{$node}) {
                        foreach (keys %deadnodes) {
                            if ($node =~ /^$_\./) {
                                $node = $_;
                                last;
                            }
                        }
                    }
                    delete $deadnodes{$node};
                    push(@active_nodes, $node);
                } elsif (/Nmap scan report for ([^ ]*) /) {
                    $node = $1;
                } elsif (/Host is up./) {
                    unless ($deadnodes{$node}) {
                        foreach (keys %deadnodes) {
                            if ($node =~ /^$_\./) {
                                $node = $_;
                                last;
                            }
                        }
                    }
                    delete $deadnodes{$node};
                    push(@active_nodes, $node);
                }
            }
            foreach (sort keys %deadnodes) {
                push(@inactive_nodes, $_);
            }
        } else {    #use fping
                    #print "use fping\n";

            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;

    #use Data::Dumper;
    #print Dumper(%status);

    return %status;
}

#-------------------------------------------------------------------------------

=head3 isValidMAC
      Description : Validate whether specified string is a MAC string.
      Arguments   : macstr - the string to be validated.
      Returns     : 1 - valid MAC String.
                    0 - invalid MAC String.
=cut

#-------------------------------------------------------------------------------
sub isValidMAC
{
    my ($class, $macstr) = @_;
    if ($macstr =~ /^[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}$/) {
        return 1;
    }
    return 0;
}

#-------------------------------------------------------------------------------

=head3 isValidHostname
      Description : Validate whether specified string is a valid hostname.
      Arguments   : hostname - the string to be validated.
      Returns     : 1 - valid hostname String.
                    0 - invalid hostname String.
=cut

#-------------------------------------------------------------------------------
sub isValidHostname
{
    my ($class, $hostname) = @_;
    if ($hostname =~ /^[a-z0-9]/) {
        if ($hostname =~ /[a-z0-9]$/) {
            if ($hostname =~ /^[\-a-z0-9]+$/) {
                return 1;
            }
        }
    }
    return 0;
}


#-------------------------------------------------------------------------------

=head3 isValidFQDN
      Description : Validate whether specified string is a valid FQDN.
      Arguments   : hostname - the string to be validated.
      Returns     : 1 - valid hostname FQDN.
                    0 - invalid hostname FQDN.
=cut

#-------------------------------------------------------------------------------
sub isValidFQDN
{
    my ($class, $hostname) = @_;
    if ($hostname =~ /^[a-z0-9][\.\-a-z0-9]+[a-z0-9]$/) {
        return 1;
    }
    return 0;
}


#-------------------------------------------------------------------------------

=head3 ip_to_int
      Description : convert an IPv4 string into int.
      Arguments   : ipstr - the IPv4 string.
      Returns     : ipint - int number
=cut

#-------------------------------------------------------------------------------
sub ip_to_int
{
    my ($class, $ipstr) = @_;
    my $ipint = 0;
    my @ipnums = split('\.', $ipstr);
    $ipint += $ipnums[0] << 24;
    $ipint += $ipnums[1] << 16;
    $ipint += $ipnums[2] << 8;
    $ipint += $ipnums[3];
    return $ipint;
}

#-------------------------------------------------------------------------------

=head3 int_to_ip
      Description : convert an int into IPv4 String.
      Arguments   : ipnit - the input int number.
      Returns     : ipstr - IPv4 String.
=cut

#-------------------------------------------------------------------------------
sub int_to_ip
{
    my ($class, $ipint) = @_;
    return inet_ntoa(inet_aton($ipint));
}

#-------------------------------------------------------------------------------

=head3 getBroadcast
      Description : Get the broadcast ips
      Arguments   : ipstr - the IPv4 string ip.
                    netmask - the subnet mask of network
      Returns     : bcipint - the IPv4 string of broadcast ip.
=cut

#-------------------------------------------------------------------------------
sub getBroadcast
{
    my ($class, $ipstr, $netmask) = @_;
    my $ipint   = xCAT::NetworkUtils->ip_to_int($ipstr);
    my $maskint = xCAT::NetworkUtils->ip_to_int($netmask);
    my $tmp     = sprintf("%d", ~$maskint);
    my $bcnum   = sprintf("%d", ($ipint | $tmp) & hex('0x00000000FFFFFFFF'));
    return xCAT::NetworkUtils->int_to_ip($bcnum);
}

#-------------------------------------------------------------------------------

=head3 get_allips_in_range
      Description : Get all IPs in a IP range, return in a list.
      Arguments   : $startip - start IP address
                    $endip - end IP address
                    $increment - increment factor
      Returns     : IP list in this range.
      Example     :
                    my $startip = "192.168.0.1";
                    my $endip = "192.168.0.100";
                    xCAT::NetworkUtils->get_allips_in_range($startip, $endip, 1);
=cut

#-------------------------------------------------------------------------------
sub get_allips_in_range
{
    my $class     = shift;
    my $startip   = shift;
    my $endip     = shift;
    my $increment = shift;
    my @iplist    = ();
    my $tmpip;

    my $startipnum = xCAT::NetworkUtils->ip_to_int($startip);
    my $endipnum   = xCAT::NetworkUtils->ip_to_int($endip);

    if ($increment > 0) {
        while ($startipnum <= $endipnum) {
            $tmpip = xCAT::NetworkUtils->int_to_ip($startipnum);
            $startipnum += $increment;
            push(@iplist, $tmpip);
        }
    } elsif ($increment < 0) {
        while ($endipnum >= $startipnum) {
            $tmpip = xCAT::NetworkUtils->int_to_ip($endipnum);
            $endipnum += $increment;
            push(@iplist, $tmpip);
        }
    }
    return \@iplist;
}

#-------------------------------------------------------------------------------

=head3 get_all_ips
      Description : Get all IP addresses from table nics, column nicips.
      Arguments   : hashref - if not set, will return a reference of list,
                              if set, will return a reference of hash.
      Returns     : All IPs reference.
=cut

#-------------------------------------------------------------------------------
sub get_all_nicips {
    my ($class, $hashref) = @_;
    my %allipshash;
    my @allipslist;

    my $table   = xCAT::Table->new('nics');
    my @entries = $table->getAllNodeAttribs(['nicips']);
    foreach (@entries) {

        # $_->{nicips} looks like "eth0:ip1,eth1:ip2,bmc:ip3..."
        if ($_->{nicips}) {
            my @nicandiplist = split(',', $_->{nicips});

            # Each record in @nicandiplist looks like "eth0:ip1"
            # delimiter has been changed to use "!"  in xCAT 2.8
            foreach (@nicandiplist) {
                my @nicandip;
                if ($_ =~ /!/) {
                    @nicandip = split('!', $_);
                } else {
                    @nicandip = split(':', $_);
                }
                if ($hashref) {
                    $allipshash{ $nicandip[1] } = 0;
                } else {
                    push(@allipslist, $nicandip[1]);
                }
            }
        }
    }
    if ($hashref) {
        return \%allipshash;
    } else {
        return \@allipslist;
    }
}

#-------------------------------------------------------------------------------

=head3  gen_net_boot_params

    Description:
        This subroutine is used to generate all possible kernel parameters for network boot (rh/sles/ubuntu + diskfull/diskless)
        The supported network boot parameters:
            ksdevice - Specify network device for Anaconda. For rh6 and earlier. Format: 'ksdevice={$mac|$nicname}'
            BOOTIF - Specify network device for Anaconda. The boot device which set by pxe. xCAT also set it if the bootload is not pxe. Format 'BOOTIF={$mac}'
            ifname - Specify a interfacename<->mac pair, it will set the interfacename to the interface which has the <mac>. Format 'ifname=$ifname:$mac'
               # This will only be generated when linuximage.nodebootif is set.
            bootdev - Specify the boot device. Mostly it's used with <ip> parameter and when there are multiple <ip> params. Format 'bootdev={$mac|$ifname}
            ip - Specify the network configuration for an interface. Format: 'ip=dhcp', 'ip=$ifname:dhcp'

            netdevice - Specify network device for Linuxrc (Suse bootloader). Format: 'netdevice={$mac|$nicname}'

            netdev - Specify the interfacename which is used by xCAT diskless boot script to select the network interface. Format: 'netdev=$nicname'

        Reference:
            Redhat anaconda doc: https://github.com/rhinstaller/anaconda/blob/master/docs/boot-options.txt
            Suse Linuxrc do: https://en.opensuse.org/SDB:Linuxrc

    Arguments:
        $installnic   <- node.installnic
        $primarynic <- node.primarynic
        $macmac    <- node.mac
        $nodebootif <- linuximage.nodebootif

    Returns:
        $net_params - The key will be the parameter name, the value for the key will be the parameter value.
        Valid Parameter Name:
            ksdevice
            netdev
            netdevice
            ip
            ifname
            BOOTIF

        And following two keys also will be returned for reference
            mac
            nicname

    Example:
        my $netparams = xCAT::NetworkUtils->gen_net_boot_params($installnic, $primmarynic, $macmac, $nodebootif);

=cut

#-------------------------------------------------------------------------------

sub gen_net_boot_params
{
    my $class      = shift;
    my $installnic = shift;
    my $primarynic = shift;
    my $macmac     = shift;
    my $nodebootif = shift;

    my $net_params;

    # arbitrary use primarynic if installnic is not set
    unless ($installnic) {
        $installnic = $primarynic;
    }

    # just use the installnic to generate the nic related kernel parameters
    my $mac;
    my $nicname;

    # set the default nicname to nodebootif from image definition
    if ($nodebootif) {
        $nicname = $nodebootif;
    }

    if ((!defined($installnic)) || ($installnic eq "") || ($installnic =~ /^mac$/i)) {
        $mac = $macmac;
        $net_params->{mac} = $mac;
    } elsif ($installnic =~ /^[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}$/) {
        $mac                  = $installnic;
        $net_params->{mac}    = $mac;
        $net_params->{setmac} = $mac;
    } else {
        $mac                   = $macmac;
        $nicname               = $installnic;
        $net_params->{nicname} = $nicname;
        $net_params->{mac}     = $mac;
    }

    # if nicname is set and mac.mac is NOT set to <mac address>, use nicname in the boot parameters
    if ($nicname && !defined($net_params->{setmac})) {
        $net_params->{ksdevice}  = "ksdevice=$nicname";
        $net_params->{ip}        = "ip=$nicname:dhcp";
        $net_params->{netdev}    = "netdev=$nicname";
        $net_params->{netdevice} = "netdevice=$nicname";
        $net_params->{bootdev}   = "bootdev=$nicname";
    } elsif ($mac) {
        $net_params->{ksdevice}  = "ksdevice=$mac";
        $net_params->{BOOTIF}    = "BOOTIF=$mac";
        $net_params->{ip}        = "ip=dhcp";
        $net_params->{netdevice} = "netdevice=$mac";
    }

    return $net_params;
}

#--------------------------------------------------------------------------------
=head3  send_tcp_msg
      establish a tcp socket to the specified IP address and port, then send the specifid message via the socket
      Arguments:
         $destip  : the destination IP address
         $destport: the destination TCP port
         $msg     : the message to send
      Returns:
         0  on success, 1 on fail
=cut
#--------------------------------------------------------------------------------
sub send_tcp_msg {
    my $self=shift;
    my $destip=shift;
    my $destport=shift;
    my $msg=shift;

    my $sock = new IO::Socket::INET(
                PeerAddr => $destip,
                PeerPort => $destport,
                Timeout  => '1',
                Proto    => 'tcp'
            );
    if ($sock) {
        print $sock $msg;
        close($sock);
        return 0;
    }else{
        return 1;
    }
}
1;
