source: trunk/klask @ 273

Last change on this file since 273 was 273, checked in by g7moreau, 7 years ago
  • Update regex match N3483G
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 121.2 KB
RevLine 
[2]1#!/usr/bin/perl -w
[32]2#
[259]3# Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
[32]4#
5# $Id: klask 273 2017-09-20 16:13:38Z g7moreau $
[2]6
7use strict;
[3]8use warnings;
[259]9use version; our $VERSION = qv('0.6.5');
[2]10
[63]11use Readonly;
[64]12use FileHandle;
[2]13use Net::SNMP;
[44]14#use YAML;
15use YAML::Syck;
[2]16use Net::Netmask;
17use Net::CIDR::Lite;
18use NetAddr::IP;
[110]19use Getopt::Long qw(GetOptions);
[68]20use Socket;
[109]21use List::Util 'shuffle';
[2]22
[8]23# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
[198]24# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
[79]25# arping net-tools fping bind9-host arpwatch
[2]26
[218]27################################################################
28# general initialization
29################################################################
30
[100]31my $KLASK_VAR      = '/var/lib/klask';
[92]32my $KLASK_CFG_FILE = '/etc/klask/klask.conf';
[28]33my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
34my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
[2]35
[26]36test_running_environnement();
37
[44]38my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
[2]39
[221]40my %DEFAULT     = %{$KLASK_CFG->{'default'}};
41my @SWITCH_LIST = @{$KLASK_CFG->{'switch'}};
[2]42
[196]43my %SWITCH_LEVEL = ();
[63]44my %SWITCH_DB    = ();
[2]45LEVEL_OF_EACH_SWITCH:
[209]46for my $sw (@SWITCH_LIST) {
[261]47   $SWITCH_LEVEL{$sw->{'hostname'}} = $sw->{'level'} || $DEFAULT{'switch_level'}  || 2;
48   $SWITCH_DB{$sw->{'hostname'}} = $sw;
[208]49
50   # SNMP parameter initialisation
[261]51   my %session = ( -hostname   => $sw->{'hostname'} );
[221]52   $session{-version} = $sw->{'version'}  || 1;
53   $session{-port}    = $sw->{'snmpport'} || $DEFAULT{'snmpport'}  || 161;
54   if (exists $sw->{'version'} and $sw->{'version'} eq '3') {
55      $session{-username} = $sw->{'username'} || 'snmpadmin';
[208]56      }
57   else {
[221]58      $session{-community} = $sw->{'community'} || $DEFAULT{'community'} || 'public';
[208]59      }
[221]60   $sw->{'snmp_param_session'} = \%session;
[2]61   }
[261]62@SWITCH_LIST = reverse sort { $SWITCH_LEVEL{$a->{'hostname'}} <=> $SWITCH_LEVEL{$b->{'hostname'}} } @{$KLASK_CFG->{'switch'}};
[2]63
[196]64#my %SWITCH_PORT_COUNT = ();
[2]65
[11]66my %CMD_DB = (
[133]67   'help'                 => \&cmd_help,
68   'version'              => \&cmd_version,
69   'exportdb'             => \&cmd_exportdb,
70   'updatedb'             => \&cmd_updatedb,
71   'searchdb'             => \&cmd_searchdb,
72   'removedb'             => \&cmd_removedb,
73   'cleandb'              => \&cmd_cleandb,
74   'search'               => \&cmd_search,
75   'enable'               => \&cmd_enable,
76   'disable'              => \&cmd_disable,
77   'status'               => \&cmd_status,
78   'updatesw'             => \&cmd_updatesw,
79   'exportsw'             => \&cmd_exportsw,
80   'iplocation'           => \&cmd_ip_location,
81   'ip-free'              => \&cmd_ip_free,
[35]82   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
[133]83   'bad-vlan-id'          => \&cmd_bad_vlan_id,
[239]84   'poe-enable'           => \&cmd_poe_enable,
85   'poe-disable'          => \&cmd_poe_disable,
86   'poe-status'           => \&cmd_poe_status,
[238]87   'port-setvlan'         => \&cmd_port_setvlan,
88   'port-getvlan'         => \&cmd_port_getvlan,
89   'vlan-setname'         => \&cmd_vlan_setname,
90   'vlan-getname'         => \&cmd_vlan_getname,
91   'vlan-list'            => \&cmd_vlan_list,
[142]92   'rebootsw'             => \&cmd_rebootsw,
[2]93   );
94
[218]95#Readonly my %INTERNAL_PORT_MAP => (
96#   0 => 'A',
97#   1 => 'B',
98#   2 => 'C',
99#   3 => 'D',
100#   4 => 'E',
101#   5 => 'F',
102#   6 => 'G',
103#   7 => 'H',
104#   );
105#Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
[2]106
[63]107Readonly my %SWITCH_KIND => (
[128]108   # HP
[171]109   J3299A           => { type => 1, model => 'HP224M',         match => 'HP J3299A ProCurve Switch 224M'       },
110   J4120A           => { type => 1, model => 'HP1600M',        match => 'HP J4120A ProCurve Switch 1600M'      },
111   J9029A           => { type => 1, model => 'HP1800-8G',      match => 'PROCURVE J9029A'                      },
112   J9449A           => { type => 1, model => 'HP1810-8G',      match => 'HP ProCurve 1810G - 8 GE'             },
113   J4093A           => { type => 1, model => 'HP2424M',        match => 'HP J4093A ProCurve Switch 2424M'      },
114   J9279A           => { type => 1, model => 'HP2510G-24',     match => 'ProCurve J9279A Switch 2510G-24'      },
115   J9280A           => { type => 1, model => 'HP2510G-48',     match => 'ProCurve J9280A Switch 2510G-48'      },
116   J4813A           => { type => 1, model => 'HP2524',         match => 'HP J4813A ProCurve Switch 2524'       },
117   J4900A           => { type => 1, model => 'HP2626A',        match => 'HP J4900A ProCurve Switch 2626'       },
118   J4900B           => { type => 1, model => 'HP2626B',        match => 'J4900B.+?Switch 2626'                 }, # ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
119   J4899B           => { type => 1, model => 'HP2650',         match => 'ProCurve J4899B Switch 2650'          },
120   J9021A           => { type => 1, model => 'HP2810-24G',     match => 'ProCurve J9021A Switch 2810-24G'      },
121   J9022A           => { type => 1, model => 'HP2810-48G',     match => 'ProCurve J9022A Switch 2810-48G'      },
122   J8692A           => { type => 1, model => 'HP3500-24G',     match => 'J8692A Switch 3500yl-24G'             },
123   J4903A           => { type => 1, model => 'HP2824',         match => 'J4903A.+?Switch 2824,'                },
124   J4110A           => { type => 1, model => 'HP8000M',        match => 'HP J4110A ProCurve Switch 8000M'      },
125   JE074A           => { type => 2, model => 'HP5120-24G',     match => 'HP Comware.+?A5120-24G EI Switch'     },
126   JE069A           => { type => 2, model => 'HP5120-48G',     match => 'HP Comware.+?A5120-48G EI Switch'     },
127   JD377A           => { type => 2, model => 'HP5500-24G',     match => 'HP Comware.+?A5500-24G EI Switch'     },
128   JD374A           => { type => 2, model => 'HP5500-24F',     match => 'HP Comware.+?A5500-24G-SFP EI Switch' },
[128]129   # BayStack
[171]130   BS350T           => { type => 1, model => 'BS350T',         match => 'BayStack 350T HW'                     },
[128]131   # Nexans
[273]132   N3483G           => { type => 2, model => 'NA3483-6G',      match => 'GigaSwitch V3 TP SFP-I 48.+ ES3'      },
[236]133   N3483P           => { type => 2, model => 'NA3483-6P',      match => 'GigaSwitch V3 TP.PSE.+ 48/54V ES3'    }, # GigaSwitch V3 TP(PSE+) SFP-I 48/54V ES3 (HW3/ENHANCED/SECURITY/V4.10C)
[145]134   # DELL
[184]135   PC7024           => { type => 2, model => 'DPC7024',        match => 'PowerConnect 7024,.+?VxWorks'         },
[171]136   N2048            => { type => 2, model => 'DN2048',         match => 'Dell Networking N2048,'               },
[183]137   N4032F           => { type => 2, model => 'DN4032F',        match => 'Dell Networking N4032F,'              },
[171]138   N4064F           => { type => 2, model => 'DN4064F',        match => 'Dell Networking N4064F,'              },
[128]139   # 3COM
[171]140   'H3C5500'        => { type => 1, model => 'H3C5500',        match => 'H3C S5500-SI Series'                  },
141   '3C17203'        => { type => 1, model => '3C17203',        match => '3Com SuperStack 3 24-Port'            },
142   '3C17204'        => { type => 1, model => '3C17204',        match => '3Com SuperStack 3 48-Port'            },
143   '3CR17562-91'    => { type => 1, model => '3CR17562-91',    match => '3Com Switch 4500 50-Port'             },
144   '3CR17255-91'    => { type => 1, model => '3CR17255-91',    match => '3Com Switch 5500G-EI 48-Port'         },
145   '3CR17251-91'    => { type => 1, model => '3CR17251-91',    match => '3Com Switch 5500G-EI 48-Port'         },
146   '3CR17571-91'    => { type => 1, model => '3CR17571-91',    match => '3Com Switch 4500 PWR 26-Port'         },
147   '3CRWX220095A'   => { type => 1, model => '3CRWX220095A',   match => '3Com Wireless LAN Controller'         },
148   '3CR17254-91'    => { type => 1, model => '3CR17254-91',    match => '3Com Switch 5500G-EI 24-Port'         },
149   '3CRS48G-24S-91' => { type => 1, model => '3CRS48G-24S-91', match => '3Com Switch 4800G 24-Port'            },
150   '3CRS48G-48S-91' => { type => 1, model => '3CRS48G-48S-91', match => '3Com Switch 4800G 48-Port'            },
151   '3C17708'        => { type => 1, model => '3C17708',        match => '3Com Switch 4050'                     },
152   '3C17709'        => { type => 1, model => '3C17709',        match => '3Com Switch 4060'                     },
153   '3C17707'        => { type => 1, model => '3C17707',        match => '3Com Switch 4070'                     },
154   '3CR17258-91'    => { type => 1, model => '3CR17258-91',    match => '3Com Switch 5500G-EI 24-Port SFP'     },
155   '3CR17181-91'    => { type => 1, model => '3CR17181-91',    match => '3Com Switch 5500-EI 28-Port FX'       },
156   '3CR17252-91'    => { type => 1, model => '3CR17252-91',    match => '3Com Switch 5500G-EI PWR 24-Port'     },
157   '3CR17253-91'    => { type => 1, model => '3CR17253-91',    match => '3Com Switch 5500G-EI PWR 48-Port'     },
158   '3CR17250-91'    => { type => 1, model => '3CR17250-91',    match => '3Com Switch 5500G-EI 24-Port'         },
159   '3CR17561-91'    => { type => 1, model => '3CR17561-91',    match => '3Com Switch 4500 26-Port'             },
160   '3CR17572-91'    => { type => 1, model => '3CR17572-91',    match => '3Com Switch 4500 PWR 50-Port'         },
161   '3C17702-US'     => { type => 1, model => '3C17702-US',     match => '3Com Switch 4900 SX'                  },
162   '3C17700'        => { type => 1, model => '3C17700',        match => '3Com Switch 4900'                     },
[18]163   );
[63]164
165Readonly my %OID_NUMBER => (
[68]166   sysDescription  => '1.3.6.1.2.1.1.1.0',
167   sysName         => '1.3.6.1.2.1.1.5.0',
168   sysContact      => '1.3.6.1.2.1.1.4.0',
169   sysLocation     => '1.3.6.1.2.1.1.6.0',
[246]170   searchPort1     => '1.3.6.1.2.1.17.4.3.1.2',          # BRIDGE-MIB (802.1D).
171   searchPort2     => '1.3.6.1.2.1.17.7.1.2.2.1.2',      # Q-BRIDGE-MIB (802.1Q) add 0 if unknown vlan id
172   vlanPortDefault => '1.3.6.1.2.1.17.7.1.4.5.1.1',      # dot1qPvid
173   vlanStatus      => '1.3.6.1.2.1.17.7.1.4.3.1.5',      # integer 4 Create, 6 Destroy
174   vlanName        => '1.3.6.1.2.1.17.7.1.4.3.1.1',      # string
175   HPicfReset      => '1.3.6.1.4.1.11.2.14.11.1.4.1',    # HP reboot switch
176   ifIndex         => '1.3.6.1.2.1.17.1.4.1.2',          # dot1dBasePortIfIndex - Interface index redirection
177   ifName          => '1.3.6.1.2.1.31.1.1.1.1',          # Interface name (give port number)
178   portUpDown      => '1.3.6.1.2.1.2.2.1.7',             # 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)  = 2 (down)
179   poeState        => '1.3.6.1.2.1.105.1.1.1.3.1',       # 1.3.6.1.2.1.105.1.1.1.3.1.NoPort = 1 (poe up)  = 2 (poe down) - Cisco and Zyxel
180   NApoeState      => '1.3.6.1.4.1.266.20.3.1.1.21',     # .NoPort = 2 (poe off)  = 8 (poe atHighPower) - Nexans
[240]181   ifAggregator    => '1.2.840.10006.300.43.1.2.1.1.12', # dot3adAggPortSelectedAggID - 0 not part of an  Aggregator - Ciso Dell HP Comware -  See https://stackoverflow.com/questions/14960157/how-to-map-portchannel-to-interfaces-via-snmp https://gist.github.com/bldewolf/6314435
[19]182   );
[18]183
[206]184Readonly my %PORT_UPDOWN => (
185   1 => 'enable',
186   2 => 'disable',
187   );
188
[63]189Readonly my $RE_MAC_ADDRESS  => qr{ [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} }xms;
190Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
191
[223]192Readonly my $RE_FLOAT_HOSTNAME => $DEFAULT{'float-regex'} || qr{ ^float }xms;
[63]193
[248]194Readonly my $SEP_AGGREGATOR_PORT => ',';  # : is already use to join switch and port
195Readonly my $SEP_SWITCH_PORT     => ':';
[246]196
197
[218]198################################################################
[206]199# main program
[218]200################################################################
[2]201
[11]202my $cmd = shift @ARGV || 'help';
203if (defined $CMD_DB{$cmd}) {
204   $CMD_DB{$cmd}->(@ARGV);
[2]205   }
206else {
[63]207   print {*STDERR} "klask: command $cmd not found\n\n";
[11]208   $CMD_DB{help}->();
[2]209   exit 1;
210   }
211
212exit;
213
[218]214################################################################
[206]215# subroutine
[218]216################################################################
[206]217
[218]218#---------------------------------------------------------------
[26]219sub test_running_environnement {
220   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
221   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
[63]222   return;
[26]223   }
224
[218]225#---------------------------------------------------------------
[34]226sub test_switchdb_environnement {
227   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
[63]228   return;
[34]229   }
230
[218]231#---------------------------------------------------------------
[34]232sub test_maindb_environnement {
233   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
[63]234   return;
[34]235   }
236
[218]237#---------------------------------------------------------------
[2]238# fast ping dont l'objectif est de remplir la table arp de la machine
[111]239sub fast_ping {
[68]240   # Launch this command without waiting...
[258]241   system "fping -q -c 1 @_ > /dev/null 2>&1 &";
[63]242   return;
[2]243   }
244
[218]245#---------------------------------------------------------------
[63]246sub shell_command {
247   my $cmd = shift;
248
[64]249   my $fh     = new FileHandle;
250   my $result = '';
[96]251   open $fh, q{-|}, "LANG=C $cmd" or die "Can't exec $cmd\n";
[64]252   $result .= <$fh>;
[63]253   close $fh;
254   chomp $result;
255   return $result;
256   }
257
[218]258#---------------------------------------------------------------
[2]259# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
260sub resolve_ip_arp_host {
261   my $param_ip_or_host = shift;
[63]262   my $interface = shift || q{*};
263   my $type      = shift || q{fast};
[186]264   my $already   = shift || q{yes};
[2]265
266   my %ret = (
267      hostname_fq  => 'unknow',
268      ipv4_address => '0.0.0.0',
269      mac_address  => 'unknow',
270      );
271
[68]272   # perl -MSocket -E 'say inet_ntoa(scalar gethostbyname("tech7meylan.hmg.inpg.fr"))'
273   my $packed_ip = scalar gethostbyname($param_ip_or_host);
274   return %ret if not defined $packed_ip;
[261]275   $ret{'ipv4_address'} = inet_ntoa($packed_ip);
276   #if ($ret{'ipv4_address'} !~ m/$RE_IPv4_ADDRESS/) {
277   #   print "Error: for computer $param_ip_or_host on interface $interface, IP $ret{'ipv4_address'} is not valide\n";
[197]278   #   return %ret;
279   #   }
[68]280
281   # perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("194.254.66.240"), AF_INET)'
[186]282   my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) if $already eq 'yes';
[261]283   $ret{'hostname_fq'} = $hostname_fq if defined $hostname_fq;
[68]284
285   # my $cmd = q{grep  -he '\b} . $param_ip_or_host . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
[261]286   #my $cmd = q{grep  -he '\b} . $ret{'ipv4_address'} . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
287   my $cmd = q{grep  -He '\b} . $ret{'ipv4_address'} . q{\b' } . "/var/lib/arpwatch/$interface.dat" . '| sed -e \'s|^/var/lib/arpwatch/\(.*\)\.dat:|\1 |;\' | sort -rn -k 4,4 | head -1';
[144]288   #grep -He 194.254.66.252 /var/lib/arpwatch/*.dat | sed -e 's|^/var/lib/arpwatch/\(.*\)\.dat:|\1\t|;' | sort -rn -k 4,4 | head -1
289
[62]290   my $cmd_arpwatch = shell_command $cmd;
[144]291   #my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
292   my ($interface2, $arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
[44]293
[261]294   $ret{'interface'}    = $interface2 || $interface;
295   $ret{'mac_address'}  = $arp       if $arp;
296   $ret{'timestamp'}    = $timestamp if $timestamp;
[2]297
[63]298   my $nowtimestamp = time;
[3]299
[96]300   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 45 * 60 ) ) ) { # 45 min
[261]301      $ret{'mac_address'} = 'unknow';
[3]302      return %ret;
303      }
304
[194]305   # ARP result
306   #
307   # LANG=C arp -a 194.254.66.62 -i eth331
308   # gw66-62.legi.grenoble-inp.fr (194.254.66.62) at 00:08:7c:bb:0f:c0 [ether] on eth331
309   #
310   # LANG=C ip neigh show to 194.254.66.62 dev eth331
311   # 194.254.66.62 lladdr 00:08:7c:bb:0f:c0 REACHABLE
[197]312   # LANG=C ip neigh show to 194.254.66.62
313   # 194.254.66.62 dev eth331 lladdr 00:08:7c:bb:0f:c0 REACHABLE
[261]314#   my $cmd_arp  = shell_command "arp -a $param_ip_or_host -i $ret{'interface'}";
[194]315#   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
[261]316#      ( $ret{'hostname_fq'}, $ret{'ipv4_address'}, $ret{'mac_address'} )  = ($1, $2, $3);
[194]317#      }
[261]318   if ($ret{'mac_address'} eq 'unknow') {
[196]319      # Last chance to have the mac_address
[261]320      if ($ret{'interface'} eq '*') {
321         my $cmd_arp  = shell_command "ip neigh show to $ret{'ipv4_address'}";
[197]322         if ( $cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s dev \s ([\w\d\.\:]+) \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms ) {
[261]323            ($ret{'interface'}, $ret{'mac_address'}) = ($1, $2);
[197]324            }
[194]325         }
[197]326      else {
[261]327         my $cmd_arp  = shell_command "ip neigh show to $ret{'ipv4_address'} dev $ret{'interface'}";
[197]328         if ( $cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms ) {
[261]329            $ret{'mac_address'} = $1;
[197]330            }
331         }
[63]332      }
[2]333
[96]334   # Normalize MAC Address
[261]335   if ($ret{'mac_address'} ne 'unknow') {
[2]336      my @paquets = ();
[261]337      for ( split m/ : /xms, $ret{'mac_address'} ) {
[63]338         my @chars = split m//xms, uc "00$_";
[2]339         push @paquets, "$chars[-2]$chars[-1]";
340         }
[261]341      $ret{'mac_address'} = join q{:}, @paquets;
[2]342      }
343
344   return %ret;
345   }
346
[246]347#---------------------------------------------------------------
[20]348# Find Surname of a switch
349sub get_switch_model {
[22]350   my $sw_snmp_description = shift || 'unknow';
[169]351   $sw_snmp_description =~ s/[\n\r]/ /g;
[63]352
[20]353   for my $sw_kind (keys %SWITCH_KIND) {
[64]354      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/ms; # option xms break search, why ?
[63]355
[20]356      return $SWITCH_KIND{$sw_kind}->{model};
357      }
[63]358
[22]359   return $sw_snmp_description;
[20]360   }
361
[218]362#---------------------------------------------------------------
[2]363# va rechercher le nom des switchs pour savoir qui est qui
[4]364sub init_switch_names {
[173]365   my ($verbose, $verb_description, $check_hostname, $check_location) = @_;
[63]366
[133]367   printf "%-26s                %-25s %s\n",'Switch','Description','Type' if $verbose;
[82]368   print "------------------------------------------------------------------------------\n" if $verbose;
[2]369
370   INIT_EACH_SWITCH:
[196]371   for my $sw (@SWITCH_LIST) {
[221]372      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[14]373      print "$error \n" if $error;
374
[2]375      my $result = $session->get_request(
[18]376         -varbindlist => [
[261]377            $OID_NUMBER{'sysDescription'},
378            $OID_NUMBER{'sysName'},
379            $OID_NUMBER{'sysContact'},
380            $OID_NUMBER{'sysLocation'},
[18]381            ]
[2]382         );
[196]383      if (!defined $result) {
384         printf {*STDERR} "ERROR: %s.\n", $session->error();
385         $session->close();
386         # Remove bad switch
[261]387         @SWITCH_LIST = grep { $_->{'hostname'} ne $sw->{'hostname'} } @SWITCH_LIST;
388         delete $SWITCH_LEVEL{$sw->{'hostname'}} if exists $SWITCH_LEVEL{$sw->{'hostname'}};
389         delete $SWITCH_DB{$sw->{'hostname'}}    if exists $SWITCH_DB{$sw->{'hostname'}};
[196]390         next INIT_EACH_SWITCH;
391         }
392
[262]393      $sw->{'description'} = $result->{$OID_NUMBER{'sysName'}} || $sw->{'hostname'};
[261]394      $sw->{model} = get_switch_model($result->{$OID_NUMBER{'sysDescription'}});
[170]395      if ($verb_description) {
[261]396         my $desc = $result->{$OID_NUMBER{'sysDescription'}};
[170]397         $desc =~ s/[\n\r]/ /g;
398         print "   description: $desc\n"
399         }
[172]400      if ($check_hostname) {
[261]401         my ($hostname) = split /\./, $sw->{'hostname'}, 2;
402         print " $hostname - error internal hostname: $sw->{'hostname'}\n" if $result->{$OID_NUMBER{'sysName'}} ne $hostname;
[172]403         }
[173]404      if ($check_location) {
[261]405         my $location = $result->{$OID_NUMBER{'sysLocation'}};
[174]406         $location =~ s/^"(.+)"$/$1/;
[261]407         print " $sw->{'hostname'} - error location: '$location' -> '$sw->{'location'}'\n" if $location ne $sw->{'location'};
[173]408         }
[261]409      #$sw->{'location'} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{'hostname'};
410      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{'hostname'};
[2]411      $session->close;
[22]412
[63]413      # Ligne à virer car on récupère maintenant le modèle du switch
[262]414      #my ($desc, $type) = split m/ : /xms, $sw->{'description'}, 2;
415      printf "%-26s 0--------->>>> %-25s %s\n", $sw->{'hostname'}, $sw->{'description'}, $sw->{model} if $verbose;
[2]416      }
417
418   print "\n" if $verbose;
[63]419   return;
[2]420   }
421
[218]422#---------------------------------------------------------------
[246]423# convert hexa (only 2 digits) to decimal
424sub digit_hex2dec {
[2]425   #00:0F:1F:43:E4:2B
[63]426   my $car = '00' . uc shift;
[2]427
[4]428   return '00' if $car eq '00UNKNOW';
[2]429   my %table = (
[62]430      '0'=>'0',  '1'=>'1',  '2'=>'2',  '3'=>'3',  '4'=>'4',
431      '5'=>'5',  '6'=>'6',  '7'=>'7',  '8'=>'8',  '9'=>'9',
[63]432      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15',
[2]433      );
[63]434   my @chars = split m//xms, $car;
[2]435   return $table{$chars[-2]}*16 + $table{$chars[-1]};
436   }
437
[218]438#---------------------------------------------------------------
[136]439
440sub normalize_mac_address {
441   my $mac_address = shift;
442
[228]443   # D07E-28D1-7AB8 or D07E.28D1.7AB8 or d07e28-d17ab8
444   if ($mac_address =~ m{^ (?: [0-9A-Fa-f]{4} [-\.]){2} [0-9A-Fa-f]{4} $}xms
445      or $mac_address =~ m{^ [0-9A-Fa-f]{6} - [0-9A-Fa-f]{6} $}xms
446      ) {
[229]447      $mac_address =~ s/[-\.]//g;
[139]448      return join q{:}, unpack('(A2)*', uc($mac_address));
449      }
450
[136]451   return join q{:}, map { substr( uc("00$_"), -2) } split m/ [:-] /xms, $mac_address;
452   }
453
[218]454#---------------------------------------------------------------
[246]455# convert MAC hex address to decimal
456sub mac_address_hex2dec {
[2]457   #00:0F:1F:43:E4:2B
[86]458   my $mac_address = shift;
[2]459
[86]460   my @paquets = split m/ : /xms, $mac_address;
[63]461   my $return = q{};
[165]462   for (@paquets) {
[246]463      $return .= q{.} . digit_hex2dec($_);
[2]464      }
465   return $return;
466   }
467
[218]468#---------------------------------------------------------------
[250]469sub format_aggregator4html {
470   my $port_hr = shift;
471   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/: /; # First occurence
472   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ /g; # Other occurence
473   return $port_hr;
474   }
475
476#---------------------------------------------------------------
477sub format_aggregator4dot {
478   my $port_hr = shift;
479   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ - /; # First occurence
480   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ /g; # Other occurence
481   return $port_hr;
482   }
483
484#---------------------------------------------------------------
[246]485# return the port and the switch where the computer is connected
[2]486sub find_switch_port {
[86]487   my $mac_address     = shift;
[63]488   my $switch_proposal = shift || q{};
[144]489   my $vlan_id = shift || 0;
[63]490
[2]491   my %ret;
[261]492   $ret{'switch_description'} = 'unknow';
[270]493   $ret{'switch_port_id'} = '0';
[2]494
[86]495   return %ret if $mac_address eq 'unknow';;
[2]496
[196]497   my @switch_search = @SWITCH_LIST;
[63]498   if ($switch_proposal ne q{}) {
[196]499      for my $sw (@SWITCH_LIST) {
[261]500         next if $sw->{'hostname'} ne $switch_proposal;
[22]501         unshift @switch_search, $sw;
[2]502         last;
503         }
504      }
505
[246]506   my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
507   my $research2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
[63]508
[2]509   LOOP_ON_SWITCH:
[22]510   for my $sw (@switch_search) {
[221]511      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[22]512      print "$error \n" if $error;
513
[2]514      my $result = $session->get_request(
[124]515         -varbindlist => [$research1]
[2]516         );
[124]517      if (not defined $result) {
518         $result = $session->get_request(
519            -varbindlist => [$research2]
520            );
521         $result->{$research1} = $result->{$research2} if defined $result;
522         }
523
524      if (not (defined $result and $result->{$research1} ne 'noSuchInstance')) {
[2]525         $session->close;
526         next LOOP_ON_SWITCH;
527         }
528
[271]529      my $swport_id = $result->{$research1};
530      my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
[2]531
[150]532      $session->close;
533
534      # IMPORTANT !!
535      # ceci empeche la detection sur certains port ...
536      # en effet les switch sont relies entre eux par un cable reseau et du coup
537      # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
538      # cette partie est a ameliore, voir a configurer dans l'entete
539      # 21->24 45->48
540      SWITCH_PORT_IGNORE:
[167]541      for my $portignore (@{$sw->{portignore}}) {
542         next LOOP_ON_SWITCH if $swport_hr eq $portignore;
[246]543         my ($swport_hr_limited) = split /$SEP_AGGREGATOR_PORT/, $swport_hr; # Beginning of the swith port (Aggregator)
[241]544         next LOOP_ON_SWITCH if $swport_hr_limited eq $portignore;
[150]545         }
546
[261]547      $ret{'switch_hostname'}    = $sw->{'hostname'};
[262]548      $ret{'switch_description'} = $sw->{'description'};
[271]549      $ret{'switch_port_id'}     = $swport_id;
[261]550      $ret{'switch_port_hr'}     = $swport_hr; # human readable
[150]551
552      last LOOP_ON_SWITCH;
[2]553      }
554   return %ret;
555   }
556
[218]557#---------------------------------------------------------------
[246]558# search all the port on all the switches where the computer is detected
[2]559sub find_all_switch_port {
[86]560   my $mac_address = shift;
[144]561   my $vlan_id     = shift || 0;
[2]562
563   my $ret = {};
564
[86]565   return $ret if $mac_address eq 'unknow';
[2]566
[246]567   my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
568   my $research2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
[2]569   LOOP_ON_ALL_SWITCH:
[196]570   for my $sw (@SWITCH_LIST) {
[221]571      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[13]572      print "$error \n" if $error;
573
[2]574      my $result = $session->get_request(
[124]575         -varbindlist => [$research1]
[2]576         );
[124]577      if (not defined $result) {
578         $result = $session->get_request(
579            -varbindlist => [$research2]
580            );
581         $result->{$research1} = $result->{$research2} if defined $result;
582         }
[13]583
[124]584      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
[271]585         my $swport_id = $result->{$research1};
586         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
[2]587
[181]588         SWITCH_PORT_IGNORE:
589         for my $portignore (@{$sw->{portignore}}) {
590            if ($swport_hr eq $portignore) {
591               $session->close;
592               next LOOP_ON_ALL_SWITCH
593               }
594            }
595
[261]596         $ret->{$sw->{'hostname'}} = {};
597         $ret->{$sw->{'hostname'}}{'hostname'}    = $sw->{'hostname'};
[262]598         $ret->{$sw->{'hostname'}}{'description'} = $sw->{'description'};
[272]599         $ret->{$sw->{'hostname'}}{'port_id'}     = $swport_id;
[261]600         $ret->{$sw->{'hostname'}}{'port_hr'}     = $swport_hr;
[2]601         }
602
603      $session->close;
604      }
605   return $ret;
606   }
607
[218]608#---------------------------------------------------------------
[2]609sub get_list_network {
610
[262]611   return keys %{$KLASK_CFG->{'network'}};
[2]612   }
613
[218]614#---------------------------------------------------------------
[2]615sub get_current_interface {
[113]616   my $vlan_name = shift;
[2]617
[262]618   return $KLASK_CFG->{'network'}{$vlan_name}{'interface'};
[2]619   }
[63]620
[218]621#---------------------------------------------------------------
[144]622sub get_current_vlan_id {
623   my $vlan_name = shift;
624
[262]625   return 0 if not exists $KLASK_CFG->{'network'}{$vlan_name};
626   return $KLASK_CFG->{'network'}{$vlan_name}{'vlan-id'};
[144]627   }
628
[218]629#---------------------------------------------------------------
[179]630sub get_current_scan_mode {
631   my $vlan_name = shift;
632
[262]633   return $KLASK_CFG->{'network'}{$vlan_name}{'scan-mode'} || $DEFAULT{'scan-mode'} || 'active';
[179]634   }
635
[218]636#---------------------------------------------------------------
[144]637sub get_current_vlan_name_for_interface {
638   my $interface = shift;
639
[262]640   for my $vlan_name (keys %{$KLASK_CFG->{'network'}}) {
641      next if $KLASK_CFG->{'network'}{$vlan_name}{'interface'} ne $interface;
[144]642      return $vlan_name;
643      }
644   }
645
[218]646#---------------------------------------------------------------
[2]647# liste l'ensemble des adresses ip d'un réseau
648sub get_list_ip {
[113]649   my @vlan_name = @_;
[2]650
651   my $cidrlist = Net::CIDR::Lite->new;
652
[113]653   for my $net (@vlan_name) {
[262]654      my @line  = @{$KLASK_CFG->{'network'}{$net}{'ip-subnet'}};
[2]655      for my $cmd (@line) {
[209]656         for my $method (keys %{$cmd}) {
[2]657            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
658            }
659         }
660      }
661
[4]662   my @res = ();
[2]663
664   for my $cidr ($cidrlist->list()) {
665      my $net = new NetAddr::IP $cidr;
[63]666      for my $ip (@{$net}) {
667         $ip =~ s{ /32 }{}xms;
[2]668         push @res,  $ip;
669         }
670      }
671
672   return @res;
673   }
674
[218]675#---------------------------------------------------------------
[9]676# liste l'ensemble des routeurs du réseau
677sub get_list_main_router {
[113]678   my @vlan_name = @_;
[9]679
680   my @res = ();
681
[113]682   for my $net (@vlan_name) {
[262]683      push @res, $KLASK_CFG->{'network'}{$net}{'main-router'};
[9]684      }
685
686   return @res;
687   }
688
[218]689#---------------------------------------------------------------
[214]690sub normalize_port_human_readable {
[150]691   my $sw_port_hr  = shift;
692
[214]693   # Manufacturer abbreviation
[162]694   $sw_port_hr =~ s/^Bridge-Aggregation/Br/i;
695   $sw_port_hr =~ s/^Port-Channel/Po/i;
696   $sw_port_hr =~ s/^Forty-?GigabitEthernet/Fo/i;
697   $sw_port_hr =~ s/^Ten-?GigabitEthernet/Te/i;
698   $sw_port_hr =~ s/^GigabitEthernet/Gi/i;
699   $sw_port_hr =~ s/^FastEthernet/Fa/i;
[150]700
[214]701   # Customer abbreviation
702   $sw_port_hr =~ s/^Ten/Te/i;
703   $sw_port_hr =~ s/^Giga/Gi/i;
704
[215]705   return ucfirst $sw_port_hr;
[150]706   }
707
[218]708#---------------------------------------------------------------
[220]709sub snmp_get_rwsession {
710   my ($sw) = @_;
711
[221]712   my %session = %{$sw->{'snmp_param_session'}};
713   $session{-community} = $sw->{'community-rw'} || $DEFAULT{'community-rw'} || 'private';
[220]714   return %session;
715   }
716
717#---------------------------------------------------------------
[271]718sub snmp_get_switchport_id2hr {
719   my ($snmp_session, $swport_id) = @_;
[198]720
[271]721   # On H3C, port id (port_id) and port index (port_ix) are not the same
[242]722   # Double SNMP request to get the name
[271]723   # First get the index, second get the name (port_hr)
[244]724
[271]725   my $research_ix = $OID_NUMBER{'ifIndex'} .'.'. $swport_id;
726   my $result_ix = $snmp_session->get_request(
727      -varbindlist => [$research_ix]
[244]728      );
729
[271]730   my $swport_ix = $swport_id;
731   $swport_ix = $result_ix->{$research_ix} if defined $result_ix;
[157]732
[271]733   return snmp_get_switchport_ix2hr($snmp_session, $swport_ix);
[244]734   }
[242]735
[244]736#---------------------------------------------------------------
[271]737sub snmp_get_switchport_ix2hr {
738   my ($snmp_session, $swport_ix) = @_;
[244]739
[271]740   my $research_hr = $OID_NUMBER{'ifName'} .'.'. $swport_ix;
[157]741   my $result_hr = $snmp_session->get_request(
[154]742      -varbindlist => [$research_hr]
743      );
[271]744   my $swport_hr = $swport_ix;
[214]745   $swport_hr = normalize_port_human_readable($result_hr->{$research_hr}) if defined $result_hr;
[240]746
747   # Aggregator port
748   if ($swport_hr =~ m/^(Trk|Br|Po)/) {
[261]749      my $research_index = $OID_NUMBER{'ifAggregator'}; # base OID
[240]750      my @args = ( -varbindlist =>  [$research_index]);
751      LOOP_ON_OID_PORT:
752      while ( defined $snmp_session->get_next_request(@args) ) {
753         my ($oid_current) = $snmp_session->var_bind_names;
754         last LOOP_ON_OID_PORT if  not Net::SNMP::oid_base_match($research_index, $oid_current);
755
756         # IEEE8023-LAG-MIB::dot3adAggPortSelectedAggID.28 = INTEGER: 337
757         # IEEE8023-LAG-MIB::dot3adAggPortAttachedAggID.28 = INTEGER: 337
[242]758         my $port_aggregator_index = $snmp_session->var_bind_list->{$oid_current};
[271]759         my ($current_port_ix) = reverse split /\./, $oid_current; # last number
[240]760
761         # prepare next loop item
762         @args = (-varbindlist => [$oid_current]);
763
[242]764         next LOOP_ON_OID_PORT if $port_aggregator_index == 0;
[271]765         next LOOP_ON_OID_PORT if not $port_aggregator_index == $swport_ix;
[240]766
[271]767         my $current_port_name = snmp_get_switchport_ix2hr($snmp_session, $current_port_ix);
[246]768         $swport_hr .= "$SEP_AGGREGATOR_PORT$current_port_name";
[240]769         }
770      }
[154]771   return $swport_hr;
772   }
773
[218]774#---------------------------------------------------------------
[213]775# Reverse search port number
[271]776sub snmp_get_switchport_hr2id {
[213]777   my ($snmp_session, $swport_hr, $verbose) = @_;
778
[240]779   # Split for Aggregator port
[245]780   # Keep only the Aggregator part
[246]781   ($swport_hr) = split /$SEP_AGGREGATOR_PORT/, $swport_hr;
[240]782
[271]783   my $swport_id = $swport_hr;
[246]784   # direct return if already numeric (next loop is expensive) / old or simple switch
[271]785   return $swport_id if $swport_id =~ m/^\d+$/;
[213]786
[271]787   my $research_ix = $OID_NUMBER{'ifIndex'}; # base OID
788   my @args = ( -varbindlist =>  [$research_ix]);
[217]789   LOOP_ON_OID_PORT:
[213]790   while ( defined $snmp_session->get_next_request(@args) ) {
791      my ($oid_current) = $snmp_session->var_bind_names;
[271]792      last LOOP_ON_OID_PORT if  not Net::SNMP::oid_base_match($research_ix, $oid_current);
[213]793     
794      my $port_ifIndex = $snmp_session->var_bind_list->{$oid_current};
[271]795      my ($port_ix) = reverse split /\./, $oid_current; # last number
[213]796      printf "PORT1: %s => %s\n", $oid_current, $port_ifIndex if $verbose;
797
[217]798      # prepare next loop item
[213]799      @args = (-varbindlist => [$oid_current]);
800     
[261]801      my $oid_ifName = $OID_NUMBER{'ifName'} .'.'. $port_ifIndex;
[213]802      my $result = $snmp_session->get_request(-varbindlist => [$oid_ifName]);
[217]803      next LOOP_ON_OID_PORT if not defined $result;
[213]804     
[214]805      my $current_port_hr = normalize_port_human_readable($result->{$oid_ifName});
[213]806      printf "PORT2: $oid_ifName => $current_port_hr\n" if $verbose;
[214]807      if ($current_port_hr eq $swport_hr) {
[271]808         print "PORT3: $current_port_hr <-> $port_ix\n" if $verbose;
[217]809         
810         # return port number ifIndex need by OID portUpDown
[271]811         $swport_id = $port_ifIndex; # other possible value could be $port_ix
[217]812         last LOOP_ON_OID_PORT;
[213]813         }
814      }
[271]815   return $swport_id;
[213]816   }
817
[218]818#---------------------------------------------------------------
[237]819# Get the list of all the VLAN define on a switch
820sub snmp_get_vlan_list {
821   my ($snmp_session, $verbose) = @_;
822
823   my %vlan = (); # Hash vlan number => vlan name
824
[261]825   my $research_index = $OID_NUMBER{'vlanName'}; # base OID
[237]826   my @args = ( -varbindlist =>  [$research_index]);
827   LOOP_ON_VLAN:
828   while ( defined $snmp_session->get_next_request(@args) ) {
829      my ($oid_current) = $snmp_session->var_bind_names;
830      last LOOP_ON_VLAN if not Net::SNMP::oid_base_match($research_index, $oid_current);
831
832      my $vlan_name = $snmp_session->var_bind_list->{$oid_current};
833      my ($vlan_index) = reverse split /\./, $oid_current; # last number
834      printf "VLAN: %s => %s\n", $oid_current, $vlan_name if $verbose;
835
836      $vlan{$vlan_index} = $vlan_name;
837
838      # prepare next loop item
839      @args = (-varbindlist => [$oid_current]);
840      }
841   return %vlan;
842   }
843
844#---------------------------------------------------------------
[151]845# Load computer database
[150]846sub computerdb_load {
847   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
[151]848
849   LOOP_ON_IP_ADDRESS:
850   for my $ip (keys %{$computerdb}) {
851
[270]852      # Rename switch_port -> switch_port_id (2017/09/15)
853      if (not exists $computerdb->{$ip}{'switch_port_id' and exists $computerdb->{$ip}{'switch_port'}}) {
854         $computerdb->{$ip}{'switch_port_id'} = $computerdb->{$ip}{'switch_port'} if defined $computerdb->{$ip}{'switch_port'};
855         }
856      delete $computerdb->{$ip}{'switch_port'} if exists $computerdb->{$ip}{'switch_port'};
857
[261]858      next LOOP_ON_IP_ADDRESS if exists $computerdb->{$ip}{'switch_port_hr'} and defined $computerdb->{$ip}{'switch_port_hr'};
[198]859
[270]860      $computerdb->{$ip}{'switch_port_hr'} = $computerdb->{$ip}{'switch_port_id'};
[151]861      }
862
[150]863   return $computerdb;
864   }
865
[218]866################################################################
867# command
868################################################################
[2]869
[218]870#---------------------------------------------------------------
[2]871sub cmd_help {
872
[63]873print <<'END';
[224]874klask - port and search manager for switches, map management
[2]875
[129]876 klask version
[224]877 klask help
[129]878
[223]879 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
[226]880 klask exportdb [--format|-f txt|html]
[223]881 klask removedb IP* computer*
882 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
[2]883
[224]884 klask updatesw [--verbose|-v]
[226]885 klask exportsw [--format|-f txt|dot]
[45]886
[226]887 klask searchdb [--kind|-k host|mac] computer [mac-address]
[2]888 klask search   computer
[226]889 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
[2]890
[226]891 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
[69]892
[224]893 klask bad-vlan-id [--day|-d days_before_alert]
[129]894
[223]895 klask enable  [--verbose|-v] switch port
896 klask disable [--verbose|-v] switch port
897 klask status  [--verbose|-v] switch port
[237]898
[247]899 klask poe-enable  [--verbose|-v] switch port
900 klask poe-disable [--verbose|-v] switch port
901 klask poe-status  [--verbose|-v] switch port
902
[238]903 klask vlan-getname switch vlan-id
904 klask vlan-list switch
[2]905END
[63]906   return;
[2]907   }
908
[218]909#---------------------------------------------------------------
[36]910sub cmd_version {
911
[63]912print <<'END';
[224]913klask - port and search manager for switches, map management
[259]914Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
[36]915
916END
[37]917   print ' $Id: klask 273 2017-09-20 16:13:38Z g7moreau $'."\n";
[63]918   return;
[36]919   }
920
[238]921#---------------------------------------------------------------
[2]922sub cmd_search {
923   my @computer = @_;
[63]924
[4]925   init_switch_names();    #nomme les switchs
[111]926   fast_ping(@computer);
[133]927
928   LOOP_ON_COMPUTER:
[2]929   for my $clientname (@computer) {
930      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
[261]931      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
[144]932      my $vlan_id   = get_current_vlan_id($vlan_name);
[261]933      my %where     = find_switch_port($resol_arp{'mac_address'}, '', $vlan_id); #retrouve l'emplacement
[133]934
[261]935      next LOOP_ON_COMPUTER if $where{'switch_description'} eq 'unknow' or $resol_arp{'hostname_fq'} eq 'unknow' or $resol_arp{'mac_address'} eq 'unknow';
[133]936
[146]937      printf '%-22s %2s %-30s %-15s %18s',
[261]938         $where{'switch_hostname'},
939         $where{'switch_port_hr'},
940         $resol_arp{'hostname_fq'},
941         $resol_arp{'ipv4_address'},
942         $resol_arp{'mac_address'}."\n";
[2]943      }
[63]944   return;
[2]945   }
946
[218]947#---------------------------------------------------------------
[2]948sub cmd_searchdb {
[136]949   my @ARGV  = @_;
950
951   my $kind;
952
[138]953   GetOptions(
[137]954      'kind=s'   => \$kind,
[136]955      );
956
957   my %possible_search = (
958      host  => \&cmd_searchdb_host,
959      mac   => \&cmd_searchdb_mac,
960      );
961
[137]962   $kind = 'host' if not defined $possible_search{$kind};
[136]963
964   $possible_search{$kind}->(@ARGV);
965   return;
966   }
967
968
[218]969#---------------------------------------------------------------
[136]970sub cmd_searchdb_host {
[2]971   my @computer = @_;
972
[111]973   fast_ping(@computer);
[151]974   my $computerdb = computerdb_load();
[63]975
[2]976   LOOP_ON_COMPUTER:
977   for my $clientname (@computer) {
978      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
[261]979      my $ip = $resol_arp{'ipv4_address'};
[63]980
[2]981      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
[63]982
[261]983      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[2]984      $year += 1900;
985      $mon++;
[63]986      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
[2]987
988      printf "%-22s %2s %-30s %-15s %-18s %s\n",
[261]989         $computerdb->{$ip}{'switch_hostname'},
990         $computerdb->{$ip}{'switch_port_hr'},
991         $computerdb->{$ip}{'hostname_fq'},
[2]992         $ip,
[261]993         $computerdb->{$ip}{'mac_address'},
[2]994         $date;
995      }
[63]996   return;
[2]997   }
998
[218]999#---------------------------------------------------------------
[136]1000sub cmd_searchdb_mac {
1001   my @mac = map { normalize_mac_address($_) } @_;
1002
[151]1003   my $computerdb = computerdb_load();
[136]1004
1005   LOOP_ON_MAC:
1006   for my $mac (@mac) {
1007      LOOP_ON_COMPUTER:
1008      for my $ip (keys %{$computerdb}) {
[261]1009         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{'mac_address'};
[198]1010
[261]1011         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[136]1012         $year += 1900;
1013         $mon++;
1014         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1015
1016         printf "%-22s %2s %-30s %-15s %-18s %s\n",
[261]1017            $computerdb->{$ip}{'switch_hostname'},
1018            $computerdb->{$ip}{'switch_port_hr'},
1019            $computerdb->{$ip}{'hostname_fq'},
[136]1020            $ip,
[261]1021            $computerdb->{$ip}{'mac_address'},
[136]1022            $date;
[138]1023         #next LOOP_ON_MAC;
[136]1024         }
1025
1026      }
1027   return;
1028   }
1029
[218]1030#---------------------------------------------------------------
[2]1031sub cmd_updatedb {
[170]1032   @ARGV = @_;
1033
[173]1034   my ($verbose, $verb_description, $check_hostname, $check_location);
[170]1035
1036   GetOptions(
[172]1037      'verbose|v'          => \$verbose,
1038      'verb-description|d' => \$verb_description,
1039      'chk-hostname|h'     => \$check_hostname,
[173]1040      'chk-location|l'     => \$check_location,
[170]1041      );
1042
1043   my @network = @ARGV;
[2]1044      @network = get_list_network() if not @network;
1045
[34]1046   test_switchdb_environnement();
1047
1048   my $computerdb = {};
[151]1049      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
[2]1050   my $timestamp = time;
[22]1051
[2]1052   my %computer_not_detected = ();
1053   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
1054
1055   my $number_of_computer = get_list_ip(@network); # + 1;
[63]1056   my $size_of_database   = keys %{$computerdb};
[31]1057      $size_of_database   = 1 if $size_of_database == 0;
[2]1058   my $i = 0;
1059   my $detected_computer = 0;
[22]1060
[173]1061   init_switch_names('yes', $verb_description, $check_hostname, $check_location);    #nomme les switchs
[2]1062
[22]1063   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
[44]1064   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[261]1065   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1066   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
[22]1067   my %db_switch_chained_port = ();
[63]1068   for my $swport (keys %db_switch_connected_on_port) {
[248]1069      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[22]1070      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
1071      }
[209]1072   for my $sw (@SWITCH_LIST) {
[261]1073      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{'hostname'}}  if exists $db_switch_output_port{$sw->{'hostname'}};
1074      if ( exists $db_switch_chained_port{$sw->{'hostname'}} ) {
1075         chop $db_switch_chained_port{$sw->{'hostname'}};
1076         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{'hostname'}};
[22]1077         }
[261]1078#      print "$sw->{'hostname'} ++ @{$sw->{portignore}}\n";
[22]1079      }
1080   }
1081
[2]1082   my %router_mac_ip = ();
1083   DETECT_ALL_ROUTER:
[9]1084#   for my $one_router ('194.254.66.254') {
1085   for my $one_router ( get_list_main_router(@network) ) {
[2]1086      my %resol_arp = resolve_ip_arp_host($one_router);
[261]1087      $router_mac_ip{ $resol_arp{'mac_address'} } = $resol_arp{'ipv4_address'};
[2]1088      }
1089
1090   ALL_NETWORK:
[179]1091   for my $current_net (@network) {
[2]1092
[179]1093      my @computer = get_list_ip($current_net);
1094      my $current_interface = get_current_interface($current_net);
[2]1095
[179]1096      fast_ping(@computer) if get_current_scan_mode($current_net) eq 'active';
[2]1097
1098      LOOP_ON_COMPUTER:
1099      for my $one_computer (@computer) {
1100         $i++;
[49]1101
[63]1102         my $total_percent = int (($i*100)/$number_of_computer);
1103
[2]1104         my $localtime = time - $timestamp;
[63]1105         my ($sec,$min) = localtime $localtime;
[2]1106
1107         my $time_elapse = 0;
1108            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
[63]1109         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
[2]1110
1111         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
[63]1112         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
1113         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
[96]1114         printf ' %-8s %-14s', $current_interface, $one_computer;
[2]1115
[186]1116         my $already_exist = exists $computerdb->{$one_computer} ? 'yes' : 'no';
1117         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface, 'fast', $already_exist);
[63]1118
[9]1119         # do not search on router connection (why ?)
[261]1120         if ( exists $router_mac_ip{$resol_arp{'mac_address'}}) {
[179]1121            $computer_not_detected{$one_computer} = $current_net;
[2]1122            next LOOP_ON_COMPUTER;
1123            }
1124
[9]1125         # do not search on switch inter-connection
[261]1126         if (exists $SWITCH_LEVEL{$resol_arp{'hostname_fq'}}) {
[179]1127            $computer_not_detected{$one_computer} = $current_net;
[2]1128            next LOOP_ON_COMPUTER;
1129            }
1130
[63]1131         my $switch_proposal = q{};
[261]1132         if (exists $computerdb->{$resol_arp{'ipv4_address'}} and exists $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'}) {
1133            $switch_proposal = $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'};
[2]1134            }
1135
[3]1136         # do not have a mac address
[261]1137         if ($resol_arp{'mac_address'} eq 'unknow' or (exists $resol_arp{'timestamps'} and $resol_arp{'timestamps'} < ($timestamp - 3 * 3600))) {
[179]1138            $computer_not_detected{$one_computer} = $current_net;
[3]1139            next LOOP_ON_COMPUTER;
1140            }
1141
[261]1142         my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
[147]1143         my $vlan_id   = get_current_vlan_id($vlan_name);
[262]1144         my %where = find_switch_port($resol_arp{'mac_address'}, $switch_proposal, $vlan_id);
[2]1145
1146         #192.168.24.156:
1147         #  arp: 00:0B:DB:D5:F6:65
1148         #  hostname: pcroyon.hmg.priv
1149         #  port: 5
1150         #  switch: sw-batH-legi:hp2524
1151         #  timestamp: 1164355525
1152
1153         # do not have a mac address
[261]1154#         if ($resol_arp{'mac_address'} eq 'unknow') {
[3]1155#            $computer_not_detected{$one_computer} = $current_interface;
1156#            next LOOP_ON_COMPUTER;
1157#            }
[2]1158
1159         # detected on a switch
[261]1160         if ($where{'switch_description'} ne 'unknow') {
[2]1161            $detected_computer++;
[261]1162            $computerdb->{$resol_arp{'ipv4_address'}} = {
1163               hostname_fq        => $resol_arp{'hostname_fq'},
1164               mac_address        => $resol_arp{'mac_address'},
1165               switch_hostname    => $where{'switch_hostname'},
1166               switch_description => $where{'switch_description'},
[270]1167               switch_port_id     => $where{'switch_port_id'},
[261]1168               switch_port_hr     => $where{'switch_port_hr'},
[2]1169               timestamp          => $timestamp,
[179]1170               network            => $current_net,
[2]1171               };
1172            next LOOP_ON_COMPUTER;
1173            }
1174
1175         # new in the database but where it is ?
[261]1176         if (not exists $computerdb->{$resol_arp{'ipv4_address'}}) {
[2]1177            $detected_computer++;
[261]1178            $computerdb->{$resol_arp{'ipv4_address'}} = {
1179               hostname_fq        => $resol_arp{'hostname_fq'},
1180               mac_address        => $resol_arp{'mac_address'},
1181               switch_hostname    => $where{'switch_hostname'},
1182               switch_description => $where{'switch_description'},
[270]1183               switch_port_id     => $where{'switch_port_id'},
[261]1184               switch_port_hr     => $where{'switch_port_hr'},
1185               timestamp          => $resol_arp{'timestamp'},
[179]1186               network            => $current_net,
[2]1187               };
1188            }
1189
1190         # mise a jour du nom de la machine si modification dans le dns
[261]1191         $computerdb->{$resol_arp{'ipv4_address'}}{'hostname_fq'} = $resol_arp{'hostname_fq'};
[63]1192
[2]1193         # mise à jour de la date de détection si détection plus récente par arpwatch
[261]1194         $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'}   = $resol_arp{'timestamp'} if exists $resol_arp{'timestamp'} and $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $resol_arp{'timestamp'};
[2]1195
1196         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
[261]1197#         push @computer_not_detected, $resol_arp{'ipv4_address'} if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1198         $computer_not_detected{$resol_arp{'ipv4_address'}} = $current_net if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
[63]1199
[2]1200         }
1201      }
1202
1203   # final end of line at the end of the loop
1204   printf "\n";
1205
[13]1206   my $dirdb = $KLASK_DB_FILE;
[63]1207      $dirdb =~ s{ / [^/]* $}{}xms;
[2]1208   mkdir "$dirdb", 0755 unless -d "$dirdb";
[44]1209   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
[2]1210
1211   for my $one_computer (keys %computer_not_detected) {
[179]1212      my $current_net = $computer_not_detected{$one_computer};
1213      my $current_interface = get_current_interface($current_net);
[258]1214      system "arping -c 1 -w 1 -rR -i $current_interface $one_computer > /dev/null 2>&1" if get_current_scan_mode($current_net) eq 'active';
[2]1215      }
[63]1216   return;
[2]1217   }
1218
[218]1219#---------------------------------------------------------------
[2]1220sub cmd_removedb {
1221   my @computer = @_;
[34]1222
1223   test_maindb_environnement();
1224
[151]1225   my $computerdb = computerdb_load();
[2]1226
1227   LOOP_ON_COMPUTER:
1228   for my $one_computer (@computer) {
1229
[66]1230      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
1231            and exists $computerdb->{$one_computer} ) {
1232         delete $computerdb->{$one_computer};
1233         next;
1234         }
1235
[2]1236      my %resol_arp = resolve_ip_arp_host($one_computer);
1237
[261]1238      delete $computerdb->{$resol_arp{'ipv4_address'}} if exists $computerdb->{$resol_arp{'ipv4_address'}};
[2]1239      }
1240
[13]1241   my $dirdb = $KLASK_DB_FILE;
[63]1242      $dirdb =~ s{ / [^/]* $}{}xms;
[2]1243   mkdir "$dirdb", 0755 unless -d "$dirdb";
[44]1244   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
[63]1245   return;
[2]1246   }
1247
[218]1248#---------------------------------------------------------------
[74]1249sub cmd_cleandb {
[110]1250   my @ARGV  = @_;
[74]1251
1252   my $days_to_clean = 15;
[188]1253   my $repairdns;
[74]1254   my $verbose;
1255   my $database_has_changed;
1256
[138]1257   GetOptions(
[74]1258      'day|d=i'   => \$days_to_clean,
1259      'verbose|v' => \$verbose,
[190]1260      'repair-dns|r' => \$repairdns,
[74]1261      );
1262
1263   my @vlan_name = get_list_network();
1264
[188]1265   my $computerdb = computerdb_load();
[74]1266   my $timestamp = time;
1267
1268   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
[106]1269   my $timestamp_3month  = 3600 * 24 * 90;
[74]1270
[104]1271   my %mactimedb = ();
[74]1272   ALL_VLAN:
[109]1273   for my $vlan (shuffle @vlan_name) {
[74]1274
[109]1275      my @ip_list   = shuffle get_list_ip($vlan);
[198]1276
[74]1277      LOOP_ON_IP_ADDRESS:
1278      for my $ip (@ip_list) {
1279
1280         next LOOP_ON_IP_ADDRESS if
1281            not exists $computerdb->{$ip};
[198]1282
[261]1283            #&& $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
1284         my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1285         my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1286         my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
[104]1287
1288         $mactimedb{$ip_mac} ||= {
1289            ip          => $ip,
1290            timestamp   => $ip_timestamp,
1291            vlan        => $vlan,
1292            hostname_fq => $ip_hostname_fq,
1293            };
[198]1294
[108]1295         if (
[261]1296            ( $mactimedb{$ip_mac}->{'timestamp'} - $ip_timestamp > $timestamp_barrier
[108]1297               or (
[261]1298                  $mactimedb{$ip_mac}->{'timestamp'} > $ip_timestamp
1299                  and $timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_3month
[108]1300                  )
1301            )
[105]1302            and (
[261]1303               not $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
[118]1304               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
[105]1305               )) {
[74]1306            print "remove ip $ip\n" if $verbose;
1307            delete $computerdb->{$ip};
1308            $database_has_changed++;
1309            }
1310
[108]1311         elsif (
[261]1312            ( $ip_timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_barrier
[108]1313               or (
[261]1314                  $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}
[108]1315                  and $timestamp - $ip_timestamp > $timestamp_3month
1316                  )
1317            )
[105]1318            and (
[118]1319               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
[261]1320               or $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
[105]1321               )) {
[104]1322            print "remove ip ".$mactimedb{$ip_mac}->{ip}."\n" if $verbose;
1323            delete $computerdb->{$mactimedb{$ip_mac}->{ip}};
[74]1324            $database_has_changed++;
1325            }
1326
[261]1327         if ( $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}) {
[104]1328            $mactimedb{$ip_mac} = {
1329               ip          => $ip,
1330               timestamp   => $ip_timestamp,
1331               vlan        => $vlan,
1332               hostname_fq => $ip_hostname_fq,
1333               };
[74]1334            }
1335         }
1336      }
1337
[188]1338   if ($repairdns) { # Search and update unkown computer in reverse DNS
[189]1339      LOOP_ON_IP_ADDRESS:
1340      for my $ip (keys %{$computerdb}) {
[261]1341         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} ne 'unknow';
[188]1342
[189]1343         my $packed_ip = scalar gethostbyname($ip);
1344         next LOOP_ON_IP_ADDRESS if not defined $packed_ip;
[188]1345
1346         my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
[189]1347         next LOOP_ON_IP_ADDRESS if not defined $hostname_fq;
[188]1348
[261]1349         $computerdb->{$ip}{'hostname_fq'} = $hostname_fq;
[188]1350         $database_has_changed++;
1351         }
1352      }
1353
[74]1354   if ( $database_has_changed ) {
1355      my $dirdb = $KLASK_DB_FILE;
1356         $dirdb =~ s{ / [^/]* $}{}xms;
1357      mkdir "$dirdb", 0755 unless -d "$dirdb";
1358      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1359      }
1360   return;
1361   }
1362
[218]1363#---------------------------------------------------------------
[2]1364sub cmd_exportdb {
[113]1365   @ARGV = @_;
[45]1366
1367   my $format = 'txt';
1368
[138]1369   GetOptions(
[45]1370      'format|f=s'  => \$format,
1371      );
1372
1373   my %possible_format = (
1374      txt  => \&cmd_exportdb_txt,
1375      html => \&cmd_exportdb_html,
1376      );
1377
1378   $format = 'txt' if not defined $possible_format{$format};
[63]1379
[45]1380   $possible_format{$format}->(@ARGV);
[63]1381   return;
[45]1382   }
1383
[218]1384#---------------------------------------------------------------
[45]1385sub cmd_exportdb_txt {
[34]1386   test_maindb_environnement();
1387
[151]1388   my $computerdb = computerdb_load();
[2]1389
[233]1390   printf "%-28s %8s              %-40s %-15s %-18s %-16s %s\n", qw(Switch Port Hostname-FQ IPv4-Address MAC-Address Date VLAN);
[78]1391   print "--------------------------------------------------------------------------------------------------------------------------------------------\n";
[2]1392
1393   LOOP_ON_IP_ADDRESS:
[165]1394   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
[63]1395
[2]1396      # to be improve in the future
[261]1397      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
[2]1398
[261]1399      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[2]1400      $year += 1900;
1401      $mon++;
[63]1402      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
[2]1403
[177]1404      my $vlan = '';
[262]1405      $vlan = $computerdb->{$ip}{'network'}.'('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
[177]1406
[233]1407      my $arrow ='<-----------';
[261]1408         $arrow ='<===========' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
[232]1409
[233]1410      printf "%-28s %8s %12s %-40s %-15s %-18s %-16s %s\n",
[261]1411         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1412         $computerdb->{$ip}{'switch_port_hr'},
[232]1413         $arrow,
[261]1414         $computerdb->{$ip}{'hostname_fq'},
[2]1415         $ip,
[261]1416         $computerdb->{$ip}{'mac_address'},
[70]1417         $date,
[177]1418         $vlan;
[2]1419      }
[63]1420   return;
[2]1421   }
1422
[218]1423#---------------------------------------------------------------
[45]1424sub cmd_exportdb_html {
1425   test_maindb_environnement();
1426
[151]1427   my $computerdb = computerdb_load();
[45]1428
1429#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1430#<script src="sorttable-klask.js"></script>
1431
[63]1432   print <<'END_HTML';
[73]1433<table class="sortable" summary="Klask Host Database">
[72]1434 <caption>Klask Host Database</caption>
[45]1435 <thead>
1436  <tr>
[73]1437   <th scope="col" class="klask-header-left">Switch</th>
[45]1438   <th scope="col" class="sorttable_nosort">Port</th>
[269]1439   <th scope="col" class="sorttable_nosort">Link</th>
[73]1440   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
[45]1441   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
[67]1442   <th scope="col" class="sorttable_alpha">MAC-Address</th>
[71]1443   <th scope="col" class="sorttable_alpha">VLAN</th>
[73]1444   <th scope="col" class="klask-header-right">Date</th>
[45]1445  </tr>
1446 </thead>
1447 <tfoot>
1448  <tr>
[73]1449   <th scope="col" class="klask-footer-left">Switch</th>
[45]1450   <th scope="col" class="fklask-port">Port</th>
[269]1451   <th scope="col" class="fklask-link">Link</th>
[73]1452   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
[45]1453   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1454   <th scope="col" class="fklask-mac">MAC-Address</th>
[71]1455   <th scope="col" class="fklask-vlan">VLAN</th>
[73]1456   <th scope="col" class="klask-footer-right">Date</th>
[45]1457  </tr>
1458 </tfoot>
1459 <tbody>
[63]1460END_HTML
[45]1461
1462   my %mac_count = ();
1463   LOOP_ON_IP_ADDRESS:
[165]1464   for my $ip (keys %{$computerdb}) {
[63]1465
[45]1466      # to be improve in the future
[261]1467      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
[63]1468
[261]1469      $mac_count{$computerdb->{$ip}{'mac_address'}}++;
[45]1470      }
1471
1472   my $typerow = 'even';
1473
1474   LOOP_ON_IP_ADDRESS:
[165]1475   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
[63]1476
[45]1477      # to be improve in the future
[261]1478      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
[45]1479
[261]1480      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[45]1481      $year += 1900;
1482      $mon++;
[63]1483      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
[45]1484
1485#      $odd_or_even++;
1486#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
[63]1487      $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]1488
[265]1489      #my $arrow ='&#8592;';
1490      #   $arrow ='&#8656;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
[266]1491      my $arrow ='&#10229;';
1492         $arrow ='&#10232;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
[163]1493
[261]1494      my $switch_hostname = $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'} || 'unkown';
[45]1495      chomp $switch_hostname;
[270]1496      my $switch_hostname_sort = sprintf '%s %06i' ,$switch_hostname, $computerdb->{$ip}{'switch_port_id'}; # Take switch index
[45]1497
[63]1498      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
[45]1499
[261]1500      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{'mac_address'}}, $computerdb->{$ip}{'mac_address'};
[45]1501
[261]1502      $computerdb->{$ip}{'hostname_fq'} = 'unknow' if $computerdb->{$ip}{'hostname_fq'} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1503      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{'hostname_fq'};
[45]1504
[178]1505      my $vlan = '';
[262]1506      $vlan = $computerdb->{$ip}{'network'}.' ('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
[71]1507
[261]1508      my $parent_port_hr = format_aggregator4html($computerdb->{$ip}{'switch_port_hr'});
[253]1509
[63]1510      print <<"END_HTML";
[45]1511  <tr class="$typerow">
1512   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
[253]1513   <td class="bklask-port">$parent_port_hr</td>
[267]1514   <td>$arrow</td>
[261]1515   <td sorttable_customkey="$host_short">$computerdb->{$ip}{'hostname_fq'}</td>
[45]1516   <td sorttable_customkey="$ip_sort">$ip</td>
[261]1517   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{'mac_address'}</td>
[71]1518   <td>$vlan</td>
[45]1519   <td>$date</td>
1520  </tr>
[63]1521END_HTML
[266]1522#   <td colspan="2">$arrow</td>
[45]1523      }
1524
1525   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1526
[261]1527   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1528   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1529   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1530   my %db_switch                   = %{$switch_connection->{'switch_db'}};
[45]1531
[266]1532   # Output switch connection
1533   LOOP_ON_OUTPUT_SWITCH:
[45]1534   for my $sw (sort keys %db_switch_output_port) {
1535
[63]1536      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
[45]1537
[63]1538      $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]1539
[264]1540      #my $arrow ='&#8702;';
1541      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1542      my $arrow ='&#10236;';
1543         $arrow ='&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
[163]1544
[45]1545      if (exists $db_switch_parent{$sw}) {
[266]1546         # Link to uplink switch
1547         next LOOP_ON_OUTPUT_SWITCH;
1548         
1549         # Do not print anymore
[261]1550         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1551         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1552         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
[45]1553
[163]1554         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1555         $year += 1900;
1556         $mon++;
1557         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
[45]1558
[205]1559         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
[45]1560
[163]1561         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
[45]1562
[261]1563         my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'};
[45]1564
[263]1565         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1566         $vlan .= ' ('.get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}).')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1567
[253]1568         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
[261]1569         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
[253]1570
[163]1571         print <<"END_HTML";
[45]1572  <tr class="$typerow">
1573   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
[253]1574   <td class="bklask-port">$parent_port_hr</td>
[269]1575   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
[221]1576   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{'switch'}</td>
[45]1577   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1578   <td sorttable_customkey="$mac_sort">$mac_address</td>
[263]1579   <td>$vlan</td>
[45]1580   <td>$date</td>
1581  </tr>
[63]1582END_HTML
[45]1583         }
1584      else {
[266]1585         # Router
[253]1586         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1587
[266]1588         my $host_short = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1589
1590         my $mac_address = $db_switch{$sw}->{'mac_address'};
1591         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1592         my $timestamp = $db_switch{$sw}->{'timestamp'};
1593
1594         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1595         $year += 1900;
1596         $mon++;
1597         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1598
1599         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1600
1601         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1602
1603         my $vlan = $db_switch{$sw}->{'network'};
1604         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1605
1606         my $arrow ='&#10235;';
1607            $arrow ='&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1608
[63]1609         print <<"END_HTML";
[45]1610  <tr class="$typerow">
[266]1611   <td sorttable_customkey="router">router</td>
1612   <td class="bklask-port"></td>
[269]1613   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$parent_port_hr</td></tr></table></td>
[266]1614   <td sorttable_customkey="$host_short">$sw</td>
1615   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1616   <td sorttable_customkey="$mac_sort">$mac_address</td>
1617   <td>$vlan</td>
1618   <td>$date</td>
1619  </tr>
1620END_HTML
[267]1621         #<td>$arrow</td><td>$parent_port_hr</td>
[266]1622
1623         next LOOP_ON_OUTPUT_SWITCH;
1624
1625         # Old print
1626         print <<"END_HTML";
1627  <tr class="$typerow">
[45]1628   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
[253]1629   <td class="bklask-port">$parent_port_hr</td>
[164]1630   <td>$arrow</td><td></td>
[162]1631   <td sorttable_customkey="router">router</td>
[45]1632   <td sorttable_customkey="999999999999"></td>
1633   <td sorttable_customkey="99999"></td>
1634   <td></td>
[71]1635   <td></td>
[45]1636  </tr>
[63]1637END_HTML
[45]1638         }
1639      }
1640
[266]1641   # Child switch connection : parent <- child
[45]1642   for my $swport (sort keys %db_switch_connected_on_port) {
[248]1643      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[45]1644      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1645
[63]1646         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
[45]1647
[261]1648         my $mac_address = $db_switch{$sw}->{'mac_address'};
1649         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1650         my $timestamp = $db_switch{$sw}->{'timestamp'};
[45]1651
[163]1652         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1653         $year += 1900;
1654         $mon++;
1655         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
[45]1656
[205]1657         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
[45]1658
[163]1659         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
[45]1660
[163]1661         $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]1662
[264]1663         #my $arrow ='&#8701;';
1664         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
1665         my $arrow ='&#10235;';
1666            $arrow ='&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
[163]1667
[263]1668         my $vlan = $db_switch{$sw}->{'network'};
1669         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1670
[45]1671         if (exists $db_switch_output_port{$sw}) {
1672
[63]1673            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
[45]1674
[253]1675            my $parent_port_hr = format_aggregator4html($port_connect);
1676            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
1677
[63]1678            print <<"END_HTML";
[45]1679  <tr class="$typerow">
1680   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
[253]1681   <td class="bklask-port">$parent_port_hr</td>
[269]1682   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
[162]1683   <td sorttable_customkey="$host_short">$sw</td>
[45]1684   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1685   <td sorttable_customkey="$mac_sort">$mac_address</td>
[263]1686   <td>$vlan</td>
[45]1687   <td>$date</td>
1688  </tr>
[63]1689END_HTML
[45]1690            }
1691         else {
[253]1692            my $parent_port_hr = format_aggregator4html($port_connect);
1693
[63]1694            print <<"END_HTML";
[45]1695  <tr class="$typerow">
1696   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
[253]1697   <td class="bklask-port">$parent_port_hr</td>
[267]1698   <td>$arrow</td>
[162]1699   <td sorttable_customkey="$sw">$sw</td>
[45]1700   <td sorttable_customkey="">$ipv4_address</td>
1701   <td sorttable_customkey="">$mac_address</td>
[263]1702   <td>$vlan</td>
[45]1703   <td>$date</td>
1704  </tr>
[63]1705END_HTML
[45]1706            }
1707         }
1708      }
1709
[63]1710   print <<'END_HTML';
[45]1711 </tbody>
1712</table>
[63]1713END_HTML
1714   return;
[45]1715   }
1716
[218]1717#---------------------------------------------------------------
[133]1718sub cmd_bad_vlan_id {
[224]1719   @ARGV = @_;
1720
1721   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1722   my $verbose;
1723
1724   GetOptions(
1725      'day|d=i'   => \$days_before_alert,
1726      );
1727
[114]1728   test_maindb_environnement();
1729
[151]1730   my $computerdb = computerdb_load();
[114]1731
[132]1732   # create a database with the most recent computer by switch port
[211]1733   my %switchportdb = ();
[114]1734   LOOP_ON_IP_ADDRESS:
[165]1735   for my $ip (keys %{$computerdb}) {
[114]1736      # to be improve in the future
[261]1737      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1738      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
[270]1739      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port_id'} eq '0';
[114]1740
[261]1741      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1742      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1743      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
[114]1744
[115]1745      my $swpt = sprintf "%-28s  %2s",
[261]1746         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1747         $computerdb->{$ip}{'switch_port_hr'};
[211]1748      $switchportdb{$swpt} ||= {
[114]1749         ip          => $ip,
1750         timestamp   => $ip_timestamp,
[262]1751         vlan        => $computerdb->{$ip}{'network'},
[114]1752         hostname_fq => $ip_hostname_fq,
1753         mac_address => $ip_mac,
1754         };
[117]1755
[132]1756      # if float computer, set date 15 day before warning...
1757      my $ip_timestamp_mod = $ip_timestamp;
[261]1758      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
[224]1759      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
[261]1760      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
[198]1761
[132]1762      if ($ip_timestamp_mod > $ip_timestamp_ref) {
[211]1763         $switchportdb{$swpt} = {
[114]1764            ip          => $ip,
1765            timestamp   => $ip_timestamp,
[262]1766            vlan        => $computerdb->{$ip}{'network'},
[114]1767            hostname_fq => $ip_hostname_fq,
1768            mac_address => $ip_mac,
1769            };
1770         }
1771      }
1772
[133]1773   LOOP_ON_RECENT_COMPUTER:
[211]1774   for my $swpt (keys %switchportdb) {
[133]1775      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
[261]1776      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
[117]1777
[211]1778      my $src_ip = $switchportdb{$swpt}->{ip};
[114]1779      my $src_timestamp = 0;
[133]1780      LOOP_ON_IP_ADDRESS:
[165]1781      for my $ip (keys %{$computerdb}) {
[261]1782         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne  $switchportdb{$swpt}->{'mac_address'};
1783         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1784         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
[198]1785
[114]1786         $src_ip = $ip;
[261]1787         $src_timestamp = $computerdb->{$ip}{'timestamp'};
[114]1788         }
[132]1789
1790      # keep only if float computer is the most recent
[133]1791      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
[261]1792      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
[132]1793
[261]1794      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
[114]1795      $year += 1900;
1796      $mon++;
[132]1797      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
[114]1798
[261]1799      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
[114]1800      $year += 1900;
1801      $mon++;
[132]1802      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
[114]1803
[262]1804      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
[176]1805
1806      printf "%s / %-10s +-> %-10s(%i)  %s %s %s %s\n",
[262]1807         $swpt, $switchportdb{$swpt}->{'vlan'}, $computerdb->{$src_ip}{'network'}, $vlan_id,
[114]1808         $date,
1809         $src_date,
[261]1810         $computerdb->{$src_ip}{'mac_address'},
1811         $computerdb->{$src_ip}{'hostname_fq'};
[114]1812      }
1813   }
1814
[218]1815#---------------------------------------------------------------
[239]1816sub cmd_poe_enable {
1817   @ARGV = @_;
1818
1819   my $verbose;
1820   GetOptions(
1821      'verbose|v' => \$verbose,
1822      );
1823
1824   my $switch_name = shift @ARGV || q{};
1825   my $switch_port = shift @ARGV || q{};
1826
1827   if ($switch_name eq q{} or $switch_port eq q{}) {
1828      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
1829      }
1830
1831   for my $sw_name (split /,/, $switch_name) {
1832      if (not defined $SWITCH_DB{$sw_name}) {
1833         die "Switch $sw_name must be defined in klask configuration file\n";
1834         }
1835
1836      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1837
1838      my $sw = $SWITCH_DB{$sw_name};
1839      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1840      print "$error \n" if $error;
1841
1842      my $result = $session->set_request(
1843         -varbindlist => [$search, INTEGER, 8], # Only NEXANS
1844         );
1845      print $session->error()."\n" if $session->error_status();
1846
1847      $session->close;
1848      }
1849   cmd_poe_status($switch_name, $switch_port);
1850   return;
1851   }
1852
1853#---------------------------------------------------------------
1854sub cmd_poe_disable {
1855   @ARGV = @_;
1856
1857   my $verbose;
1858   GetOptions(
1859      'verbose|v' => \$verbose,
1860      );
1861
1862   my $switch_name = shift @ARGV || q{};
1863   my $switch_port = shift @ARGV || q{};
1864
1865   if ($switch_name eq q{} or $switch_port eq q{}) {
1866      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
1867      }
1868
1869   for my $sw_name (split /,/, $switch_name) {
1870      if (not defined $SWITCH_DB{$sw_name}) {
1871         die "Switch $sw_name must be defined in klask configuration file\n";
1872         }
1873
1874      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1875
1876      my $sw = $SWITCH_DB{$sw_name};
1877      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1878      print "$error \n" if $error;
1879
1880      my $result = $session->set_request(
1881         -varbindlist => [$search, INTEGER, 2], # Only NEXANS
1882         );
1883      print $session->error()."\n" if $session->error_status();
1884
1885      $session->close;
1886      }
1887   cmd_poe_status($switch_name, $switch_port);
1888   return;
1889   }
1890
1891#---------------------------------------------------------------
1892sub cmd_poe_status {
1893   @ARGV = @_;
1894
1895   my $verbose;
1896   GetOptions(
1897      'verbose|v' => \$verbose,
1898      );
1899
1900   my $switch_name = shift @ARGV || q{};
1901   my $switch_port = shift @ARGV || q{};
1902
1903   if ($switch_name eq q{} or $switch_port eq q{}) {
1904      die "Usage: klask poe-status SWITCH_NAME PORT\n";
1905      }
1906
1907   for my $sw_name (split /,/, $switch_name) {
1908      if (not defined $SWITCH_DB{$sw_name}) {
1909         die "Switch $sw_name must be defined in klask configuration file\n";
1910         }
1911
1912      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1913
1914      my $sw = $SWITCH_DB{$sw_name};
1915      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1916      print "$error \n" if $error;
1917
1918      my $result = $session->get_request(
1919         -varbindlist => [$search],
1920         );
1921
1922      if (defined $result and $result->{$search} ne 'noSuchInstance') {
1923         my $poe = $result->{$search} || 'empty';
[247]1924         $poe =~ s/8/enable/;
1925         $poe =~ s/2/disable/;
1926         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe;
[239]1927         }
1928      else {
[247]1929         print "Klask do not find PoE Status on switch $sw_name on port $switch_port\n";
[239]1930         }
1931
1932      $session->close;
1933      }
1934   return;
1935   }
1936
1937#---------------------------------------------------------------
[220]1938# not finish - do not use
[238]1939sub cmd_port_setvlan {
[129]1940   my $switch_name = shift || q{};
1941   my $mac_address = shift || q{};
1942
1943   if ($switch_name eq q{} or $mac_address eq q{}) {
1944      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1945      }
1946
[261]1947   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
[129]1948
1949   for my $sw_name (split /,/, $switch_name) {
1950      if (not defined $SWITCH_DB{$sw_name}) {
1951         die "Switch $sw_name must be defined in klask configuration file\n";
1952         }
1953
[246]1954      my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
1955      my $research2 = $OID_NUMBER{'searchPort2'} .'.'. 0 . mac_address_hex2dec($mac_address);
[129]1956      print "Klask search OID $research1 on switch $sw_name\n";
1957      print "Klask search OID $research2 on switch $sw_name\n";
1958
[209]1959      my $sw = $SWITCH_DB{$sw_name};
[221]1960      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[129]1961      print "$error \n" if $error;
1962
1963      my $result = $session->get_request(
1964         -varbindlist => [$research1]
1965         );
1966      if (not defined $result) {
1967         $result = $session->get_request(
1968            -varbindlist => [$research2]
1969            );
1970         $result->{$research1} = $result->{$research2} if defined $result;
1971         }
1972
1973      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1974         my $swport = $result->{$research1};
1975         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1976         }
1977      else {
1978         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1979         }
1980
1981      $session->close;
1982      }
1983   return;
1984   }
1985
[218]1986#---------------------------------------------------------------
[238]1987sub cmd_port_getvlan {
[141]1988   @ARGV = @_;
1989
1990   my $verbose;
1991   GetOptions(
1992      'verbose|v' => \$verbose,
1993      );
1994
1995   my $switch_name = shift @ARGV || q{};
1996   my $switch_port = shift @ARGV || q{};
1997
1998   if ($switch_name eq q{} or $switch_port eq q{}) {
[238]1999      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
[141]2000      }
2001
2002   for my $sw_name (split /,/, $switch_name) {
2003      if (not defined $SWITCH_DB{$sw_name}) {
2004         die "Switch $sw_name must be defined in klask configuration file\n";
2005         }
2006
[142]2007      my $search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
[141]2008
[209]2009      my $sw = $SWITCH_DB{$sw_name};
[221]2010      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[141]2011      print "$error \n" if $error;
2012
2013      my $result = $session->get_request(
2014         -varbindlist => [$search],
2015         );
2016
2017      if (defined $result and $result->{$search} ne 'noSuchInstance') {
2018         my $vlan_id = $result->{$search} || 'empty';
2019         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2020         }
2021      else {
[142]2022         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
[141]2023         }
2024
2025      $session->close;
2026      }
2027   return;
[129]2028   }
2029
[218]2030#---------------------------------------------------------------
[238]2031sub cmd_vlan_setname {
[129]2032   }
2033
[218]2034#---------------------------------------------------------------
[239]2035# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
[141]2036sub cmd_rebootsw {
2037   @ARGV = @_;
2038
2039   my $verbose;
2040   GetOptions(
2041      'verbose|v' => \$verbose,
2042      );
2043
2044   my $switch_name = shift @ARGV || q{};
2045
[142]2046   if ($switch_name eq q{}) {
[141]2047      die "Usage: klask rebootsw SWITCH_NAME\n";
2048      }
2049
2050   for my $sw_name (split /,/, $switch_name) {
2051      if (not defined $SWITCH_DB{$sw_name}) {
2052         die "Switch $sw_name must be defined in klask configuration file\n";
2053         }
2054
2055      my $sw = $SWITCH_DB{$sw_name};
[220]2056      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
[141]2057      print "$error \n" if $error;
2058
2059      my $result = $session->set_request(
[239]2060         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
[141]2061         );
2062
2063      $session->close;
2064      }
2065   return;
2066   }
2067
[218]2068#---------------------------------------------------------------
[238]2069sub cmd_vlan_getname {
[129]2070   my $switch_name = shift || q{};
2071   my $vlan_id     = shift || q{};
2072
2073   if ($switch_name eq q{} or $vlan_id eq q{}) {
[238]2074      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
[129]2075      }
2076
[261]2077   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
[129]2078
2079   for my $sw_name (split /,/, $switch_name) {
2080      if (not defined $SWITCH_DB{$sw_name}) {
2081         die "Switch $sw_name must be defined in klask configuration file\n";
2082         }
2083
[261]2084      my $search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
[129]2085
[209]2086      my $sw = $SWITCH_DB{$sw_name};
[221]2087      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[129]2088      print "$error \n" if $error;
2089
2090      my $result = $session->get_request(
2091         -varbindlist => [$search_vlan_name]
2092         );
2093
2094      if (defined $result and $result->{$search_vlan_name} ne 'noSuchInstance') {
[131]2095         my $vlan_name = $result->{$search_vlan_name} || 'empty';
[130]2096         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
[129]2097         }
2098      else {
2099         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2100         }
2101
2102      $session->close;
2103      }
2104   return;
2105   }
2106
[218]2107#---------------------------------------------------------------
[238]2108sub cmd_vlan_list {
[237]2109   my $switch_name = shift || q{};
2110
2111   if ($switch_name eq q{}) {
[238]2112      die "Usage: klask vlan-list SWITCH_NAME\n";
[237]2113      }
2114
[261]2115   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
[237]2116
2117   for my $sw_name (split /,/, $switch_name) {
2118      if (not defined $SWITCH_DB{$sw_name}) {
2119         die "Switch $sw_name must be defined in klask configuration file\n";
2120         }
2121
2122      my $sw = $SWITCH_DB{$sw_name};
2123      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2124      print "$error \n" if $error;
2125
2126      my %vlandb = snmp_get_vlan_list($session);
2127      $session->close;
2128     
2129      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2130      for my $vlanid (keys %vlandb) {
2131         printf "%7i - %s\n", $vlanid, $vlandb{$vlanid};
2132         }
2133      }
2134   return;
2135   }
2136
2137#---------------------------------------------------------------
[111]2138sub cmd_ip_location {
[151]2139   my $computerdb = computerdb_load();
[2]2140
2141   LOOP_ON_IP_ADDRESS:
[165]2142   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
[2]2143
[261]2144      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
[2]2145
[261]2146      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
[133]2147      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
[63]2148
2149      my $sw_location = q{};
[133]2150      LOOP_ON_ALL_SWITCH:
[196]2151      for my $sw (@SWITCH_LIST) {
[261]2152         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2153         $sw_location = $sw->{'location'};
[2]2154         last;
2155         }
2156
[63]2157      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
[2]2158      }
[63]2159   return;
[2]2160   }
2161
[218]2162#---------------------------------------------------------------
[69]2163sub cmd_ip_free {
[224]2164   @ARGV = @_;
[69]2165
[224]2166   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
[69]2167   my $format = 'txt';
[97]2168   my $verbose;
[69]2169
[138]2170   GetOptions(
[224]2171      'day|d=i'      => \$days_to_death,
[69]2172      'format|f=s'   => \$format,
[97]2173      'verbose|v'    => \$verbose,
[69]2174      );
2175
[72]2176   my %possible_format = (
2177      txt  => \&cmd_ip_free_txt,
2178      html => \&cmd_ip_free_html,
[97]2179      none => sub {},
[72]2180      );
2181   $format = 'txt' if not defined $possible_format{$format};
2182
[110]2183   my @vlan_name = @ARGV;
[69]2184   @vlan_name = get_list_network() if not @vlan_name;
2185
2186   my $computerdb = {};
[151]2187      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
[69]2188   my $timestamp = time;
2189
[224]2190   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
[69]2191
[72]2192   my %result_ip = ();
[69]2193
2194   ALL_NETWORK:
2195   for my $vlan (@vlan_name) {
2196
2197      my @ip_list = get_list_ip($vlan);
[97]2198
[69]2199      LOOP_ON_IP_ADDRESS:
2200      for my $ip (@ip_list) {
2201
[135]2202         if (exists $computerdb->{$ip}) {
[261]2203            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
[198]2204
[261]2205            my $mac_address = $computerdb->{$ip}{'mac_address'};
[135]2206            LOOP_ON_DATABASE:
[165]2207            for my $ip_db (keys %{$computerdb}) {
[261]2208               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2209               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
[135]2210               }
2211            }
[69]2212
2213         my $ip_date_last_detection = '';
2214         if (exists $computerdb->{$ip}) {
[261]2215            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[69]2216            $year += 1900;
2217            $mon++;
2218            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2219            }
2220
2221         my $packed_ip = scalar gethostbyname($ip);
2222         my $hostname_fq = 'unknown';
[186]2223            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
[135]2224
2225         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2226
2227         $result_ip{$ip} ||= {};
[261]2228         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2229         $result_ip{$ip}->{'hostname_fq'} = $hostname_fq;
2230         $result_ip{$ip}->{'vlan'} = $vlan;
[97]2231
2232         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
[69]2233         }
2234      }
2235
[72]2236   $possible_format{$format}->(%result_ip);
2237   }
2238
[218]2239#---------------------------------------------------------------
[72]2240sub cmd_ip_free_txt {
2241   my %result_ip = @_;
[198]2242
[69]2243   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2244   print "-------------------------------------------------------------------------------\n";
[72]2245   LOOP_ON_IP_ADDRESS:
[165]2246   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
[261]2247         my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2248         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
[69]2249      }
2250   }
2251
[218]2252#---------------------------------------------------------------
[72]2253sub cmd_ip_free_html {
2254   my %result_ip = @_;
2255
2256   print <<'END_HTML';
[73]2257<table class="sortable" summary="Klask Free IP Database">
2258 <caption>Klask Free IP Database</caption>
[72]2259 <thead>
2260  <tr>
[73]2261   <th scope="col" class="klask-header-left">IPv4-Address</th>
[72]2262   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2263   <th scope="col" class="sorttable_alpha">VLAN</th>
[73]2264   <th scope="col" class="klask-header-right">Date</th>
[72]2265  </tr>
2266 </thead>
2267 <tfoot>
2268  <tr>
[73]2269   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2270   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
[72]2271   <th scope="col" class="fklask-vlan">VLAN</th>
[73]2272   <th scope="col" class="klask-footer-right">Date</th>
[72]2273  </tr>
2274 </tfoot>
2275 <tbody>
2276END_HTML
2277
2278   my $typerow = 'even';
2279
2280   LOOP_ON_IP_ADDRESS:
[165]2281   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
[72]2282
2283      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2284
2285      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
[261]2286      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
[72]2287
[261]2288      my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
[180]2289
[72]2290      print <<"END_HTML";
2291  <tr class="$typerow">
2292   <td sorttable_customkey="$ip_sort">$ip</td>
[261]2293   <td sorttable_customkey="$host_short">$result_ip{$ip}->{'hostname_fq'}</td>
[180]2294   <td>$vlan_nameid</td>
[261]2295   <td>$result_ip{$ip}->{'date_last_detection'}</td>
[72]2296  </tr>
2297END_HTML
2298      }
2299   print <<'END_HTML';
2300 </tbody>
2301</table>
2302END_HTML
2303   }
2304
[218]2305#---------------------------------------------------------------
[2]2306sub cmd_enable {
[210]2307   @ARGV = @_;
[63]2308
[210]2309   my $verbose;
2310
2311   GetOptions(
2312      'verbose|v' => \$verbose,
2313      );
2314
2315   my $switch_name = shift @ARGV || q{};
2316   my $port        = shift @ARGV || q{};
2317
2318   if ($switch_name eq q{} or $port eq q{}) {
2319      die "Usage: klask disable SWITCH_NAME PORT\n";
2320      }
2321
2322   if (not defined $SWITCH_DB{$switch_name}) {
2323      die "Switch $switch_name must be defined in klask configuration file\n";
2324      }
2325
2326   my $sw = $SWITCH_DB{$switch_name};
[220]2327   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
[210]2328   print "$error \n" if $error;
2329
[216]2330   # Retrieve numeric port value
[271]2331   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2332   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
[216]2333
[271]2334   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
[216]2335   print "Info: switch $switch_name port $port SNMP OID $search_portstatus\n" if $verbose;
2336
[210]2337   my $result = $session->set_request(
2338      -varbindlist => [$search_portstatus, INTEGER, 1],
2339      );
[216]2340   print $session->error()."\n" if $session->error_status();
[210]2341
2342   $session->close;
2343
[63]2344   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2345   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
[210]2346   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
[63]2347   return;
[2]2348   }
2349
[218]2350#---------------------------------------------------------------
[2]2351sub cmd_disable {
[210]2352   @ARGV = @_;
[63]2353
[210]2354   my $verbose;
2355
2356   GetOptions(
2357      'verbose|v' => \$verbose,
2358      );
2359
2360   my $switch_name = shift @ARGV || q{};
2361   my $port        = shift @ARGV || q{};
2362
2363   if ($switch_name eq q{} or $port eq q{}) {
2364      die "Usage: klask disable SWITCH_NAME PORT\n";
2365      }
2366
2367   if (not defined $SWITCH_DB{$switch_name}) {
2368      die "Switch $switch_name must be defined in klask configuration file\n";
2369      }
2370
2371   my $sw = $SWITCH_DB{$switch_name};
[220]2372   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
[210]2373   print "$error \n" if $error;
2374
[216]2375   # Retrieve numeric port value
[271]2376   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2377   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
[216]2378
[271]2379   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
[216]2380   print "Info: switch $switch_name port $port SNMP OID $search_portstatus\n" if $verbose;
2381
[210]2382   my $result = $session->set_request(
2383      -varbindlist => [$search_portstatus, INTEGER, 2],
2384      );
[213]2385   print $session->error()."\n" if $session->error_status();
[210]2386
2387   $session->close;
2388
2389   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
[63]2390   return;
[2]2391   }
2392
[218]2393#---------------------------------------------------------------
[2]2394sub cmd_status {
[206]2395   @ARGV = @_;
[63]2396
[206]2397   my $verbose;
2398
2399   GetOptions(
2400      'verbose|v' => \$verbose,
2401      );
2402
2403   my $switch_name = shift @ARGV || q{};
2404   my $port        = shift @ARGV || q{};
2405
[207]2406   if ($switch_name eq q{} or $port eq q{}) {
2407      die "Usage: klask status SWITCH_NAME PORT\n";
2408      }
2409
2410   if (not defined $SWITCH_DB{$switch_name}) {
2411      die "Switch $switch_name must be defined in klask configuration file\n";
2412      }
2413
[206]2414   my $sw = $SWITCH_DB{$switch_name};
[221]2415   my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[206]2416   print "$error \n" if $error;
2417
[213]2418   # Retrieve numeric port value
[271]2419   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2420   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
[213]2421
[271]2422   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2423   print "Info: switch $switch_name port $port ($port_id) SNMP OID $search_portstatus\n" if $verbose;
[213]2424
[206]2425   my $result = $session->get_request(
[209]2426      -varbindlist => [$search_portstatus]
[206]2427      );
[216]2428   print $session->error()."\n" if $session->error_status();
[206]2429   if (defined $result) {
[209]2430      print "$PORT_UPDOWN{$result->{$search_portstatus}}\n";
[206]2431      }
2432
[216]2433   $session->close;
2434
[206]2435   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
[63]2436   return;
[2]2437   }
2438
[218]2439#---------------------------------------------------------------
[35]2440sub cmd_search_mac_on_switch {
[138]2441   @ARGV = @_;
[63]2442
[138]2443   my $verbose;
[144]2444   my $vlan_id = 0;
[138]2445
2446   GetOptions(
2447      'verbose|v' => \$verbose,
[144]2448      'vlan|l=i'  => \$vlan_id,
[138]2449      );
2450
2451   my $switch_name = shift @ARGV || q{};
2452   my $mac_address = shift @ARGV || q{};
2453
[63]2454   if ($switch_name eq q{} or $mac_address eq q{}) {
[39]2455      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
[38]2456      }
[39]2457
[138]2458   $mac_address = normalize_mac_address($mac_address);
[261]2459   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*} or $switch_name eq q{all};
[39]2460
[112]2461   for my $sw_name (split /,/, $switch_name) {
2462      if (not defined $SWITCH_DB{$sw_name}) {
2463         die "Switch $sw_name must be defined in klask configuration file\n";
2464         }
[39]2465
[246]2466      my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2467      my $research2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
[138]2468      print "Klask search OID $research1 on switch $sw_name\n" if $verbose;
2469      print "Klask search OID $research2 on switch $sw_name\n" if $verbose;
[35]2470
[209]2471      my $sw = $SWITCH_DB{$sw_name};
[221]2472      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[112]2473      print "$error \n" if $error;
[63]2474
[112]2475      my $result = $session->get_request(
[124]2476         -varbindlist => [$research1]
[112]2477         );
[124]2478      if (not defined $result) {
2479         $result = $session->get_request(
2480            -varbindlist => [$research2]
2481            );
2482         $result->{$research1} = $result->{$research2} if defined $result;
2483         }
[112]2484
[124]2485      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
[271]2486         my $swport_id = $result->{$research1};
2487         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
[181]2488         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
[123]2489         }
2490      else {
[138]2491         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
[112]2492         }
2493
[35]2494      $session->close;
2495      }
[63]2496   return;
[35]2497   }
2498
[218]2499#---------------------------------------------------------------
[4]2500sub cmd_updatesw {
[113]2501   @ARGV = @_;
[2]2502
[79]2503   my $verbose;
2504
[138]2505   GetOptions(
[79]2506      'verbose|v' => \$verbose,
2507      );
2508
[4]2509   init_switch_names('yes');    #nomme les switchs
[2]2510   print "\n";
2511
2512   my %where = ();
2513   my %db_switch_output_port = ();
[83]2514   my %db_switch_ip_hostnamefq = ();
[2]2515
2516   DETECT_ALL_ROUTER:
[11]2517   for my $one_router ( get_list_main_router(get_list_network()) ) {
[196]2518      print "Info: router loop $one_router\n" if $verbose;
[63]2519      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
[83]2520
[261]2521      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
2522      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $verbose;
[83]2523
[261]2524      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
[144]2525      my $vlan_id   = get_current_vlan_id($vlan_name);
[262]2526      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
[2]2527      }
2528
2529   ALL_ROUTER_IP_ADDRESS:
[83]2530   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
[63]2531
[83]2532      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
[2]2533
2534      ALL_SWITCH_CONNECTED:
[83]2535      for my $switch_detected ( keys %{$where{$ip_router}} ) {
[2]2536
[83]2537         my $switch = $where{$ip_router}->{$switch_detected};
[2]2538
[272]2539         next ALL_SWITCH_CONNECTED if $switch->{'port_id'} eq '0';
[63]2540
[261]2541         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
2542         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
[2]2543         }
[63]2544      }
[2]2545
2546   my %db_switch_link_with = ();
2547
[83]2548   my @list_all_switch = ();
[2]2549   my @list_switch_ipv4 = ();
[209]2550   for my $sw (@SWITCH_LIST) {
[261]2551      push @list_all_switch, $sw->{'hostname'};
[2]2552      }
2553
[45]2554   my $timestamp = time;
2555
[2]2556   ALL_SWITCH:
[198]2557   for my $one_switch (@list_all_switch) {
2558      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
[199]2559      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
[257]2560         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
2561         fast_ping($fake_ip);
[261]2562         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $verbose;
[257]2563         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
[261]2564         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
2565            $resol_arp{'mac_address'}   = $resol_arp_alt{'mac_address'};
2566            $resol_arp{'interface'}     = $resol_arp_alt{'interface'};
2567            $resol_arp{'ipv4_address'} .= '*';
[257]2568            # Force a MAC trace on switch
[261]2569            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
[199]2570            }
2571         }
[198]2572      print "Info: switch loop $one_switch\n" if $verbose;
[261]2573      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
[63]2574
[261]2575      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
[63]2576
[261]2577      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
[144]2578      my $vlan_id   = get_current_vlan_id($vlan_name);
[262]2579      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
[2]2580
[80]2581      if ($verbose) {
[261]2582         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
[198]2583         print "VERBOSE_3: $one_switch --- ",
[261]2584            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
[82]2585            "\n";
[80]2586         }
2587
[261]2588      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
2589      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $verbose;
[45]2590
[261]2591      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
2592      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
2593      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
[262]2594      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
[2]2595      }
[63]2596
[2]2597   ALL_SWITCH_IP_ADDRESS:
[199]2598   for my $ip (@list_switch_ipv4) {
2599#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
[63]2600
[83]2601      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2602
[2]2603      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
[84]2604#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
[2]2605
2606      DETECTED_SWITCH:
2607      for my $switch_detected ( keys %{$where{$ip}} ) {
2608
2609         my $switch = $where{$ip}->{$switch_detected};
[261]2610         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
[2]2611
[272]2612         next if $switch->{'port_id'}  eq '0';
[261]2613         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
2614         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
[2]2615
[83]2616         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
[261]2617         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{'hostname'} } = $switch->{'port_hr'};
[83]2618         print "VERBOSE_7: +++++\n" if $verbose;
[2]2619         }
2620
2621      }
[63]2622
[2]2623   my %db_switch_connected_on_port = ();
2624   my $maybe_more_than_one_switch_connected = 'yes';
[181]2625   my $cloop = 0;
[63]2626
[181]2627   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2628      $cloop++;
2629      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
[2]2630      for my $sw (keys %db_switch_link_with) {
2631         for my $connect (keys %{$db_switch_link_with{$sw}}) {
[63]2632
[160]2633            my $port_hr = $db_switch_link_with{$sw}->{$connect};
[63]2634
[248]2635            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2636            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
[2]2637            }
2638         }
2639
2640      $maybe_more_than_one_switch_connected  = 'no';
2641
2642      SWITCH_AND_PORT:
2643      for my $swport (keys %db_switch_connected_on_port) {
[63]2644
[2]2645         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
[63]2646
[2]2647         $maybe_more_than_one_switch_connected = 'yes';
2648
[248]2649         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[2]2650         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
[181]2651         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
[2]2652
2653         CONNECTED:
2654         for my $sw_connected (@sw_on_same_port) {
[63]2655
[2]2656            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
[63]2657
[2]2658            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
[63]2659
[2]2660            for my $other_sw (@sw_on_same_port) {
2661               next if $other_sw eq $sw_connected;
[63]2662
[2]2663               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2664               }
[63]2665
[2]2666            # We can not do better for this switch for this loop
2667            next SWITCH_AND_PORT;
2668            }
2669         }
2670      }
2671
2672   my %db_switch_parent =();
2673
2674   for my $sw (keys %db_switch_link_with) {
2675      for my $connect (keys %{$db_switch_link_with{$sw}}) {
[63]2676
[160]2677         my $port_hr = $db_switch_link_with{$sw}->{$connect};
[63]2678
[248]2679         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2680         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
[63]2681
[161]2682         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
[2]2683         }
2684      }
2685
[63]2686   print "Switch output port and parent port connection\n";
[2]2687   print "---------------------------------------------\n";
2688   for my $sw (sort keys %db_switch_output_port) {
2689      if (exists $db_switch_parent{$sw}) {
[261]2690         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
[2]2691         }
2692      else {
[82]2693         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
[2]2694         }
2695      }
2696   print "\n";
2697
2698   print "Switch parent and children port inter-connection\n";
2699   print "------------------------------------------------\n";
[63]2700   for my $swport (sort keys %db_switch_connected_on_port) {
[248]2701      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[2]2702      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2703         if (exists $db_switch_output_port{$sw}) {
[82]2704            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
[2]2705            }
2706         else {
[82]2707            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
[2]2708            }
2709         }
2710      }
2711
2712   my $switch_connection = {
2713      output_port       => \%db_switch_output_port,
2714      parent            => \%db_switch_parent,
2715      connected_on_port => \%db_switch_connected_on_port,
2716      link_with         => \%db_switch_link_with,
[25]2717      switch_db         => \%SWITCH_DB,
[260]2718      timestamp         => $timestamp,
[2]2719      };
[63]2720
[44]2721   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
[63]2722   return;
[2]2723   }
2724
[218]2725#---------------------------------------------------------------
[4]2726sub cmd_exportsw {
[113]2727   @ARGV = @_;
[2]2728
[34]2729   test_switchdb_environnement();
2730
[4]2731   my $format = 'txt';
2732
[138]2733   GetOptions(
[4]2734      'format|f=s'  => \$format,
2735      );
2736
2737   my %possible_format = (
2738      txt => \&cmd_exportsw_txt,
2739      dot => \&cmd_exportsw_dot,
2740      );
2741
2742   $format = 'txt' if not defined $possible_format{$format};
[63]2743
[4]2744   $possible_format{$format}->(@ARGV);
[63]2745   return;
[4]2746   }
2747
[218]2748#---------------------------------------------------------------
[4]2749sub cmd_exportsw_txt {
2750
[44]2751   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[4]2752
[261]2753   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2754   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2755   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
[4]2756
[63]2757   print "Switch output port and parent port connection\n";
[4]2758   print "---------------------------------------------\n";
2759   for my $sw (sort keys %db_switch_output_port) {
[233]2760      my $arrow ='-->';
2761         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
[4]2762      if (exists $db_switch_parent{$sw}) {
[261]2763         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
[4]2764         }
2765      else {
[233]2766         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
[4]2767         }
2768      }
2769   print "\n";
2770
2771   print "Switch parent and children port inter-connection\n";
2772   print "------------------------------------------------\n";
[63]2773   for my $swport (sort keys %db_switch_connected_on_port) {
[248]2774      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[4]2775      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
[233]2776         my $arrow ='<--';
2777            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
[4]2778         if (exists $db_switch_output_port{$sw}) {
[233]2779            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
[4]2780            }
2781         else {
[233]2782            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
[4]2783            }
2784         }
2785      }
[63]2786   return;
[4]2787   }
2788
[218]2789#---------------------------------------------------------------
[4]2790sub cmd_exportsw_dot {
2791
[44]2792   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[63]2793
[261]2794   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2795   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2796   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2797   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
2798   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
2799   my $timestamp                   =   $switch_connection->{'timestamp'};
[25]2800
[2]2801   my %db_building= ();
[196]2802   for my $sw (@SWITCH_LIST) {
[261]2803      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
[2]2804      $db_building{$building} ||= {};
2805      $db_building{$building}->{$location} ||= {};
[261]2806      $db_building{$building}->{$location}{ $sw->{'hostname'} } = 'y';
[2]2807      }
[63]2808
2809
[2]2810   print "digraph G {\n";
[132]2811   print "rankdir = LR;\n";
[203]2812   #print "splines=polyline;\n";
[2]2813
[4]2814   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2815   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
[2]2816
[260]2817   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
[202]2818   $year += 1900;
2819   $mon++;
2820   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2821   print "\"$date\" [ color = white, fillcolor = black, shape = polygon, sides=14, style = filled, fontcolor = white ]\n";
[203]2822   print "site -> \"$date\" [color = white];\n";
[201]2823
[2]2824   my $b=0;
2825   for my $building (keys %db_building) {
2826      $b++;
[63]2827
[4]2828      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2829      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
[2]2830
2831      my $l = 0;
2832      for my $loc (keys %{$db_building{$building}}) {
2833         $l++;
[63]2834
2835         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
[33]2836#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
[4]2837         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
[2]2838
2839         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2840
[254]2841            my $peripheries = 1;
2842            $peripheries = 2 if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2843            print "\"$sw:$db_switch_output_port{$sw}\" [label = \"".format_aggregator4dot($db_switch_output_port{$sw})."\", color = black, fillcolor = lightblue,  peripheries = $peripheries, style = filled];\n";
[2]2844
[25]2845            my $swname  = $sw;
[63]2846               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
[25]2847            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
[4]2848            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2849            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
[2]2850
2851
2852            for my $swport (keys %db_switch_connected_on_port) {
[248]2853               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[2]2854               next if not $sw_connect eq $sw;
2855               next if $port_connect eq $db_switch_output_port{$sw};
[254]2856               my $peripheries = 1;
2857               $peripheries = 2 if $port_connect =~ m/^(Trk|Br|Po)/;
2858               print "\"$sw:$port_connect\" [label = \"".format_aggregator4dot($port_connect)."\", color = black, fillcolor = plum,  peripheries = $peripheries, style = filled];\n";
[4]2859               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
[2]2860              }
2861            }
2862         }
2863      }
2864
[63]2865#   print "Switch output port and parent port connection\n";
[2]2866#   print "---------------------------------------------\n";
2867   for my $sw (sort keys %db_switch_output_port) {
2868      if (exists $db_switch_parent{$sw}) {
[272]2869#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port_id'};
[2]2870         }
2871      else {
[255]2872         my $style = 'solid';
2873         $style = 'bold' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
[256]2874         printf "   \"%s:%s\" -> internet [style=$style, color = navyblue]\n", $sw, $db_switch_output_port{$sw};
[2]2875         }
2876      }
2877   print "\n";
2878
2879#   print "Switch parent and children port inter-connection\n";
2880#   print "------------------------------------------------\n";
[63]2881   for my $swport (sort keys %db_switch_connected_on_port) {
[248]2882      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[2]2883      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
[254]2884         my $style = 'solid';
2885         $style = 'bold' if $port_connect =~ m/^(Trk|Br|Po)/;
[2]2886         if (exists $db_switch_output_port{$sw}) {
[254]2887            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
[2]2888            }
2889         else {
[254]2890            printf "   \"%s\"   -> \"%s:%s\" [style=$style]\n", $sw, $sw_connect, $port_connect;
[2]2891            }
2892         }
2893      }
2894
2895print "}\n";
[63]2896   return;
[2]2897   }
2898
2899
[218]2900################################################################
2901# documentation
2902################################################################
2903
[2]2904__END__
2905
2906=head1 NAME
2907
[219]2908klask - port and search manager for switches, map management
[2]2909
2910
[63]2911=head1 USAGE
[2]2912
[226]2913 klask version
2914 klask help
2915
[219]2916 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
[226]2917 klask exportdb [--format|-f txt|html]
[223]2918 klask removedb IP* computer*
[219]2919 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
[2]2920
[219]2921 klask updatesw [--verbose|-v]
[226]2922 klask exportsw [--format|-f txt|dot]
[5]2923
[226]2924 klask searchdb [--kind|-k host|mac] computer [mac-address]
[2]2925 klask search   computer
[219]2926 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
[2]2927
[226]2928 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
[69]2929
[224]2930 klask bad-vlan-id [--day|-d days_before_alert]
2931
[219]2932 klask enable  [--verbose|-v] switch port
2933 klask disable [--verbose|-v] switch port
2934 klask status  [--verbose|-v] switch port
[2]2935
[247]2936 klask poe-enable  [--verbose|-v] switch port
2937 klask poe-disable [--verbose|-v] switch port
2938 klask poe-status  [--verbose|-v] switch port
2939
[238]2940 klask vlan-getname switch vlan-id
2941 klask vlan-list switch
[2]2942
[237]2943
[2]2944=head1 DESCRIPTION
2945
[246]2946Klask is a small tool to find where is connected a host in a big network
2947and on which VLAN.
[212]2948Klask mean search in brittany.
[200]2949No hight level protocol like CDL, LLDP are use.
2950Everything is just done with SNMP request on MAC address.
[2]2951
[212]2952Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
[200]2953If you use PVST or MSTP and create loop between VLAN,
[212]2954you have to use C<portignore> functionality on switch port to cut manually loop
[200]2955(see config file below).
2956
[212]2957When you use a management port to administrate a switch,
2958it's not possible to create the map with this switch because it does not have a MAC address,
2959so other switch cannot find the real downlink port...
2960One way to work around this problem is, if you have a computer directly connected on the switch,
2961to put this IP as the fake ip for the switch.
[200]2962The MAC address associated will be use just for the map detection.
[212]2963The C<fake-ip> parameter is defined in the config file.
[200]2964
[219]2965Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
[2]2966
[6]2967
[2]2968=head1 COMMANDS
2969
[225]2970Some command are defined in the source code but are not documented here.
2971Theses could be not well defined, not finished, not well tested...
2972You can read the source code and use them at your own risk
2973(like for all the Klask code).
[2]2974
2975=head2 search
2976
[226]2977 klask search   computer
2978
[200]2979This command takes one or more computer in argument.
2980It search a computer on the network and give the port and the switch on which the computer is connected.
[2]2981
[227]2982=head2 search-mac-on-switch
[2]2983
[226]2984 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2985
2986This command search a MAC address on a switch.
[229]2987To search on all switch, you could put C<'*'> or C<all>.
2988The VLAN parameter could help.
[226]2989
2990
[2]2991=head2 enable
2992
[223]2993 klask enable  [--verbose|-v] switch port
[219]2994
[227]2995This command activate a port (or an agrregate bridge port) on a switch by SNMP.
[219]2996So you need to give the switch name and a port on the command line.
[225]2997See L</ABBREVIATION FOR PORT>.
[2]2998
[227]2999Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
[2]3000
[219]3001
[2]3002=head2 disable
3003
[219]3004 klask disable [--verbose|-v] switch port
3005
[227]3006This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
[219]3007So you need to give the switch name and a port on the command line.
[225]3008See L</ABBREVIATION FOR PORT>.
[2]3009
[227]3010Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
[2]3011
[219]3012
[2]3013=head2 status
3014
[223]3015 klask status  [--verbose|-v] switch port
[219]3016
[200]3017This command return the status of a port number on a switch by SNMP.
[219]3018The return value could be C<enable> or C<disable> word.
3019So you need to give the switch name and a port on the command line.
[225]3020See L</ABBREVIATION FOR PORT>.
[2]3021
[227]3022If it's not possible to change port status with command L</enable> and L</disable>
3023(SNMP community read write access),
3024it's always possible to have the port status even for bridge agrregate port.
[219]3025
[227]3026
[2]3027=head2 updatedb
3028
[223]3029 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
3030
[226]3031This command will scan networks and update the computer database.
3032To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3033This file is easy to read and write because Klask use YAML format and not XML
3034(see L</CONFIGURATION>).
[2]3035
[226]3036Option are not stable and could be use manually when you have a new kind of switch.
[223]3037Maybe some option will be transfered in a future C<checksw> command!
[2]3038
[227]3039The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3040By default, a network is C<active>.
3041This means that an C<fping> command is done at the beginning on all the IP of the network
3042and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3043will have an C<arping>
3044(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3045In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3046It's good for big subnet with few computer (telephone...).
3047The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3048
[223]3049=head2 exportdb
[2]3050
[226]3051 klask exportdb [--format|-f txt|html]
[223]3052
[226]3053This command print the content of the computer database.
3054There is actually only two format : TXT and HTML.
3055By default, format is TXT.
[200]3056It's very easy to have more format, it's just need times...
[2]3057
[190]3058=head2 removedb
[2]3059
[223]3060 klask removedb IP* computer*
3061
[190]3062This command remove an entry in the database.
3063There is only one kind of parameter, the IP of the computers to be removed.
3064You can put as many IP as you want...
3065
[226]3066Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
[190]3067
3068=head2 cleandb
3069
[223]3070 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
[190]3071
[223]3072Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3073Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3074This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3075This functionality could be use when computer define in VLAN 1
3076could have a float IP when they are connected on VLAN 2.
3077In the Klask database, the float DNS entries are less important.
[190]3078
[226]3079When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
[223]3080
[5]3081=head2 updatesw
3082
[224]3083 klask updatesw [--verbose|-v]
3084
[200]3085This command build a map of your manageable switch on your network.
[226]3086The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
[5]3087
3088
[224]3089=head2 exportsw
[5]3090
[226]3091 klask exportsw [--format|-f txt|dot]
[224]3092
[200]3093This command print the content of the switch database. There is actually two format.
[226]3094One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3095By default, format is TXT.
[5]3096
3097 klask exportsw --format dot > /tmp/map.dot
3098 dot -Tpng /tmp/map.dot > /tmp/map.png
3099
3100
[224]3101=head2 ip-free
3102
[226]3103 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
[224]3104
3105This command return IP address that was not use (detected by Klask) at this time.
3106The list returned could be limited to just one VLAN.
3107IP returned could have been never used or no computer have been detected since the number of days specified
3108(2 years by default).
[225]3109This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
[224]3110
3111 default:
3112   days-to-death: 730
3113
3114Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3115
3116
3117=head2 bad-vlan-id
3118
3119 klask bad-vlan-id [--day|-d days_before_alert]
3120
3121This command return a list of switch port that are not configure with the good VLAN.
3122Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3123and another prior trace where they had the good IP (good DNS name).
3124The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
[225]3125This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
[224]3126
3127 default:
3128   days-before-alert: 15
3129
3130This functionality is not need if your switch use RADIUS 802.1X configuration...
3131
3132
[247]3133=head2 poe-enable
[224]3134
[247]3135 klask poe-enable  [--verbose|-v] switch port
3136
3137This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3138So you need to give the switch name and a port on the command line.
3139See L</ABBREVIATION FOR PORT>.
3140
3141Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3142You need to have the SNMP write access on the switch in order to modify it's configuration.
3143
3144
3145=head2 poe-disable
3146
3147 klask poe-disable [--verbose|-v] switch port
3148
3149This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3150So you need to give the switch name and a port on the command line.
3151See L</ABBREVIATION FOR PORT>.
3152
3153Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3154You need to have the SNMP write access on the switch in order to modify it's configuration.
3155
3156
3157=head2 poe-status
3158
3159 klask poe-status  [--verbose|-v] switch port
3160
3161This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3162The return value could be C<enable> or C<disable> word.
3163So you need to give the switch name and a port on the command line.
3164See L</ABBREVIATION FOR PORT>.
3165
3166If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3167(SNMP community read write access),
3168it's always possible to have the PoE port status.
3169
3170Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3171
3172
[2]3173=head1 CONFIGURATION
3174
[219]3175Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
[201]3176The configuration is done in a F</etc/klask/klask.conf> YAML file.
[200]3177This format have many advantage over XML, it's easier to read and to write !
[2]3178
3179Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3180
3181 default:
3182   community: public
[220]3183   community-rw: private
[2]3184   snmpport: 161
[223]3185   float-regex: '(?^msx: ^float )'
[227]3186   scan-mode: active
[2]3187
3188 network:
[5]3189   labnet:
3190     ip-subnet:
3191       - add: 192.168.1.0/24
3192       - add: 192.168.2.0/24
3193     interface: eth0
[201]3194     vlan-id: 12
[5]3195     main-router: gw1.labnet.local
[2]3196
[5]3197   schoolnet:
3198     ip-subnet:
[227]3199       - add: 192.168.3.0/24
3200       - add: 192.168.4.0/24
[5]3201     interface: eth0.38
[201]3202     vlan-id: 13
[5]3203     main-router: gw2.schoolnet.local
[227]3204     scan-mode: passive
[5]3205
[227]3206   etunet:
3207     ip-subnet:
3208       - add: 192.168.5.0/24
3209     interface: eth2
3210     vlan-id: 14
3211     main-router: gw3.etunet.local
3212     scan-mode: passive
3213
[2]3214 switch:
3215   - hostname: sw1.klask.local
[227]3216     location: BatY / 1 floor / K004
[2]3217     portignore:
3218       - 1
3219       - 2
3220
3221   - hostname: sw2.klask.local
[227]3222     location: BatY / 2 floor / K203
[5]3223     type: HP2424
[2]3224     portignore:
3225       - 1
3226       - 2
[200]3227     fake-ip: 192.168.9.14
[2]3228
[227]3229   - hostname: sw3.klask.local
3230     location: BatY / 2 floor / K203
3231
[200]3232I think it's pretty easy to understand.
3233The default section can be overide in any section, if parameter mean something in theses sections.
[212]3234Network to be scan are define in the network section. You must put an add by network.
3235Maybe I will make a delete line to suppress specific computers.
[200]3236The switch section define your switch.
[219]3237You have to write the port number to ignore, this was important if your switchs are cascades
[212]3238(right now, method C<updatesw> find them automatically)
3239and is still important if you have loop (with PVST or MSTP).
3240Just put the ports numbers between switch.
[2]3241
[222]3242The C<community> parameter is use to get SNMP data on switch.
3243It could be overload for each switch.
3244By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3245Some few command change the switch state as the commands L</enable> and L</disable>.
3246In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3247Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
[2]3248
[222]3249
[218]3250=head1 ABBREVIATION FOR PORT
3251
3252HP Procurve and Nexans switches have a simplistic numbering scheme.
3253It's just number: 1, 2, 3... 24.
3254On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3255Nothing is done on theses ports names.
3256
3257On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3258followed by tree number.
3259In order to have short name,
3260we made the following rules:
3261
3262 Bridge-Aggregation     -> Br
3263 FastEthernet           -> Fa
3264 Forty-GigabitEthernet  -> Fo
3265 FortyGigabitEthernet   -> Fo
3266 GigabitEthernet        -> Gi
3267 Giga                   -> Gi
3268 Port-Channel           -> Po
3269 Ten-GigabitEthernet    -> Te
3270 TenGigabitEthernet     -> Te
3271 Ten                    -> Te
3272
3273All Klask command automatically normalize the port name on standart output
3274and also on input command line.
3275
[246]3276In the case of use an aggregator port (Po, Tk, Br ...),
3277the real ports used are also return.
[218]3278
[246]3279
[259]3280=head1 SWITCH SUPPORTED
3281
3282Here is a list of switches where Klask gives or gave (for old switches) good results.
3283We have only a few manageable switches to actually test Klask.
3284It is quite possible that switches from other brands will work just as well.
3285You just have to do a test on it and add the line of description that goes well in the source code.
3286Contact us for any additional information.
3287
3288In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3289This makes it possible to adjust the code names of the different manufacturers!
3290
3291HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3292J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3293J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F).
3294
3295BayStack: BayStack 350T HW(BS350T)
3296
3297Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3298
3299DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3300
3301H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3302
3303H3C: H3C5500
3304
33053COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
33063CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
33073CR17572-91, 3C17702-US, 3C17700.
3308
3309
[2]3310=head1 FILES
3311
[92]3312 /etc/klask/klask.conf
[100]3313 /var/lib/klask/klaskdb
3314 /var/lib/klask/switchdb
[2]3315
[219]3316
[2]3317=head1 SEE ALSO
3318
3319Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3320
[219]3321=over
[2]3322
[220]3323=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
[219]3324
[220]3325=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
[219]3326
3327=back
3328
3329
[2]3330=head1 VERSION
3331
[36]3332$Id: klask 273 2017-09-20 16:13:38Z g7moreau $
[2]3333
3334
3335=head1 AUTHOR
3336
[5]3337Written by Gabriel Moreau, Grenoble - France
[2]3338
3339
[63]3340=head1 LICENSE AND COPYRIGHT
[2]3341
3342GPL version 2 or later and Perl equivalent
[45]3343
[259]3344Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.