source: trunk/klask @ 370

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