package Huawei::Info::Ipaddr;

use 5.012;
use warnings;
no warnings 'uninitialized';
use Data::Dumper;

use Net::Compare;
use File::Next;

#继承EXPORTER属性
use Exporter;
use parent 'Exporter';

#设备版本信息和默认导出函数
our $VERSION = 0.09;
our @EXPORT =
  qw 'ip_info info_parser device_arp device_mac device_nei device_eth device_info';

#------------------------------------------------------------------------------
# 常量 ip_str mac_str 数据格式
#------------------------------------------------------------------------------
my $ip_str  = '(\d+\.){3}\d+';
my $mac_str = '([0-9a-fA-F]{4}\-){2}([0-9a-fA-F]{4})';

#------------------------------------------------------------------------------
# 数据中心 -- 机房数据字典
#------------------------------------------------------------------------------
my %dc_map = (
  "CC-DC1"   => "chaosuan",
  "CC-DC2"   => "pengboshi",
  "HT"       => "huitian",
  "CYM"      => "chaoyangmen",
  "650"      => "ZH650",
  "CC-ZXDS"  => "CC-ZXDS",
  "CC-RCDS"  => "CC-RCDS",
  "CC-ZHXHL" => "CC-ZHXHL",
  "CC-ZHXDX" => "CC-ZHXDX",
  "CC-ZHXLD" => "CC-ZHXLD",
);

#------------------------------------------------------------------------------
# 华为交换机 IP-MAC 解析函数入口 -- 接收待解析的文件夹
#------------------------------------------------------------------------------
sub ip_info {

  #接收待解析的配置文件夹
  my $dir = shift;

  #计算配置解析结果
  my $rev = info_parser($dir);

  #解析 arp_info
  my $arp_info = $rev->{"arp_info"};

  #解析 mac_info
  my $mac_info = $rev->{"mac_info"};

  #解析 nei_info
  my $nei_info = $rev->{"nei_info"};

  #解析 eth_info
  my $eth_info = $rev->{"eth_info"};

  #解析 location
  my $location = $rev->{"location"};

  #设备本身 ipaddr 信息
  my $ret1;

  #接入层连接设备 ipaddr 信息
  my $ret2;

  #Incomplete ARP 表项
  my $ret3;

  #匹配到的IPPHONE
  my $ret4;

  #匹配到的 802.1X 认证
  my $ret5;

  #IP解析未匹配到 MAC
  my $ret6;

  #遍历当前ARP解析项
  foreach my $device ( keys %{$arp_info} ) {

    #获取当前设备所在机房位置
    my $device_location = $location->{$device};

    #获取当前设备 arp_info 哈希对象
    my $device_rev = $arp_info->{$device};

    #获取当前设备的 mac_info 哈希对象
    my $device_mac = $mac_info->{$device};

    #阶段一 -- 获取设备自身IP地址,索引为设备下自身接口
    foreach my $self_interface ( keys %{ $device_rev->{"self_ip"} } ) {

      #获取当前接口下的IP地址详情
      my $interface_info = $device_rev->{"self_ip"}{$self_interface};

      #遍历当前元素下所有的字段属性
      foreach my $ip_info ( keys %{$interface_info} ) {

        #获取IP地址属性实例
        my $ip_ret = $interface_info->{$ip_info};

        #获取arp等信息
        my $exp_time     = $ip_ret->{"exp_time"};
        my $mac          = $ip_ret->{"mac"};
        my $vlan         = $ip_ret->{"vlan"};
        my $vpn_instance = $ip_ret->{"vpn_instance"};

        #将解析到的结果写入数据结构
        $ret1->{$ip_info}{"location"}     = $device_location;
        $ret1->{$ip_info}{"hostname"}     = $device;
        $ret1->{$ip_info}{"interface"}    = $self_interface;
        $ret1->{$ip_info}{"mac"}          = $mac;
        $ret1->{$ip_info}{"vlan"}         = $vlan;
        $ret1->{$ip_info}{"vpn_instance"} = $vpn_instance;
        $ret1->{$ip_info}{"gate_device"}  = undef;
      }
    }

    #阶段二 -- 获取设备下异常 arp 表项,索引为设备下自身接口
    foreach my $self_interface ( keys %{ $device_rev->{"incomplete"} } ) {

      #获取当前接口下的IP地址详情
      my $interface_info = $device_rev->{"incomplete"}{$self_interface};

      #遍历当前元素下所有的字段属性
      foreach my $ip_info ( keys %{$interface_info} ) {

        #获取IP地址属性实例
        my $ip_ret = $interface_info->{$ip_info};

        #获取arp等信息
        my $exp_time     = $ip_ret->{"exp_time"};
        my $mac          = $ip_ret->{"mac"};
        my $vlan         = $ip_ret->{"vlan"};
        my $vpn_instance = $ip_ret->{"vpn_instance"};

        #将解析到的结果写入数据结构
        $ret3->{$ip_info}{"location"}     = $device_location;
        $ret3->{$ip_info}{"hostname"}     = $device;
        $ret3->{$ip_info}{"interface"}    = $self_interface;
        $ret3->{$ip_info}{"mac"}          = $mac;
        $ret3->{$ip_info}{"vlan"}         = $vlan;
        $ret3->{$ip_info}{"vpn_instance"} = $vpn_instance;
        $ret3->{$ip_info}{"gate_device"}  = undef;
      }
    }

    #阶段三 -- 计算设备当前的ARP表项,索引为设备下自身接口
    foreach my $self_interface ( keys %{ $device_rev->{"dynamic"} } ) {

      #获取当前接口下的IP地址详情
      my $interface_info = $device_rev->{"dynamic"}{$self_interface};

      #遍历当前元素下所有的字段属性
      foreach my $ip_info ( keys %{$interface_info} ) {

        #获取IP地址属性实例
        my $ip_ret = $interface_info->{$ip_info};

        #获取arp等信息
        my $exp_time     = $ip_ret->{"exp_time"};
        my $mac          = $ip_ret->{"mac"};
        my $vlan         = $ip_ret->{"vlan"};
        my $vpn_instance = $ip_ret->{"vpn_instance"};

        #检查ARP学习接口是否为ETH-TRUNK
        if ( $self_interface =~ /Eth-Trunk/ ) {

          #裁剪eth-trunk相关参数
          $self_interface =~ s/\.\d+$//;

          #获取当前ETH-TRUNK的成员接口信息
          my $eth_member = $eth_info->{$device}{$self_interface};

          #遍历当前eth_member接口发现的邻居下的MAC
          foreach my $member ( @{$eth_member} ) {

            #获取当前接口的邻居设备,输出为邻居设备名
            my $device_nei = $nei_info->{$device}{$member}{"neighbor"};

            #判断是否为 ipPhone
            if ( $device_nei =~ /eSpace/i ) {

              #如果命中IP电话,将解析到的结果写入数据结构
              $ret4->{$ip_info}{"location"}     = $device_location;
              $ret4->{$ip_info}{"hostname"}     = $device;
              $ret4->{$ip_info}{"interface"}    = $member;
              $ret4->{$ip_info}{"mac"}          = $mac;
              $ret4->{$ip_info}{"vlan"}         = $vlan;
              $ret4->{$ip_info}{"vpn_instance"} = $vpn_instance;
              $ret4->{$ip_info}{"gate_device"}  = $device;

              #找到即停
              next;
            }

            #判断是否存在邻居信息
            elsif ( $device_nei && defined $device_nei ) {

              #获取邻居设备的机房位置
              my $nei_location = $location->{$device_nei};

              #调取该设备的MAC地址对象
              my $nei_mac = $mac_info->{$device_nei};

              #遍历邻居的MAC地址信息,寻找能与当前ARP配对接口
              foreach my $nei_interface ( keys %{$nei_mac} ) {

                #获取当前邻居的MAC地址表
                my $nei_ret = $nei_mac->{$nei_interface};

                #判断MAC是否为 authen 类型
                my $mac_type = $nei_ret->{"type"};

                #遍历该接口下所有的MAC地址信息
                foreach my $mac_rev ( keys %{$nei_ret} ) {

                  #如果非MAC地址则调整
                  next unless $mac_rev =~ $mac_str;

                  #获取 mac_vlan 地址
                  my $mac_vlan = $nei_ret->{$mac_rev};

                  #判断MAC地址是否为802.1X认证
                  if ( $mac_type =~ /authen/
                    && $mac eq $mac_rev )
                  {

                    #将解析到的结果写入数据结构
                    $ret5->{$ip_info}{"location"}     = $nei_location;
                    $ret5->{$ip_info}{"hostname"}     = $device_nei;
                    $ret5->{$ip_info}{"vlan"}         = $mac_vlan;
                    $ret5->{$ip_info}{"mac"}          = $mac;
                    $ret5->{$ip_info}{"interface"}    = $nei_interface;
                    $ret5->{$ip_info}{"vpn_instance"} = $vpn_instance;
                    $ret5->{$ip_info}{"gate_device"}  = $device;
                  }

                  #判断MAC地址是否为 静态绑定 sticky
                  elsif ( $mac_type =~ /sticky/
                    && $mac eq $mac_rev )
                  {

                    #将解析到的结果写入数据结构
                    $ret4->{$ip_info}{"location"}     = $nei_location;
                    $ret4->{$ip_info}{"hostname"}     = $device_nei;
                    $ret4->{$ip_info}{"interface"}    = $nei_interface;
                    $ret4->{$ip_info}{"mac"}          = $mac;
                    $ret4->{$ip_info}{"vlan"}         = $mac_vlan;
                    $ret4->{$ip_info}{"vpn_instance"} = $vpn_instance;
                    $ret4->{$ip_info}{"gate_device"}  = $device;
                  }

                  #进行逻辑判断,是否匹配当前ARP表项
                  elsif ( $mac eq $mac_rev ) {

                    #将解析到的结果写入数据结构
                    $ret2->{$ip_info}{"location"}     = $nei_location;
                    $ret2->{$ip_info}{"hostname"}     = $device_nei;
                    $ret2->{$ip_info}{"vlan"}         = $mac_vlan;
                    $ret2->{$ip_info}{"mac"}          = $mac;
                    $ret2->{$ip_info}{"interface"}    = $nei_interface;
                    $ret2->{$ip_info}{"vpn_instance"} = $vpn_instance;
                    $ret2->{$ip_info}{"gate_device"}  = $device;
                  }
                }
              }
            }

            #如果对端不支持邻居发现
            elsif ( not defined $device_nei ) {

              #查找设备本身ETH-TRUNK下的接口MAC
              my $dev_mac = $mac_info->{$device};

              #遍历设备本身的MAC地址信息,寻找能与当前ARP配对接口
              foreach my $dev_interface ( keys %{$dev_mac} ) {

                #获取当前邻居的MAC地址表
                my $dev_ret = $dev_mac->{$dev_interface};

                #判断MAC是否为 authen 类型
                my $mac_type = $dev_ret->{"type"};

                #遍历该接口下所有的MAC地址信息
                foreach my $mac_rev ( keys %{$dev_ret} ) {

                  #如果非MAC地址则调整
                  next unless $mac_rev =~ $mac_str;

                  #获取 mac_vlan 地址
                  my $mac_vlan = $dev_ret->{$mac_rev};

                  #判断MAC地址是否为802.1X认证
                  if ( $mac_type =~ /authen/
                    && $mac eq $mac_rev )
                  {

                    #将解析到的结果写入数据结构
                    $ret5->{$ip_info}{"location"}     = $device_location;
                    $ret5->{$ip_info}{"hostname"}     = $device;
                    $ret5->{$ip_info}{"vlan"}         = $mac_vlan;
                    $ret5->{$ip_info}{"mac"}          = $mac;
                    $ret5->{$ip_info}{"interface"}    = $dev_interface;
                    $ret5->{$ip_info}{"vpn_instance"} = $vpn_instance;
                    $ret5->{$ip_info}{"gate_device"}  = $device;
                  }

                  #判断MAC地址是否为 静态绑定 sticky
                  elsif ( $mac_type =~ /sticky/
                    && $mac eq $mac_rev )
                  {

                    #将解析到的结果写入数据结构
                    $ret4->{$ip_info}{"location"}     = $device_location;
                    $ret4->{$ip_info}{"hostname"}     = $device;
                    $ret4->{$ip_info}{"vlan"}         = $mac_vlan;
                    $ret4->{$ip_info}{"mac"}          = $mac;
                    $ret4->{$ip_info}{"interface"}    = $dev_interface;
                    $ret4->{$ip_info}{"vpn_instance"} = $vpn_instance;
                    $ret4->{$ip_info}{"gate_device"}  = $device;
                  }

                  #进行逻辑判断,是否匹配当前ARP表项
                  elsif ( $mac eq $mac_rev ) {

                    #将解析到的结果写入数据结构
                    $ret2->{$ip_info}{"location"}     = $device_location;
                    $ret2->{$ip_info}{"hostname"}     = $device;
                    $ret2->{$ip_info}{"vlan"}         = $mac_vlan;
                    $ret2->{$ip_info}{"mac"}          = $mac;
                    $ret2->{$ip_info}{"interface"}    = $dev_interface;
                    $ret2->{$ip_info}{"vpn_instance"} = $vpn_instance;
                    $ret2->{$ip_info}{"gate_device"}  = $device;
                  }
                }
              }
            }
          }
        }

        #检查ARP学习接口是否支持LLDP发现
        elsif ( $self_interface =~ /GE|Eth\d/ ) {

          #裁剪eth-trunk相关参数
          $self_interface =~ s/\.\d+$//;

          #获取当前接口的邻居设备,输出为邻居设备名
          my $device_nei = $nei_info->{$device}{$self_interface}{"neighbor"};

          #判断是否为 ipPhone
          if ( $device_nei =~ /eSpace/i ) {

            #如果命中IP电话,将解析到的结果写入数据结构
            $ret4->{$ip_info}{"location"}     = $device_location;
            $ret4->{$ip_info}{"hostname"}     = $device;
            $ret4->{$ip_info}{"interface"}    = $self_interface;
            $ret4->{$ip_info}{"mac"}          = $mac;
            $ret4->{$ip_info}{"vlan"}         = $vlan;
            $ret4->{$ip_info}{"vpn_instance"} = $vpn_instance;
            $ret4->{$ip_info}{"gate_device"}  = $device;

            #找到即停
            next;
          }

          #查看是否匹配
          elsif ( $device_nei && defined $device_nei ) {

            #获取邻居设备的机房位置
            my $nei_location = $location->{$device_nei};

            #调取该设备的MAC地址对象
            my $nei_mac = $mac_info->{$device_nei};

            #遍历邻居的MAC地址信息,寻找能与当前ARP配对接口
            foreach my $nei_interface ( keys %{$nei_mac} ) {

              #获取当前邻居的MAC地址表
              my $nei_ret = $nei_mac->{$nei_interface};

              #判断MAC是否为 authen 类型
              my $mac_type = $nei_ret->{"type"};

              #遍历该接口下所有的MAC地址信息
              foreach my $mac_rev ( keys %{$nei_ret} ) {

                #如果非MAC地址则调整
                next unless $mac_rev =~ $mac_str;

                #获取 mac_vlan 地址
                my $mac_vlan = $nei_ret->{$mac_rev};

                #判断MAC地址是否为802.1X认证
                if ( $mac_type =~ /authen/
                  && $mac eq $mac_rev )
                {

                  #将解析到的结果写入数据结构
                  $ret5->{$ip_info}{"location"}     = $nei_location;
                  $ret5->{$ip_info}{"hostname"}     = $device_nei;
                  $ret5->{$ip_info}{"interface"}    = $nei_interface;
                  $ret5->{$ip_info}{"mac"}          = $mac;
                  $ret5->{$ip_info}{"vlan"}         = $mac_vlan;
                  $ret5->{$ip_info}{"vpn_instance"} = $vpn_instance;
                  $ret5->{$ip_info}{"gate_device"}  = $device;
                }

                #判断MAC地址是否为 静态绑定 sticky
                elsif ( $mac_type =~ /sticky/
                  && $mac eq $mac_rev )
                {

                  #将解析到的结果写入数据结构
                  $ret4->{$ip_info}{"location"}     = $nei_location;
                  $ret4->{$ip_info}{"hostname"}     = $device_nei;
                  $ret4->{$ip_info}{"interface"}    = $nei_interface;
                  $ret4->{$ip_info}{"mac"}          = $mac;
                  $ret4->{$ip_info}{"vlan"}         = $mac_vlan;
                  $ret4->{$ip_info}{"vpn_instance"} = $vpn_instance;
                  $ret4->{$ip_info}{"gate_device"}  = $device;
                }

                #进行逻辑判断,是否匹配当前ARP表项
                elsif ( $mac eq $mac_rev ) {

                  #将解析到的结果写入数据结构
                  $ret2->{$ip_info}{"location"}     = $nei_location;
                  $ret2->{$ip_info}{"hostname"}     = $device_nei;
                  $ret2->{$ip_info}{"interface"}    = $nei_interface;
                  $ret2->{$ip_info}{"mac"}          = $mac;
                  $ret2->{$ip_info}{"vlan"}         = $mac_vlan;
                  $ret2->{$ip_info}{"vpn_instance"} = $vpn_instance;
                  $ret2->{$ip_info}{"gate_device"}  = $device;
                }
              }
            }
          }

          #如果对端不支持邻居发现再查找本地MAC地址
          elsif ( not defined $device_nei ) {

            #查找设备本身ETH-TRUNK下的接口MAC
            my $dev_mac = $mac_info->{$device};

            #遍历设备本身的MAC地址信息,寻找能与当前ARP配对接口
            foreach my $dev_interface ( keys %{$dev_mac} ) {

              #获取当前邻居的MAC地址表
              my $dev_ret = $dev_mac->{$dev_interface};

              #判断MAC是否为 authen 类型
              my $mac_type = $dev_ret->{"type"};

              #遍历该接口下所有的MAC地址信息
              foreach my $mac_rev ( keys %{$dev_ret} ) {

                #如果非MAC地址则调整
                next unless $mac_rev =~ $mac_str;

                #获取 mac_vlan 地址
                my $mac_vlan = $dev_ret->{$mac_rev};

                #判断MAC地址是否为802.1X认证
                if ( $mac_type =~ /authen/
                  && $mac eq $mac_rev )
                {

                  #将解析到的结果写入数据结构
                  $ret5->{$ip_info}{"location"}     = $device_location;
                  $ret5->{$ip_info}{"hostname"}     = $device;
                  $ret5->{$ip_info}{"vlan"}         = $mac_vlan;
                  $ret5->{$ip_info}{"mac"}          = $mac;
                  $ret5->{$ip_info}{"interface"}    = $dev_interface;
                  $ret5->{$ip_info}{"vpn_instance"} = $vpn_instance;
                  $ret5->{$ip_info}{"gate_device"}  = $device;
                }

                #判断MAC地址是否为 静态绑定 sticky
                elsif ( $mac_type =~ /sticky/
                  && $mac eq $mac_rev )
                {

                  #将解析到的结果写入数据结构
                  $ret4->{$ip_info}{"location"}     = $device_location;
                  $ret4->{$ip_info}{"hostname"}     = $device;
                  $ret4->{$ip_info}{"vlan"}         = $mac_vlan;
                  $ret4->{$ip_info}{"mac"}          = $mac;
                  $ret4->{$ip_info}{"interface"}    = $dev_interface;
                  $ret4->{$ip_info}{"vpn_instance"} = $vpn_instance;
                  $ret4->{$ip_info}{"gate_device"}  = $device;
                }

                #进行逻辑判断,是否匹配当前ARP表项
                elsif ( $mac eq $mac_rev ) {

                  #将解析到的结果写入数据结构
                  $ret2->{$ip_info}{"location"}     = $device_location;
                  $ret2->{$ip_info}{"hostname"}     = $device;
                  $ret2->{$ip_info}{"vlan"}         = $mac_vlan;
                  $ret2->{$ip_info}{"mac"}          = $mac;
                  $ret2->{$ip_info}{"interface"}    = $dev_interface;
                  $ret2->{$ip_info}{"vpn_instance"} = $vpn_instance;
                  $ret2->{$ip_info}{"gate_device"}  = $device;
                }
              }
            }
          }
        }

        #如果都没找到再次查找设备本身 MAC
        else {

          #遍历邻居的MAC地址信息,寻找能与当前ARP配对接口
          foreach my $dev_interface ( keys %{$device_mac} ) {

            #获取当前邻居的MAC地址表
            my $dev_ret = $device_mac->{$dev_interface};

            #判断MAC是否为 authen 类型
            my $mac_type = $dev_ret->{"type"};

            #遍历该接口下所有的MAC地址信息
            foreach my $mac_rev ( keys %{$dev_ret} ) {

              #如果非MAC地址则调整
              next unless $mac_rev =~ $mac_str;

              #获取 mac_vlan 地址
              my $mac_vlan = $dev_ret->{$mac_rev};

              #判断MAC地址是否为802.1X认证
              if ( $mac_type =~ /authen/ && $mac eq $mac_rev ) {

                #将解析到的结果写入数据结构
                $ret5->{$ip_info}{"location"}     = $device_location;
                $ret5->{$ip_info}{"hostname"}     = $device;
                $ret5->{$ip_info}{"interface"}    = $dev_interface;
                $ret5->{$ip_info}{"mac"}          = $mac;
                $ret5->{$ip_info}{"vlan"}         = $mac_vlan;
                $ret5->{$ip_info}{"vpn_instance"} = $vpn_instance;
                $ret5->{$ip_info}{"gate_device"}  = $device;
              }

              #判断MAC地址是否为 静态绑定 sticky
              elsif ( $mac_type =~ /sticky/
                && $mac eq $mac_rev )
              {

                #将解析到的结果写入数据结构
                $ret4->{$ip_info}{"location"}     = $device_location;
                $ret4->{$ip_info}{"hostname"}     = $device;
                $ret4->{$ip_info}{"interface"}    = $dev_interface;
                $ret4->{$ip_info}{"mac"}          = $mac;
                $ret4->{$ip_info}{"vlan"}         = $mac_vlan;
                $ret4->{$ip_info}{"vpn_instance"} = $vpn_instance;
                $ret4->{$ip_info}{"gate_device"}  = $device;
              }

              #进行逻辑判断,是否匹配当前ARP表项
              elsif ( $mac eq $mac_rev ) {

                #将解析到的结果写入数据结构
                $ret2->{$ip_info}{"location"}     = $device_location;
                $ret2->{$ip_info}{"hostname"}     = $device;
                $ret2->{$ip_info}{"interface"}    = $dev_interface;
                $ret2->{$ip_info}{"mac"}          = $mac;
                $ret2->{$ip_info}{"vlan"}         = $mac_vlan;
                $ret2->{$ip_info}{"vpn_instance"} = $vpn_instance;
                $ret2->{$ip_info}{"gate_device"}  = $device;
              }
            }
          }
        }

        #将未解析到的结果写入数据结构
        $ret6->{$ip_info}{"location"}     = $device_location;
        $ret6->{$ip_info}{"hostname"}     = $device;
        $ret6->{$ip_info}{"interface"}    = $self_interface;
        $ret6->{$ip_info}{"mac"}          = $mac;
        $ret6->{$ip_info}{"vlan"}         = $vlan;
        $ret6->{$ip_info}{"vpn_instance"} = $vpn_instance;
        $ret6->{$ip_info}{"gate_device"}  = undef;
      }
    }
  }

  #处理解析成功且出现在未解析ret6中的重复项,(设备无序)
  foreach my $ip ( keys %{$ret6} ) {

    #进行条件判断,如出现在ret1 ret2 ret4 ret5 中则剔除
    if ( exists $ret1->{$ip}
      || exists $ret2->{$ip}
      || exists $ret4->{$ip}
      || exists $ret5->{$ip} )
    {

      #如果之前解析过,则剔除该元素
      delete $ret6->{$ip};
    }
  }

  #处理解析成功且出现在未解析ret5中的重复项,(设备无序)
  foreach my $ip ( keys %{$ret5} ) {

    #进行条件判断,如出现在ret1 ret2 ret4中则剔除
    if ( exists $ret1->{$ip}
      || exists $ret2->{$ip}
      || exists $ret4->{$ip} )
    {

      #如果之前解析过,则剔除该元素
      delete $ret5->{$ip};
    }
  }

  #处理解析成功且出现在未解析ret5中的重复项,(设备无序)
  foreach my $ip ( keys %{$ret2} ) {

    #进行条件判断,如出现在ret1 ret4 ret5 中则剔除
    if ( exists $ret1->{$ip}
      || exists $ret4->{$ip}
      || exists $ret5->{$ip} )
    {

      #如果之前解析过,则剔除该元素
      delete $ret5->{$ip};
    }
  }

  #返回计算结果
  my $ret = [ $ret1, $ret2, $ret3, $ret4, $ret5, $ret6 ];
  return $ret;
}

#------------------------------------------------------------------------------
# 华为交换机配置解析函数入口 -- 实现 ARP MAC INTERFACE LLDP 配置发现 入参为文件夹
#------------------------------------------------------------------------------
sub info_parser {

  #接收待解析的配置文件夹
  my $dir = shift;

  #实例化解析结果
  my $ret;

  #实例化 File::Next 对象
  my $files = File::Next::files($dir);

  #迭代取出文件内所有可读文件
  while ( defined( my $file = $files->() ) ) {

    #初始化变量
    my $rev;

    #抓取cfg文件开始解析
    if ( $file =~ /\.(config|conf|cfg|)$/ ) {

      #解析单个文件输出
      $rev = device_info($file);

      #获取解析对象主机名
      my $hostname = $rev->{"hostname"};

      #获取设备信息,将解析结果拆解到 函数 $ret
      $ret->{"arp_info"}{$hostname} = $rev->{"arp_info"};
      $ret->{"mac_info"}{$hostname} = $rev->{"mac_info"};
      $ret->{"nei_info"}{$hostname} = $rev->{"nei_info"};
      $ret->{"eth_info"}{$hostname} = $rev->{"eth_info"};
      $ret->{"location"}{$hostname} = $rev->{"location"};
    }
    else {
      print "$file not parsed \n";
    }
  }

  #返回计算结果
  return $ret;
}

#------------------------------------------------------------------------------
# 华为交换机ARP MAC LLDP INTERFACE 解析函数入口,入参为单个文件名
#------------------------------------------------------------------------------
sub device_info {
  my $file = shift;

  #固化ARP MAC NEIGHBOR INTERFACE 获取命令行
  my $arp_cmd = "dis arp";
  my $mac_cmd = "dis mac-address";
  my $nei_cmd = "dis lldp neighbor brief";
  my $eth_cmd = "dis interface brief";

  #捕捉命令行输出结果
  my $arp_rev = catch_cmd( $arp_cmd, $file );
  my $mac_rev = catch_cmd( $mac_cmd, $file );
  my $nei_rev = catch_cmd( $nei_cmd, $file );
  my $eth_rev = catch_cmd( $eth_cmd, $file );

  #计算mac和arp相关属性
  my $arp_ret  = device_arp($arp_rev);
  my $mac_ret  = device_mac($mac_rev);
  my $nei_ret  = device_nei($nei_rev);
  my $eth_ret  = device_eth($eth_rev);
  my $sysname  = device_sysname($nei_rev);
  my $location = device_location($nei_rev);

  #输出计算结果
  my $ret = {
    "arp_info" => $arp_ret,
    "mac_info" => $mac_ret,
    "nei_info" => $nei_ret,
    "eth_info" => $eth_ret,
    "hostname" => $sysname,
    "location" => $location,
  };

  return $ret;
}

#------------------------------------------------------------------------------
#交换机ARP地址解析用例
#IP ADDRESS      MAC ADDRESS    EXP(M) TYPE/VLAN      INTERFACE    VPN-INSTANCE
#------------------------------------------------------------------------------
#10.4.26.21      0433-8948-9b00        I               MEth0/0/0        MGMT
#------------------------------------------------------------------------------
sub device_arp {

  #接收切割的配置文件
  my $config = shift;

  #构造数据结构
  my $ret;

  #遍历元素,构造数据结构
  foreach my $cli ( @{$config} ) {

    #捕捉实际的 dis arp 输出命令
    #优先匹配较早版本的ARP格式
    if ( $cli
      =~ /^(?<ipaddr>(\d{1,3}\.){3}(\d{1,3}))\s+(?<mac>([0-9a-fA-F]{4}\-){2}[0-9a-fA-F]{4})\s+((?<exp_time>\d+)\s+)?(?<type>(I\s\-|D\-\d+))(\s+(?<interface>.*?))(\s+(?<vpn_instance>.*?))?$/
      )
    {
      #获取解析结果
      my $mac          = $+{mac};
      my $ipaddr       = $+{ipaddr};
      my $interface    = $+{interface};
      my $vpn_instance = $+{vpn_instance};
      my $exp_time     = $+{exp_time};

      #如果MAC为Incomplete
      if ( $mac =~ /Incomplete/i ) {

        #封装特定的数据结构
        $ret->{"incomplete"}{$interface}{$ipaddr}{"ipaddr"}       = $ipaddr;
        $ret->{"incomplete"}{$interface}{$ipaddr}{"mac"}          = "Incomplete";
        $ret->{"incomplete"}{$interface}{$ipaddr}{"exp_time"}     = $exp_time || "forever";
        $ret->{"incomplete"}{$interface}{$ipaddr}{"vlan"}         = undef;
        $ret->{"incomplete"}{$interface}{$ipaddr}{"interface"}    = $interface;
        $ret->{"incomplete"}{$interface}{$ipaddr}{"vpn_instance"} = $vpn_instance;

        #继续下一个迭代
        next;
      }

      #改写type
      my $type = $+{type};

      if ( $type && $type =~ /I/ ) {

        #设备本身接口地址
        $type = "self_ip";

      }
      elsif ( $type && $type =~ /D/ ) {

        #学习到的ARP
        $type = "dynamic";
      }

      #输出解析结果
      $ret->{$type}{$interface}{$ipaddr}{"ipaddr"}       = $ipaddr;
      $ret->{$type}{$interface}{$ipaddr}{"mac"}          = $mac;
      $ret->{$type}{$interface}{$ipaddr}{"exp_time"}     = $exp_time || "forever";
      $ret->{$type}{$interface}{$ipaddr}{"vlan"}         = undef;
      $ret->{$type}{$interface}{$ipaddr}{"interface"}    = $interface;
      $ret->{$type}{$interface}{$ipaddr}{"vpn_instance"} = $vpn_instance;
    }

    #捕捉数据中心CE系列交换机ARP条目
    elsif ( $cli
      =~ /^(?<ipaddr>(\d{1,3}\.){3}(\d{1,3}))\s+((?<mac>(([0-9a-fA-F]{4}\-){2}[0-9a-fA-F]{4}))\s+)?((?<mac>Incomplete)\s+)?((?<exp_time>\d+)\s+)?((?<type_vlan>(.*?)\s+))?((?<interface>.*?)\s+)?(?<vpn_instance>.*?)$/
      )
    {
      #获取解析结果
      my $mac          = $+{mac};
      my $ipaddr       = $+{ipaddr};
      my $interface    = $+{interface};
      my $vpn_instance = $+{vpn_instance};
      my $exp_time     = $+{exp_time};

      #分解type vlan数据
      my ( $type, $vlan ) = split( /\//, $+{type_vlan} );

      #如果MAC为Incomplete
      if ( $mac =~ /Incomplete/i ) {

        #封装特定的数据结构
        $ret->{"incomplete"}{$interface}{$ipaddr}{"ipaddr"}       = $ipaddr;
        $ret->{"incomplete"}{$interface}{$ipaddr}{"mac"}          = "Incomplete";
        $ret->{"incomplete"}{$interface}{$ipaddr}{"exp_time"}     = $exp_time || "forever";
        $ret->{"incomplete"}{$interface}{$ipaddr}{"vlan"}         = $vlan;
        $ret->{"incomplete"}{$interface}{$ipaddr}{"interface"}    = $interface;
        $ret->{"incomplete"}{$interface}{$ipaddr}{"vpn_instance"} = $vpn_instance;

        #继续下一个迭代
        next;
      }

      #改写type
      if ( $type && $type =~ /I/ ) {

        #设备本身接口地址
        $type = "self_ip";

      }
      elsif ( $type && $type =~ /D/ ) {

        #学习到的ARP
        $type = "dynamic";
      }

      #如果捕捉到 dynamic 的MGMT口则跳过
      next if $type eq "dynamic" && $interface =~ /MEth/i;

      #输出解析结果
      $ret->{$type}{$interface}{$ipaddr}{"ipaddr"}       = $ipaddr;
      $ret->{$type}{$interface}{$ipaddr}{"mac"}          = $mac;
      $ret->{$type}{$interface}{$ipaddr}{"exp_time"}     = $exp_time || "forever";
      $ret->{$type}{$interface}{$ipaddr}{"vlan"}         = $vlan;
      $ret->{$type}{$interface}{$ipaddr}{"interface"}    = $interface;
      $ret->{$type}{$interface}{$ipaddr}{"vpn_instance"} = $vpn_instance;
    }
  }

  #返回输出结果
  return $ret;
}

#------------------------------------------------------------------------------
#交换机MAC地址解析用例  -- 交换机本身IP 为GE打头的接口
#MAC Address    VLAN/VSI/BD   Learned-From        Type                Age
#------------------------------------------------------------------------------
#0000-5e00-0102 3200/-/-      Eth-Trunk120        dynamic         1829268
#------------------------------------------------------------------------------
sub device_mac {

  #接收切割的配置文件
  my $config = shift;

  #初始化数据结构
  my $ret;

  #遍历元素,构造数据结构
  foreach my $cli ( @{$config} ) {

    #捕捉实际的 dis arp 输出命令
    if ( $cli =~ $mac_str ) {

      #实例化变量
      my ( $mac, $vlan_vsi_bd, $interface, $type ) = split( /\s+/, $cli );

      #抽取MAC对应的VLAN信息
      my ( $vlan, $vsi, $bd ) = split( /\//, $vlan_vsi_bd );

      #过滤非物理接口
      if ( $interface =~ /GE|Vlanif|Eth\d/i ) {

        #输出匹配信息 如果未命中VLAN则使用VXLAN的BD绑定
        if ( $vlan && $vlan ne '-' ) {
          $ret->{$interface}{$mac} = "VLAN/" . $vlan;
        }

        #检测VSI是否为空
        elsif ( $vsi && $vsi ne '-' ) {
          $ret->{$interface}{$mac} = "VSI/" . $vsi;
        }

        #检测BD是否为空
        elsif ( $bd && $bd ne '-' ) {
          $ret->{$interface}{$mac} = "BD/" . $bd;
        }
      }

      #如果不匹配上述格式则跳转下一个
      else {

        #继续下个数据迭代
        next;
      }

      #写入MAC地址类型 -- 动态还是静态MAC
      $ret->{$interface}{"type"} = $type;
    }
  }

  #返回输出结果
  return $ret;
}

#------------------------------------------------------------------------------
#交换机MAC地址解析用例  -- 交换机本身IP 为GE打头的接口
#<CC-DC1-NZX-SW702>dis lldp neighbor brief
#Local Interface         Exptime(s) Neighbor Interface      Neighbor Device
#------------------------------------------------------------------------------
#10GE1/0/1                      97  10GE1/0/18              CC-DC1-NZX-S1
#------------------------------------------------------------------------------
sub device_nei {

  #接收切割的配置文件
  my $config = shift;

  #初始化数据结构
  my $ret;

  #初略判断交换机版本 -- CE6855系列支持这种格式
  my $ce6855_later  = grep { /Neighbor Interface/ } @{$config};
  my $ce6855_before = grep { /Neighbor Intf/ } @{$config};

  #遍历元素,构造数据结构
  foreach my $cli ( @{$config} ) {

    #捕捉实际的 dis lldp neighbor brief 输出命令
    #用于匹配数据中心CE系列交换机输出
    if ( $ce6855_later && $cli =~ /eSpace|GE/ ) {

      #截取相关字段
      my ( $src_int, $exp_time, $dst_int, $neighbor ) = split( /\s+/, $cli );

      #设置跳转逻辑
      #next if $dst_int =~ $mac_str;

      #构造以接口为索引的哈希对象
      $ret->{$src_int}{"neighbor"}           = $neighbor;
      $ret->{$src_int}{"neighbor_interface"} = $dst_int;
    }

    #兼容早期非数据中心交换机
    elsif ( $ce6855_before && $cli =~ /eSpace|GE/ ) {

      #截取相关字段
      my ( $src_int, $neighbor, $dst_int, $exp_time ) = split( /\s+/, $cli );

      #设置跳转逻辑
      #next if $dst_int =~ $mac_str;

      #构造以接口为索引的哈希对象
      $ret->{$src_int}{"neighbor"}           = $neighbor;
      $ret->{$src_int}{"neighbor_interface"} = $dst_int;
    }
  }

  #返回输出结果
  return $ret;
}

#------------------------------------------------------------------------------
#交换机INTERFACE接口解析用例  -- 交换机ETH-TRUNK接口成员包含缩进
#dis interface brief
#Eth-Trunk0                 up       up        0.01%  0.01%       0          0
#  10GE1/0/1                up       up        0.01%  0.01%       0          0
#  10GE1/0/2                up       up        0.01%  0.01%       0          0
#------------------------------------------------------------------------------
sub device_eth {

  #接收切割的配置文件
  my $config = shift;

  #初始化接口eth-trunk解析结果
  my $ret;

  #初始化eth-trunk代码块命中状态
  my $matched;

  #抓取配置中ETH-TRUNK
  my $eth_trunk;

  foreach my $cli ( @{$config} ) {

    #捕捉eth-trunk代码块并设置命中状态
    if ( $cli =~ /^(?<eth_trunk>(Eth-Trunk\d+))/ ) {

      #将命中数据写入变量
      $eth_trunk = $+{eth_trunk};

      #初始化数据结构为匿名数组
      $ret->{$eth_trunk} = [];

      #改写命中状态
      $matched++;
    }

    #命中eth-trunk代码块进一步解析成员接口
    elsif ( $matched && $cli =~ /^\s{2}(?<interface>\w+(.*?))\s+/ ) {

      #获取捕捉的接口信息,早期版本存在BUG
      my $interface = $+{interface};
      $interface =~ s/GigabitEthernet/GE/i;

      #将成员接口写入eth-trunk对象
      push @{ $ret->{$eth_trunk} }, $interface;

    }
  }

  #返回输出结果
  return $ret;
}

#------------------------------------------------------------------------------
#获取设备的主机名信息
#------------------------------------------------------------------------------
sub device_sysname {

  #读取切片的配置信息
  my $config = shift;

  #初始化数据结构
  my $ret;

  #遍历配置信息
  foreach ( @{$config} ) {
    if (/<(?<hostname>.*?)>/) {

      #将命中数据写入变量
      $ret = $+{hostname};

      #找到即跳出
      last;
    }
  }

  #输出计算结果
  return $ret;
}

#------------------------------------------------------------------------------
#获取交换机的分布位置
#------------------------------------------------------------------------------
sub device_location {

  #读取切片的配置信息
  my $config = shift;

  #初始化数据结构
  my $ret;

  #抓取配置中主机名
  my $sysname = device_sysname($config);

  #遍历哈希
  foreach my $dc ( keys %dc_map ) {

    #检查是否命中数据字典
    if ( $sysname && $sysname =~ $dc ) {

      #匹配机房字典
      $ret = $dc_map{$dc};

      #找到即跳出
      last;
    }
  }

  #输出计算结果
  return $ret;
}

1;