source: trunk/klask @ 314

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