source: trunk/klask @ 392

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