source: trunk/klask @ 268

Last change on this file since 268 was 268, checked in by g7moreau, 7 years ago
  • Stupid bug
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 120.8 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 268 2017-09-15 07:41:39Z 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
[171]132   N3483G           => { type => 2, model => 'NA3483-6G',      match => 'GigaSwitch V3 TP SFP-I 48V 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';
493   $ret{'switch_port'} = '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
[167]529      my $swport_num = $result->{$research1};
[245]530      my $swport_hr = snmp_get_switchport_num2hr($session, $swport_num);
[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'};
[261]549      $ret{'switch_port'}        = $swport_num;
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') {
[167]585         my $swport_num = $result->{$research1};
[245]586         my $swport_hr = snmp_get_switchport_num2hr($session, $swport_num);
[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'};
599         $ret->{$sw->{'hostname'}}{'port'}        = $swport_num;
[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#---------------------------------------------------------------
[244]718sub snmp_get_switchport_num2hr {
719   my ($snmp_session, $swport_num) = @_;
[198]720
[242]721   # On H3C, port number and port index are not the same
722   # Double SNMP request to get the name
[246]723   # First get the index, second get the name
[244]724
[261]725   my $research_index = $OID_NUMBER{'ifIndex'} .'.'. $swport_num;
[244]726   my $result_index = $snmp_session->get_request(
727      -varbindlist => [$research_index]
728      );
729
[242]730   my $swport_index = $swport_num;
[244]731   $swport_index = $result_index->{$research_index} if defined $result_index;
[157]732
[244]733   return snmp_get_switchport_index2hr($snmp_session, $swport_index);
734   }
[242]735
[244]736#---------------------------------------------------------------
737sub snmp_get_switchport_index2hr {
738   my ($snmp_session, $swport_index) = @_;
739
[261]740   my $research_hr = $OID_NUMBER{'ifName'} .'.'. $swport_index;
[157]741   my $result_hr = $snmp_session->get_request(
[154]742      -varbindlist => [$research_hr]
743      );
[242]744   my $swport_hr = $swport_index;
[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};
759         my ($current_port_index) = 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;
765         next LOOP_ON_OID_PORT if not $port_aggregator_index == $swport_index;
[240]766
[244]767         my $current_port_name = snmp_get_switchport_index2hr($snmp_session, $current_port_index);
[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
[244]776sub snmp_get_switchport_hr2num {
[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
[213]783   my $swport_num = $swport_hr;
[246]784   # direct return if already numeric (next loop is expensive) / old or simple switch
785   return $swport_num if $swport_num =~ m/^\d+$/;
[213]786
[261]787   my $research_index = $OID_NUMBER{'ifIndex'}; # base OID
[213]788   my @args = ( -varbindlist =>  [$research_index]);
[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;
[217]792      last LOOP_ON_OID_PORT if  not Net::SNMP::oid_base_match($research_index, $oid_current);
[213]793     
794      my $port_ifIndex = $snmp_session->var_bind_list->{$oid_current};
795      my ($port_index) = reverse split /\./, $oid_current; # last number
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) {
808         print "PORT3: $current_port_hr <-> $port_index\n" if $verbose;
[217]809         
810         # return port number ifIndex need by OID portUpDown
811         $swport_num = $port_ifIndex; # other possible value could be $port_index
812         last LOOP_ON_OID_PORT;
[213]813         }
814      }
815   return $swport_num;
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
[261]852      next LOOP_ON_IP_ADDRESS if exists $computerdb->{$ip}{'switch_port_hr'} and defined $computerdb->{$ip}{'switch_port_hr'};
[198]853
[261]854      $computerdb->{$ip}{'switch_port_hr'} = $computerdb->{$ip}{'switch_port'};
[151]855      }
856
[150]857   return $computerdb;
858   }
859
[218]860################################################################
861# command
862################################################################
[2]863
[218]864#---------------------------------------------------------------
[2]865sub cmd_help {
866
[63]867print <<'END';
[224]868klask - port and search manager for switches, map management
[2]869
[129]870 klask version
[224]871 klask help
[129]872
[223]873 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
[226]874 klask exportdb [--format|-f txt|html]
[223]875 klask removedb IP* computer*
876 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
[2]877
[224]878 klask updatesw [--verbose|-v]
[226]879 klask exportsw [--format|-f txt|dot]
[45]880
[226]881 klask searchdb [--kind|-k host|mac] computer [mac-address]
[2]882 klask search   computer
[226]883 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
[2]884
[226]885 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
[69]886
[224]887 klask bad-vlan-id [--day|-d days_before_alert]
[129]888
[223]889 klask enable  [--verbose|-v] switch port
890 klask disable [--verbose|-v] switch port
891 klask status  [--verbose|-v] switch port
[237]892
[247]893 klask poe-enable  [--verbose|-v] switch port
894 klask poe-disable [--verbose|-v] switch port
895 klask poe-status  [--verbose|-v] switch port
896
[238]897 klask vlan-getname switch vlan-id
898 klask vlan-list switch
[2]899END
[63]900   return;
[2]901   }
902
[218]903#---------------------------------------------------------------
[36]904sub cmd_version {
905
[63]906print <<'END';
[224]907klask - port and search manager for switches, map management
[259]908Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
[36]909
910END
[37]911   print ' $Id: klask 268 2017-09-15 07:41:39Z g7moreau $'."\n";
[63]912   return;
[36]913   }
914
[238]915#---------------------------------------------------------------
[2]916sub cmd_search {
917   my @computer = @_;
[63]918
[4]919   init_switch_names();    #nomme les switchs
[111]920   fast_ping(@computer);
[133]921
922   LOOP_ON_COMPUTER:
[2]923   for my $clientname (@computer) {
924      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
[261]925      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
[144]926      my $vlan_id   = get_current_vlan_id($vlan_name);
[261]927      my %where     = find_switch_port($resol_arp{'mac_address'}, '', $vlan_id); #retrouve l'emplacement
[133]928
[261]929      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]930
[146]931      printf '%-22s %2s %-30s %-15s %18s',
[261]932         $where{'switch_hostname'},
933         $where{'switch_port_hr'},
934         $resol_arp{'hostname_fq'},
935         $resol_arp{'ipv4_address'},
936         $resol_arp{'mac_address'}."\n";
[2]937      }
[63]938   return;
[2]939   }
940
[218]941#---------------------------------------------------------------
[2]942sub cmd_searchdb {
[136]943   my @ARGV  = @_;
944
945   my $kind;
946
[138]947   GetOptions(
[137]948      'kind=s'   => \$kind,
[136]949      );
950
951   my %possible_search = (
952      host  => \&cmd_searchdb_host,
953      mac   => \&cmd_searchdb_mac,
954      );
955
[137]956   $kind = 'host' if not defined $possible_search{$kind};
[136]957
958   $possible_search{$kind}->(@ARGV);
959   return;
960   }
961
962
[218]963#---------------------------------------------------------------
[136]964sub cmd_searchdb_host {
[2]965   my @computer = @_;
966
[111]967   fast_ping(@computer);
[151]968   my $computerdb = computerdb_load();
[63]969
[2]970   LOOP_ON_COMPUTER:
971   for my $clientname (@computer) {
972      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
[261]973      my $ip = $resol_arp{'ipv4_address'};
[63]974
[2]975      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
[63]976
[261]977      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[2]978      $year += 1900;
979      $mon++;
[63]980      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
[2]981
982      printf "%-22s %2s %-30s %-15s %-18s %s\n",
[261]983         $computerdb->{$ip}{'switch_hostname'},
984         $computerdb->{$ip}{'switch_port_hr'},
985         $computerdb->{$ip}{'hostname_fq'},
[2]986         $ip,
[261]987         $computerdb->{$ip}{'mac_address'},
[2]988         $date;
989      }
[63]990   return;
[2]991   }
992
[218]993#---------------------------------------------------------------
[136]994sub cmd_searchdb_mac {
995   my @mac = map { normalize_mac_address($_) } @_;
996
[151]997   my $computerdb = computerdb_load();
[136]998
999   LOOP_ON_MAC:
1000   for my $mac (@mac) {
1001      LOOP_ON_COMPUTER:
1002      for my $ip (keys %{$computerdb}) {
[261]1003         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{'mac_address'};
[198]1004
[261]1005         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[136]1006         $year += 1900;
1007         $mon++;
1008         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1009
1010         printf "%-22s %2s %-30s %-15s %-18s %s\n",
[261]1011            $computerdb->{$ip}{'switch_hostname'},
1012            $computerdb->{$ip}{'switch_port_hr'},
1013            $computerdb->{$ip}{'hostname_fq'},
[136]1014            $ip,
[261]1015            $computerdb->{$ip}{'mac_address'},
[136]1016            $date;
[138]1017         #next LOOP_ON_MAC;
[136]1018         }
1019
1020      }
1021   return;
1022   }
1023
[218]1024#---------------------------------------------------------------
[2]1025sub cmd_updatedb {
[170]1026   @ARGV = @_;
1027
[173]1028   my ($verbose, $verb_description, $check_hostname, $check_location);
[170]1029
1030   GetOptions(
[172]1031      'verbose|v'          => \$verbose,
1032      'verb-description|d' => \$verb_description,
1033      'chk-hostname|h'     => \$check_hostname,
[173]1034      'chk-location|l'     => \$check_location,
[170]1035      );
1036
1037   my @network = @ARGV;
[2]1038      @network = get_list_network() if not @network;
1039
[34]1040   test_switchdb_environnement();
1041
1042   my $computerdb = {};
[151]1043      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
[2]1044   my $timestamp = time;
[22]1045
[2]1046   my %computer_not_detected = ();
1047   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
1048
1049   my $number_of_computer = get_list_ip(@network); # + 1;
[63]1050   my $size_of_database   = keys %{$computerdb};
[31]1051      $size_of_database   = 1 if $size_of_database == 0;
[2]1052   my $i = 0;
1053   my $detected_computer = 0;
[22]1054
[173]1055   init_switch_names('yes', $verb_description, $check_hostname, $check_location);    #nomme les switchs
[2]1056
[22]1057   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
[44]1058   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[261]1059   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1060   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
[22]1061   my %db_switch_chained_port = ();
[63]1062   for my $swport (keys %db_switch_connected_on_port) {
[248]1063      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[22]1064      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
1065      }
[209]1066   for my $sw (@SWITCH_LIST) {
[261]1067      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{'hostname'}}  if exists $db_switch_output_port{$sw->{'hostname'}};
1068      if ( exists $db_switch_chained_port{$sw->{'hostname'}} ) {
1069         chop $db_switch_chained_port{$sw->{'hostname'}};
1070         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{'hostname'}};
[22]1071         }
[261]1072#      print "$sw->{'hostname'} ++ @{$sw->{portignore}}\n";
[22]1073      }
1074   }
1075
[2]1076   my %router_mac_ip = ();
1077   DETECT_ALL_ROUTER:
[9]1078#   for my $one_router ('194.254.66.254') {
1079   for my $one_router ( get_list_main_router(@network) ) {
[2]1080      my %resol_arp = resolve_ip_arp_host($one_router);
[261]1081      $router_mac_ip{ $resol_arp{'mac_address'} } = $resol_arp{'ipv4_address'};
[2]1082      }
1083
1084   ALL_NETWORK:
[179]1085   for my $current_net (@network) {
[2]1086
[179]1087      my @computer = get_list_ip($current_net);
1088      my $current_interface = get_current_interface($current_net);
[2]1089
[179]1090      fast_ping(@computer) if get_current_scan_mode($current_net) eq 'active';
[2]1091
1092      LOOP_ON_COMPUTER:
1093      for my $one_computer (@computer) {
1094         $i++;
[49]1095
[63]1096         my $total_percent = int (($i*100)/$number_of_computer);
1097
[2]1098         my $localtime = time - $timestamp;
[63]1099         my ($sec,$min) = localtime $localtime;
[2]1100
1101         my $time_elapse = 0;
1102            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
[63]1103         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
[2]1104
1105         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
[63]1106         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
1107         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
[96]1108         printf ' %-8s %-14s', $current_interface, $one_computer;
[2]1109
[186]1110         my $already_exist = exists $computerdb->{$one_computer} ? 'yes' : 'no';
1111         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface, 'fast', $already_exist);
[63]1112
[9]1113         # do not search on router connection (why ?)
[261]1114         if ( exists $router_mac_ip{$resol_arp{'mac_address'}}) {
[179]1115            $computer_not_detected{$one_computer} = $current_net;
[2]1116            next LOOP_ON_COMPUTER;
1117            }
1118
[9]1119         # do not search on switch inter-connection
[261]1120         if (exists $SWITCH_LEVEL{$resol_arp{'hostname_fq'}}) {
[179]1121            $computer_not_detected{$one_computer} = $current_net;
[2]1122            next LOOP_ON_COMPUTER;
1123            }
1124
[63]1125         my $switch_proposal = q{};
[261]1126         if (exists $computerdb->{$resol_arp{'ipv4_address'}} and exists $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'}) {
1127            $switch_proposal = $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'};
[2]1128            }
1129
[3]1130         # do not have a mac address
[261]1131         if ($resol_arp{'mac_address'} eq 'unknow' or (exists $resol_arp{'timestamps'} and $resol_arp{'timestamps'} < ($timestamp - 3 * 3600))) {
[179]1132            $computer_not_detected{$one_computer} = $current_net;
[3]1133            next LOOP_ON_COMPUTER;
1134            }
1135
[261]1136         my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
[147]1137         my $vlan_id   = get_current_vlan_id($vlan_name);
[262]1138         my %where = find_switch_port($resol_arp{'mac_address'}, $switch_proposal, $vlan_id);
[2]1139
1140         #192.168.24.156:
1141         #  arp: 00:0B:DB:D5:F6:65
1142         #  hostname: pcroyon.hmg.priv
1143         #  port: 5
1144         #  switch: sw-batH-legi:hp2524
1145         #  timestamp: 1164355525
1146
1147         # do not have a mac address
[261]1148#         if ($resol_arp{'mac_address'} eq 'unknow') {
[3]1149#            $computer_not_detected{$one_computer} = $current_interface;
1150#            next LOOP_ON_COMPUTER;
1151#            }
[2]1152
1153         # detected on a switch
[261]1154         if ($where{'switch_description'} ne 'unknow') {
[2]1155            $detected_computer++;
[261]1156            $computerdb->{$resol_arp{'ipv4_address'}} = {
1157               hostname_fq        => $resol_arp{'hostname_fq'},
1158               mac_address        => $resol_arp{'mac_address'},
1159               switch_hostname    => $where{'switch_hostname'},
1160               switch_description => $where{'switch_description'},
1161               switch_port        => $where{'switch_port'},
1162               switch_port_hr     => $where{'switch_port_hr'},
[2]1163               timestamp          => $timestamp,
[179]1164               network            => $current_net,
[2]1165               };
1166            next LOOP_ON_COMPUTER;
1167            }
1168
1169         # new in the database but where it is ?
[261]1170         if (not exists $computerdb->{$resol_arp{'ipv4_address'}}) {
[2]1171            $detected_computer++;
[261]1172            $computerdb->{$resol_arp{'ipv4_address'}} = {
1173               hostname_fq        => $resol_arp{'hostname_fq'},
1174               mac_address        => $resol_arp{'mac_address'},
1175               switch_hostname    => $where{'switch_hostname'},
1176               switch_description => $where{'switch_description'},
1177               switch_port        => $where{'switch_port'},
1178               switch_port_hr     => $where{'switch_port_hr'},
1179               timestamp          => $resol_arp{'timestamp'},
[179]1180               network            => $current_net,
[2]1181               };
1182            }
1183
1184         # mise a jour du nom de la machine si modification dans le dns
[261]1185         $computerdb->{$resol_arp{'ipv4_address'}}{'hostname_fq'} = $resol_arp{'hostname_fq'};
[63]1186
[2]1187         # mise à jour de la date de détection si détection plus récente par arpwatch
[261]1188         $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]1189
1190         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
[261]1191#         push @computer_not_detected, $resol_arp{'ipv4_address'} if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1192         $computer_not_detected{$resol_arp{'ipv4_address'}} = $current_net if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
[63]1193
[2]1194         }
1195      }
1196
1197   # final end of line at the end of the loop
1198   printf "\n";
1199
[13]1200   my $dirdb = $KLASK_DB_FILE;
[63]1201      $dirdb =~ s{ / [^/]* $}{}xms;
[2]1202   mkdir "$dirdb", 0755 unless -d "$dirdb";
[44]1203   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
[2]1204
1205   for my $one_computer (keys %computer_not_detected) {
[179]1206      my $current_net = $computer_not_detected{$one_computer};
1207      my $current_interface = get_current_interface($current_net);
[258]1208      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]1209      }
[63]1210   return;
[2]1211   }
1212
[218]1213#---------------------------------------------------------------
[2]1214sub cmd_removedb {
1215   my @computer = @_;
[34]1216
1217   test_maindb_environnement();
1218
[151]1219   my $computerdb = computerdb_load();
[2]1220
1221   LOOP_ON_COMPUTER:
1222   for my $one_computer (@computer) {
1223
[66]1224      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
1225            and exists $computerdb->{$one_computer} ) {
1226         delete $computerdb->{$one_computer};
1227         next;
1228         }
1229
[2]1230      my %resol_arp = resolve_ip_arp_host($one_computer);
1231
[261]1232      delete $computerdb->{$resol_arp{'ipv4_address'}} if exists $computerdb->{$resol_arp{'ipv4_address'}};
[2]1233      }
1234
[13]1235   my $dirdb = $KLASK_DB_FILE;
[63]1236      $dirdb =~ s{ / [^/]* $}{}xms;
[2]1237   mkdir "$dirdb", 0755 unless -d "$dirdb";
[44]1238   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
[63]1239   return;
[2]1240   }
1241
[218]1242#---------------------------------------------------------------
[74]1243sub cmd_cleandb {
[110]1244   my @ARGV  = @_;
[74]1245
1246   my $days_to_clean = 15;
[188]1247   my $repairdns;
[74]1248   my $verbose;
1249   my $database_has_changed;
1250
[138]1251   GetOptions(
[74]1252      'day|d=i'   => \$days_to_clean,
1253      'verbose|v' => \$verbose,
[190]1254      'repair-dns|r' => \$repairdns,
[74]1255      );
1256
1257   my @vlan_name = get_list_network();
1258
[188]1259   my $computerdb = computerdb_load();
[74]1260   my $timestamp = time;
1261
1262   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
[106]1263   my $timestamp_3month  = 3600 * 24 * 90;
[74]1264
[104]1265   my %mactimedb = ();
[74]1266   ALL_VLAN:
[109]1267   for my $vlan (shuffle @vlan_name) {
[74]1268
[109]1269      my @ip_list   = shuffle get_list_ip($vlan);
[198]1270
[74]1271      LOOP_ON_IP_ADDRESS:
1272      for my $ip (@ip_list) {
1273
1274         next LOOP_ON_IP_ADDRESS if
1275            not exists $computerdb->{$ip};
[198]1276
[261]1277            #&& $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
1278         my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1279         my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1280         my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
[104]1281
1282         $mactimedb{$ip_mac} ||= {
1283            ip          => $ip,
1284            timestamp   => $ip_timestamp,
1285            vlan        => $vlan,
1286            hostname_fq => $ip_hostname_fq,
1287            };
[198]1288
[108]1289         if (
[261]1290            ( $mactimedb{$ip_mac}->{'timestamp'} - $ip_timestamp > $timestamp_barrier
[108]1291               or (
[261]1292                  $mactimedb{$ip_mac}->{'timestamp'} > $ip_timestamp
1293                  and $timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_3month
[108]1294                  )
1295            )
[105]1296            and (
[261]1297               not $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
[118]1298               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
[105]1299               )) {
[74]1300            print "remove ip $ip\n" if $verbose;
1301            delete $computerdb->{$ip};
1302            $database_has_changed++;
1303            }
1304
[108]1305         elsif (
[261]1306            ( $ip_timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_barrier
[108]1307               or (
[261]1308                  $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}
[108]1309                  and $timestamp - $ip_timestamp > $timestamp_3month
1310                  )
1311            )
[105]1312            and (
[118]1313               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
[261]1314               or $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
[105]1315               )) {
[104]1316            print "remove ip ".$mactimedb{$ip_mac}->{ip}."\n" if $verbose;
1317            delete $computerdb->{$mactimedb{$ip_mac}->{ip}};
[74]1318            $database_has_changed++;
1319            }
1320
[261]1321         if ( $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}) {
[104]1322            $mactimedb{$ip_mac} = {
1323               ip          => $ip,
1324               timestamp   => $ip_timestamp,
1325               vlan        => $vlan,
1326               hostname_fq => $ip_hostname_fq,
1327               };
[74]1328            }
1329         }
1330      }
1331
[188]1332   if ($repairdns) { # Search and update unkown computer in reverse DNS
[189]1333      LOOP_ON_IP_ADDRESS:
1334      for my $ip (keys %{$computerdb}) {
[261]1335         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} ne 'unknow';
[188]1336
[189]1337         my $packed_ip = scalar gethostbyname($ip);
1338         next LOOP_ON_IP_ADDRESS if not defined $packed_ip;
[188]1339
1340         my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
[189]1341         next LOOP_ON_IP_ADDRESS if not defined $hostname_fq;
[188]1342
[261]1343         $computerdb->{$ip}{'hostname_fq'} = $hostname_fq;
[188]1344         $database_has_changed++;
1345         }
1346      }
1347
[74]1348   if ( $database_has_changed ) {
1349      my $dirdb = $KLASK_DB_FILE;
1350         $dirdb =~ s{ / [^/]* $}{}xms;
1351      mkdir "$dirdb", 0755 unless -d "$dirdb";
1352      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1353      }
1354   return;
1355   }
1356
[218]1357#---------------------------------------------------------------
[2]1358sub cmd_exportdb {
[113]1359   @ARGV = @_;
[45]1360
1361   my $format = 'txt';
1362
[138]1363   GetOptions(
[45]1364      'format|f=s'  => \$format,
1365      );
1366
1367   my %possible_format = (
1368      txt  => \&cmd_exportdb_txt,
1369      html => \&cmd_exportdb_html,
1370      );
1371
1372   $format = 'txt' if not defined $possible_format{$format};
[63]1373
[45]1374   $possible_format{$format}->(@ARGV);
[63]1375   return;
[45]1376   }
1377
[218]1378#---------------------------------------------------------------
[45]1379sub cmd_exportdb_txt {
[34]1380   test_maindb_environnement();
1381
[151]1382   my $computerdb = computerdb_load();
[2]1383
[233]1384   printf "%-28s %8s              %-40s %-15s %-18s %-16s %s\n", qw(Switch Port Hostname-FQ IPv4-Address MAC-Address Date VLAN);
[78]1385   print "--------------------------------------------------------------------------------------------------------------------------------------------\n";
[2]1386
1387   LOOP_ON_IP_ADDRESS:
[165]1388   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
[63]1389
[2]1390      # to be improve in the future
[261]1391      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
[2]1392
[261]1393      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[2]1394      $year += 1900;
1395      $mon++;
[63]1396      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
[2]1397
[177]1398      my $vlan = '';
[262]1399      $vlan = $computerdb->{$ip}{'network'}.'('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
[177]1400
[233]1401      my $arrow ='<-----------';
[261]1402         $arrow ='<===========' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
[232]1403
[233]1404      printf "%-28s %8s %12s %-40s %-15s %-18s %-16s %s\n",
[261]1405         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1406         $computerdb->{$ip}{'switch_port_hr'},
[232]1407         $arrow,
[261]1408         $computerdb->{$ip}{'hostname_fq'},
[2]1409         $ip,
[261]1410         $computerdb->{$ip}{'mac_address'},
[70]1411         $date,
[177]1412         $vlan;
[2]1413      }
[63]1414   return;
[2]1415   }
1416
[218]1417#---------------------------------------------------------------
[45]1418sub cmd_exportdb_html {
1419   test_maindb_environnement();
1420
[151]1421   my $computerdb = computerdb_load();
[45]1422
1423#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1424#<script src="sorttable-klask.js"></script>
1425
[63]1426   print <<'END_HTML';
[73]1427<table class="sortable" summary="Klask Host Database">
[72]1428 <caption>Klask Host Database</caption>
[45]1429 <thead>
1430  <tr>
[73]1431   <th scope="col" class="klask-header-left">Switch</th>
[45]1432   <th scope="col" class="sorttable_nosort">Port</th>
[164]1433   <th scope="col" class="sorttable_nosort" colspan="2">Link</th>
[73]1434   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
[45]1435   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
[67]1436   <th scope="col" class="sorttable_alpha">MAC-Address</th>
[71]1437   <th scope="col" class="sorttable_alpha">VLAN</th>
[73]1438   <th scope="col" class="klask-header-right">Date</th>
[45]1439  </tr>
1440 </thead>
1441 <tfoot>
1442  <tr>
[73]1443   <th scope="col" class="klask-footer-left">Switch</th>
[45]1444   <th scope="col" class="fklask-port">Port</th>
[163]1445   <th scope="col" class="fklask-link" colspan="2">Link</th>
[73]1446   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
[45]1447   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1448   <th scope="col" class="fklask-mac">MAC-Address</th>
[71]1449   <th scope="col" class="fklask-vlan">VLAN</th>
[73]1450   <th scope="col" class="klask-footer-right">Date</th>
[45]1451  </tr>
1452 </tfoot>
1453 <tbody>
[63]1454END_HTML
[45]1455
1456   my %mac_count = ();
1457   LOOP_ON_IP_ADDRESS:
[165]1458   for my $ip (keys %{$computerdb}) {
[63]1459
[45]1460      # to be improve in the future
[261]1461      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
[63]1462
[261]1463      $mac_count{$computerdb->{$ip}{'mac_address'}}++;
[45]1464      }
1465
1466   my $typerow = 'even';
1467
1468   LOOP_ON_IP_ADDRESS:
[165]1469   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
[63]1470
[45]1471      # to be improve in the future
[261]1472      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
[45]1473
[261]1474      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[45]1475      $year += 1900;
1476      $mon++;
[63]1477      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
[45]1478
1479#      $odd_or_even++;
1480#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
[63]1481      $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]1482
[265]1483      #my $arrow ='&#8592;';
1484      #   $arrow ='&#8656;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
[266]1485      my $arrow ='&#10229;';
1486         $arrow ='&#10232;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
[163]1487
[261]1488      my $switch_hostname = $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'} || 'unkown';
[45]1489      chomp $switch_hostname;
[261]1490      my $switch_hostname_sort = sprintf '%s %3s' ,$switch_hostname, $computerdb->{$ip}{'switch_port_hr'};
[45]1491
[63]1492      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
[45]1493
[261]1494      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{'mac_address'}}, $computerdb->{$ip}{'mac_address'};
[45]1495
[261]1496      $computerdb->{$ip}{'hostname_fq'} = 'unknow' if $computerdb->{$ip}{'hostname_fq'} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1497      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{'hostname_fq'};
[45]1498
[178]1499      my $vlan = '';
[262]1500      $vlan = $computerdb->{$ip}{'network'}.' ('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
[71]1501
[261]1502      my $parent_port_hr = format_aggregator4html($computerdb->{$ip}{'switch_port_hr'});
[253]1503
[63]1504      print <<"END_HTML";
[45]1505  <tr class="$typerow">
1506   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
[253]1507   <td class="bklask-port">$parent_port_hr</td>
[267]1508   <td>$arrow</td>
[261]1509   <td sorttable_customkey="$host_short">$computerdb->{$ip}{'hostname_fq'}</td>
[45]1510   <td sorttable_customkey="$ip_sort">$ip</td>
[261]1511   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{'mac_address'}</td>
[71]1512   <td>$vlan</td>
[45]1513   <td>$date</td>
1514  </tr>
[63]1515END_HTML
[266]1516#   <td colspan="2">$arrow</td>
[45]1517      }
1518
1519   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1520
[261]1521   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1522   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1523   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1524   my %db_switch                   = %{$switch_connection->{'switch_db'}};
[45]1525
[266]1526   # Output switch connection
1527   LOOP_ON_OUTPUT_SWITCH:
[45]1528   for my $sw (sort keys %db_switch_output_port) {
1529
[63]1530      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
[45]1531
[63]1532      $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]1533
[264]1534      #my $arrow ='&#8702;';
1535      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1536      my $arrow ='&#10236;';
1537         $arrow ='&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
[163]1538
[45]1539      if (exists $db_switch_parent{$sw}) {
[266]1540         # Link to uplink switch
1541         next LOOP_ON_OUTPUT_SWITCH;
1542         
1543         # Do not print anymore
[261]1544         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1545         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1546         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
[45]1547
[163]1548         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1549         $year += 1900;
1550         $mon++;
1551         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
[45]1552
[205]1553         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
[45]1554
[163]1555         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
[45]1556
[261]1557         my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'};
[45]1558
[263]1559         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1560         $vlan .= ' ('.get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}).')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1561
[253]1562         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
[261]1563         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
[253]1564
[163]1565         print <<"END_HTML";
[45]1566  <tr class="$typerow">
1567   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
[253]1568   <td class="bklask-port">$parent_port_hr</td>
[267]1569   <td><table boder="0"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
[221]1570   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{'switch'}</td>
[45]1571   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1572   <td sorttable_customkey="$mac_sort">$mac_address</td>
[263]1573   <td>$vlan</td>
[45]1574   <td>$date</td>
1575  </tr>
[63]1576END_HTML
[45]1577         }
1578      else {
[266]1579         # Router
[253]1580         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1581
[266]1582         my $host_short = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1583
1584         my $mac_address = $db_switch{$sw}->{'mac_address'};
1585         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1586         my $timestamp = $db_switch{$sw}->{'timestamp'};
1587
1588         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1589         $year += 1900;
1590         $mon++;
1591         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1592
1593         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1594
1595         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1596
1597         my $vlan = $db_switch{$sw}->{'network'};
1598         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1599
1600         my $arrow ='&#10235;';
1601            $arrow ='&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1602
[63]1603         print <<"END_HTML";
[45]1604  <tr class="$typerow">
[266]1605   <td sorttable_customkey="router">router</td>
1606   <td class="bklask-port"></td>
[268]1607   <td><table boder="0"><tr><td>$arrow</td><td>$parent_port_hr</td></tr></table></td>
[266]1608   <td sorttable_customkey="$host_short">$sw</td>
1609   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1610   <td sorttable_customkey="$mac_sort">$mac_address</td>
1611   <td>$vlan</td>
1612   <td>$date</td>
1613  </tr>
1614END_HTML
[267]1615         #<td>$arrow</td><td>$parent_port_hr</td>
[266]1616
1617         next LOOP_ON_OUTPUT_SWITCH;
1618
1619         # Old print
1620         print <<"END_HTML";
1621  <tr class="$typerow">
[45]1622   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
[253]1623   <td class="bklask-port">$parent_port_hr</td>
[164]1624   <td>$arrow</td><td></td>
[162]1625   <td sorttable_customkey="router">router</td>
[45]1626   <td sorttable_customkey="999999999999"></td>
1627   <td sorttable_customkey="99999"></td>
1628   <td></td>
[71]1629   <td></td>
[45]1630  </tr>
[63]1631END_HTML
[45]1632         }
1633      }
1634
[266]1635   # Child switch connection : parent <- child
[45]1636   for my $swport (sort keys %db_switch_connected_on_port) {
[248]1637      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[45]1638      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1639
[63]1640         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
[45]1641
[261]1642         my $mac_address = $db_switch{$sw}->{'mac_address'};
1643         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1644         my $timestamp = $db_switch{$sw}->{'timestamp'};
[45]1645
[163]1646         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1647         $year += 1900;
1648         $mon++;
1649         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
[45]1650
[205]1651         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
[45]1652
[163]1653         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
[45]1654
[163]1655         $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]1656
[264]1657         #my $arrow ='&#8701;';
1658         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
1659         my $arrow ='&#10235;';
1660            $arrow ='&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
[163]1661
[263]1662         my $vlan = $db_switch{$sw}->{'network'};
1663         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1664
[45]1665         if (exists $db_switch_output_port{$sw}) {
1666
[63]1667            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
[45]1668
[253]1669            my $parent_port_hr = format_aggregator4html($port_connect);
1670            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
1671
[63]1672            print <<"END_HTML";
[45]1673  <tr class="$typerow">
1674   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
[253]1675   <td class="bklask-port">$parent_port_hr</td>
[267]1676   <td><table boder="0"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
[162]1677   <td sorttable_customkey="$host_short">$sw</td>
[45]1678   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1679   <td sorttable_customkey="$mac_sort">$mac_address</td>
[263]1680   <td>$vlan</td>
[45]1681   <td>$date</td>
1682  </tr>
[63]1683END_HTML
[45]1684            }
1685         else {
[253]1686            my $parent_port_hr = format_aggregator4html($port_connect);
1687
[63]1688            print <<"END_HTML";
[45]1689  <tr class="$typerow">
1690   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
[253]1691   <td class="bklask-port">$parent_port_hr</td>
[267]1692   <td>$arrow</td>
[162]1693   <td sorttable_customkey="$sw">$sw</td>
[45]1694   <td sorttable_customkey="">$ipv4_address</td>
1695   <td sorttable_customkey="">$mac_address</td>
[263]1696   <td>$vlan</td>
[45]1697   <td>$date</td>
1698  </tr>
[63]1699END_HTML
[45]1700            }
1701         }
1702      }
1703
[63]1704   print <<'END_HTML';
[45]1705 </tbody>
1706</table>
[63]1707END_HTML
1708   return;
[45]1709   }
1710
[218]1711#---------------------------------------------------------------
[133]1712sub cmd_bad_vlan_id {
[224]1713   @ARGV = @_;
1714
1715   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1716   my $verbose;
1717
1718   GetOptions(
1719      'day|d=i'   => \$days_before_alert,
1720      );
1721
[114]1722   test_maindb_environnement();
1723
[151]1724   my $computerdb = computerdb_load();
[114]1725
[132]1726   # create a database with the most recent computer by switch port
[211]1727   my %switchportdb = ();
[114]1728   LOOP_ON_IP_ADDRESS:
[165]1729   for my $ip (keys %{$computerdb}) {
[114]1730      # to be improve in the future
[261]1731      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1732      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
1733      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port'} eq '0';
[114]1734
[261]1735      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1736      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1737      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
[114]1738
[115]1739      my $swpt = sprintf "%-28s  %2s",
[261]1740         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1741         $computerdb->{$ip}{'switch_port_hr'};
[211]1742      $switchportdb{$swpt} ||= {
[114]1743         ip          => $ip,
1744         timestamp   => $ip_timestamp,
[262]1745         vlan        => $computerdb->{$ip}{'network'},
[114]1746         hostname_fq => $ip_hostname_fq,
1747         mac_address => $ip_mac,
1748         };
[117]1749
[132]1750      # if float computer, set date 15 day before warning...
1751      my $ip_timestamp_mod = $ip_timestamp;
[261]1752      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
[224]1753      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
[261]1754      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
[198]1755
[132]1756      if ($ip_timestamp_mod > $ip_timestamp_ref) {
[211]1757         $switchportdb{$swpt} = {
[114]1758            ip          => $ip,
1759            timestamp   => $ip_timestamp,
[262]1760            vlan        => $computerdb->{$ip}{'network'},
[114]1761            hostname_fq => $ip_hostname_fq,
1762            mac_address => $ip_mac,
1763            };
1764         }
1765      }
1766
[133]1767   LOOP_ON_RECENT_COMPUTER:
[211]1768   for my $swpt (keys %switchportdb) {
[133]1769      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
[261]1770      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
[117]1771
[211]1772      my $src_ip = $switchportdb{$swpt}->{ip};
[114]1773      my $src_timestamp = 0;
[133]1774      LOOP_ON_IP_ADDRESS:
[165]1775      for my $ip (keys %{$computerdb}) {
[261]1776         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne  $switchportdb{$swpt}->{'mac_address'};
1777         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1778         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
[198]1779
[114]1780         $src_ip = $ip;
[261]1781         $src_timestamp = $computerdb->{$ip}{'timestamp'};
[114]1782         }
[132]1783
1784      # keep only if float computer is the most recent
[133]1785      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
[261]1786      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
[132]1787
[261]1788      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
[114]1789      $year += 1900;
1790      $mon++;
[132]1791      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
[114]1792
[261]1793      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
[114]1794      $year += 1900;
1795      $mon++;
[132]1796      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
[114]1797
[262]1798      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
[176]1799
1800      printf "%s / %-10s +-> %-10s(%i)  %s %s %s %s\n",
[262]1801         $swpt, $switchportdb{$swpt}->{'vlan'}, $computerdb->{$src_ip}{'network'}, $vlan_id,
[114]1802         $date,
1803         $src_date,
[261]1804         $computerdb->{$src_ip}{'mac_address'},
1805         $computerdb->{$src_ip}{'hostname_fq'};
[114]1806      }
1807   }
1808
[218]1809#---------------------------------------------------------------
[239]1810sub cmd_poe_enable {
1811   @ARGV = @_;
1812
1813   my $verbose;
1814   GetOptions(
1815      'verbose|v' => \$verbose,
1816      );
1817
1818   my $switch_name = shift @ARGV || q{};
1819   my $switch_port = shift @ARGV || q{};
1820
1821   if ($switch_name eq q{} or $switch_port eq q{}) {
1822      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
1823      }
1824
1825   for my $sw_name (split /,/, $switch_name) {
1826      if (not defined $SWITCH_DB{$sw_name}) {
1827         die "Switch $sw_name must be defined in klask configuration file\n";
1828         }
1829
1830      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1831
1832      my $sw = $SWITCH_DB{$sw_name};
1833      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1834      print "$error \n" if $error;
1835
1836      my $result = $session->set_request(
1837         -varbindlist => [$search, INTEGER, 8], # Only NEXANS
1838         );
1839      print $session->error()."\n" if $session->error_status();
1840
1841      $session->close;
1842      }
1843   cmd_poe_status($switch_name, $switch_port);
1844   return;
1845   }
1846
1847#---------------------------------------------------------------
1848sub cmd_poe_disable {
1849   @ARGV = @_;
1850
1851   my $verbose;
1852   GetOptions(
1853      'verbose|v' => \$verbose,
1854      );
1855
1856   my $switch_name = shift @ARGV || q{};
1857   my $switch_port = shift @ARGV || q{};
1858
1859   if ($switch_name eq q{} or $switch_port eq q{}) {
1860      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
1861      }
1862
1863   for my $sw_name (split /,/, $switch_name) {
1864      if (not defined $SWITCH_DB{$sw_name}) {
1865         die "Switch $sw_name must be defined in klask configuration file\n";
1866         }
1867
1868      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1869
1870      my $sw = $SWITCH_DB{$sw_name};
1871      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1872      print "$error \n" if $error;
1873
1874      my $result = $session->set_request(
1875         -varbindlist => [$search, INTEGER, 2], # Only NEXANS
1876         );
1877      print $session->error()."\n" if $session->error_status();
1878
1879      $session->close;
1880      }
1881   cmd_poe_status($switch_name, $switch_port);
1882   return;
1883   }
1884
1885#---------------------------------------------------------------
1886sub cmd_poe_status {
1887   @ARGV = @_;
1888
1889   my $verbose;
1890   GetOptions(
1891      'verbose|v' => \$verbose,
1892      );
1893
1894   my $switch_name = shift @ARGV || q{};
1895   my $switch_port = shift @ARGV || q{};
1896
1897   if ($switch_name eq q{} or $switch_port eq q{}) {
1898      die "Usage: klask poe-status SWITCH_NAME PORT\n";
1899      }
1900
1901   for my $sw_name (split /,/, $switch_name) {
1902      if (not defined $SWITCH_DB{$sw_name}) {
1903         die "Switch $sw_name must be defined in klask configuration file\n";
1904         }
1905
1906      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1907
1908      my $sw = $SWITCH_DB{$sw_name};
1909      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1910      print "$error \n" if $error;
1911
1912      my $result = $session->get_request(
1913         -varbindlist => [$search],
1914         );
1915
1916      if (defined $result and $result->{$search} ne 'noSuchInstance') {
1917         my $poe = $result->{$search} || 'empty';
[247]1918         $poe =~ s/8/enable/;
1919         $poe =~ s/2/disable/;
1920         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe;
[239]1921         }
1922      else {
[247]1923         print "Klask do not find PoE Status on switch $sw_name on port $switch_port\n";
[239]1924         }
1925
1926      $session->close;
1927      }
1928   return;
1929   }
1930
1931#---------------------------------------------------------------
[220]1932# not finish - do not use
[238]1933sub cmd_port_setvlan {
[129]1934   my $switch_name = shift || q{};
1935   my $mac_address = shift || q{};
1936
1937   if ($switch_name eq q{} or $mac_address eq q{}) {
1938      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1939      }
1940
[261]1941   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
[129]1942
1943   for my $sw_name (split /,/, $switch_name) {
1944      if (not defined $SWITCH_DB{$sw_name}) {
1945         die "Switch $sw_name must be defined in klask configuration file\n";
1946         }
1947
[246]1948      my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
1949      my $research2 = $OID_NUMBER{'searchPort2'} .'.'. 0 . mac_address_hex2dec($mac_address);
[129]1950      print "Klask search OID $research1 on switch $sw_name\n";
1951      print "Klask search OID $research2 on switch $sw_name\n";
1952
[209]1953      my $sw = $SWITCH_DB{$sw_name};
[221]1954      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[129]1955      print "$error \n" if $error;
1956
1957      my $result = $session->get_request(
1958         -varbindlist => [$research1]
1959         );
1960      if (not defined $result) {
1961         $result = $session->get_request(
1962            -varbindlist => [$research2]
1963            );
1964         $result->{$research1} = $result->{$research2} if defined $result;
1965         }
1966
1967      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1968         my $swport = $result->{$research1};
1969         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1970         }
1971      else {
1972         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1973         }
1974
1975      $session->close;
1976      }
1977   return;
1978   }
1979
[218]1980#---------------------------------------------------------------
[238]1981sub cmd_port_getvlan {
[141]1982   @ARGV = @_;
1983
1984   my $verbose;
1985   GetOptions(
1986      'verbose|v' => \$verbose,
1987      );
1988
1989   my $switch_name = shift @ARGV || q{};
1990   my $switch_port = shift @ARGV || q{};
1991
1992   if ($switch_name eq q{} or $switch_port eq q{}) {
[238]1993      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
[141]1994      }
1995
1996   for my $sw_name (split /,/, $switch_name) {
1997      if (not defined $SWITCH_DB{$sw_name}) {
1998         die "Switch $sw_name must be defined in klask configuration file\n";
1999         }
2000
[142]2001      my $search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
[141]2002
[209]2003      my $sw = $SWITCH_DB{$sw_name};
[221]2004      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[141]2005      print "$error \n" if $error;
2006
2007      my $result = $session->get_request(
2008         -varbindlist => [$search],
2009         );
2010
2011      if (defined $result and $result->{$search} ne 'noSuchInstance') {
2012         my $vlan_id = $result->{$search} || 'empty';
2013         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2014         }
2015      else {
[142]2016         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
[141]2017         }
2018
2019      $session->close;
2020      }
2021   return;
[129]2022   }
2023
[218]2024#---------------------------------------------------------------
[238]2025sub cmd_vlan_setname {
[129]2026   }
2027
[218]2028#---------------------------------------------------------------
[239]2029# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
[141]2030sub cmd_rebootsw {
2031   @ARGV = @_;
2032
2033   my $verbose;
2034   GetOptions(
2035      'verbose|v' => \$verbose,
2036      );
2037
2038   my $switch_name = shift @ARGV || q{};
2039
[142]2040   if ($switch_name eq q{}) {
[141]2041      die "Usage: klask rebootsw SWITCH_NAME\n";
2042      }
2043
2044   for my $sw_name (split /,/, $switch_name) {
2045      if (not defined $SWITCH_DB{$sw_name}) {
2046         die "Switch $sw_name must be defined in klask configuration file\n";
2047         }
2048
2049      my $sw = $SWITCH_DB{$sw_name};
[220]2050      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
[141]2051      print "$error \n" if $error;
2052
2053      my $result = $session->set_request(
[239]2054         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
[141]2055         );
2056
2057      $session->close;
2058      }
2059   return;
2060   }
2061
[218]2062#---------------------------------------------------------------
[238]2063sub cmd_vlan_getname {
[129]2064   my $switch_name = shift || q{};
2065   my $vlan_id     = shift || q{};
2066
2067   if ($switch_name eq q{} or $vlan_id eq q{}) {
[238]2068      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
[129]2069      }
2070
[261]2071   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
[129]2072
2073   for my $sw_name (split /,/, $switch_name) {
2074      if (not defined $SWITCH_DB{$sw_name}) {
2075         die "Switch $sw_name must be defined in klask configuration file\n";
2076         }
2077
[261]2078      my $search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
[129]2079
[209]2080      my $sw = $SWITCH_DB{$sw_name};
[221]2081      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[129]2082      print "$error \n" if $error;
2083
2084      my $result = $session->get_request(
2085         -varbindlist => [$search_vlan_name]
2086         );
2087
2088      if (defined $result and $result->{$search_vlan_name} ne 'noSuchInstance') {
[131]2089         my $vlan_name = $result->{$search_vlan_name} || 'empty';
[130]2090         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
[129]2091         }
2092      else {
2093         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2094         }
2095
2096      $session->close;
2097      }
2098   return;
2099   }
2100
[218]2101#---------------------------------------------------------------
[238]2102sub cmd_vlan_list {
[237]2103   my $switch_name = shift || q{};
2104
2105   if ($switch_name eq q{}) {
[238]2106      die "Usage: klask vlan-list SWITCH_NAME\n";
[237]2107      }
2108
[261]2109   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
[237]2110
2111   for my $sw_name (split /,/, $switch_name) {
2112      if (not defined $SWITCH_DB{$sw_name}) {
2113         die "Switch $sw_name must be defined in klask configuration file\n";
2114         }
2115
2116      my $sw = $SWITCH_DB{$sw_name};
2117      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2118      print "$error \n" if $error;
2119
2120      my %vlandb = snmp_get_vlan_list($session);
2121      $session->close;
2122     
2123      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2124      for my $vlanid (keys %vlandb) {
2125         printf "%7i - %s\n", $vlanid, $vlandb{$vlanid};
2126         }
2127      }
2128   return;
2129   }
2130
2131#---------------------------------------------------------------
[111]2132sub cmd_ip_location {
[151]2133   my $computerdb = computerdb_load();
[2]2134
2135   LOOP_ON_IP_ADDRESS:
[165]2136   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
[2]2137
[261]2138      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
[2]2139
[261]2140      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
[133]2141      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
[63]2142
2143      my $sw_location = q{};
[133]2144      LOOP_ON_ALL_SWITCH:
[196]2145      for my $sw (@SWITCH_LIST) {
[261]2146         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2147         $sw_location = $sw->{'location'};
[2]2148         last;
2149         }
2150
[63]2151      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
[2]2152      }
[63]2153   return;
[2]2154   }
2155
[218]2156#---------------------------------------------------------------
[69]2157sub cmd_ip_free {
[224]2158   @ARGV = @_;
[69]2159
[224]2160   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
[69]2161   my $format = 'txt';
[97]2162   my $verbose;
[69]2163
[138]2164   GetOptions(
[224]2165      'day|d=i'      => \$days_to_death,
[69]2166      'format|f=s'   => \$format,
[97]2167      'verbose|v'    => \$verbose,
[69]2168      );
2169
[72]2170   my %possible_format = (
2171      txt  => \&cmd_ip_free_txt,
2172      html => \&cmd_ip_free_html,
[97]2173      none => sub {},
[72]2174      );
2175   $format = 'txt' if not defined $possible_format{$format};
2176
[110]2177   my @vlan_name = @ARGV;
[69]2178   @vlan_name = get_list_network() if not @vlan_name;
2179
2180   my $computerdb = {};
[151]2181      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
[69]2182   my $timestamp = time;
2183
[224]2184   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
[69]2185
[72]2186   my %result_ip = ();
[69]2187
2188   ALL_NETWORK:
2189   for my $vlan (@vlan_name) {
2190
2191      my @ip_list = get_list_ip($vlan);
[97]2192
[69]2193      LOOP_ON_IP_ADDRESS:
2194      for my $ip (@ip_list) {
2195
[135]2196         if (exists $computerdb->{$ip}) {
[261]2197            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
[198]2198
[261]2199            my $mac_address = $computerdb->{$ip}{'mac_address'};
[135]2200            LOOP_ON_DATABASE:
[165]2201            for my $ip_db (keys %{$computerdb}) {
[261]2202               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2203               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
[135]2204               }
2205            }
[69]2206
2207         my $ip_date_last_detection = '';
2208         if (exists $computerdb->{$ip}) {
[261]2209            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
[69]2210            $year += 1900;
2211            $mon++;
2212            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2213            }
2214
2215         my $packed_ip = scalar gethostbyname($ip);
2216         my $hostname_fq = 'unknown';
[186]2217            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
[135]2218
2219         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2220
2221         $result_ip{$ip} ||= {};
[261]2222         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2223         $result_ip{$ip}->{'hostname_fq'} = $hostname_fq;
2224         $result_ip{$ip}->{'vlan'} = $vlan;
[97]2225
2226         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
[69]2227         }
2228      }
2229
[72]2230   $possible_format{$format}->(%result_ip);
2231   }
2232
[218]2233#---------------------------------------------------------------
[72]2234sub cmd_ip_free_txt {
2235   my %result_ip = @_;
[198]2236
[69]2237   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2238   print "-------------------------------------------------------------------------------\n";
[72]2239   LOOP_ON_IP_ADDRESS:
[165]2240   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
[261]2241         my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2242         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
[69]2243      }
2244   }
2245
[218]2246#---------------------------------------------------------------
[72]2247sub cmd_ip_free_html {
2248   my %result_ip = @_;
2249
2250   print <<'END_HTML';
[73]2251<table class="sortable" summary="Klask Free IP Database">
2252 <caption>Klask Free IP Database</caption>
[72]2253 <thead>
2254  <tr>
[73]2255   <th scope="col" class="klask-header-left">IPv4-Address</th>
[72]2256   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2257   <th scope="col" class="sorttable_alpha">VLAN</th>
[73]2258   <th scope="col" class="klask-header-right">Date</th>
[72]2259  </tr>
2260 </thead>
2261 <tfoot>
2262  <tr>
[73]2263   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2264   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
[72]2265   <th scope="col" class="fklask-vlan">VLAN</th>
[73]2266   <th scope="col" class="klask-footer-right">Date</th>
[72]2267  </tr>
2268 </tfoot>
2269 <tbody>
2270END_HTML
2271
2272   my $typerow = 'even';
2273
2274   LOOP_ON_IP_ADDRESS:
[165]2275   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
[72]2276
2277      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2278
2279      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
[261]2280      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
[72]2281
[261]2282      my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
[180]2283
[72]2284      print <<"END_HTML";
2285  <tr class="$typerow">
2286   <td sorttable_customkey="$ip_sort">$ip</td>
[261]2287   <td sorttable_customkey="$host_short">$result_ip{$ip}->{'hostname_fq'}</td>
[180]2288   <td>$vlan_nameid</td>
[261]2289   <td>$result_ip{$ip}->{'date_last_detection'}</td>
[72]2290  </tr>
2291END_HTML
2292      }
2293   print <<'END_HTML';
2294 </tbody>
2295</table>
2296END_HTML
2297   }
2298
[218]2299#---------------------------------------------------------------
[2]2300sub cmd_enable {
[210]2301   @ARGV = @_;
[63]2302
[210]2303   my $verbose;
2304
2305   GetOptions(
2306      'verbose|v' => \$verbose,
2307      );
2308
2309   my $switch_name = shift @ARGV || q{};
2310   my $port        = shift @ARGV || q{};
2311
2312   if ($switch_name eq q{} or $port eq q{}) {
2313      die "Usage: klask disable SWITCH_NAME PORT\n";
2314      }
2315
2316   if (not defined $SWITCH_DB{$switch_name}) {
2317      die "Switch $switch_name must be defined in klask configuration file\n";
2318      }
2319
2320   my $sw = $SWITCH_DB{$switch_name};
[220]2321   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
[210]2322   print "$error \n" if $error;
2323
[216]2324   # Retrieve numeric port value
[244]2325   my $port_num = snmp_get_switchport_hr2num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
[216]2326   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
2327
2328   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2329   print "Info: switch $switch_name port $port SNMP OID $search_portstatus\n" if $verbose;
2330
[210]2331   my $result = $session->set_request(
2332      -varbindlist => [$search_portstatus, INTEGER, 1],
2333      );
[216]2334   print $session->error()."\n" if $session->error_status();
[210]2335
2336   $session->close;
2337
[63]2338   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2339   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
[210]2340   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
[63]2341   return;
[2]2342   }
2343
[218]2344#---------------------------------------------------------------
[2]2345sub cmd_disable {
[210]2346   @ARGV = @_;
[63]2347
[210]2348   my $verbose;
2349
2350   GetOptions(
2351      'verbose|v' => \$verbose,
2352      );
2353
2354   my $switch_name = shift @ARGV || q{};
2355   my $port        = shift @ARGV || q{};
2356
2357   if ($switch_name eq q{} or $port eq q{}) {
2358      die "Usage: klask disable SWITCH_NAME PORT\n";
2359      }
2360
2361   if (not defined $SWITCH_DB{$switch_name}) {
2362      die "Switch $switch_name must be defined in klask configuration file\n";
2363      }
2364
2365   my $sw = $SWITCH_DB{$switch_name};
[220]2366   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
[210]2367   print "$error \n" if $error;
2368
[216]2369   # Retrieve numeric port value
[244]2370   my $port_num = snmp_get_switchport_hr2num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
[216]2371   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
2372
2373   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2374   print "Info: switch $switch_name port $port SNMP OID $search_portstatus\n" if $verbose;
2375
[210]2376   my $result = $session->set_request(
2377      -varbindlist => [$search_portstatus, INTEGER, 2],
2378      );
[213]2379   print $session->error()."\n" if $session->error_status();
[210]2380
2381   $session->close;
2382
2383   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
[63]2384   return;
[2]2385   }
2386
[218]2387#---------------------------------------------------------------
[2]2388sub cmd_status {
[206]2389   @ARGV = @_;
[63]2390
[206]2391   my $verbose;
2392
2393   GetOptions(
2394      'verbose|v' => \$verbose,
2395      );
2396
2397   my $switch_name = shift @ARGV || q{};
2398   my $port        = shift @ARGV || q{};
2399
[207]2400   if ($switch_name eq q{} or $port eq q{}) {
2401      die "Usage: klask status SWITCH_NAME PORT\n";
2402      }
2403
2404   if (not defined $SWITCH_DB{$switch_name}) {
2405      die "Switch $switch_name must be defined in klask configuration file\n";
2406      }
2407
[206]2408   my $sw = $SWITCH_DB{$switch_name};
[221]2409   my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[206]2410   print "$error \n" if $error;
2411
[213]2412   # Retrieve numeric port value
[244]2413   my $port_num = snmp_get_switchport_hr2num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
[216]2414   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
[213]2415
2416   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2417   print "Info: switch $switch_name port $port ($port_num) SNMP OID $search_portstatus\n" if $verbose;
2418
[206]2419   my $result = $session->get_request(
[209]2420      -varbindlist => [$search_portstatus]
[206]2421      );
[216]2422   print $session->error()."\n" if $session->error_status();
[206]2423   if (defined $result) {
[209]2424      print "$PORT_UPDOWN{$result->{$search_portstatus}}\n";
[206]2425      }
2426
[216]2427   $session->close;
2428
[206]2429   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
[63]2430   return;
[2]2431   }
2432
[218]2433#---------------------------------------------------------------
[35]2434sub cmd_search_mac_on_switch {
[138]2435   @ARGV = @_;
[63]2436
[138]2437   my $verbose;
[144]2438   my $vlan_id = 0;
[138]2439
2440   GetOptions(
2441      'verbose|v' => \$verbose,
[144]2442      'vlan|l=i'  => \$vlan_id,
[138]2443      );
2444
2445   my $switch_name = shift @ARGV || q{};
2446   my $mac_address = shift @ARGV || q{};
2447
[63]2448   if ($switch_name eq q{} or $mac_address eq q{}) {
[39]2449      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
[38]2450      }
[39]2451
[138]2452   $mac_address = normalize_mac_address($mac_address);
[261]2453   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*} or $switch_name eq q{all};
[39]2454
[112]2455   for my $sw_name (split /,/, $switch_name) {
2456      if (not defined $SWITCH_DB{$sw_name}) {
2457         die "Switch $sw_name must be defined in klask configuration file\n";
2458         }
[39]2459
[246]2460      my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2461      my $research2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
[138]2462      print "Klask search OID $research1 on switch $sw_name\n" if $verbose;
2463      print "Klask search OID $research2 on switch $sw_name\n" if $verbose;
[35]2464
[209]2465      my $sw = $SWITCH_DB{$sw_name};
[221]2466      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
[112]2467      print "$error \n" if $error;
[63]2468
[112]2469      my $result = $session->get_request(
[124]2470         -varbindlist => [$research1]
[112]2471         );
[124]2472      if (not defined $result) {
2473         $result = $session->get_request(
2474            -varbindlist => [$research2]
2475            );
2476         $result->{$research1} = $result->{$research2} if defined $result;
2477         }
[112]2478
[124]2479      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
[181]2480         my $swport_num = $result->{$research1};
[245]2481         my $swport_hr = snmp_get_switchport_num2hr($session, $swport_num);
[181]2482         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
[123]2483         }
2484      else {
[138]2485         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
[112]2486         }
2487
[35]2488      $session->close;
2489      }
[63]2490   return;
[35]2491   }
2492
[218]2493#---------------------------------------------------------------
[4]2494sub cmd_updatesw {
[113]2495   @ARGV = @_;
[2]2496
[79]2497   my $verbose;
2498
[138]2499   GetOptions(
[79]2500      'verbose|v' => \$verbose,
2501      );
2502
[4]2503   init_switch_names('yes');    #nomme les switchs
[2]2504   print "\n";
2505
2506   my %where = ();
2507   my %db_switch_output_port = ();
[83]2508   my %db_switch_ip_hostnamefq = ();
[2]2509
2510   DETECT_ALL_ROUTER:
[11]2511   for my $one_router ( get_list_main_router(get_list_network()) ) {
[196]2512      print "Info: router loop $one_router\n" if $verbose;
[63]2513      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
[83]2514
[261]2515      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
2516      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $verbose;
[83]2517
[261]2518      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
[144]2519      my $vlan_id   = get_current_vlan_id($vlan_name);
[262]2520      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
[2]2521      }
2522
2523   ALL_ROUTER_IP_ADDRESS:
[83]2524   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
[63]2525
[83]2526      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
[2]2527
2528      ALL_SWITCH_CONNECTED:
[83]2529      for my $switch_detected ( keys %{$where{$ip_router}} ) {
[2]2530
[83]2531         my $switch = $where{$ip_router}->{$switch_detected};
[2]2532
[262]2533         next ALL_SWITCH_CONNECTED if $switch->{'port'} eq '0';
[63]2534
[261]2535         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
2536         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
[2]2537         }
[63]2538      }
[2]2539
2540   my %db_switch_link_with = ();
2541
[83]2542   my @list_all_switch = ();
[2]2543   my @list_switch_ipv4 = ();
[209]2544   for my $sw (@SWITCH_LIST) {
[261]2545      push @list_all_switch, $sw->{'hostname'};
[2]2546      }
2547
[45]2548   my $timestamp = time;
2549
[2]2550   ALL_SWITCH:
[198]2551   for my $one_switch (@list_all_switch) {
2552      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
[199]2553      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
[257]2554         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
2555         fast_ping($fake_ip);
[261]2556         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $verbose;
[257]2557         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
[261]2558         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
2559            $resol_arp{'mac_address'}   = $resol_arp_alt{'mac_address'};
2560            $resol_arp{'interface'}     = $resol_arp_alt{'interface'};
2561            $resol_arp{'ipv4_address'} .= '*';
[257]2562            # Force a MAC trace on switch
[261]2563            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
[199]2564            }
2565         }
[198]2566      print "Info: switch loop $one_switch\n" if $verbose;
[261]2567      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
[63]2568
[261]2569      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
[63]2570
[261]2571      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
[144]2572      my $vlan_id   = get_current_vlan_id($vlan_name);
[262]2573      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
[2]2574
[80]2575      if ($verbose) {
[261]2576         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
[198]2577         print "VERBOSE_3: $one_switch --- ",
[261]2578            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
[82]2579            "\n";
[80]2580         }
2581
[261]2582      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
2583      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $verbose;
[45]2584
[261]2585      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
2586      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
2587      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
[262]2588      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
[2]2589      }
[63]2590
[2]2591   ALL_SWITCH_IP_ADDRESS:
[199]2592   for my $ip (@list_switch_ipv4) {
2593#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
[63]2594
[83]2595      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2596
[2]2597      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
[84]2598#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
[2]2599
2600      DETECTED_SWITCH:
2601      for my $switch_detected ( keys %{$where{$ip}} ) {
2602
2603         my $switch = $where{$ip}->{$switch_detected};
[261]2604         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
[2]2605
[262]2606         next if $switch->{'port'}     eq '0';
[261]2607         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
2608         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
[2]2609
[83]2610         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
[261]2611         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{'hostname'} } = $switch->{'port_hr'};
[83]2612         print "VERBOSE_7: +++++\n" if $verbose;
[2]2613         }
2614
2615      }
[63]2616
[2]2617   my %db_switch_connected_on_port = ();
2618   my $maybe_more_than_one_switch_connected = 'yes';
[181]2619   my $cloop = 0;
[63]2620
[181]2621   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2622      $cloop++;
2623      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
[2]2624      for my $sw (keys %db_switch_link_with) {
2625         for my $connect (keys %{$db_switch_link_with{$sw}}) {
[63]2626
[160]2627            my $port_hr = $db_switch_link_with{$sw}->{$connect};
[63]2628
[248]2629            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2630            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
[2]2631            }
2632         }
2633
2634      $maybe_more_than_one_switch_connected  = 'no';
2635
2636      SWITCH_AND_PORT:
2637      for my $swport (keys %db_switch_connected_on_port) {
[63]2638
[2]2639         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
[63]2640
[2]2641         $maybe_more_than_one_switch_connected = 'yes';
2642
[248]2643         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[2]2644         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
[181]2645         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
[2]2646
2647         CONNECTED:
2648         for my $sw_connected (@sw_on_same_port) {
[63]2649
[2]2650            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
[63]2651
[2]2652            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
[63]2653
[2]2654            for my $other_sw (@sw_on_same_port) {
2655               next if $other_sw eq $sw_connected;
[63]2656
[2]2657               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2658               }
[63]2659
[2]2660            # We can not do better for this switch for this loop
2661            next SWITCH_AND_PORT;
2662            }
2663         }
2664      }
2665
2666   my %db_switch_parent =();
2667
2668   for my $sw (keys %db_switch_link_with) {
2669      for my $connect (keys %{$db_switch_link_with{$sw}}) {
[63]2670
[160]2671         my $port_hr = $db_switch_link_with{$sw}->{$connect};
[63]2672
[248]2673         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2674         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
[63]2675
[161]2676         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
[2]2677         }
2678      }
2679
[63]2680   print "Switch output port and parent port connection\n";
[2]2681   print "---------------------------------------------\n";
2682   for my $sw (sort keys %db_switch_output_port) {
2683      if (exists $db_switch_parent{$sw}) {
[261]2684         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
[2]2685         }
2686      else {
[82]2687         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
[2]2688         }
2689      }
2690   print "\n";
2691
2692   print "Switch parent and children port inter-connection\n";
2693   print "------------------------------------------------\n";
[63]2694   for my $swport (sort keys %db_switch_connected_on_port) {
[248]2695      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[2]2696      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2697         if (exists $db_switch_output_port{$sw}) {
[82]2698            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
[2]2699            }
2700         else {
[82]2701            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
[2]2702            }
2703         }
2704      }
2705
2706   my $switch_connection = {
2707      output_port       => \%db_switch_output_port,
2708      parent            => \%db_switch_parent,
2709      connected_on_port => \%db_switch_connected_on_port,
2710      link_with         => \%db_switch_link_with,
[25]2711      switch_db         => \%SWITCH_DB,
[260]2712      timestamp         => $timestamp,
[2]2713      };
[63]2714
[44]2715   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
[63]2716   return;
[2]2717   }
2718
[218]2719#---------------------------------------------------------------
[4]2720sub cmd_exportsw {
[113]2721   @ARGV = @_;
[2]2722
[34]2723   test_switchdb_environnement();
2724
[4]2725   my $format = 'txt';
2726
[138]2727   GetOptions(
[4]2728      'format|f=s'  => \$format,
2729      );
2730
2731   my %possible_format = (
2732      txt => \&cmd_exportsw_txt,
2733      dot => \&cmd_exportsw_dot,
2734      );
2735
2736   $format = 'txt' if not defined $possible_format{$format};
[63]2737
[4]2738   $possible_format{$format}->(@ARGV);
[63]2739   return;
[4]2740   }
2741
[218]2742#---------------------------------------------------------------
[4]2743sub cmd_exportsw_txt {
2744
[44]2745   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[4]2746
[261]2747   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2748   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2749   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
[4]2750
[63]2751   print "Switch output port and parent port connection\n";
[4]2752   print "---------------------------------------------\n";
2753   for my $sw (sort keys %db_switch_output_port) {
[233]2754      my $arrow ='-->';
2755         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
[4]2756      if (exists $db_switch_parent{$sw}) {
[261]2757         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]2758         }
2759      else {
[233]2760         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
[4]2761         }
2762      }
2763   print "\n";
2764
2765   print "Switch parent and children port inter-connection\n";
2766   print "------------------------------------------------\n";
[63]2767   for my $swport (sort keys %db_switch_connected_on_port) {
[248]2768      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[4]2769      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
[233]2770         my $arrow ='<--';
2771            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
[4]2772         if (exists $db_switch_output_port{$sw}) {
[233]2773            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
[4]2774            }
2775         else {
[233]2776            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
[4]2777            }
2778         }
2779      }
[63]2780   return;
[4]2781   }
2782
[218]2783#---------------------------------------------------------------
[4]2784sub cmd_exportsw_dot {
2785
[44]2786   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[63]2787
[261]2788   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2789   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2790   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2791   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
2792   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
2793   my $timestamp                   =   $switch_connection->{'timestamp'};
[25]2794
[2]2795   my %db_building= ();
[196]2796   for my $sw (@SWITCH_LIST) {
[261]2797      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
[2]2798      $db_building{$building} ||= {};
2799      $db_building{$building}->{$location} ||= {};
[261]2800      $db_building{$building}->{$location}{ $sw->{'hostname'} } = 'y';
[2]2801      }
[63]2802
2803
[2]2804   print "digraph G {\n";
[132]2805   print "rankdir = LR;\n";
[203]2806   #print "splines=polyline;\n";
[2]2807
[4]2808   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2809   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
[2]2810
[260]2811   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
[202]2812   $year += 1900;
2813   $mon++;
2814   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2815   print "\"$date\" [ color = white, fillcolor = black, shape = polygon, sides=14, style = filled, fontcolor = white ]\n";
[203]2816   print "site -> \"$date\" [color = white];\n";
[201]2817
[2]2818   my $b=0;
2819   for my $building (keys %db_building) {
2820      $b++;
[63]2821
[4]2822      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2823      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
[2]2824
2825      my $l = 0;
2826      for my $loc (keys %{$db_building{$building}}) {
2827         $l++;
[63]2828
2829         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
[33]2830#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
[4]2831         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
[2]2832
2833         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2834
[254]2835            my $peripheries = 1;
2836            $peripheries = 2 if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2837            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]2838
[25]2839            my $swname  = $sw;
[63]2840               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
[25]2841            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
[4]2842            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2843            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
[2]2844
2845
2846            for my $swport (keys %db_switch_connected_on_port) {
[248]2847               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[2]2848               next if not $sw_connect eq $sw;
2849               next if $port_connect eq $db_switch_output_port{$sw};
[254]2850               my $peripheries = 1;
2851               $peripheries = 2 if $port_connect =~ m/^(Trk|Br|Po)/;
2852               print "\"$sw:$port_connect\" [label = \"".format_aggregator4dot($port_connect)."\", color = black, fillcolor = plum,  peripheries = $peripheries, style = filled];\n";
[4]2853               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
[2]2854              }
2855            }
2856         }
2857      }
2858
[63]2859#   print "Switch output port and parent port connection\n";
[2]2860#   print "---------------------------------------------\n";
2861   for my $sw (sort keys %db_switch_output_port) {
2862      if (exists $db_switch_parent{$sw}) {
[262]2863#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port'};
[2]2864         }
2865      else {
[255]2866         my $style = 'solid';
2867         $style = 'bold' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
[256]2868         printf "   \"%s:%s\" -> internet [style=$style, color = navyblue]\n", $sw, $db_switch_output_port{$sw};
[2]2869         }
2870      }
2871   print "\n";
2872
2873#   print "Switch parent and children port inter-connection\n";
2874#   print "------------------------------------------------\n";
[63]2875   for my $swport (sort keys %db_switch_connected_on_port) {
[248]2876      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
[2]2877      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
[254]2878         my $style = 'solid';
2879         $style = 'bold' if $port_connect =~ m/^(Trk|Br|Po)/;
[2]2880         if (exists $db_switch_output_port{$sw}) {
[254]2881            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
[2]2882            }
2883         else {
[254]2884            printf "   \"%s\"   -> \"%s:%s\" [style=$style]\n", $sw, $sw_connect, $port_connect;
[2]2885            }
2886         }
2887      }
2888
2889print "}\n";
[63]2890   return;
[2]2891   }
2892
2893
[218]2894################################################################
2895# documentation
2896################################################################
2897
[2]2898__END__
2899
2900=head1 NAME
2901
[219]2902klask - port and search manager for switches, map management
[2]2903
2904
[63]2905=head1 USAGE
[2]2906
[226]2907 klask version
2908 klask help
2909
[219]2910 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
[226]2911 klask exportdb [--format|-f txt|html]
[223]2912 klask removedb IP* computer*
[219]2913 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
[2]2914
[219]2915 klask updatesw [--verbose|-v]
[226]2916 klask exportsw [--format|-f txt|dot]
[5]2917
[226]2918 klask searchdb [--kind|-k host|mac] computer [mac-address]
[2]2919 klask search   computer
[219]2920 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
[2]2921
[226]2922 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
[69]2923
[224]2924 klask bad-vlan-id [--day|-d days_before_alert]
2925
[219]2926 klask enable  [--verbose|-v] switch port
2927 klask disable [--verbose|-v] switch port
2928 klask status  [--verbose|-v] switch port
[2]2929
[247]2930 klask poe-enable  [--verbose|-v] switch port
2931 klask poe-disable [--verbose|-v] switch port
2932 klask poe-status  [--verbose|-v] switch port
2933
[238]2934 klask vlan-getname switch vlan-id
2935 klask vlan-list switch
[2]2936
[237]2937
[2]2938=head1 DESCRIPTION
2939
[246]2940Klask is a small tool to find where is connected a host in a big network
2941and on which VLAN.
[212]2942Klask mean search in brittany.
[200]2943No hight level protocol like CDL, LLDP are use.
2944Everything is just done with SNMP request on MAC address.
[2]2945
[212]2946Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
[200]2947If you use PVST or MSTP and create loop between VLAN,
[212]2948you have to use C<portignore> functionality on switch port to cut manually loop
[200]2949(see config file below).
2950
[212]2951When you use a management port to administrate a switch,
2952it's not possible to create the map with this switch because it does not have a MAC address,
2953so other switch cannot find the real downlink port...
2954One way to work around this problem is, if you have a computer directly connected on the switch,
2955to put this IP as the fake ip for the switch.
[200]2956The MAC address associated will be use just for the map detection.
[212]2957The C<fake-ip> parameter is defined in the config file.
[200]2958
[219]2959Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
[2]2960
[6]2961
[2]2962=head1 COMMANDS
2963
[225]2964Some command are defined in the source code but are not documented here.
2965Theses could be not well defined, not finished, not well tested...
2966You can read the source code and use them at your own risk
2967(like for all the Klask code).
[2]2968
2969=head2 search
2970
[226]2971 klask search   computer
2972
[200]2973This command takes one or more computer in argument.
2974It search a computer on the network and give the port and the switch on which the computer is connected.
[2]2975
[227]2976=head2 search-mac-on-switch
[2]2977
[226]2978 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2979
2980This command search a MAC address on a switch.
[229]2981To search on all switch, you could put C<'*'> or C<all>.
2982The VLAN parameter could help.
[226]2983
2984
[2]2985=head2 enable
2986
[223]2987 klask enable  [--verbose|-v] switch port
[219]2988
[227]2989This command activate a port (or an agrregate bridge port) on a switch by SNMP.
[219]2990So you need to give the switch name and a port on the command line.
[225]2991See L</ABBREVIATION FOR PORT>.
[2]2992
[227]2993Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
[2]2994
[219]2995
[2]2996=head2 disable
2997
[219]2998 klask disable [--verbose|-v] switch port
2999
[227]3000This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
[219]3001So you need to give the switch name and a port on the command line.
[225]3002See L</ABBREVIATION FOR PORT>.
[2]3003
[227]3004Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
[2]3005
[219]3006
[2]3007=head2 status
3008
[223]3009 klask status  [--verbose|-v] switch port
[219]3010
[200]3011This command return the status of a port number on a switch by SNMP.
[219]3012The return value could be C<enable> or C<disable> word.
3013So you need to give the switch name and a port on the command line.
[225]3014See L</ABBREVIATION FOR PORT>.
[2]3015
[227]3016If it's not possible to change port status with command L</enable> and L</disable>
3017(SNMP community read write access),
3018it's always possible to have the port status even for bridge agrregate port.
[219]3019
[227]3020
[2]3021=head2 updatedb
3022
[223]3023 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
3024
[226]3025This command will scan networks and update the computer database.
3026To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3027This file is easy to read and write because Klask use YAML format and not XML
3028(see L</CONFIGURATION>).
[2]3029
[226]3030Option are not stable and could be use manually when you have a new kind of switch.
[223]3031Maybe some option will be transfered in a future C<checksw> command!
[2]3032
[227]3033The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3034By default, a network is C<active>.
3035This means that an C<fping> command is done at the beginning on all the IP of the network
3036and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3037will have an C<arping>
3038(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3039In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3040It's good for big subnet with few computer (telephone...).
3041The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3042
[223]3043=head2 exportdb
[2]3044
[226]3045 klask exportdb [--format|-f txt|html]
[223]3046
[226]3047This command print the content of the computer database.
3048There is actually only two format : TXT and HTML.
3049By default, format is TXT.
[200]3050It's very easy to have more format, it's just need times...
[2]3051
[190]3052=head2 removedb
[2]3053
[223]3054 klask removedb IP* computer*
3055
[190]3056This command remove an entry in the database.
3057There is only one kind of parameter, the IP of the computers to be removed.
3058You can put as many IP as you want...
3059
[226]3060Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
[190]3061
3062=head2 cleandb
3063
[223]3064 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
[190]3065
[223]3066Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3067Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3068This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3069This functionality could be use when computer define in VLAN 1
3070could have a float IP when they are connected on VLAN 2.
3071In the Klask database, the float DNS entries are less important.
[190]3072
[226]3073When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
[223]3074
[5]3075=head2 updatesw
3076
[224]3077 klask updatesw [--verbose|-v]
3078
[200]3079This command build a map of your manageable switch on your network.
[226]3080The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
[5]3081
3082
[224]3083=head2 exportsw
[5]3084
[226]3085 klask exportsw [--format|-f txt|dot]
[224]3086
[200]3087This command print the content of the switch database. There is actually two format.
[226]3088One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3089By default, format is TXT.
[5]3090
3091 klask exportsw --format dot > /tmp/map.dot
3092 dot -Tpng /tmp/map.dot > /tmp/map.png
3093
3094
[224]3095=head2 ip-free
3096
[226]3097 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
[224]3098
3099This command return IP address that was not use (detected by Klask) at this time.
3100The list returned could be limited to just one VLAN.
3101IP returned could have been never used or no computer have been detected since the number of days specified
3102(2 years by default).
[225]3103This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
[224]3104
3105 default:
3106   days-to-death: 730
3107
3108Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3109
3110
3111=head2 bad-vlan-id
3112
3113 klask bad-vlan-id [--day|-d days_before_alert]
3114
3115This command return a list of switch port that are not configure with the good VLAN.
3116Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3117and another prior trace where they had the good IP (good DNS name).
3118The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
[225]3119This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
[224]3120
3121 default:
3122   days-before-alert: 15
3123
3124This functionality is not need if your switch use RADIUS 802.1X configuration...
3125
3126
[247]3127=head2 poe-enable
[224]3128
[247]3129 klask poe-enable  [--verbose|-v] switch port
3130
3131This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3132So you need to give the switch name and a port on the command line.
3133See L</ABBREVIATION FOR PORT>.
3134
3135Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3136You need to have the SNMP write access on the switch in order to modify it's configuration.
3137
3138
3139=head2 poe-disable
3140
3141 klask poe-disable [--verbose|-v] switch port
3142
3143This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3144So you need to give the switch name and a port on the command line.
3145See L</ABBREVIATION FOR PORT>.
3146
3147Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3148You need to have the SNMP write access on the switch in order to modify it's configuration.
3149
3150
3151=head2 poe-status
3152
3153 klask poe-status  [--verbose|-v] switch port
3154
3155This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3156The return value could be C<enable> or C<disable> word.
3157So you need to give the switch name and a port on the command line.
3158See L</ABBREVIATION FOR PORT>.
3159
3160If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3161(SNMP community read write access),
3162it's always possible to have the PoE port status.
3163
3164Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3165
3166
[2]3167=head1 CONFIGURATION
3168
[219]3169Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
[201]3170The configuration is done in a F</etc/klask/klask.conf> YAML file.
[200]3171This format have many advantage over XML, it's easier to read and to write !
[2]3172
3173Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3174
3175 default:
3176   community: public
[220]3177   community-rw: private
[2]3178   snmpport: 161
[223]3179   float-regex: '(?^msx: ^float )'
[227]3180   scan-mode: active
[2]3181
3182 network:
[5]3183   labnet:
3184     ip-subnet:
3185       - add: 192.168.1.0/24
3186       - add: 192.168.2.0/24
3187     interface: eth0
[201]3188     vlan-id: 12
[5]3189     main-router: gw1.labnet.local
[2]3190
[5]3191   schoolnet:
3192     ip-subnet:
[227]3193       - add: 192.168.3.0/24
3194       - add: 192.168.4.0/24
[5]3195     interface: eth0.38
[201]3196     vlan-id: 13
[5]3197     main-router: gw2.schoolnet.local
[227]3198     scan-mode: passive
[5]3199
[227]3200   etunet:
3201     ip-subnet:
3202       - add: 192.168.5.0/24
3203     interface: eth2
3204     vlan-id: 14
3205     main-router: gw3.etunet.local
3206     scan-mode: passive
3207
[2]3208 switch:
3209   - hostname: sw1.klask.local
[227]3210     location: BatY / 1 floor / K004
[2]3211     portignore:
3212       - 1
3213       - 2
3214
3215   - hostname: sw2.klask.local
[227]3216     location: BatY / 2 floor / K203
[5]3217     type: HP2424
[2]3218     portignore:
3219       - 1
3220       - 2
[200]3221     fake-ip: 192.168.9.14
[2]3222
[227]3223   - hostname: sw3.klask.local
3224     location: BatY / 2 floor / K203
3225
[200]3226I think it's pretty easy to understand.
3227The default section can be overide in any section, if parameter mean something in theses sections.
[212]3228Network to be scan are define in the network section. You must put an add by network.
3229Maybe I will make a delete line to suppress specific computers.
[200]3230The switch section define your switch.
[219]3231You have to write the port number to ignore, this was important if your switchs are cascades
[212]3232(right now, method C<updatesw> find them automatically)
3233and is still important if you have loop (with PVST or MSTP).
3234Just put the ports numbers between switch.
[2]3235
[222]3236The C<community> parameter is use to get SNMP data on switch.
3237It could be overload for each switch.
3238By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3239Some few command change the switch state as the commands L</enable> and L</disable>.
3240In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3241Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
[2]3242
[222]3243
[218]3244=head1 ABBREVIATION FOR PORT
3245
3246HP Procurve and Nexans switches have a simplistic numbering scheme.
3247It's just number: 1, 2, 3... 24.
3248On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3249Nothing is done on theses ports names.
3250
3251On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3252followed by tree number.
3253In order to have short name,
3254we made the following rules:
3255
3256 Bridge-Aggregation     -> Br
3257 FastEthernet           -> Fa
3258 Forty-GigabitEthernet  -> Fo
3259 FortyGigabitEthernet   -> Fo
3260 GigabitEthernet        -> Gi
3261 Giga                   -> Gi
3262 Port-Channel           -> Po
3263 Ten-GigabitEthernet    -> Te
3264 TenGigabitEthernet     -> Te
3265 Ten                    -> Te
3266
3267All Klask command automatically normalize the port name on standart output
3268and also on input command line.
3269
[246]3270In the case of use an aggregator port (Po, Tk, Br ...),
3271the real ports used are also return.
[218]3272
[246]3273
[259]3274=head1 SWITCH SUPPORTED
3275
3276Here is a list of switches where Klask gives or gave (for old switches) good results.
3277We have only a few manageable switches to actually test Klask.
3278It is quite possible that switches from other brands will work just as well.
3279You just have to do a test on it and add the line of description that goes well in the source code.
3280Contact us for any additional information.
3281
3282In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3283This makes it possible to adjust the code names of the different manufacturers!
3284
3285HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3286J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3287J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F).
3288
3289BayStack: BayStack 350T HW(BS350T)
3290
3291Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3292
3293DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3294
3295H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3296
3297H3C: H3C5500
3298
32993COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
33003CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
33013CR17572-91, 3C17702-US, 3C17700.
3302
3303
[2]3304=head1 FILES
3305
[92]3306 /etc/klask/klask.conf
[100]3307 /var/lib/klask/klaskdb
3308 /var/lib/klask/switchdb
[2]3309
[219]3310
[2]3311=head1 SEE ALSO
3312
3313Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3314
[219]3315=over
[2]3316
[220]3317=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
[219]3318
[220]3319=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
[219]3320
3321=back
3322
3323
[2]3324=head1 VERSION
3325
[36]3326$Id: klask 268 2017-09-15 07:41:39Z g7moreau $
[2]3327
3328
3329=head1 AUTHOR
3330
[5]3331Written by Gabriel Moreau, Grenoble - France
[2]3332
3333
[63]3334=head1 LICENSE AND COPYRIGHT
[2]3335
3336GPL version 2 or later and Perl equivalent
[45]3337
[259]3338Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.