package Ocsinventory::Agent::Backend::OS::Linux::Network::Networks;

use strict;
use warnings;
use Data::Dumper;
use File::stat;
use Time::Local;

sub check {
    my $params = shift;
    my $common = $params->{common};
    return unless ($common->can_run("ifconfig") || $common->can_run("ip")) && $common->can_run("route") && $common->can_load("Net::IP qw(:PROC)");
    1;
}

sub getLeaseFile {

    my $if = @_; 
    my @directories = qw( 
        /var/db
        /var/lib/dhclient
        /var/lib/dhcp3
        /var/lib/dhcp
        /var/lib/NetworkManager
    );
    my @patterns = ("*$if*.lease", "*.lease", "dhclient.leases.$if");
    my @files;
    
    foreach my $directory (@directories) {
        next unless -d $directory;
        foreach my $pattern (@patterns) {
            push @files,
                 grep { -s $_ }
                 glob("$directory/$pattern");
        }
    }   
    return unless @files;
    @files = 
        map {$_->[0]}
        sort {$a->[1]->ctime()<=>$b->[1]->ctime()}
        map {[$_,stat($_)]}
        @files;
    return $files[-1];

}

sub _ipdhcp {

    my $if = shift;
    my $path;
    my $dhcp;
    my $ipdhcp;
    my $leasepath;

    if( $leasepath = getLeaseFile($if) ) {
        if (open DHCP, $leasepath) {
            my $lease;
            while(<DHCP>){
                $lease = 1 if(/lease\s*{/i);
                $lease = 0 if(/^\s*}\s*$/);
                #Interface name
                if ($lease) { #inside a lease section
                    if (/interface\s+"(.+?)"\s*/){
                        $dhcp = ($1 =~ /^$if$/);
                    }
                    #Server IP
                    if (/option\s+dhcp-server-identifier\s+(\d{1,3}(?:\.\d{1,3}){3})\s*;/ and $dhcp){
                        $ipdhcp = $1;
                    }
                }
            }
            close DHCP or warn;
        } else {
            warn "Can't open $leasepath\n";
        }
    }
    return $ipdhcp;
}

# Initialise the distro entry
sub run {

    my $params = shift;
    my $common = $params->{common};
    my $logger = $params->{logger};

    my $description;
    my $driver;
    my $ipaddress;
    my $ipgateway;
    my $ipmask;
    my $ipsubnet;
    my $macaddr;
    my $pcislot;
    my $status;
    my $type;
    my $virtualdev;
    my $settings;
    my $speed;
    my $current_speed;
    my $duplex;
    my $ssid;
    my $bssid;
    my $mode;
    my $version;
    my $bitrate;
    my $address;
    my $mask4;
    my $mask6;
    my $binip;
    my $binmask;
    my $binsubnet;
    my $mtu;

    my %gateway;

    if ($common->can_run("ip")){
        my @ipLink = `ip -o link`;
        foreach my $line (`ip -o addr`) {
            $ipsubnet = $ipaddress = $ipmask = $ipgateway = $macaddr = $status = $type = $speed = $duplex = $driver = $pcislot = $virtualdev = $mtu = undef;
            $description = "";
            if ($line =~ /lo.*(127\.0\.0\.1\/8)/) { # ignore default loopback interface
                   next;
            }   
            if ($line =~ /lo.*(::1\/128)/) { # ignore default loopback interface
                   next;
            }   
            
            $description = $1 if ($line =~ /\d:\s+([\w\d]+)\s+/i);

            if ($line =~ /inet (\S+)\/(\d{2})/i){
                $ipaddress = $1; 
                $mask4 = $2; 
            } elsif ($line =~ /inet6 (\S+)\/(\d{2})/i){
                $ipaddress = $1; 
                $mask6 = $2; 
            }
    
            if (ip_is_ipv4($ipaddress)){
                foreach (`route -n`) {
                    if (/^(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)/) {
                        $gateway{$1} = $2;
                    }
                }

                if (defined ($gateway{'0.0.0.0'})) {
                    $common->setHardware({
                        DEFAULTGATEWAY => $gateway{'0.0.0.0'}
                    });
                }
            
            
                $ipmask = ip_bintoip(ip_get_mask($mask4,4),4) if $mask4;
                $binip = ip_iptobin($ipaddress,4) if $ipaddress;
                $binmask = ip_iptobin($ipmask,4) if $ipmask;
                $binsubnet = $binip & $binmask if ($binip && $binmask);
                $ipsubnet = ip_bintoip($binsubnet,4) if $binsubnet;
            } elsif (ip_is_ipv6($ipaddress)) {
            
                foreach (`route -nA inet6`) {
                    if(/^(\S+)\/\d{1,3}\s+(\S+)/) {
                        next if ($1 eq $2);
                        $gateway{$1} = $2;
                    }
                }

                $ipmask = ip_bintoip(ip_get_mask($mask6,6),6) if $mask6;;
                $binip = ip_iptobin(ip_expand_address($ipaddress,6),6) if $ipaddress;
                $binmask = ip_iptobin(ip_expand_address($ipmask,6),6) if $ipmask;
                $binsubnet = $binip & $binmask if ($binip && $binmask);
                $ipsubnet = ip_compress_address(ip_bintoip($binsubnet,6),6) if $binsubnet;
            }

            my @line1 = split " ", $line;
            my @linkData = split " ", (grep {/$line1[1]/} @ipLink)[0];
            $macaddr = uc $linkData[-3];
            $mtu = $linkData[4];

            if ($linkData[2] =~ /,UP/) {
                $status = "UP";
            } else {
                $status = "DOWN";
            }

            $type = $linkData[-4];
            if ($type =~ /ether/){
                $type = "Ethernet";
            } elsif ($type =~ /loopback/) {
                $type = "Loopback";
            }

            $ipgateway = $gateway{$ipsubnet} if $ipsubnet;
            # replace '0.0.0.0' and '::' (ie 'default gateway') by the default gateway IP address if it exists
            if (defined($ipgateway) and $ipgateway eq '0.0.0.0' and defined($gateway{'0.0.0.0'})) {
                $ipgateway = $gateway{'0.0.0.0'};
            }
            if (defined($ipgateway) and $ipgateway eq '::' and defined($gateway{'::'})) {
                $ipgateway = $gateway{'::'};
            }

            if (open UEVENT, "</sys/class/net/$description/device/uevent") {
                foreach (<UEVENT>) {
                    $driver = $1 if /^DRIVER=(\S+)/;
                    $pcislot = $1 if /^PCI_SLOT_NAME=(\S+)/;
                }
                close UEVENT;
            }

            # Retrieve speed from /sys/class/net/$description/speed
            if ( ! -z "/sys/class/net/$description/speed") {
                if(open SPEED, "</sys/class/net/$description/speed") {
                    foreach (<SPEED>){
                         $current_speed=$_;
                       }
                    close SPEED;
                }
                
                if (defined $current_speed) {
                    chomp($current_speed);
                    if ($current_speed eq "65535"){
                        $current_speed = 0;
                    } elsif ( $current_speed gt 100 ){
                        $speed = ($current_speed/1000)." Gbps";
                      } else {
                        $speed = $current_speed." Mbps";
                    }
                }
            }
 
            # Retrieve duplex from /sys/class/net/$description/duplex
            if (open DUPLEX, "</sys/class/net/$description/duplex"){
                foreach (<DUPLEX>){
                    $duplex=chomp($_);
                }
                close DUPLEX;
            }
 
            # Reliable way to get the info
            if (-d "/sys/devices/virtual/net/") {
                $virtualdev = (-d "/sys/devices/virtual/net/$description")?"1":"0";
            } elsif ($common->can_run("brctl")) {
                # Let's guess
                my %bridge;
                foreach (`brctl show`) {
                      next if /^bridge name/;
                      $bridge{$1} = 1 if /^(\w+)\s/;
                }
                if ($pcislot) {
                      $virtualdev = "1";
                } elsif ($bridge{$description}) {
                      $virtualdev = "0";
                }
                $type = "bridge";
            }

            if (-d "/sys/class/net/$description/wireless"){
                my @wifistatus = `iwconfig $description`;
                foreach my $line (@wifistatus){
                    $ssid = $1 if ($line =~ /ESSID:(\S+)/);
                    $version = $1 if ($line =~ /IEEE (\S+)/);
                    $mode = $1 if ($line =~ /Mode:(\S+)/);
                    $bssid = $1 if ($line =~ /Access Point: (\S+)/);
                    $bitrate = $1 if ($line =~ /Bit\sRate=\s*(\S+\sMb\/s)/i);
                }
                $type = "Wifi";
            }
            if ($description ne "") {
                if ($type eq "Wifi") {
                      $common->addNetwork({
                          DESCRIPTION => $description,
                          DRIVER => $driver,
                          IPADDRESS => $ipaddress,
                          IPDHCP => _ipdhcp($description),
                          IPGATEWAY => $ipgateway,
                          IPMASK => $ipmask,
                          IPSUBNET => $ipsubnet,
                          MACADDR => $macaddr,
                          PCISLOT => $pcislot,
                          STATUS => $status,
                          TYPE => $type,
                          SPEED => $bitrate,
                          SSID => $ssid,
                          BSSID => $bssid,
                          IEEE => $version,
                          MODE => $mode,
                    });
                } else { 
                      $common->addNetwork({
                          DESCRIPTION => $description,
                          DRIVER => $driver,
                          IPADDRESS => $ipaddress,
                          IPDHCP => _ipdhcp($description),
                          IPGATEWAY => $ipgateway,
                          IPMASK => $ipmask,
                          IPSUBNET => $ipsubnet,
                          MACADDR => $macaddr,
                          PCISLOT => $pcislot,
                          STATUS => $status,
                          TYPE => $type,
                          VIRTUALDEV => $virtualdev,
                          DUPLEX => $duplex?"Full":"Half",
                          SPEED => $speed,
                          MTU => $mtu,
                    });
                }
            }
        }
    }

    elsif ($common->can_run("ifconfig")){
        foreach my $line (`ifconfig -a`) {
            if ($line =~ /^$/ && $description && $macaddr) {
            # end of interface section
            # I write the entry
                if (ip_is_ipv4($ipaddress)){
                    foreach (`route -n`) {
                        if (/^(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)/) {
                            $gateway{$1} = $2;
                        }
                    }

                    if (defined ($gateway{'0.0.0.0'})) {
                        $common->setHardware({
                            DEFAULTGATEWAY => $gateway{'0.0.0.0'}
                        });
                    }

                    $binip = ip_iptobin ($ipaddress ,4) if $ipaddress;
                    $binmask = ip_iptobin ($ipmask,4) if $ipmask;
                    $binsubnet = $binip & $binmask if ($binip && $binmask);
                    $ipsubnet = ip_bintoip($binsubnet,4) if $binsubnet;
                } elsif (ip_is_ipv6($ipaddress)) {
                    # Table de routage IPv6 du noyau
                    # Destination                    Next Hop                   Flag Met Ref Use If
                    # fe80::/64                      [::]                       U    256 0     0 wlp6s0
                    # foreach (`route -6`) {
                    #    if (/^(\S+\/\d{2})\s+(\S+)/) {
                    #        $gateway{$1} = $2;
                    #    }
                    #}
                    #if (defined ($gateway{'fe80::/64'})) {
                    #    $common->setHardware({
                    #        DEFAULTGATEWAY => $gateway{'fe80::/64'}
                    #    });
                    #}
                    
                    foreach (`route -nA inet6`) {
                        if(/^(\S+)\/\d{1,3}\s+(\S+)/) {
                            next if ($1 eq $2);
                            $gateway{$1} = $2;
                        }
                    }
                    
                    
                    $ipmask = ip_bintoip(ip_get_mask($mask6,6),6) if $mask6;;
                    $binip = ip_iptobin(ip_expand_address($ipaddress,6),6) if $ipaddress;
                    $binmask = ip_iptobin(ip_expand_address($ipmask,6),6) if $ipmask;
                    $binsubnet = $binip & $binmask if ($binip && $binmask);
                    $ipsubnet = ip_compress_address(ip_bintoip($binsubnet,6),6) if $binsubnet;
                }

                $ipgateway = $gateway{$ipsubnet} if $ipsubnet;

                if (-d "/sys/class/net/$description/wireless"){
                    my @wifistatus = `iwconfig $description`;
                    foreach my $line (@wifistatus){
                        $ssid = $1 if ($line =~ /ESSID:(\S+)/);
                        $version = $1 if ($line =~ /IEEE (\S+)/);
                        $mode = $1 if ($line =~ /Mode:(\S+)/);
                        $bssid = $1 if ($line =~ /Access Point: (\S+)/);
                        $bitrate = $1 if ($line =~ /Bit\sRate=\s*(\S+\sMb\/s)/i);
                    }
                    $type = "Wifi";
                } 

                if (-f "/sys/class/net/$description/mode"){
                    $type = "infiniband";
                }

                # replace '0.0.0.0' and '::' (ie 'default gateway') by the default gateway IP address if it exists
                if (defined($ipgateway) and $ipgateway eq '0.0.0.0' and defined($gateway{'0.0.0.0'})) {
                    $ipgateway = $gateway{'0.0.0.0'};
                }
                if (defined($ipgateway) and $ipgateway eq '::' and defined($gateway{'::'})) {
                    $ipgateway = $gateway{'::'};
                }

                if (open UEVENT, "</sys/class/net/$description/device/uevent") {
                    foreach (<UEVENT>) {
                        $driver = $1 if /^DRIVER=(\S+)/;
                        $pcislot = $1 if /^PCI_SLOT_NAME=(\S+)/;
                    }
                    close UEVENT;
                }

                # Retrieve speed from /sys/class/net/$description/speed
                if ( ! -z "/sys/class/net/$description/speed") {
                    open SPEED, "</sys/class/net/$description/speed";
                    foreach (<SPEED>){
                        $current_speed=$_;
                    }
                    close SPEED;
                    chomp($current_speed);

                    $current_speed=0 if $current_speed eq "";
                    if ($current_speed eq "65535"){
                        $current_speed = 0;
                    } 
                    if ($current_speed gt 100 ){
                        $speed = ($current_speed/1000)." Gbps";
                    } else {
                        $speed = $current_speed." Mbps";
                    }
                }
 
                # Retrieve duplex from /sys/class/net/$description/duplex
                if (! -z "/sys/class/net/$description/duplex") {
                    open DUPLEX, "</sys/class/net/$description/duplex";
                    foreach (<DUPLEX>){
                        $duplex=chomp($_);
                    }
                    close DUPLEX;
                }

                # Reliable way to get the info
                if (-d "/sys/devices/virtual/net/") {
                    $virtualdev = (-d "/sys/devices/virtual/net/$description")?"1":"0";
                } elsif ($common->can_run("brctl")) {
                    # Let's guess
                    my %bridge;
                    foreach (`brctl show`) {
                        next if /^bridge name/;
                        $bridge{$1} = 1 if /^(\w+)\s/;
                    }
                    if ($pcislot) {
                        $virtualdev = "1";
                    } elsif ($bridge{$description}) {
                        $virtualdev = "0";
                    }
                    $type = "bridge";
                }

                if ($type eq "Wifi") {
                    $common->addNetwork({
                        DESCRIPTION => $description,
                        DRIVER => $driver,
                        IPADDRESS => $ipaddress,
                        IPDHCP => _ipdhcp($description),
                        IPGATEWAY => $ipgateway,
                        IPMASK => $ipmask,
                        IPSUBNET => $ipsubnet,
                        MACADDR => $macaddr,
                        PCISLOT => $pcislot,
                        STATUS => $status,
                        TYPE => $type,
                        SPEED => $bitrate,
                        SSID => $ssid,
                        BSSID => $bssid,
                        IEEE => $version,
                        MODE => $mode,
                    });
                } else { 
                    $common->addNetwork({
                        DESCRIPTION => $description,
                        DRIVER => $driver,
                        IPADDRESS => $ipaddress,
                        IPDHCP => _ipdhcp($description),
                        IPGATEWAY => $ipgateway,
                        IPMASK => $ipmask,
                        IPSUBNET => $ipsubnet,
                        MACADDR => $macaddr,
                        PCISLOT => $pcislot,
                        STATUS => $status,
                        TYPE => $type,
                        VIRTUALDEV => $virtualdev,
                        DUPLEX => $duplex?"Full":"Half",
                        SPEED => $speed,
                        MTU => $mtu,
                    });
                }
            }

            if ($line =~ /^$/) { # End of section
                $description = $driver = $ipaddress = $ipgateway = $ipmask = $ipsubnet = $macaddr = $pcislot = $status = $type = $virtualdev = $speed = $duplex = $mtu = undef;
            } else { # In a section
                if ($line =~ /^(\S+):\s\S+\s*mtu\s(\d+)/) {
                    $description = $1; # Interface name
                    $mtu = $2; 
                }
                
                $description = $1 if($line =~ /^(\w+)\s+/); # Interface name
                
                # BUG: ipv4 address gets overwritten by ipv6 address but we want all ip addresses
                if ($line =~ /inet add?r:(\S+)/i || $line =~ /^\s*inet\s+(\S+)/i || $line =~ /inet (\S+)\s+netmask/i){ 
                    $ipaddress=$1;
                } elsif ($line =~ /inet6 (\S+)\s+prefixlen\s+(\d{2})/i){
                    $ipaddress=$1;
                    $mask6=$2;
                }
                if ($line =~ /\S*mask:(\S+)/i || $line =~ /\S*netmask (\S+)\s/i){
                    $ipmask=$1; 
                } 
                $macaddr = $1 if ($line =~ /hwadd?r\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/i || $line =~ /ether\s+(\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/i);
                $status = 1 if ($line =~ /^\s+UP\s/ || $line =~ /flags=.*[<,]UP[,>]/);
                $type = $1 if ($line =~ /link encap:(\S+)/i);
                $type = $2 if ($line =~ /^\s+(loop|ether).*\((\S+)\)/i);
                $mtu = $1 if ($line =~ /MTU:(\d+)/i);
            }
        }
    }
}

1;

