# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::networks;
use xCAT::Table;
use Data::Dumper;
use Sys::Syslog;
use Socket;
use xCAT::Utils;
use xCAT::NetworkUtils;
use Getopt::Long;

sub handled_commands
{
    return {makenetworks => "networks",};
}

sub preprocess_request
{
    my $req = shift;
    my $callback  = shift;

	# exit if preprocessed
	if ($req->{_xcatpreprocessed}->[0] == 1) { return [$req]; }
    
	my @requests = ({%$req}); #first element is local instance

	$::args = $req->{arg};

	if (defined(@{$::args})) {
        @ARGV = @{$::args};
    } 

	Getopt::Long::Configure("no_pass_through");
    if (
        !GetOptions(
                    'help|h|?'    => \$::HELP,
					'display|d'	=> \$::DISPLAY,
					'verbose|V' => \$::VERBOSE,
                    'version|v' => \$::VERSION,
        )
      )
    {
    #    return 1;
    }

	# Option -h for Help
    if ($::HELP )
    {
        &makenetworks_usage($callback);
        return undef;
    }

	# Option -v for version - do we need this???
    if ($::VERSION)
    {
        my $rsp;
        my $version=xCAT::Utils->Version();
        $rsp->{data}->[0] = "makenetworks - $version";
        xCAT::MsgUtils->message("I", $rsp, $callback);
        return undef;
    }

	# process the network interfaces on this system
    if (&donets($callback) != 0) {
        my $rsp;
        push @{$rsp->{data}}, "Could not get network information.\n";
        xCAT::MsgUtils->message("E", $rsp, $callback);
        return undef;
    }

    my @sn = xCAT::Utils->getSNList();
    foreach my $s (@sn)
    {
        my $reqcopy = {%$req};
        $reqcopy->{'_xcatdest'} = $s;
		$reqcopy->{_xcatpreprocessed}->[0] = 1;
        push @requests, $reqcopy;
    }

    return \@requests;
}

sub process_request
{
	my $request  = shift;
    my $callback = shift;

	$::args     = $request->{arg};

    if (defined(@{$::args})) {
        @ARGV = @{$::args};
    }

    Getopt::Long::Configure("no_pass_through");
    if (
        !GetOptions(
                    'help|h|?'    => \$::HELP,
                    'display|d' => \$::DISPLAY,
                    'verbose|V' => \$::VERBOSE,
                    'version|v' => \$::VERSION,
        )
      )
    {
    #    return 1;
    }

	# process the network interfaces on this system
	#	- management node was already done
	if (!xCAT::Utils->isMN()) {
		if (&donets($callback) != 0) {
			my $rsp;
			push @{$rsp->{data}}, "Could not get network information.\n";
			xCAT::MsgUtils->message("E", $rsp, $callback);
			return 1;
		}
	}
	return 0;
}

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

=head3  donets
			Get network information and display or create xCAT network defs 

        Returns:
            0 - OK
            1 - error

        Usage:
			my $rc = &donets($callback);

=cut

#-----------------------------------------------------------------------------
sub donets
{
	my $callback = shift;

	my $host = `hostname`;
    chomp $host;

	# get all the existing xCAT network defs
    my @netlist;
    @netlist = xCAT::DBobjUtils->getObjectsOfType('network');

	my %nethash;
	if (scalar(@netlist)) {
		my %objtype;
    	foreach my $netn (@netlist) {
        	$objtype{$netn} = 'network';
    	}
	
    	%nethash = xCAT::DBobjUtils->getobjdefs(\%objtype, $callback);
    	if (!%nethash) {
        	my $rsp;
        	$rsp->{data}->[0] = "Could not get xCAT network definitions.\n";
        	xCAT::MsgUtils->message("E", $rsp, $::callback);
        	return 1;
    	}
	}

	my $nettab = xCAT::Table->new('networks', -create => 1, -autocommit => 0);

	if (xCAT::Utils->isAIX()) {

		# get list of interfaces "ifconfig -l"
		my $ifgcmd = "ifconfig -l";
		my @interfaces = split(/\s+/, xCAT::Utils->runcmd($ifgcmd, 0));
		if ($::RUNCMD_RC != 0) {
			my $rsp;
			push @{$rsp->{data}}, "Could not run \'$ifgcmd\'.\n";
			xCAT::MsgUtils->message("E", $rsp, $callback);
			return 1;
		}

                my $master=xCAT::Utils->get_site_Master();
                my $masterip = xCAT::NetworkUtils->getipaddr($master);  
                if ($masterip =~ /:/) {
                    # do each ethernet interface for ipv6
	            foreach my $i (@interfaces) {

	               if ($i =~ /^en/) {
	    
                        # "ifconfig en0 |grep fe80" to get net and prefix length
                        my $cmd = "ifconfig $i |grep -i inet6";
                        my @netinfo = xCAT::Utils->runcmd($cmd, -1);
                        if ($::RUNCMD_RC != 0) {
                               # no ipv6 address configured 
                               next;
                        }

                        # only handle the ipv6 addr without %
                        foreach my $line (@netinfo)
                        {
                            next if ($line =~ /\%/);
                            
                            my $gateway;
                            my $netmask;
                            my @fields;

                            @fields = split(/ /, $line);
                            ($gateway, $netmask) = split(/\//, $fields[1]);

                            my $eip = Net::IP::ip_expand_address ($gateway,6);
                            my $bip = Net::IP::ip_iptobin($eip,6);
                            my $bmask = Net::IP::ip_get_mask($netmask,6);
                            my $bnet = $bip & $bmask;
                            my $ipnet = Net::IP::ip_bintoip($bnet,6);
                            my $net = Net::IP::ip_compress_address($ipnet,6);

                            my $netname = $net . "-" . $netmask;

                            # see if this network (or equivalent) is already defined
                            # - compare net and prefix_len values
                            my $foundmatch = 0;
                            foreach my $netn (@netlist) {
                                # get net and prefix_len
                                my $dnet = $nethash{$netn}{'net'};
                                my $dprefix_len = $nethash{$netn}{'mask'};

                                if (($net == $dnet) && ($netmask == $dprefix_len))
                                {
                                    $foundmatch = 1;
                                    last;
                                }
                            }

                            if ($::DISPLAY) {
                                    my $rsp;
                                    push @{$rsp->{data}}, "\n#From $host.";
                pus    h @{$rsp->{data}}, "$netname:";
                                    push @{$rsp->{data}}, "    objtype=network";
                                    push @{$rsp->{data}}, "    net=$net";
                                    push @{$rsp->{data}}, "    mask=$netmask";
                                    push @{$rsp->{data}}, "    mgtifname=$i";
                                    push @{$rsp->{data}}, "    gateway=$gateway\n";
                                    if ($foundmatch) {
                pus    h @{$rsp->{data}}, "# Note: An equivalent xCAT network definition already exists.\n";
                                    }
                xCA    T::MsgUtils->message("I", $rsp, $callback);
                            } else {

                                    if ($foundmatch) {
                                        next;
                                    }

                                    # add new network def
                                    $nettab->setAttribs({'net' => $net, 'mask' => $netmask}, {'netname' => $netname, 'gateway' => $gateway, 'mgtifname' => $i});
                            }
                        }
                }  
            }
        } else {
            # do each ethernet interface for ipv4
            foreach my $i (@interfaces) {
    
                if ($i =~ /^en/) {
    
            		# "mktcpip -S en0" to get nm & gw
            		my $mkcmd = "mktcpip -S $i";
            		my @netinfo = xCAT::Utils->runcmd($mkcmd, 0);
            		if ($::RUNCMD_RC != 0) {
            			my $rsp;
            			push @{$rsp->{data}}, "Could not run \'$mkcmd\'.\n";
            			xCAT::MsgUtils->message("E", $rsp, $callback);
            			return 1;
            		}
    
            		my $netmask;
            		my $ipaddr;
            		my @fields;
            		my $gateway;
            		foreach my $line (@netinfo) {
            			next if ($line =~ /^\s*#/);
    
            			@fields = split(/:/, $line);
            		}
            		$ipaddr = $fields[1];
            		$netmask = $fields[2];
                        if ($fields[6])
                        {
                            if(xCAT::Utils::isInSameSubnet($fields[6], $ipaddr, $netmask, 0))
                            {
                                $gateway = $fields[6]; 
                            }
                        }

                        # set gateway to keyword <myself>,
                        # to indicate to use the cluster-facing ip address 
                        # on this management node or service node
                        if (!$gateway)
                        {
                            $gateway = "<myself>";
                        }
                        
    
            		# split interface IP
                    my ($ip1, $ip2, $ip3, $ip4) = split('\.', $ipaddr);
    
            		# split mask
                    my ($m1, $m2, $m3, $m4) = split('\.', $netmask);
    
            		# AND nm and ip to get net attribute
            		my $n1 = ((int $ip1) & (int $m1));
                    my $n2 = ((int $ip2) & (int $m2));
                    my $n3 = ((int $ip3) & (int $m3));
                    my $n4 = ((int $ip4) & (int $m4));
    
            		my $net = "$n1.$n2.$n3.$n4";
    
            		# use convention for netname attr
            		my $netn;
            		my $maskn;
            		($netn = $net) =~ s/\./\_/g;
            		($maskn = $netmask) =~ s/\./\_/g;
            		#  ( 1_2_3_4-255_255_255_192 - ugh!)
            		my $netname = $netn . "-" . $maskn;
    
            		# see if this network (or equivalent) is already defined
            		# - compare net and mask values
                            my $foundmatch = 0;
            		foreach my $netn (@netlist) {
            			# split definition mask
            			my ($dm1, $dm2, $dm3, $dm4) = split('\.', $nethash{$netn}{'mask'});
    
            			# split definition net addr
            			my ($dn1, $dn2, $dn3, $dn4) = split('\.', $nethash{$netn}{'net'});
    
            			# check for the same netmask and network address
            			if ( ($n1 == $dn1) && ($n2 ==$dn2) && ($n3 == $dn3) && ($n4 == $dn4) ) {
            				if ( ($m1 == $dm1) && ($m2 ==$dm2) && ($m3 == $dm3) && ($m4== $dm4) ) {
            					$foundmatch=1;
                                                    last;
            				}
            			}
            		}
    
            		if ($::DISPLAY) {
            			my $rsp;
            			push @{$rsp->{data}}, "\n#From $host.";
                		push @{$rsp->{data}}, "$netname:";
            			push @{$rsp->{data}}, "    objtype=network";
            			push @{$rsp->{data}}, "    net=$net";
            			push @{$rsp->{data}}, "    mask=$netmask";
            		        push @{$rsp->{data}}, "    mgtifname=$i";	
            			push @{$rsp->{data}}, "    gateway=$gateway\n";
            			if ($foundmatch) {
                            push @{$rsp->{data}}, "# Note: An equivalent xCAT network definition already exists.\n";
            			}
                		xCAT::MsgUtils->message("I", $rsp, $callback);
            		} else {
    
            			if ($foundmatch) {
                            next;
                        }
    
            			# add new network def 
            			$nettab->setAttribs({'net' => $net, 'mask' => $netmask}, {'netname' => $netname, 'gateway' => $gateway, 'mgtifname' => $i});
            		}
    	        }
            } # end foreach
        } #end if ipv4

      } else { 

		# For Linux systems
        my @ip6table = split /\n/,`/sbin/ip -6 route`;
    	my @rtable = split /\n/, `/bin/netstat -rn`;
    	open($rconf, "/etc/resolv.conf");
    	my @nameservers;
    	if ($rconf)
    	{
        	my @rcont;
        	while (<$rconf>)
        	{
            	push @rcont, $_;
        	}
        	close($rconf);
        	foreach (grep /nameserver/, @rcont)
        	{
                    my $line = $_;
                    my @pair;
                    $line =~ s/#.*//;
                    $line =~ s/^\s*//; 
                    @pair = split(/\s+/, $line);
                    if ($pair[0] eq 'nameserver' && $pair[1] ne '') {
                        push @nameservers, $pair[1];
                    }
        	}
    	}
    	splice @rtable, 0, 2;

        my %netgw = ();
    	foreach my $rtent (@rtable)
        { 
            my @entarr = split /\s+/, $rtent;
            if ($entarr[3] eq 'UG')
            {
                $netgw{$entarr[0]}{$entarr[2]} = $entarr[1];
            }
        }
        #routers advertise their role completely outside of DHCPv6 scope, we don't need to
        #get ipv6 routes and in fact *cannot* dictate router via DHCPv6 at this specific moment.
        foreach (@ip6table)
        { 
            my @ent = split /\s+/, $_;
            if ($ent[0] eq 'fe80::/64' or $ent[0] eq 'unreachable' or $ent[1] eq 'via') {
                #Do not contemplate link-local, unreachable, or gatewayed networks further
                #DHCPv6 relay will be manually entered into networks as was the case for IPv4
                next;
            }
            my $net = shift @ent;
            my $dev = shift @ent;
            if ($dev eq 'dev') {
                $dev = shift @ent;
            } else {
                die "Unrecognized IPv6 routing entry $_";
            }
            my @myv6addrs=split /\n/,`ip -6 addr show dev $dev scope global`;
            #for v6, deprecating mask since the CIDR slash syntax is ubiquitous
            my $consideredaddr=$net;
            $consideredaddr=~ s!/(.*)!!;
            my $consideredbits=$1;
            #below may be redundant, but apply resolution in case ambiguous net, e.g. 2001:0db8:0::/64 is the same thing as 2001:0db8::/64
            $consideredaddr = xCAT::NetworkUtils->getipaddr($consideredaddr);
            my $netexists=0;
			foreach my $netn (@netlist) { #search for network that doesn't exist yet
                    my $curnet=$nethash{$netn}{'net'};
                    unless ($curnet =~ /:/) {  #only ipv6 here
                        next;
                    }
                    $curnet =~ s!/(.*)!!; #remove 
                    my $curnetbits=$1;
                    unless ($consideredbits==$curnetbits) { #only matches if netmask matches
                        next;
                    }
					$currnet = xCAT::NetworkUtils->getipaddr($currnet);
                    unless ($currnet eq  $consideredaddr) {
                        next;
                    }
                    $netexists=1;
            }
			if ($::DISPLAY) {
				push @{$rsp->{data}}, "\n#From $host.";
				push @{$rsp->{data}}, "$net:";
				push @{$rsp->{data}}, "    objtype=network";
                   push @{$rsp->{data}}, "    net=$net";
                   push @{$rsp->{data}}, "    mgtifname=$dev";
			} else {
				unless ($netexiss) {
					$nettab->setAttribs({'net' => $net, 'mask' => ''}, {'netname' => $net, 'mgtifname' => $dev});
				}
			}
            
        }
    	foreach (@rtable)
    	{ #should be the lines to think about, do something with U, and something else with UG

			my $foundmatch=0;
			my $rsp;
        	my $net;
        	my $mask;
        	my $mgtifname;
        	my $gw;
        	my @ent = split /\s+/, $_;
        	my $firstoctet = $ent[0];
        	$firstoctet =~ s/^(\d+)\..*/$1/;

        	if ($ent[0] eq "169.254.0.0" or ($firstoctet >= 224 and $firstoctet <= 239) or $ent[0] eq "127.0.0.0")
        	{
            	next;
        	}

        	if ($ent[3] eq 'U')
        	{
            	$net       = $ent[0];
            	$mask      = $ent[2];
				$mgtifname = $ent[7];
                if (defined($netgw{'0.0.0.0'}{'0.0.0.0'}))
                {
                    if(xCAT::NetworkUtils->ishostinsubnet($netgw{'0.0.0.0'}{'0.0.0.0'}, $mask, $net))
                    {
                        $gw =  $netgw{'0.0.0.0'}{'0.0.0.0'}; #default gatetway
                    }
                }

				# use convention for netname attr
                my $netn;
                my $maskn;
                ($netn = $net) =~ s/\./\_/g;
                ($maskn = $mask) =~ s/\./\_/g;
                #  ( 1_2_3_4-255_255_255_192 - ugh!)
                my $netname = $netn . "-" . $maskn;

				# see if this network (or equivalent) is already defined
                # - compare net and mask values

				# split mask
                my ($m1, $m2, $m3, $m4) = split('\.', $mask);

				# split net addr
				my ($n1, $n2, $n3, $n4) = split('\.', $net);

				foreach my $netn (@netlist) {

					# split definition mask
					my ($dm1, $dm2, $dm3, $dm4) = split('\.', $nethash{$netn}{'mask'});

					# split definition net addr
					my ($dn1, $dn2, $dn3, $dn4) = split('\.', $nethash{$netn}{'net'});

					# check for the same netmask and network address
					if ( ($n1 == $dn1) && ($n2 ==$dn2) && ($n3 == $dn3) && ($n4 == $dn4) ) {
						if ( ($m1 == $dm1) && ($m2 ==$dm2) && ($m3 == $dm3) && ($m4== $dm4) ) {
							$foundmatch=1;
						}
					}
				}

				if ($::DISPLAY) {
					push @{$rsp->{data}}, "\n#From $host.";
					push @{$rsp->{data}}, "$netname:";
					push @{$rsp->{data}}, "    objtype=network";
                    push @{$rsp->{data}}, "    net=$net";
                    push @{$rsp->{data}}, "    mask=$mask";
                    if ($gw)
                    {
                        push @{$rsp->{data}}, "    gateway=$gw";
                    }
                    push @{$rsp->{data}}, "    mgtifname=$mgtifname";
				} else {
					if (!$foundmatch) {
						$nettab->setAttribs({'net' => $net, 'mask' => $mask}, {'netname' => $netname, 'mgtifname' => $mgtifname, 'gateway' => $gw});
					}
				}

            	my $tent = $nettab->getAttribs({'net' => $net, 'mask' => $mask}, 'nameservers');
            	unless ($tent and $tent->{nameservers})
            	{
                	my $text = join ',', @nameservers;
					if ($::DISPLAY) {
                    	push @{$rsp->{data}}, "    nameservers=$text";
					} else {
						if (!$foundmatch) {
                			$nettab->setAttribs({'net' => $net, 'mask' => $mask}, {nameservers => $text});
						}
					}
            	}
            	unless ($tent and $tent->{tftpserver})
            	{
                	my $netdev = $ent[7];
                	my @netlines = split /\n/, `/sbin/ip addr show dev $netdev`;
                	foreach (grep /\s*inet\b/, @netlines)
                	{
                    	my @row = split(/\s+/, $_);
                    	my $ipaddr = $row[2];
                    	$ipaddr =~ s/\/.*//;
                    	my @maska = split(/\./, $mask);
                    	my @ipa   = split(/\./, $ipaddr);
                    	my @neta  = split(/\./, $net);
                    	my $isme  = 1;
                    	foreach (0 .. 3)
                    	{
                        	my $oct = (0 + $maska[$_]) & ($ipa[$_] + 0);
                        	unless ($oct == $neta[$_])
                        	{
                            	$isme = 0;
                            	last;
                        	}
                    	}
                    	if ($isme)
                    	{
							if ($::DISPLAY) {
                        		push @{$rsp->{data}}, "    tftpserver=$ipaddr";
                    		} else {
								if (!$foundmatch) {
                        			$nettab->setAttribs({'net' => $net, 'mask' => $mask}, {tftpserver => $ipaddr});
								}
							}
                        	last;
                    	}
                	}
            	}
            	#Nothing much sane to do for the other fields at the moment?
        	}
        	elsif ($ent[3] eq 'UG')
        	{

            	#TODO: networks through gateway. and how we might care..
        	}
        	else
        	{

            	#TODO: anything to do with such entries?
        	}

			if ($::DISPLAY) {

				if ($foundmatch) {
					push @{$rsp->{data}}, "# Note: An equivalent xCAT network definition already exists.\n";
				}
				xCAT::MsgUtils->message("I", $rsp, $callback);
			}
    	}
	}

	$nettab->commit;

	return 0;
}

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

=head3  makenetworks_usage

=cut

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

sub makenetworks_usage
{
	my $callback = shift;

    my $rsp;
	push @{$rsp->{data}}, "\nUsage: makenetworks - Gather cluster network information and add it to the xCAT database.\n";
	push @{$rsp->{data}}, "  makenetworks [-h|--help ]\n";
	push @{$rsp->{data}}, "  makenetworks [-v|--version]\n";
	push @{$rsp->{data}}, "  makenetworks [-V|--verbose] [-d|--display]\n";
    xCAT::MsgUtils->message("I", $rsp, $callback);
    return 0;
}

1;