source: trunk/klask @ 389

Last change on this file since 389 was 389, checked in by g7moreau, 6 years ago
  • Update documentation and add it for insertdb new command
  • 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 389 2018-01-05 10:41:47Z 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}->{'hostname'},
955         "\n";
956      }
957
958   return sha512_base64($checksum_data);
959   }
960
961#---------------------------------------------------------------
962sub update_switchdb {
963   my %args = (
964      verbose => 0,
965      @_);
966
967   init_switch_names('yes'); # nomme les switchs
968   print "\n";
969
970   my %where = ();
971   my %db_switch_output_port = ();
972   my %db_switch_ip_hostnamefq = ();
973
974   DETECT_ALL_ROUTER:
975   for my $one_router (get_list_main_router(get_list_network())) {
976      print "Info: router loop $one_router\n" if $args{'verbose'};
977      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
978
979      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
980      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $args{'verbose'};
981
982      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
983      my $vlan_id   = get_current_vlan_id($vlan_name);
984      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
985      }
986
987   ALL_ROUTER_IP_ADDRESS:
988   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
989
990      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
991
992      ALL_SWITCH_CONNECTED:
993      for my $switch_detected (keys %{$where{$ip_router}}) {
994
995         my $switch = $where{$ip_router}->{$switch_detected};
996
997         next ALL_SWITCH_CONNECTED if $switch->{'port_id'} eq '0';
998
999         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
1000         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $args{'verbose'};
1001         }
1002      }
1003
1004   my %db_switch_link_with = ();
1005
1006   my @list_all_switch  = ();
1007   my @list_switch_ipv4 = ();
1008   for my $sw (@SWITCH_LIST) {
1009      push @list_all_switch, $sw->{'hostname'};
1010      }
1011
1012   my $timestamp = time;
1013
1014   ALL_SWITCH:
1015   for my $one_switch (@list_all_switch) {
1016      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
1017      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
1018         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
1019         fast_ping($fake_ip);
1020         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $args{'verbose'};
1021         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
1022         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
1023            $resol_arp{'mac_address'} = $resol_arp_alt{'mac_address'};
1024            $resol_arp{'interface'}   = $resol_arp_alt{'interface'};
1025            $resol_arp{'ipv4_address'} .= '*';
1026            # Force a MAC trace on switch
1027            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
1028            }
1029         }
1030      print "Info: switch loop $one_switch\n" if $args{'verbose'};
1031      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
1032
1033      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
1034
1035      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1036      my $vlan_id   = get_current_vlan_id($vlan_name);
1037      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
1038
1039      if ($args{'verbose'}) {
1040         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
1041         print "VERBOSE_3: $one_switch --- ",
1042            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
1043            "\n";
1044         }
1045
1046      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
1047      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $args{'verbose'};
1048
1049      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
1050      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
1051      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
1052      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
1053      }
1054
1055   ALL_SWITCH_IP_ADDRESS:
1056   for my $ip (@list_switch_ipv4) {
1057      #   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1058
1059      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $args{'verbose'};
1060
1061      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1062      #      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
1063
1064      DETECTED_SWITCH:
1065      for my $switch_detected (keys %{$where{$ip}}) {
1066
1067         my $switch = $where{$ip}->{$switch_detected};
1068         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $args{'verbose'};
1069
1070         next if $switch->{'port_id'}  eq '0';
1071         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
1072         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
1073
1074         $db_switch_link_with{$db_switch_ip_hostnamefq{$ip}} ||= {};
1075         $db_switch_link_with{$db_switch_ip_hostnamefq{$ip}}->{$switch->{'hostname'}} = $switch->{'port_hr'};
1076         print "VERBOSE_7: +++++\n" if $args{'verbose'};
1077         }
1078
1079      }
1080
1081   my %db_switch_connected_on_port          = ();
1082   my $maybe_more_than_one_switch_connected = 'yes';
1083   my $cloop                                = 0;
1084
1085   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
1086      $cloop++;
1087      print "VERBOSE_9: cloop reduction step: $cloop\n" if $args{'verbose'};
1088      for my $sw (keys %db_switch_link_with) {
1089         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1090
1091            my $port_hr = $db_switch_link_with{$sw}->{$connect};
1092
1093            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
1094            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
1095            }
1096         }
1097
1098      $maybe_more_than_one_switch_connected = 'no';
1099
1100      SWITCH_AND_PORT:
1101      for my $swport (keys %db_switch_connected_on_port) {
1102
1103         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1104
1105         $maybe_more_than_one_switch_connected = 'yes';
1106
1107         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1108         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1109         print "VERBOSE_10: $swport -- " . $#sw_on_same_port . " -- @sw_on_same_port\n" if $args{'verbose'};
1110
1111         CONNECTED:
1112         for my $sw_connected (@sw_on_same_port) {
1113
1114            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1115
1116            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1117
1118            for my $other_sw (@sw_on_same_port) {
1119               next if $other_sw eq $sw_connected;
1120
1121               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1122               }
1123
1124            # We can not do better for this switch for this loop
1125            next SWITCH_AND_PORT;
1126            }
1127         }
1128      }
1129
1130   my %db_switch_parent = ();
1131
1132   for my $sw (keys %db_switch_link_with) {
1133      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1134
1135         my $port_hr = $db_switch_link_with{$sw}->{$connect};
1136
1137         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
1138         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
1139
1140         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
1141         }
1142      }
1143
1144   print "Switch output port and parent port connection\n";
1145   print "---------------------------------------------\n";
1146   for my $sw (sort keys %db_switch_output_port) {
1147      if (exists $db_switch_parent{$sw}) {
1148         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
1149         }
1150      else {
1151         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1152         }
1153      }
1154   print "\n";
1155
1156   print "Switch parent and children port inter-connection\n";
1157   print "------------------------------------------------\n";
1158   for my $swport (sort keys %db_switch_connected_on_port) {
1159      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1160      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1161         if (exists $db_switch_output_port{$sw}) {
1162            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1163            }
1164         else {
1165            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1166            }
1167         }
1168      }
1169
1170   my $switch_connection = {
1171      output_port       => \%db_switch_output_port,
1172      parent            => \%db_switch_parent,
1173      connected_on_port => \%db_switch_connected_on_port,
1174      link_with         => \%db_switch_link_with,
1175      switch_db         => \%SWITCH_DB,
1176      timestamp         => $timestamp,
1177      checksum          => get_switchdb_checksum(%SWITCH_DB),
1178      };
1179
1180   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1181   return;
1182   }
1183
1184################################################################
1185# command
1186################################################################
1187
1188#---------------------------------------------------------------
1189sub cmd_help {
1190
1191   print <<'END';
1192klask - port and search manager for switches, map management
1193
1194 klask version
1195 klask help
1196
1197 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
1198 klask exportdb [--format|-f txt|html]
1199 klask removedb ipv4_addr* computer*
1200 klask insertdb --ip ipv4_addr --mac mac_addr --network vlan_name
1201 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
1202
1203 klask updatesw [--verbose|-v]
1204 klask exportsw [--format|-f txt|dot] [--modulo|-m XX] [--shift|-s YY] [--way all|desc|child|parent] [--no-header|-H]
1205
1206 klask searchdb [--kind|-k host|mac] computer [mac-address]
1207 klask search   computer
1208 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
1209
1210 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
1211
1212 klask bad-vlan-id [--day|-d days_before_alert] [--format|-f txt|html]
1213
1214 klask enable  [--verbose|-v] switch port
1215 klask disable [--verbose|-v] switch port
1216 klask status  [--verbose|-v] switch port
1217
1218 klask poe-enable  [--verbose|-v] switch port
1219 klask poe-disable [--verbose|-v] switch port
1220 klask poe-status  [--verbose|-v] switch port
1221
1222 klask vlan-getname switch vlan-id
1223 klask vlan-list switch
1224END
1225   return;
1226   }
1227
1228#---------------------------------------------------------------
1229sub cmd_version {
1230
1231   print <<'END';
1232klask - port and search manager for switches, map management
1233Copyright (C) 2005-2018 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
1234License GNU GPL version 2 or later and Perl equivalent
1235END
1236
1237   print "Version $VERSION\n\n";
1238   print ' $Id: klask 389 2018-01-05 10:41:47Z g7moreau $'."\n";
1239   return;
1240   }
1241
1242#---------------------------------------------------------------
1243sub cmd_search {
1244   my @computer = @_;
1245
1246   init_switch_names(); # nomme les switchs
1247   fast_ping(@computer);
1248
1249   LOOP_ON_COMPUTER:
1250   for my $clientname (@computer) {
1251      my %resol_arp = resolve_ip_arp_host($clientname); # resolution arp
1252      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1253      my $vlan_id   = get_current_vlan_id($vlan_name);
1254      my %where     = find_switch_port($resol_arp{'mac_address'}, '', $vlan_id); # retrouve l'emplacement
1255
1256      next LOOP_ON_COMPUTER if $where{'switch_description'} eq 'unknow' or $resol_arp{'hostname_fq'} eq 'unknow' or $resol_arp{'mac_address'} eq 'unknow';
1257
1258      printf '%-22s %2s %-30s %-15s %18s',
1259         $where{'switch_hostname'},
1260         $where{'switch_port_hr'},
1261         $resol_arp{'hostname_fq'},
1262         $resol_arp{'ipv4_address'},
1263         $resol_arp{'mac_address'} . "\n";
1264      }
1265   return;
1266   }
1267
1268#---------------------------------------------------------------
1269sub cmd_searchdb {
1270   my @ARGV = @_;
1271
1272   my $kind;
1273
1274   GetOptions(
1275      'kind=s'   => \$kind,
1276      );
1277
1278   my %possible_search = (
1279      host => \&cmd_searchdb_host,
1280      mac  => \&cmd_searchdb_mac,
1281      );
1282
1283   $kind = 'host' if not defined $possible_search{$kind};
1284
1285   $possible_search{$kind}->(@ARGV);
1286   return;
1287   }
1288
1289#---------------------------------------------------------------
1290sub cmd_searchdb_host {
1291   my @computer = @_;
1292
1293   fast_ping(@computer);
1294   my $computerdb = computerdb_load();
1295
1296   LOOP_ON_COMPUTER:
1297   for my $clientname (@computer) {
1298      my %resol_arp = resolve_ip_arp_host($clientname); # resolution arp
1299      my $ip        = $resol_arp{'ipv4_address'};
1300
1301      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
1302
1303      my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
1304      $year += 1900;
1305      $mon++;
1306      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1307
1308      printf "%-22s %2s %-30s %-15s %-18s %s\n",
1309         $computerdb->{$ip}{'switch_hostname'},
1310         $computerdb->{$ip}{'switch_port_hr'},
1311         $computerdb->{$ip}{'hostname_fq'},
1312         $ip,
1313         $computerdb->{$ip}{'mac_address'},
1314         $date;
1315      }
1316   return;
1317   }
1318
1319#---------------------------------------------------------------
1320sub cmd_searchdb_mac {
1321   my @mac = map {normalize_mac_address($_)} @_;
1322
1323   my $computerdb = computerdb_load();
1324
1325   LOOP_ON_MAC:
1326   for my $mac (@mac) {
1327      LOOP_ON_COMPUTER:
1328      for my $ip (keys %{$computerdb}) {
1329         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{'mac_address'};
1330
1331         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
1332         $year += 1900;
1333         $mon++;
1334         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1335
1336         printf "%-22s %2s %-30s %-15s %-18s %s\n",
1337            $computerdb->{$ip}{'switch_hostname'},
1338            $computerdb->{$ip}{'switch_port_hr'},
1339            $computerdb->{$ip}{'hostname_fq'},
1340            $ip,
1341            $computerdb->{$ip}{'mac_address'},
1342            $date;
1343         #next LOOP_ON_MAC;
1344         }
1345
1346      }
1347   return;
1348   }
1349
1350#---------------------------------------------------------------
1351sub cmd_updatedb {
1352   @ARGV = @_;
1353
1354   my ($verbose, $verb_description, $check_hostname, $check_location, $no_rebuildsw);
1355
1356   GetOptions(
1357      'verbose|v'          => \$verbose,
1358      'verb-description|d' => \$verb_description,
1359      'chk-hostname|h'     => \$check_hostname,
1360      'chk-location|l'     => \$check_location,
1361      'no-rebuildsw|R'     => \$no_rebuildsw,
1362      );
1363
1364   my @network = @ARGV;
1365   @network = get_list_network() if not @network;
1366
1367   test_switchdb_environnement();
1368
1369   my $computerdb = {};
1370   $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
1371   my $timestamp = time;
1372
1373   my %computer_not_detected = ();
1374   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
1375
1376   my $number_of_computer = get_list_ip(@network); # + 1;
1377   my $size_of_database   = keys %{$computerdb};
1378   $size_of_database = 1 if $size_of_database == 0;
1379   my $i                 = 0;
1380   my $detected_computer = 0;
1381
1382   init_switch_names('yes', $verb_description, $check_hostname, $check_location); # nomme les switchs
1383
1384   {
1385   my $switch_checksum = get_switchdb_checksum(%SWITCH_DB);
1386   # Remplis le champs portignore des ports d'inter-connection pour chaque switch
1387   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1388   if ($switch_checksum ne $switch_connection->{'checksum'}) { # Verify checksum
1389      if ($no_rebuildsw) {
1390         print "WARNING: switch database is outdate, please rebuild if with updatesw command\n";
1391         }
1392      else {
1393         print "WARNING: switch database is going to be rebuilt\n";
1394         update_switchdb(verbose => $verbose)
1395         }
1396      }
1397
1398   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1399   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1400   my %db_switch_chained_port = ();
1401   for my $swport (keys %db_switch_connected_on_port) {
1402      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1403      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
1404      }
1405   for my $sw (@SWITCH_LIST) {
1406      push @{$sw->{'portignore'}}, $db_switch_output_port{$sw->{'hostname'}}  if exists $db_switch_output_port{$sw->{'hostname'}};
1407      if ( exists $db_switch_chained_port{$sw->{'hostname'}} ) {
1408         chop $db_switch_chained_port{$sw->{'hostname'}};
1409         push @{$sw->{'portignore'}}, split m/ : /xms, $db_switch_chained_port{$sw->{'hostname'}};
1410         }
1411#      print "$sw->{'hostname'} ++ @{$sw->{'portignore'}}\n";
1412      }
1413   }
1414
1415   my %router_mac_ip = ();
1416   DETECT_ALL_ROUTER:
1417   #   for my $one_router ('194.254.66.254') {
1418   for my $one_router (get_list_main_router(@network)) {
1419      my %resol_arp = resolve_ip_arp_host($one_router);
1420      $router_mac_ip{$resol_arp{'mac_address'}} = $resol_arp{'ipv4_address'};
1421      }
1422
1423   ALL_NETWORK:
1424   for my $current_net (@network) {
1425
1426      my @computer          = get_list_ip($current_net);
1427      my $current_interface = get_current_interface($current_net);
1428
1429      fast_ping(@computer) if get_current_scan_mode($current_net) eq 'active';
1430
1431      LOOP_ON_COMPUTER:
1432      for my $one_computer (@computer) {
1433         $i++;
1434
1435         my $total_percent = int(($i * 100) / $number_of_computer);
1436
1437         my $localtime = time - $timestamp;
1438         my ($sec, $min) = localtime $localtime;
1439
1440         my $time_elapse = 0;
1441         $time_elapse = $localtime * (100 - $total_percent) / $total_percent if $total_percent != 0;
1442         my ($sec_elapse, $min_elapse) = localtime $time_elapse;
1443
1444         printf "\rComputer scanned: %4i/%i (%2i%%)", $i, $number_of_computer, $total_percent;
1445         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database, int(($detected_computer * 100) / $size_of_database);
1446         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime / 60), $localtime % 60, int($time_elapse / 60), $time_elapse % 60;
1447         printf ' %-8s %-14s', $current_interface, $one_computer;
1448
1449         my $already_exist = exists $computerdb->{$one_computer} ? 'yes' : 'no';
1450         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface, 'fast', $already_exist);
1451
1452         # do not search on router connection (why ?)
1453         if (exists $router_mac_ip{$resol_arp{'mac_address'}}) {
1454            $computer_not_detected{$one_computer} = $current_net;
1455            next LOOP_ON_COMPUTER;
1456            }
1457
1458         # do not search on switch inter-connection
1459         if (exists $SWITCH_LEVEL{$resol_arp{'hostname_fq'}}) {
1460            $computer_not_detected{$one_computer} = $current_net;
1461            next LOOP_ON_COMPUTER;
1462            }
1463
1464         my $switch_proposal = q{};
1465         if (exists $computerdb->{$resol_arp{'ipv4_address'}} and exists $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'}) {
1466            $switch_proposal = $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'};
1467            }
1468
1469         # do not have a mac address
1470         if ($resol_arp{'mac_address'} eq 'unknow' or (exists $resol_arp{'timestamps'} and $resol_arp{'timestamps'} < ($timestamp - 3 * 3600))) {
1471            $computer_not_detected{$one_computer} = $current_net;
1472            next LOOP_ON_COMPUTER;
1473            }
1474
1475         my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1476         my $vlan_id   = get_current_vlan_id($vlan_name);
1477         my %where     = find_switch_port($resol_arp{'mac_address'}, $switch_proposal, $vlan_id);
1478
1479         #192.168.24.156:
1480         #  arp: 00:0B:DB:D5:F6:65
1481         #  hostname: pcroyon.hmg.priv
1482         #  port: 5
1483         #  switch: sw-batH-legi:hp2524
1484         #  timestamp: 1164355525
1485
1486         # do not have a mac address
1487         #         if ($resol_arp{'mac_address'} eq 'unknow') {
1488         #            $computer_not_detected{$one_computer} = $current_interface;
1489         #            next LOOP_ON_COMPUTER;
1490         #            }
1491
1492         # detected on a switch
1493         if ($where{'switch_description'} ne 'unknow') {
1494            $detected_computer++;
1495            $computerdb->{$resol_arp{'ipv4_address'}} = {
1496               hostname_fq        => $resol_arp{'hostname_fq'},
1497               mac_address        => $resol_arp{'mac_address'},
1498               switch_hostname    => $where{'switch_hostname'},
1499               switch_description => $where{'switch_description'},
1500               switch_port_id     => $where{'switch_port_id'},
1501               switch_port_hr     => $where{'switch_port_hr'},
1502               timestamp          => $timestamp,
1503               network            => $current_net,
1504               };
1505            next LOOP_ON_COMPUTER;
1506            }
1507
1508         # new in the database but where it is ?
1509         if (not exists $computerdb->{$resol_arp{'ipv4_address'}}) {
1510            $detected_computer++;
1511            $computerdb->{$resol_arp{'ipv4_address'}} = {
1512               hostname_fq        => $resol_arp{'hostname_fq'},
1513               mac_address        => $resol_arp{'mac_address'},
1514               switch_hostname    => $where{'switch_hostname'},
1515               switch_description => $where{'switch_description'},
1516               switch_port_id     => $where{'switch_port_id'},
1517               switch_port_hr     => $where{'switch_port_hr'},
1518               timestamp          => $resol_arp{'timestamp'},
1519               network            => $current_net,
1520               };
1521            }
1522
1523         # mise a jour du nom de la machine si modification dans le dns
1524         $computerdb->{$resol_arp{'ipv4_address'}}{'hostname_fq'} = $resol_arp{'hostname_fq'};
1525
1526         # mise à jour de la date de détection si détection plus récente par arpwatch
1527         $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'}   = $resol_arp{'timestamp'} if exists $resol_arp{'timestamp'} and $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $resol_arp{'timestamp'};
1528
1529         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
1530         # push @computer_not_detected, $resol_arp{'ipv4_address'} if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1531         $computer_not_detected{$resol_arp{'ipv4_address'}} = $current_net if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1532
1533         }
1534      }
1535
1536   # final end of line at the end of the loop
1537   printf "\n";
1538
1539   computerdb_save($computerdb);
1540
1541   for my $one_computer (keys %computer_not_detected) {
1542      my $current_net       = $computer_not_detected{$one_computer};
1543      my $current_interface = get_current_interface($current_net);
1544      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';
1545      }
1546   return;
1547   }
1548
1549#---------------------------------------------------------------
1550sub cmd_removedb {
1551   my @computer = @_;
1552
1553   test_maindb_environnement();
1554
1555   my $computerdb = computerdb_load();
1556
1557   LOOP_ON_COMPUTER:
1558   for my $one_computer (@computer) {
1559
1560      if ($one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
1561            and exists $computerdb->{$one_computer}) {
1562         delete $computerdb->{$one_computer};
1563         next;
1564         }
1565
1566      my %resol_arp = resolve_ip_arp_host($one_computer);
1567
1568      delete $computerdb->{$resol_arp{'ipv4_address'}} if exists $computerdb->{$resol_arp{'ipv4_address'}};
1569      }
1570
1571   computerdb_save($computerdb);
1572   return;
1573   }
1574
1575#---------------------------------------------------------------
1576sub cmd_insertdb {
1577   my @ARGV = @_;
1578
1579   my ($ip, $mac, $net);
1580
1581   GetOptions(
1582      'ip|i=s'      => \$ip,
1583      'mac|m=s'     => \$mac,
1584      'network|n=s' => \$net,
1585      );
1586
1587   if (not $ip or not $mac or not $net) {
1588      print {*STDERR} "Error: mandatory parameter --ip\n"      if not $ip;
1589      print {*STDERR} "Error: mandatory parameter --mac\n"     if not $mac;
1590      print {*STDERR} "Error: mandatory parameter --network\n" if not $net;
1591      print {*STDERR} "\n";
1592      $CMD_DB{'help'}->();
1593      exit 1;
1594      }
1595
1596   test_maindb_environnement();
1597
1598   $mac = normalize_mac_address($mac);
1599
1600   if ($ip !~ m/^ $RE_IPv4_ADDRESS $/xms or $mac !~ m/^ $RE_MAC_ADDRESS $/xms or not exists $KLASK_CFG->{'network'}{$net}) {
1601      print {*STDERR} "Error: parameter not valid --ip $ip\n"       if $ip !~ m/^ $RE_IPv4_ADDRESS $/xms;
1602      print {*STDERR} "Error: parameter not valid --mac $mac\n"     if $mac !~ m/^ $RE_MAC_ADDRESS $/xms;
1603      print {*STDERR} "Error: parameter not valid --network $net\n" if not exists $KLASK_CFG->{'network'}{$net};
1604      print {*STDERR} "\n";
1605      $CMD_DB{'help'}->();
1606      exit 1;
1607      }
1608
1609   my %resol_arp = resolve_ip_arp_host($ip); # Get hostname_fq ipv4_address
1610
1611   if ($resol_arp{'hostname_fq'} eq 'unknow') {
1612      print {*STDERR} "Error: DNS name not defined for IPv4 $ip (mandatory)\n";
1613      exit 1;
1614      }
1615
1616   if ($resol_arp{'ipv4_address'} ne $ip) {
1617      print {*STDERR} "Error: IPv4 DNS resolution strange between $ip and $resol_arp{'ipv4_address'}\n";
1618      exit 1;
1619      }
1620
1621   my $computerdb = computerdb_load();
1622   my $timestamp  = time;
1623
1624   LOOP_ON_COMPUTER:
1625   for my $current_ip (keys %{$computerdb}) {
1626      if ($current_ip eq $ip) {
1627         print {*STDERR} "Error: IPv4 $ip already exists in the computer database\n";
1628         exit 1;
1629         }
1630
1631      if ($computerdb->{$current_ip}{'mac_address'} eq $mac) {
1632         $timestamp = $computerdb->{$current_ip}{'timestamp'} if $computerdb->{$current_ip}{'timestamp'} < $timestamp;
1633         }
1634      }
1635
1636   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1637   $timestamp -= $days_before_alert * 24 * 3600 + 1; # Just decrement one second to be oldier
1638
1639   $computerdb->{$ip} = {
1640      hostname_fq        => $resol_arp{'hostname_fq'},
1641      mac_address        => $mac,
1642      switch_hostname    => 'Unknow',
1643      switch_description => 'Unknow',
1644      switch_port_id     => 0,
1645      switch_port_hr     => 0,
1646      timestamp          => $timestamp,
1647      network            => $net,
1648      };
1649
1650   computerdb_save($computerdb);
1651   return;
1652   }
1653
1654#---------------------------------------------------------------
1655sub cmd_cleandb {
1656   my @ARGV = @_;
1657
1658   my $days_to_clean = 15;
1659   my $repairdns;
1660   my $verbose;
1661   my $database_has_changed;
1662
1663   GetOptions(
1664      'day|d=i'      => \$days_to_clean,
1665      'verbose|v'    => \$verbose,
1666      'repair-dns|r' => \$repairdns,
1667      );
1668
1669   my @vlan_name = get_list_network();
1670
1671   my $computerdb = computerdb_load();
1672   my $timestamp  = time;
1673
1674   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
1675   my $timestamp_3month  = 3600 * 24 * 90;
1676
1677   my %mactimedb = ();
1678   ALL_VLAN:
1679   for my $vlan (shuffle @vlan_name) {
1680
1681      my @ip_list = shuffle get_list_ip($vlan);
1682
1683      LOOP_ON_IP_ADDRESS:
1684      for my $ip (@ip_list) {
1685
1686         next LOOP_ON_IP_ADDRESS
1687            if not exists $computerdb->{$ip};
1688
1689         #&& $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
1690         my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1691         my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1692         my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1693
1694         $mactimedb{$ip_mac} ||= {
1695            ip          => $ip,
1696            timestamp   => $ip_timestamp,
1697            vlan        => $vlan,
1698            hostname_fq => $ip_hostname_fq,
1699            };
1700
1701         if (
1702            ( $mactimedb{$ip_mac}->{'timestamp'} - $ip_timestamp > $timestamp_barrier
1703               or (
1704                  $mactimedb{$ip_mac}->{'timestamp'} > $ip_timestamp
1705                  and $timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_3month
1706                  )
1707            )
1708            and (
1709               not $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1710               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1711               )) {
1712            print "remove ip $ip\n" if $verbose;
1713            delete $computerdb->{$ip};
1714            $database_has_changed++;
1715            }
1716
1717         elsif (
1718            ( $ip_timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_barrier
1719               or (
1720                  $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}
1721                  and $timestamp - $ip_timestamp > $timestamp_3month
1722                  )
1723            )
1724            and (
1725               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1726               or $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1727               )) {
1728            print "remove ip ".$mactimedb{$ip_mac}->{'ip'}."\n" if $verbose;
1729            delete $computerdb->{$mactimedb{$ip_mac}->{'ip'}};
1730            $database_has_changed++;
1731            }
1732
1733         if ($ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}) {
1734            $mactimedb{$ip_mac} = {
1735               ip          => $ip,
1736               timestamp   => $ip_timestamp,
1737               vlan        => $vlan,
1738               hostname_fq => $ip_hostname_fq,
1739               };
1740            }
1741         }
1742      }
1743
1744   if ($repairdns) { # Search and update unkown computer in reverse DNS
1745      LOOP_ON_IP_ADDRESS:
1746      for my $ip (keys %{$computerdb}) {
1747         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} ne 'unknow';
1748
1749         my $packed_ip = scalar gethostbyname($ip);
1750         next LOOP_ON_IP_ADDRESS if not defined $packed_ip;
1751
1752         my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
1753         next LOOP_ON_IP_ADDRESS if not defined $hostname_fq;
1754
1755         $computerdb->{$ip}{'hostname_fq'} = $hostname_fq;
1756         $database_has_changed++;
1757         }
1758      }
1759
1760   computerdb_save($computerdb) if $database_has_changed;
1761   return;
1762   }
1763
1764#---------------------------------------------------------------
1765sub cmd_exportdb {
1766   @ARGV = @_;
1767
1768   my $format = 'txt';
1769
1770   GetOptions(
1771      'format|f=s'  => \$format,
1772      );
1773
1774   my %possible_format = (
1775      txt  => \&cmd_exportdb_txt,
1776      html => \&cmd_exportdb_html,
1777      );
1778
1779   $format = 'txt' if not defined $possible_format{$format};
1780
1781   $possible_format{$format}->(@ARGV);
1782   return;
1783   }
1784
1785#---------------------------------------------------------------
1786sub cmd_exportdb_txt {
1787   test_maindb_environnement();
1788
1789   my $computerdb = computerdb_load();
1790
1791   my $tb_computer = Text::Table->new(
1792      {align => 'left',   align_title => 'left',   title => 'Switch'},
1793      {align => 'right',  align_title => 'right',  title => 'Port'},
1794      {align => 'center', align_title => 'center', title => 'Link'},
1795      {align => 'left',   align_title => 'left',   title => 'Hostname-FQ'},
1796      {align => 'left',   align_title => 'left',   title => 'IPv4-Address'},
1797      {align => 'left',   align_title => 'left',   title => 'MAC-Address'},
1798      {align => 'left',   align_title => 'left',   title => 'Date'},
1799      {align => 'left',   align_title => 'left',   title => 'VLAN'},
1800      );
1801
1802   LOOP_ON_IP_ADDRESS:
1803   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1804
1805      # to be improve in the future
1806      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1807
1808      my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
1809      $year += 1900;
1810      $mon++;
1811      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1812
1813      my $vlan = '';
1814      $vlan = $computerdb->{$ip}{'network'} . '(' . get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1815
1816      my $arrow = '<---';
1817      $arrow = '<===' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1818
1819      $tb_computer->add(
1820         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1821         $computerdb->{$ip}{'switch_port_hr'},
1822         $arrow,
1823         $computerdb->{$ip}{'hostname_fq'},
1824         $ip,
1825         $computerdb->{$ip}{'mac_address'},
1826         $date,
1827         $vlan,
1828         );
1829      }
1830
1831   print $tb_computer->title();
1832   print $tb_computer->rule('-');
1833   print $tb_computer->body();
1834
1835   return;
1836   }
1837
1838#---------------------------------------------------------------
1839sub cmd_exportdb_html {
1840   test_maindb_environnement();
1841
1842   my $computerdb = computerdb_load();
1843
1844   #<link rel="stylesheet" type="text/css" href="style-klask.css" />
1845   #<script src="sorttable-klask.js"></script>
1846
1847   print <<'END_HTML';
1848<table class="sortable">
1849 <caption>Klask Host Database</caption>
1850 <thead>
1851  <tr>
1852   <th scope="col" class="klask-header-left">Switch</th>
1853   <th scope="col" class="sorttable_nosort">Port</th>
1854   <th scope="col" class="sorttable_nosort">Link</th>
1855   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1856   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1857   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1858   <th scope="col" class="sorttable_alpha">VLAN</th>
1859   <th scope="col" class="klask-header-right">Date</th>
1860  </tr>
1861 </thead>
1862 <tbody>
1863END_HTML
1864
1865   my %mac_count = ();
1866   LOOP_ON_IP_ADDRESS:
1867   for my $ip (keys %{$computerdb}) {
1868
1869      # to be improve in the future
1870      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1871
1872      $mac_count{$computerdb->{$ip}{'mac_address'}}++;
1873      }
1874
1875   my $typerow = 'even';
1876
1877   LOOP_ON_IP_ADDRESS:
1878   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1879
1880      # to be improve in the future
1881      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1882
1883      my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
1884      $year += 1900;
1885      $mon++;
1886      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1887
1888      #      $odd_or_even++;
1889      #      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1890      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1891
1892      #my $arrow ='&#8592;';
1893      #   $arrow ='&#8656;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1894      my $arrow = '&#10229;';
1895      $arrow = '&#10232;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1896
1897      my $switch_hostname = $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'} || 'unkown';
1898      chomp $switch_hostname;
1899      my $switch_hostname_sort = format_switchport4sort($switch_hostname, $computerdb->{$ip}{'switch_port_hr'});
1900
1901      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1902
1903      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{'mac_address'}}, $computerdb->{$ip}{'mac_address'};
1904
1905      $computerdb->{$ip}{'hostname_fq'} = 'unknow' if $computerdb->{$ip}{'hostname_fq'} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1906      my ($host_short) = split m/ \. /xms, $computerdb->{$ip}{'hostname_fq'};
1907
1908      my $vlan = '';
1909      $vlan = $computerdb->{$ip}{'network'}.' ('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1910
1911      my $parent_port_hr = format_aggregator4html($computerdb->{$ip}{'switch_port_hr'});
1912
1913      fqdn_html_breakable($switch_hostname);
1914      fqdn_html_breakable(my $hostname_fq_html = $computerdb->{$ip}{'hostname_fq'});
1915
1916      print <<"END_HTML";
1917  <tr class="$typerow">
1918   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1919   <td class="bklask-port">$parent_port_hr</td>
1920   <td class="bklask-arrow">$arrow</td>
1921   <td sorttable_customkey="$host_short">$hostname_fq_html</td>
1922   <td sorttable_customkey="$ip_sort">$ip</td>
1923   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{'mac_address'}</td>
1924   <td>$vlan</td>
1925   <td>$date</td>
1926  </tr>
1927END_HTML
1928      }
1929
1930   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1931
1932   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1933   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1934   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1935   my %db_switch                   = %{$switch_connection->{'switch_db'}};
1936
1937   # Output switch connection
1938   LOOP_ON_OUTPUT_SWITCH:
1939   for my $sw (sort keys %db_switch_output_port) {
1940
1941      my $switch_hostname_sort = format_switchport4sort($sw, $db_switch_output_port{$sw});
1942
1943      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1944
1945      #my $arrow ='&#8702;';
1946      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1947      my $arrow = '&#10236;';
1948      $arrow = '&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1949
1950      if (exists $db_switch_parent{$sw}) {
1951         # Link to uplink switch
1952         next LOOP_ON_OUTPUT_SWITCH;
1953
1954         # Do not print anymore
1955         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1956         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1957         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
1958
1959         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
1960         $year += 1900;
1961         $mon++;
1962         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1963
1964         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1965
1966         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1967
1968         my $host_short = format_switchport4sort(split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'});
1969
1970         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1971         $vlan .= ' (' . get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}) . ')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1972
1973         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1974         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
1975
1976         fqdn_html_breakable($sw);
1977         fqdn_html_breakable(my $sw_child_html = $db_switch_parent{$sw}->{'switch'});
1978
1979         print <<"END_HTML";
1980  <tr class="$typerow">
1981   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1982   <td class="bklask-port">$parent_port_hr</td>
1983   <td class="bklask-arrow">$arrow $child_port_hr</td>
1984   <td sorttable_customkey="$host_short">$sw_child_html</td>
1985   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1986   <td sorttable_customkey="$mac_sort">$mac_address</td>
1987   <td>$vlan</td>
1988   <td>$date</td>
1989  </tr>
1990END_HTML
1991         }
1992      else {
1993         # Router
1994         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1995
1996         my $host_short = format_switchport4sort($sw, $db_switch_output_port{$sw});
1997
1998         my $mac_address  = $db_switch{$sw}->{'mac_address'};
1999         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
2000         my $timestamp    = $db_switch{$sw}->{'timestamp'};
2001
2002         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
2003         $year += 1900;
2004         $mon++;
2005         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2006
2007         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
2008
2009         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
2010
2011         my $vlan = $db_switch{$sw}->{'network'};
2012         $vlan .= ' (' . get_current_vlan_id($db_switch{$sw}->{'network'}) . ')' if $db_switch{$sw}->{'network'};
2013
2014         my $arrow = '&#10235;';
2015         $arrow = '&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2016
2017         fqdn_html_breakable($sw);
2018
2019         print <<"END_HTML";
2020  <tr class="$typerow">
2021   <td sorttable_customkey="router">router</td>
2022   <td class="bklask-port"></td>
2023   <td class="bklask-arrow">$arrow $parent_port_hr</td>
2024   <td sorttable_customkey="$host_short">$sw</td>
2025   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
2026   <td sorttable_customkey="$mac_sort">$mac_address</td>
2027   <td>$vlan</td>
2028   <td>$date</td>
2029  </tr>
2030END_HTML
2031         next LOOP_ON_OUTPUT_SWITCH;
2032         }
2033      }
2034
2035   # Child switch connection : parent <- child
2036   for my $swport (sort keys %db_switch_connected_on_port) {
2037      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2038      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2039
2040         my $switch_hostname_sort = format_switchport4sort($sw_connect, $port_connect);
2041
2042         my $mac_address  = $db_switch{$sw}->{'mac_address'};
2043         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
2044         my $timestamp    = $db_switch{$sw}->{'timestamp'};
2045
2046         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
2047         $year += 1900;
2048         $mon++;
2049         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2050
2051         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
2052
2053         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
2054
2055         $typerow = $typerow eq 'even' ? 'odd' : 'even';
2056
2057         #my $arrow ='&#8701;';
2058         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
2059         my $arrow = '&#10235;';
2060         $arrow = '&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
2061
2062         my $vlan = $db_switch{$sw}->{'network'};
2063         $vlan .= ' (' . get_current_vlan_id($db_switch{$sw}->{'network'}) . ')' if $db_switch{$sw}->{'network'};
2064
2065         if (exists $db_switch_output_port{$sw}) {
2066
2067            my $host_short = format_switchport4sort(split(m/\./xms, $sw, 1), $db_switch_output_port{$sw});
2068
2069            my $parent_port_hr = format_aggregator4html($port_connect);
2070            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
2071
2072            fqdn_html_breakable($sw);
2073            fqdn_html_breakable($sw_connect);
2074
2075            print <<"END_HTML";
2076  <tr class="$typerow">
2077   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
2078   <td class="bklask-port">$parent_port_hr</td>
2079   <td class="bklask-arrow">$arrow $child_port_hr</td>
2080   <td sorttable_customkey="$host_short">$sw</td>
2081   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
2082   <td sorttable_customkey="$mac_sort">$mac_address</td>
2083   <td>$vlan</td>
2084   <td>$date</td>
2085  </tr>
2086END_HTML
2087            }
2088         else {
2089            my $parent_port_hr = format_aggregator4html($port_connect);
2090
2091            fqdn_html_breakable($sw);
2092            fqdn_html_breakable($sw_connect);
2093
2094            print <<"END_HTML";
2095  <tr class="$typerow">
2096   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
2097   <td class="bklask-port">$parent_port_hr</td>
2098   <td class="bklask-arrow">$arrow</td>
2099   <td sorttable_customkey="$sw">$sw</td>
2100   <td sorttable_customkey="">$ipv4_address</td>
2101   <td sorttable_customkey="">$mac_address</td>
2102   <td>$vlan</td>
2103   <td>$date</td>
2104  </tr>
2105END_HTML
2106            }
2107         }
2108      }
2109
2110   print <<'END_HTML';
2111 </tbody>
2112 <tfoot>
2113  <tr>
2114   <th scope="col" class="klask-footer-left">Switch</th>
2115   <th scope="col" class="fklask-port">Port</th>
2116   <th scope="col" class="fklask-link">Link</th>
2117   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2118   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
2119   <th scope="col" class="fklask-mac">MAC-Address</th>
2120   <th scope="col" class="fklask-vlan">VLAN</th>
2121   <th scope="col" class="klask-footer-right">Date</th>
2122  </tr>
2123 </tfoot>
2124</table>
2125END_HTML
2126   return;
2127   }
2128
2129#---------------------------------------------------------------
2130sub cmd_bad_vlan_id {
2131   @ARGV = @_;
2132
2133   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
2134   my $format = 'txt';
2135   my $verbose;
2136
2137   GetOptions(
2138      'day|d=i'    => \$days_before_alert,
2139      'format|f=s' => \$format,
2140      );
2141
2142   my %possible_format = (
2143      txt  => \&cmd_bad_vlan_id_txt,
2144      html => \&cmd_bad_vlan_id_html,
2145      none => sub { },
2146      );
2147   $format = 'txt' if not defined $possible_format{$format};
2148
2149   test_maindb_environnement();
2150
2151   my $computerdb = computerdb_load();
2152
2153   # create a database with the most recent computer by switch port
2154   my %switchportdb = ();
2155   LOOP_ON_IP_ADDRESS:
2156   for my $ip (keys %{$computerdb}) {
2157      # to be really improve in the future
2158      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2159      #next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
2160      #next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port_id'} eq '0';
2161
2162      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
2163      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
2164      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
2165
2166      my $swpt = sprintf "%-28s  %2s",
2167         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
2168         $computerdb->{$ip}{'switch_port_hr'};
2169      $switchportdb{$swpt} ||= {
2170         ip          => $ip,
2171         timestamp   => $ip_timestamp,
2172         vlan        => $computerdb->{$ip}{'network'},
2173         hostname_fq => $ip_hostname_fq,
2174         mac_address => $ip_mac,
2175         };
2176
2177      # if float computer, set date 15 day before warning...
2178      my $ip_timestamp_mod = $ip_timestamp;
2179      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
2180      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2181      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
2182
2183      if ($ip_timestamp_mod > $ip_timestamp_ref) {
2184         $switchportdb{$swpt} = {
2185            ip          => $ip,
2186            timestamp   => $ip_timestamp,
2187            vlan        => $computerdb->{$ip}{'network'},
2188            hostname_fq => $ip_hostname_fq,
2189            mac_address => $ip_mac,
2190            };
2191         }
2192      }
2193
2194   my @result = ();
2195
2196   LOOP_ON_RECENT_COMPUTER:
2197   for my $swpt (keys %switchportdb) {
2198      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
2199      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
2200
2201      my $src_ip        = $switchportdb{$swpt}->{'ip'};
2202      my $src_timestamp = 0;
2203      LOOP_ON_IP_ADDRESS:
2204      for my $ip (keys %{$computerdb}) {
2205         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne $switchportdb{$swpt}->{'mac_address'};
2206         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
2207         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
2208
2209         $src_ip        = $ip;
2210         $src_timestamp = $computerdb->{$ip}{'timestamp'};
2211         }
2212
2213      # keep only if float computer is the most recent
2214      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
2215      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
2216
2217      my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
2218      $year += 1900;
2219      $mon++;
2220      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
2221
2222      ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
2223      $year += 1900;
2224      $mon++;
2225      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
2226
2227      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
2228      my ($switch_hostname, $port_hr) = split /\s+/, $swpt, 2;
2229
2230      push @result, {
2231         switch      => $switch_hostname,
2232         port_hr     => $port_hr,
2233         vlan_bad    => $switchportdb{$swpt}->{'vlan'},
2234         vlan_good   => $computerdb->{$src_ip}{'network'},
2235         vlan_id     => $vlan_id,
2236         date_last   => $date,
2237         date_good   => $src_date,
2238         mac_address => $computerdb->{$src_ip}{'mac_address'},
2239         hostname_fq => $computerdb->{$src_ip}{'hostname_fq'},
2240         };
2241      }
2242
2243   $possible_format{$format}->(@result);
2244   }
2245
2246#---------------------------------------------------------------
2247sub cmd_bad_vlan_id_txt {
2248   my @result = @_;
2249
2250   my $tb_bad = Text::Table->new(
2251      {align  => 'left',   align_title => 'left',   title => 'Switch'},
2252      {is_sep => 1,              title => ' ',       body => ' '},
2253      {align  => 'right',  align_title => 'right',  title => 'Port'},
2254      {is_sep => 1,              title => ' ',       body => ' !'},
2255      {align  => 'left',   align_title => 'left',   title => 'VLAN-Bad'},
2256      {is_sep => 1,              title => ' ',       body => ' +-> '},
2257      {align  => 'left',   align_title => 'left',   title => 'VLAN-Good'},
2258      {is_sep => 1,              title => ' ',       body => ' '},
2259      {align  => 'left',   align_title => 'left',   title => 'VLAN-ID'},
2260      {is_sep => 1,              title => ' ',       body => ' '},
2261      {align  => 'left',   align_title => 'left',   title => 'Date-Last'},
2262      {is_sep => 1,              title => ' ',       body => '  '},
2263      {align  => 'left',   align_title => 'left',   title => 'Date-Good'},
2264      {is_sep => 1,              title => ' ',       body => '  '},
2265      {align  => 'left',   align_title => 'left',   title => 'MAC-Address'},
2266      {is_sep => 1,              title => ' ',       body => ' '},
2267      {align  => 'left',   align_title => 'left',   title => 'Hostname-FQ'},
2268      );
2269
2270   for my $item (@result) {
2271      $tb_bad->add(
2272         $item->{'switch'},
2273         $item->{'port_hr'},
2274         $item->{'vlan_bad'},
2275         $item->{'vlan_good'},
2276         '(' . $item->{'vlan_id'} . ')',
2277         $item->{'date_last'},
2278         $item->{'date_good'},
2279         $item->{'mac_address'},
2280         $item->{'hostname_fq'},
2281         );
2282      }
2283
2284   print $tb_bad->title();
2285   print $tb_bad->rule('-');
2286   print $tb_bad->body();
2287   }
2288
2289#---------------------------------------------------------------
2290sub cmd_bad_vlan_id_html {
2291   my @result = @_;
2292
2293   print <<'END_HTML';
2294<table class="sortable">
2295 <caption>Klask VLAN ID Mismatch Database</caption>
2296 <thead>
2297  <tr>
2298   <th scope="col" class="klask-header-left">Switch</th>
2299   <th scope="col" class="sorttable_nosort">Port</th>
2300   <th scope="col" class="sorttable_alpha">VLAN-Bad</th>
2301   <th scope="col" class="sorttable_alpha">VLAN-Good</th>
2302   <th scope="col" class="sorttable_alpha">Date-Last</th>
2303   <th scope="col" class="sorttable_alpha">Date-Good</th>
2304   <th scope="col" class="sorttable_alpha">MAC-Address</th>
2305   <th scope="col" class="klask-header-right">Hostname-FQ</th>
2306  </tr>
2307 </thead>
2308 <tbody>
2309END_HTML
2310
2311   my $typerow = 'even';
2312
2313   for my $item (@result) {
2314
2315      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2316
2317      my $switch_hostname_sort = format_switchport4sort($item->{'switch'}, $item->{'port_hr'});
2318      my ($host_short) = split m/ \. /xms, $item->{'hostname_fq'};
2319
2320      my $vlan_nameid = $item->{'vlan_good'} . ' (' . $item->{'vlan_id'} . ')';
2321
2322      fqdn_html_breakable(my $hostname_fq_html = $item->{'hostname_fq'});
2323
2324      print <<"END_HTML";
2325  <tr class="$typerow">
2326   <td sorttable_customkey="$switch_hostname_sort">$item->{'switch'}</td>
2327   <td class="bklask-port">$item->{'port_hr'}</td>
2328   <td>!$item->{'vlan_bad'}</td>
2329   <td>$vlan_nameid</td>
2330   <td>$item->{'date_last'}</td>
2331   <td>$item->{'date_good'}</td>
2332   <td>$item->{'mac_address'}</td>
2333   <td sorttable_customkey="$host_short">$hostname_fq_html</td>
2334  </tr>
2335END_HTML
2336      }
2337   print <<'END_HTML';
2338 </tbody>
2339 <tfoot>
2340  <tr>
2341   <th scope="col" class="klask-footer-left">Switch</th>
2342   <th scope="col" class="fklask-nosort">Port</th>
2343   <th scope="col" class="fklask-alpha">VLAN-Bad</th>
2344   <th scope="col" class="fklask-alpha">VLAN-Good</th>
2345   <th scope="col" class="fklask-alpha">Date-Last</th>
2346   <th scope="col" class="fklask-alpha">Date-Good</th>
2347   <th scope="col" class="fklask-alpha">MAC-Address</th>
2348   <th scope="col" class="klask-footer-right">Hostname-FQ</th>
2349  </tr>
2350 </tfoot>
2351</table>
2352END_HTML
2353   }
2354
2355#---------------------------------------------------------------
2356sub cmd_poe_enable {
2357   @ARGV = @_;
2358
2359   my $verbose;
2360   GetOptions(
2361      'verbose|v' => \$verbose,
2362      );
2363
2364   my $switch_name = shift @ARGV || q{};
2365   my $switch_port = shift @ARGV || q{};
2366
2367   if ($switch_name eq q{} or $switch_port eq q{}) {
2368      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
2369      }
2370
2371   for my $sw_name (split /,/, $switch_name) {
2372      if (not defined $SWITCH_DB{$sw_name}) {
2373         die "Switch $sw_name must be defined in klask configuration file\n";
2374         }
2375
2376      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2377
2378      my $sw = $SWITCH_DB{$sw_name};
2379      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2380      print "$error \n" if $error;
2381
2382      my $result = $session->set_request(
2383         -varbindlist => [$oid_search, INTEGER, 8], # Only NEXANS
2384         );
2385      print $session->error() . "\n" if $session->error_status();
2386
2387      $session->close;
2388      }
2389   cmd_poe_status($switch_name, $switch_port);
2390   return;
2391   }
2392
2393#---------------------------------------------------------------
2394sub cmd_poe_disable {
2395   @ARGV = @_;
2396
2397   my $verbose;
2398   GetOptions(
2399      'verbose|v' => \$verbose,
2400      );
2401
2402   my $switch_name = shift @ARGV || q{};
2403   my $switch_port = shift @ARGV || q{};
2404
2405   if ($switch_name eq q{} or $switch_port eq q{}) {
2406      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
2407      }
2408
2409   for my $sw_name (split /,/, $switch_name) {
2410      if (not defined $SWITCH_DB{$sw_name}) {
2411         die "Switch $sw_name must be defined in klask configuration file\n";
2412         }
2413
2414      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2415
2416      my $sw = $SWITCH_DB{$sw_name};
2417      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2418      print "$error \n" if $error;
2419
2420      my $result = $session->set_request(
2421         -varbindlist => [$oid_search, INTEGER, 2], # Only NEXANS
2422         );
2423      print $session->error() . "\n" if $session->error_status();
2424
2425      $session->close;
2426      }
2427   cmd_poe_status($switch_name, $switch_port);
2428   return;
2429   }
2430
2431#---------------------------------------------------------------
2432sub cmd_poe_status {
2433   @ARGV = @_;
2434
2435   my $verbose;
2436   GetOptions(
2437      'verbose|v' => \$verbose,
2438      );
2439
2440   my $switch_name = shift @ARGV || q{};
2441   my $switch_port = shift @ARGV || q{};
2442
2443   if ($switch_name eq q{} or $switch_port eq q{}) {
2444      die "Usage: klask poe-status SWITCH_NAME PORT\n";
2445      }
2446
2447   for my $sw_name (split /,/, $switch_name) {
2448      if (not defined $SWITCH_DB{$sw_name}) {
2449         die "Switch $sw_name must be defined in klask configuration file\n";
2450         }
2451
2452      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2453
2454      my $sw = $SWITCH_DB{$sw_name};
2455      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2456      print "$error \n" if $error;
2457
2458      my $result = $session->get_request(
2459         -varbindlist => [$oid_search],
2460         );
2461
2462      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2463         my $poe_status = $result->{$oid_search} || 'empty';
2464         $poe_status =~ s/8/enable/;
2465         $poe_status =~ s/2/disable/;
2466         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe_status;
2467         }
2468      else {
2469         print "Klask do not find PoE status on switch $sw_name on port $switch_port\n";
2470         }
2471
2472      $session->close;
2473      }
2474   return;
2475   }
2476
2477#---------------------------------------------------------------
2478sub cmd_host_setlocation {
2479   @ARGV = @_;
2480
2481   my ($verbose, $force);
2482   GetOptions(
2483      'verbose|v' => \$verbose,
2484      'force|f'   => \$force,
2485      );
2486
2487   my $switch_name     = shift @ARGV || q{};
2488   my $switch_location = shift @ARGV || q{};
2489
2490   if ($switch_name eq q{} or $switch_location eq q{}) {
2491      die "Usage: klask host-setlocation SWITCH_NAME LOCATION\n";
2492      }
2493
2494   for my $sw_name (split /,/, $switch_name) {
2495      if (not defined $SWITCH_DB{$sw_name}) {
2496         die "Switch $sw_name must be defined in klask configuration file\n";
2497         }
2498
2499      my $oid_search = $OID_NUMBER{'sysLocation'};
2500
2501      my $sw = $SWITCH_DB{$sw_name};
2502      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2503      print "$error \n" if $error;
2504
2505      my $result = $session->set_request(
2506         -varbindlist => [$oid_search, OCTET_STRING, $switch_location],
2507         );
2508      print $session->error()."\n" if $session->error_status();
2509
2510      $session->close;
2511      }
2512   return;
2513   }
2514
2515#---------------------------------------------------------------
2516# not finish - do not use
2517sub cmd_port_setvlan {
2518   my $switch_name = shift || q{};
2519   my $mac_address = shift || q{};
2520
2521   if ($switch_name eq q{} or $mac_address eq q{}) {
2522      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2523      }
2524
2525   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST) if $switch_name eq q{*};
2526
2527   for my $sw_name (split /,/, $switch_name) {
2528      if (not defined $SWITCH_DB{$sw_name}) {
2529         die "Switch $sw_name must be defined in klask configuration file\n";
2530         }
2531
2532      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2533      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} . '.' . 0 . mac_address_hex2dec($mac_address);
2534      print "Klask search OID $oid_search_port1 on switch $sw_name\n";
2535      print "Klask search OID $oid_search_port2 on switch $sw_name\n";
2536
2537      my $sw = $SWITCH_DB{$sw_name};
2538      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2539      print "$error \n" if $error;
2540
2541      my $result = $session->get_request(
2542         -varbindlist => [$oid_search_port1]
2543         );
2544      if (not defined $result) {
2545         $result = $session->get_request(
2546            -varbindlist => [$oid_search_port2]
2547            );
2548         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
2549         }
2550
2551      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
2552         my $swport = $result->{$oid_search_port1};
2553         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
2554         }
2555      else {
2556         print "Klask do not find MAC $mac_address on switch $sw_name\n";
2557         }
2558
2559      $session->close;
2560      }
2561   return;
2562   }
2563
2564#---------------------------------------------------------------
2565sub cmd_port_getvlan {
2566   @ARGV = @_;
2567
2568   my $verbose;
2569   GetOptions(
2570      'verbose|v' => \$verbose,
2571      );
2572
2573   my $switch_name = shift @ARGV || q{};
2574   my $switch_port = shift @ARGV || q{};
2575
2576   if ($switch_name eq q{} or $switch_port eq q{}) {
2577      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
2578      }
2579
2580   for my $sw_name (split /,/, $switch_name) {
2581      if (not defined $SWITCH_DB{$sw_name}) {
2582         die "Switch $sw_name must be defined in klask configuration file\n";
2583         }
2584
2585      my $oid_search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
2586
2587      my $sw = $SWITCH_DB{$sw_name};
2588      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2589      print "$error \n" if $error;
2590
2591      my $result = $session->get_request(
2592         -varbindlist => [$oid_search],
2593         );
2594
2595      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2596         my $vlan_id = $result->{$oid_search} || 'empty';
2597         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2598         }
2599      else {
2600         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
2601         }
2602
2603      $session->close;
2604      }
2605   return;
2606   }
2607
2608#---------------------------------------------------------------
2609sub cmd_vlan_setname {
2610   }
2611
2612#---------------------------------------------------------------
2613# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
2614sub cmd_rebootsw {
2615   @ARGV = @_;
2616
2617   my $verbose;
2618   GetOptions(
2619      'verbose|v' => \$verbose,
2620      );
2621
2622   my $switch_name = shift @ARGV || q{};
2623
2624   if ($switch_name eq q{}) {
2625      die "Usage: klask rebootsw SWITCH_NAME\n";
2626      }
2627
2628   for my $sw_name (split /,/, $switch_name) {
2629      if (not defined $SWITCH_DB{$sw_name}) {
2630         die "Switch $sw_name must be defined in klask configuration file\n";
2631         }
2632
2633      my $sw = $SWITCH_DB{$sw_name};
2634      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2635      print "$error \n" if $error;
2636
2637      my $result = $session->set_request(
2638         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
2639         );
2640
2641      $session->close;
2642      }
2643   return;
2644   }
2645
2646#---------------------------------------------------------------
2647sub cmd_vlan_getname {
2648   my $switch_name = shift || q{};
2649   my $vlan_id     = shift || q{};
2650
2651   if ($switch_name eq q{} or $vlan_id eq q{}) {
2652      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
2653      }
2654
2655   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST) if $switch_name eq q{*};
2656
2657   for my $sw_name (split /,/, $switch_name) {
2658      if (not defined $SWITCH_DB{$sw_name}) {
2659         die "Switch $sw_name must be defined in klask configuration file\n";
2660         }
2661
2662      my $oid_search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
2663
2664      my $sw = $SWITCH_DB{$sw_name};
2665      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2666      print "$error \n" if $error;
2667
2668      my $result = $session->get_request(
2669         -varbindlist => [$oid_search_vlan_name]
2670         );
2671
2672      if (defined $result and $result->{$oid_search_vlan_name} ne 'noSuchInstance') {
2673         my $vlan_name = $result->{$oid_search_vlan_name} || 'empty';
2674         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
2675         }
2676      else {
2677         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2678         }
2679
2680      $session->close;
2681      }
2682   return;
2683   }
2684
2685#---------------------------------------------------------------
2686sub cmd_vlan_list {
2687   my $switch_name = shift || q{};
2688
2689   if ($switch_name eq q{}) {
2690      die "Usage: klask vlan-list SWITCH_NAME\n";
2691      }
2692
2693   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST) if $switch_name eq q{*};
2694
2695   for my $sw_name (split /,/, $switch_name) {
2696      if (not defined $SWITCH_DB{$sw_name}) {
2697         die "Switch $sw_name must be defined in klask configuration file\n";
2698         }
2699
2700      my $sw = $SWITCH_DB{$sw_name};
2701      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2702      print "$error \n" if $error;
2703
2704      my %vlandb = snmp_get_vlan_list($session);
2705      $session->close;
2706
2707      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2708      for my $vlan_id (keys %vlandb) {
2709         printf "%7i - %s\n", $vlan_id, $vlandb{$vlan_id};
2710         }
2711      }
2712   return;
2713   }
2714
2715#---------------------------------------------------------------
2716sub cmd_ip_location {
2717   my $computerdb = computerdb_load();
2718
2719   LOOP_ON_IP_ADDRESS:
2720   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
2721
2722      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2723
2724      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
2725      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
2726
2727      my $sw_location = q{};
2728      LOOP_ON_ALL_SWITCH:
2729      for my $sw (@SWITCH_LIST) {
2730         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2731         $sw_location = $sw->{'location'};
2732         last;
2733         }
2734
2735      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
2736      }
2737   return;
2738   }
2739
2740#---------------------------------------------------------------
2741sub cmd_ip_free {
2742   @ARGV = @_;
2743
2744   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
2745   my $format = 'txt';
2746   my $verbose;
2747
2748   GetOptions(
2749      'day|d=i'    => \$days_to_death,
2750      'format|f=s' => \$format,
2751      'verbose|v'  => \$verbose,
2752      );
2753
2754   my %possible_format = (
2755      txt  => \&cmd_ip_free_txt,
2756      html => \&cmd_ip_free_html,
2757      none => sub { },
2758      );
2759   $format = 'txt' if not defined $possible_format{$format};
2760
2761   my @vlan_name = @ARGV;
2762   @vlan_name = get_list_network() if not @vlan_name;
2763
2764   my $computerdb = {};
2765   $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
2766   my $timestamp = time;
2767
2768   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
2769
2770   my %result_ip = ();
2771
2772   ALL_NETWORK:
2773   for my $vlan (@vlan_name) {
2774
2775      my @ip_list = get_list_ip($vlan);
2776
2777      LOOP_ON_IP_ADDRESS:
2778      for my $ip (@ip_list) {
2779
2780         if (exists $computerdb->{$ip}) {
2781            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
2782
2783            my $mac_address = $computerdb->{$ip}{'mac_address'};
2784            LOOP_ON_DATABASE:
2785            for my $ip_db (keys %{$computerdb}) {
2786               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2787               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
2788               }
2789            }
2790
2791         my $ip_date_last_detection = '';
2792         if (exists $computerdb->{$ip}) {
2793            my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
2794            $year += 1900;
2795            $mon++;
2796            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2797            }
2798
2799         my $packed_ip   = scalar gethostbyname($ip);
2800         my $hostname_fq = 'unknown';
2801            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
2802
2803         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2804
2805         $result_ip{$ip} ||= {};
2806         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2807         $result_ip{$ip}->{'hostname_fq'}         = $hostname_fq;
2808         $result_ip{$ip}->{'vlan'}                = $vlan;
2809
2810         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
2811         }
2812      }
2813
2814   $possible_format{$format}->(%result_ip);
2815   }
2816
2817#---------------------------------------------------------------
2818sub cmd_ip_free_txt {
2819   my %result_ip = @_;
2820
2821   my $tb_computer = Text::Table->new(
2822      {align => 'left', align_title => 'left', title => 'IPv4-Address'},
2823      {align => 'left', align_title => 'left', title => 'Hostname-FQ'},
2824      {align => 'left', align_title => 'left', title => 'VLAN'},
2825      {align => 'left', align_title => 'left', title => 'Date'},
2826      );
2827
2828   #printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2829   #print "-------------------------------------------------------------------------------\n";
2830   LOOP_ON_IP_ADDRESS:
2831   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2832      my $vlan_nameid = $result_ip{$ip}->{'vlan'} . '(' . get_current_vlan_id($result_ip{$ip}->{'vlan'}) . ')';
2833      #printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
2834      $tb_computer->add(
2835         $ip,
2836         $result_ip{$ip}->{'hostname_fq'},
2837         $vlan_nameid,
2838         $result_ip{$ip}->{'date_last_detection'},
2839         );
2840      }
2841   print $tb_computer->title();
2842   print $tb_computer->rule('-');
2843   print $tb_computer->body();
2844   }
2845
2846#---------------------------------------------------------------
2847sub cmd_ip_free_html {
2848   my %result_ip = @_;
2849
2850   print <<'END_HTML';
2851<table class="sortable">
2852 <caption>Klask Free IPv4 Database</caption>
2853 <thead>
2854  <tr>
2855   <th scope="col" class="klask-header-left">IPv4-Address</th>
2856   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2857   <th scope="col" class="sorttable_alpha">VLAN</th>
2858   <th scope="col" class="klask-header-right">Date</th>
2859  </tr>
2860 </thead>
2861 <tbody>
2862END_HTML
2863
2864   my $typerow = 'even';
2865
2866   LOOP_ON_IP_ADDRESS:
2867   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2868
2869      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2870
2871      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
2872      my ($host_short) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
2873
2874      my $vlan_nameid = $result_ip{$ip}->{'vlan'} . ' (' . get_current_vlan_id($result_ip{$ip}->{'vlan'}) . ')';
2875
2876      fqdn_html_breakable(my $hostname_fq_html = $result_ip{$ip}->{'hostname_fq'});
2877
2878      print <<"END_HTML";
2879  <tr class="$typerow">
2880   <td sorttable_customkey="$ip_sort">$ip</td>
2881   <td sorttable_customkey="$host_short">$hostname_fq_html</td>
2882   <td>$vlan_nameid</td>
2883   <td>$result_ip{$ip}->{'date_last_detection'}</td>
2884  </tr>
2885END_HTML
2886      }
2887   print <<'END_HTML';
2888 </tbody>
2889 <tfoot>
2890  <tr>
2891   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2892   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2893   <th scope="col" class="fklask-vlan">VLAN</th>
2894   <th scope="col" class="klask-footer-right">Date</th>
2895  </tr>
2896 </tfoot>
2897</table>
2898END_HTML
2899   }
2900
2901#---------------------------------------------------------------
2902sub cmd_enable {
2903   @ARGV = @_;
2904
2905   my $verbose;
2906
2907   GetOptions(
2908      'verbose|v' => \$verbose,
2909      );
2910
2911   my $switch_name = shift @ARGV || q{};
2912   my $port_hr     = shift @ARGV || q{};
2913
2914   if ($switch_name eq q{} or $port_hr eq q{}) {
2915      die "Usage: klask disable SWITCH_NAME PORT\n";
2916      }
2917
2918   if (not defined $SWITCH_DB{$switch_name}) {
2919      die "Switch $switch_name must be defined in klask configuration file\n";
2920      }
2921
2922   my $sw = $SWITCH_DB{$switch_name};
2923   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2924   print "$error \n" if $error;
2925
2926   # Retrieve numeric port value
2927   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2928   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2929
2930   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} . '.' . $port_id;
2931   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2932
2933   my $result = $session->set_request(
2934      -varbindlist => [$oid_search_portstatus, INTEGER, 1],
2935      );
2936   print $session->error()."\n" if $session->error_status();
2937
2938   $session->close;
2939
2940   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2941   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2942   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2943   return;
2944   }
2945
2946#---------------------------------------------------------------
2947sub cmd_disable {
2948   @ARGV = @_;
2949
2950   my $verbose;
2951
2952   GetOptions(
2953      'verbose|v' => \$verbose,
2954      );
2955
2956   my $switch_name = shift @ARGV || q{};
2957   my $port_hr     = shift @ARGV || q{};
2958
2959   if ($switch_name eq q{} or $port_hr eq q{}) {
2960      die "Usage: klask disable SWITCH_NAME PORT\n";
2961      }
2962
2963   if (not defined $SWITCH_DB{$switch_name}) {
2964      die "Switch $switch_name must be defined in klask configuration file\n";
2965      }
2966
2967   my $sw = $SWITCH_DB{$switch_name};
2968   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2969   print "$error \n" if $error;
2970
2971   # Retrieve numeric port value
2972   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2973   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2974
2975   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} . '.' . $port_id;
2976   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2977
2978   my $result = $session->set_request(
2979      -varbindlist => [$oid_search_portstatus, INTEGER, 2],
2980      );
2981   print $session->error()."\n" if $session->error_status();
2982
2983   $session->close;
2984
2985   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2986   return;
2987   }
2988
2989#---------------------------------------------------------------
2990sub cmd_status {
2991   @ARGV = @_;
2992
2993   my $verbose;
2994
2995   GetOptions(
2996      'verbose|v' => \$verbose,
2997      );
2998
2999   my $switch_name = shift @ARGV || q{};
3000   my $port_hr     = shift @ARGV || q{};
3001
3002   if ($switch_name eq q{} or $port_hr eq q{}) {
3003      die "Usage: klask status SWITCH_NAME PORT\n";
3004      }
3005
3006   if (not defined $SWITCH_DB{$switch_name}) {
3007      die "Switch $switch_name must be defined in klask configuration file\n";
3008      }
3009
3010   my $sw = $SWITCH_DB{$switch_name};
3011   my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
3012   print "$error \n" if $error;
3013
3014   # Retrieve numeric port value
3015   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
3016   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
3017
3018   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} . '.' . $port_id;
3019   print "Info: switch $switch_name port $port_hr ($port_id) SNMP OID $oid_search_portstatus\n" if $verbose;
3020
3021   my $result = $session->get_request(
3022      -varbindlist => [$oid_search_portstatus]
3023      );
3024   print $session->error()."\n" if $session->error_status();
3025   if (defined $result) {
3026      print "$PORT_UPDOWN{$result->{$oid_search_portstatus}}\n";
3027      }
3028
3029   $session->close;
3030
3031   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
3032   return;
3033   }
3034
3035#---------------------------------------------------------------
3036sub cmd_search_mac_on_switch {
3037   @ARGV = @_;
3038
3039   my $verbose;
3040   my $vlan_id = 0;
3041
3042   GetOptions(
3043      'verbose|v' => \$verbose,
3044      'vlan|l=i'  => \$vlan_id,
3045      );
3046
3047   my $switch_name = shift @ARGV || q{};
3048   my $mac_address = shift @ARGV || q{};
3049
3050   if ($switch_name eq q{} or $mac_address eq q{}) {
3051      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
3052      }
3053
3054   $mac_address = normalize_mac_address($mac_address);
3055   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST) if $switch_name eq q{*} or $switch_name eq q{all};
3056
3057   for my $sw_name (split /,/, $switch_name) {
3058      if (not defined $SWITCH_DB{$sw_name}) {
3059         die "Switch $sw_name must be defined in klask configuration file\n";
3060         }
3061
3062      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
3063      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} . '.' . $vlan_id . mac_address_hex2dec($mac_address);
3064      print "Klask search OID $oid_search_port1 on switch $sw_name\n" if $verbose;
3065      print "Klask search OID $oid_search_port2 on switch $sw_name\n" if $verbose;
3066
3067      my $sw = $SWITCH_DB{$sw_name};
3068      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
3069      print "$error \n" if $error;
3070
3071      my $result = $session->get_request(
3072         -varbindlist => [$oid_search_port1]
3073         );
3074      if (not defined $result) {
3075         $result = $session->get_request(
3076            -varbindlist => [$oid_search_port2]
3077            );
3078         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
3079         }
3080
3081      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
3082         my $swport_id = $result->{$oid_search_port1};
3083         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
3084         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
3085         }
3086      else {
3087         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
3088         }
3089
3090      $session->close;
3091      }
3092   return;
3093   }
3094
3095#---------------------------------------------------------------
3096sub cmd_updatesw {
3097   @ARGV = @_;
3098
3099   my $verbose;
3100
3101   GetOptions(
3102      'verbose|v' => \$verbose,
3103      );
3104   
3105   update_switchdb(verbose => $verbose);
3106   }
3107
3108#---------------------------------------------------------------
3109sub cmd_exportsw {
3110   @ARGV = @_;
3111
3112   test_switchdb_environnement();
3113
3114   my $format       = 'txt';
3115   my $graph_modulo = 0;
3116   my $graph_shift  = 1;
3117   my $way          = 'all';
3118   my $no_header;
3119
3120   GetOptions(
3121      'format|f=s'  => \$format,
3122      'modulo|m=i'  => \$graph_modulo,
3123      'shift|s=i'   => \$graph_shift,
3124      'way|w=s'     => \$way,
3125      'no-header|H' => \$no_header,
3126      );
3127
3128   my %possible_format = (
3129      txt => \&cmd_exportsw_txt,
3130      dot => \&cmd_exportsw_dot,
3131      );
3132
3133   $format = 'txt' if not defined $possible_format{$format};
3134
3135   $possible_format{$format}->(modulo => $graph_modulo, shift => $graph_shift, way => $way, 'no-header' => $no_header, @ARGV);
3136   return;
3137   }
3138
3139#---------------------------------------------------------------
3140sub cmd_exportsw_txt {
3141   my %args = (
3142      'way'       => 'all',
3143      'no-header' => undef,
3144      @_);
3145
3146   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
3147   my $interway = 0;
3148
3149   if ($args{'way'} =~ m/all|desc/) {
3150      print "\n" if $interway++;
3151      # Switch description
3152      my %db_switch_global = %{$switch_connection->{'switch_db'}};
3153      my $arrow            = '0--------->>>>';
3154
3155      my $tb_desc = Text::Table->new(
3156         {align => 'left',   align_title => 'left',   title => 'Switch'},
3157         {align => 'center', align_title => 'center', title => 'Is'},
3158         {align => 'left',   align_title => 'left',   title => 'Description'},
3159         {align => 'left',   align_title => 'left',   title => 'Model'},
3160         {align => 'left',   align_title => 'left',   title => 'Revision'},
3161         );
3162
3163      for my $sw (values %db_switch_global) {
3164         $tb_desc->add($sw->{'hostname'}, $arrow, $sw->{'description'}, $sw->{'model'}, $sw->{'revision'});
3165         }
3166
3167      print $tb_desc->title()   unless $args{'no-header'};
3168      print $tb_desc->rule('-') unless $args{'no-header'};
3169      print $tb_desc->body();
3170      $tb_desc->clear;
3171      }
3172
3173   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
3174   my %db_switch_parent            = %{$switch_connection->{'parent'}};
3175   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
3176
3177   if ($args{'way'} =~ m/all|child/) {
3178      print "\n" if $interway++;
3179      # Switch output port and parent port connection
3180      my $tb_child = Text::Table->new( # http://www.perlmonks.org/?node_id=988320
3181         {align => 'left',   align_title => 'left',   title => 'Child-Switch'},
3182         {align => 'right',  align_title => 'right',  title => 'Output-Port'},
3183         {align => 'center', align_title => 'center', title => 'Link'},
3184         {align => 'left',   align_title => 'left',   title => 'Input-Port'},
3185         {align => 'left',   align_title => 'left',   title => 'Parent-Switch'},
3186         );
3187      for my $sw (sort keys %db_switch_output_port) {
3188         my $arrow = '--->';
3189         $arrow = '===>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
3190         if (exists $db_switch_parent{$sw}) {
3191            $tb_child->add($sw, $db_switch_output_port{$sw}, $arrow, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'});
3192
3193            }
3194         else {
3195            $tb_child->add($sw, $db_switch_output_port{$sw}, $arrow, '', 'router');
3196            }
3197         }
3198      my @colrange = map {scalar $tb_child->colrange($_)} (0 .. 4); # force scaler context
3199      $tb_child->add(map {' ' x $_} reverse @colrange);             # add empty line to force symetric table output
3200      print $tb_child->title()   unless $args{'no-header'};
3201      print $tb_child->rule('-') unless $args{'no-header'};
3202      print $tb_child->body(0, $tb_child->body_height() - 1);       # remove last fake line
3203      $tb_child->clear;
3204      }
3205
3206   if ($args{'way'} =~ m/all|parent/) {
3207      print "\n" if $interway++;
3208      # Switch parent and children port inter-connection
3209      my $tb_parent = Text::Table->new(                             # http://www.perlmonks.org/?node_id=988320
3210         {align => 'left',   align_title => 'left',   title => 'Parent-Switch'},
3211         {align => 'right',  align_title => 'right',  title => 'Input-Port'},
3212         {align => 'center', align_title => 'center', title => 'Link'},
3213         {align => 'left',   align_title => 'left',   title => 'Output-Port'},
3214         {align => 'left',   align_title => 'left',   title => 'Child-Switch'},
3215         );
3216      for my $swport (sort keys %db_switch_connected_on_port) {
3217         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
3218         for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
3219            my $arrow = '<---';
3220            $arrow = '<===' if $port_connect =~ m/^(Trk|Br|Po)/;
3221            if (exists $db_switch_output_port{$sw}) {
3222               $tb_parent->add($sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw);
3223               }
3224            else {
3225               $tb_parent->add($sw_connect, $port_connect, $arrow, '', $sw);
3226               }
3227            }
3228         }
3229      my @colrange = map {scalar $tb_parent->colrange($_)} (0 .. 4); # force scaler context
3230      $tb_parent->add(map {' ' x $_} reverse @colrange);             # add empty line to force symetric table output
3231      print $tb_parent->title()   unless $args{'no-header'};
3232      print $tb_parent->rule('-') unless $args{'no-header'};
3233      print $tb_parent->body(0, $tb_parent->body_height() - 1);      # remove last fake line
3234      $tb_parent->clear;
3235      }
3236   return;
3237   }
3238
3239#---------------------------------------------------------------
3240sub cmd_exportsw_dot {
3241   my %args = (
3242      modulo   => 0,
3243      shift    => 1,
3244      @_);
3245
3246   my $graph_modulo = $args{'modulo'};
3247   my $graph_shift  = $args{'shift'};
3248
3249   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
3250
3251   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
3252   my %db_switch_parent            = %{$switch_connection->{'parent'}};
3253   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
3254   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
3255   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
3256   my $timestamp                   =   $switch_connection->{'timestamp'};
3257
3258   my $invisible_node = 0; # Count number of invisible node
3259
3260   my %db_building    = ();
3261   my %db_switch_line = (); # Number of line drawed on a switch
3262   for my $sw (values %db_switch_global) {
3263      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
3264      $db_building{$building} ||= {};
3265      $db_building{$building}->{$location} ||= {};
3266      $db_building{$building}->{$location}{$sw->{'hostname'}} = 'y';
3267
3268      $db_switch_line{$sw} = 0;
3269      }
3270
3271
3272   print "digraph G {\n";
3273   print "rankdir=LR;\n";
3274   #print "splines=polyline;\n";
3275
3276   print "site [label=\"site\", color=black, fillcolor=gold, shape=invhouse, style=filled];\n";
3277   print "internet [label=\"internet\", color=black, fillcolor=cyan, shape=house, style=filled];\n";
3278
3279   my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
3280   $year += 1900;
3281   $mon++;
3282   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
3283   print "\"$date\" [label=\"MAP DATE\\n\\n$date\", color=white, fillcolor=black, shape=polygon, sides=14, style=filled, fontcolor=white];\n";
3284   print "site -> \"$date\" [style=invis];\n";
3285
3286   my $b = 0;
3287   for my $building (keys %db_building) {
3288      $b++;
3289
3290      print "\"building$b\" [label=\"$building\", color=black, fillcolor=gold, style=filled];\n";
3291      print "site -> \"building$b\" [len=2, color=firebrick];\n";
3292
3293      my $l = 0;
3294      for my $loc (keys %{$db_building{$building}}) {
3295         $l++;
3296
3297         print "\"location$b-$l\" [label=\"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color=black, fillcolor=orange, style=filled];\n";
3298#         print "\"location$b-$l\" [label=\"$building / $loc\", color=black, fillcolor=orange, style=filled];\n";
3299         print "\"building$b\" -> \"location$b-$l\" [len=2, color=firebrick];\n";
3300
3301         for my $sw (keys %{$db_building{$building}->{$loc}}) {
3302
3303            my $peripheries = 1;
3304            my $color       = 'lightblue';
3305            if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
3306               $peripheries = 2;
3307               $color       = "\"$color:$color\"";
3308               }
3309            print "\"$sw:$db_switch_output_port{$sw}\" [label=\"".format_aggregator4dot($db_switch_output_port{$sw})."\", color=black, fillcolor=lightblue, peripheries=$peripheries, style=filled];\n";
3310
3311            my $swname  = $sw;
3312               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{'model'}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{'model'};
3313            print "\"$sw\" [label=\"$swname\", color=black, fillcolor=palegreen, shape=rect, style=filled];\n";
3314            print "\"location$b-$l\" -> \"$sw\" [len=2, color=firebrick, arrowtail=dot];\n";
3315            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=invdot];\n";
3316
3317
3318            for my $swport (keys %db_switch_connected_on_port) {
3319               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
3320               next if not $sw_connect eq $sw;
3321               next if $port_connect eq $db_switch_output_port{$sw};
3322               my $peripheries = 1;
3323               my $color       = 'plum';
3324               if ($port_connect =~ m/^(Trk|Br|Po)/) {
3325                  $peripheries = 2;
3326                  $color       = "\"$color:$color\"";
3327                  }
3328               print "\"$sw:$port_connect\" [label=\"".format_aggregator4dot($port_connect)."\", color=black, fillcolor=plum, peripheries=$peripheries, style=filled];\n";
3329               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=inv];\n";
3330
3331               #$db_switch_line{$sw}++;
3332               #if ($db_switch_line{$sw} % 9 == 0) {
3333               #   # Create invisible node
3334               #   $invisible_node++;
3335               #   my $invisible = '__Invisible_' . $invisible_node;
3336               #   print "$invisible [shape=none, label=\"\"]\n";
3337               #   print "\"$sw:$port_connect\" -> $invisible [style=invis]\n";
3338               #   print "$invisible            -> \"$sw\"    [style=invis]\n";
3339               #   }
3340               }
3341            }
3342         }
3343      }
3344
3345   #   print "Switch output port and parent port connection\n";
3346   #   print "---------------------------------------------\n";
3347   for my $sw (sort keys %db_switch_output_port) {
3348      if (exists $db_switch_parent{$sw}) {
3349#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port_id'};
3350         }
3351      else {
3352         my $style = 'solid';
3353         my $color = 'black'; # navyblue
3354         if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
3355            $style = 'bold';
3356            $color = "\"$color:invis:$color\"";
3357            }
3358         printf "   \"%s:%s\" -> internet [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw};
3359         }
3360      }
3361   print "\n";
3362
3363   # shift graph between 1 or 2 when $graph_shift = 3
3364   my $graph_breaker = 1;
3365
3366   #   print "Switch parent and children port inter-connection\n";
3367   #   print "------------------------------------------------\n";
3368   for my $swport (sort keys %db_switch_connected_on_port) {
3369      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
3370      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
3371         my $style = 'solid';
3372         my $color = 'black'; # navyblue
3373         if ($port_connect =~ m/^(Trk|Br|Po)/) {
3374            $style = 'bold';
3375            $color = "\"$color:invis:$color\"";
3376            }
3377         if (exists $db_switch_output_port{$sw}) {
3378            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
3379
3380            next if $graph_modulo == 0; # No shift (invisible nodes) in graph
3381            $db_switch_line{$sw_connect}++;
3382            if ($db_switch_line{$sw_connect} % $graph_modulo == 0) {
3383               # Create invisible node
3384               $invisible_node++;
3385               my $invisible = '__Invisible_' . $invisible_node;
3386               print  "   \"$invisible.a\" [shape=none, label=\"\"];\n";
3387               printf "   \"%s:%s\"  -> \"$invisible.a\" [style=invis];\n", $sw, $db_switch_output_port{$sw};
3388               $graph_breaker++;
3389               if ($graph_shift == 2 or ($graph_shift == 3 and ($graph_breaker % 2) == 0)) {
3390                  # Two invisible node
3391                  print  "   \"$invisible.b\" [shape=none, label=\"\"];\n";
3392                  print  "   \"$invisible.a\" -> \"$invisible.b\" [style=invis];\n";
3393                  printf "   \"$invisible.b\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
3394                  }
3395               else {
3396                  # One invisible node
3397                  printf "   \"$invisible.a\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
3398                  }
3399               }
3400            }
3401         else {
3402            printf "   \"%s\"   -> \"%s:%s\" [style=$style];\n", $sw, $sw_connect, $port_connect;
3403            }
3404         }
3405      }
3406
3407   print "}\n";
3408   return;
3409   }
3410
3411################################################################
3412# documentation
3413################################################################
3414
3415__END__
3416
3417=head1 NAME
3418
3419klask - port and search manager for switches, map management
3420
3421
3422=head1 USAGE
3423
3424 klask version
3425 klask help
3426
3427 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
3428 klask exportdb [--format|-f txt|html]
3429 klask removedb ipv4_addr* computer*
3430 klask insertdb --ip ipv4_addr --mac mac_addr --network vlan_name
3431 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3432
3433 klask updatesw [--verbose|-v]
3434 klask exportsw [--format|-f txt|dot] [--modulo|-m XX] [--shift|-s YY] [--way all|desc|child|parent] [--no-header|-H]
3435
3436 klask searchdb [--kind|-k host|mac] computer [mac-address]
3437 klask search   computer
3438 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3439
3440 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3441
3442 klask bad-vlan-id [--day|-d days_before_alert] [--format|-f txt|html]
3443
3444 klask enable  [--verbose|-v] switch port
3445 klask disable [--verbose|-v] switch port
3446 klask status  [--verbose|-v] switch port
3447
3448 klask poe-enable  [--verbose|-v] switch port
3449 klask poe-disable [--verbose|-v] switch port
3450 klask poe-status  [--verbose|-v] switch port
3451
3452 klask vlan-getname switch vlan-id
3453 klask vlan-list switch
3454
3455
3456=head1 DESCRIPTION
3457
3458Klask is a small tool to find where is connected a host in a big network
3459and on which VLAN.
3460Klask mean search in brittany.
3461No hight level protocol like CDL, LLDP are use.
3462Everything is just done with SNMP request on MAC address.
3463
3464Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
3465If you use PVST or MSTP and create loop between VLAN,
3466you have to use C<portignore> functionality on switch port to cut manually loop
3467(see config file below).
3468
3469When you use a management port to administrate a switch,
3470it's not possible to create the map with this switch because it does not have a MAC address,
3471so other switch cannot find the real downlink port...
3472One way to work around this problem is, if you have a computer directly connected on the switch,
3473to put this IPv4 as the fake ip for the switch.
3474The MAC address associated will be use just for the map detection.
3475The C<fake-ip> parameter is defined in the config file.
3476
3477Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
3478
3479
3480=head1 COMMANDS
3481
3482Some command are defined in the source code but are not documented here.
3483Theses could be not well defined, not finished, not well tested...
3484You can read the source code and use them at your own risk
3485(like for all the Klask code).
3486
3487=head2 search
3488
3489 klask search   computer
3490
3491This command takes one or more computer in argument.
3492It search a computer on the network and give the port and the switch on which the computer is connected.
3493
3494=head2 search-mac-on-switch
3495
3496 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3497
3498This command search a MAC address on a switch.
3499To search on all switch, you could put C<'*'> or C<all>.
3500The VLAN parameter could help.
3501
3502
3503=head2 enable
3504
3505 klask enable  [--verbose|-v] switch port
3506
3507This command activate a port (or an agrregate bridge port) on a switch by SNMP.
3508So you need to give the switch name and a port on the command line.
3509See L</ABBREVIATION FOR PORT>.
3510
3511Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3512
3513
3514=head2 disable
3515
3516 klask disable [--verbose|-v] switch port
3517
3518This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
3519So you need to give the switch name and a port on the command line.
3520See L</ABBREVIATION FOR PORT>.
3521
3522Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3523
3524
3525=head2 status
3526
3527 klask status  [--verbose|-v] switch port
3528
3529This command return the status of a port number on a switch by SNMP.
3530The return value could be C<enable> or C<disable> word.
3531So you need to give the switch name and a port on the command line.
3532See L</ABBREVIATION FOR PORT>.
3533
3534If it's not possible to change port status with command L</enable> and L</disable>
3535(SNMP community read write access),
3536it's always possible to have the port status even for bridge agrregate port.
3537
3538
3539=head2 updatedb
3540
3541 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
3542
3543This command will scan networks and update the computer database.
3544To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3545This file is easy to read and write because Klask use YAML format and not XML
3546(see L</CONFIGURATION>).
3547
3548Option are not stable and could be use manually when you have a new kind of switch.
3549Maybe some option will be transfered in a future C<checksw> command!
3550
3551The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3552By default, a network is C<active>.
3553This means that an C<fping> command is done at the beginning on all the IPv4 of the network
3554and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3555will have an C<arping>
3556(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3557In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3558It's good for big subnet with few computer (telephone...).
3559The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3560
3561At the beginning, the command verify that the switch map checksum is always valid.
3562Otherwise, a rebuild procedure will ne done automatically.
3563
3564=head2 exportdb
3565
3566 klask exportdb [--format|-f txt|html]
3567
3568This command print the content of the computer database.
3569There is actually only two format : TXT and HTML.
3570By default, format is TXT.
3571It's very easy to have more format, it's just need times...
3572
3573=head2 removedb
3574
3575 klask removedb ipv4_addr* computer*
3576
3577This command remove an entry in the database.
3578There is only one kind of parameter, the IPv4 of the computers to be removed.
3579You can put as many IPv4 as you want...
3580
3581Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
3582
3583=head2 insertdb
3584
3585 klask insertdb --ip ipv4_addr --mac mac_addr --network vlan_name
3586
3587This command insert an entry in the database.
3588The IPv4 must not exist previously in the database
3589and the DNS resolution name muts be active.
3590
3591The date or timestamp of the entry will be C<number_of_day> in the past (see L<cleandb>)
3592from today or from the oldest entry with the same MAC-Address.
3593This command can be use to add pseudo entry and help in the process to detect bad vlan configuration (see L<bad-vlan-id>).
3594
3595=head2 cleandb
3596
3597 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3598
3599Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3600Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3601This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3602This functionality could be use when computer define in VLAN 1
3603could have a float IPv4 when they are connected on VLAN 2.
3604In the Klask database, the float DNS entries are less important.
3605
3606When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
3607
3608=head2 updatesw
3609
3610 klask updatesw [--verbose|-v]
3611
3612This command build a map of your manageable switch on your network.
3613The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3614
3615The database has a checksum which depend of all the active switches.
3616It's use when rebuilding the database in case of change in switch configuration (one more for example).
3617
3618=head2 exportsw
3619
3620 klask exportsw [--format|-f txt|dot] [--modulo|-m XX] [--shift|-s YY] [--way all|desc|child|parent] [--no-header|-H]
3621
3622This command print the content of the switch database. There is actually two format.
3623One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3624By default, format is TXT.
3625
3626B<Options for TXT format:>
3627
3628Tree tables are print :
3629C<desc> gives the switches description, model and revision,
3630C<child> return the switch to parent switch table and
3631C<parent> return the switch parent to parent child table.
3632With option C<--way>, you can choose which on to print.
3633C<all> will print all and C<child,parent> will print only C<child> and C<parent> table.
3634With option C<--no-header>, you can remove the header for each table.
3635
3636B<Options for DOT format:>
3637
3638 klask exportsw --format dot > /tmp/map.dot
3639 dot -Tpng /tmp/map.dot > /tmp/map.png
3640
3641In case you have too many switch connected on one switch,
3642the graphviz result graph could be too much vertical.
3643With C<--modulo> > 0, you can specify how many switches (connected on one switch) are on the same columns
3644before shifting them to one column to the left and back again.
3645The C<--shift> parameter must be 1, 2 or 3.
3646With C<--shift> egual to 2, the shift will be to two column to the left.
3647With 3, it will be 1 to the left and 2 to the left one time over two !
3648In practise, we just add virtuals nodes in the dot file,
3649that means the result graph is generated with theses virtuals but invisibles nodes...
3650
3651=head2 ip-free
3652
3653 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3654
3655This command return IPv4 address that was not use (detected by Klask) at this time.
3656The list returned could be limited to just one VLAN.
3657IP returned could have been never used or no computer have been detected since the number of days specified
3658(2 years by default).
3659This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3660
3661 default:
3662   days-to-death: 730
3663
3664Computer that does not have the good IPv4 but takes a float one (see L</cleandb>) are taken into account.
3665
3666
3667=head2 bad-vlan-id
3668
3669 klask bad-vlan-id [--day|-d days_before_alert] [--format|-f txt|html]
3670
3671This command return a list of switch port that are not configure with the good VLAN.
3672Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3673and another prior trace where they had the good IPv4 (good DNS name).
3674The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
3675This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3676
3677 default:
3678   days-before-alert: 15
3679
3680This functionality is not need if your switch use RADIUS 802.1X configuration...
3681
3682
3683=head2 poe-enable
3684
3685 klask poe-enable  [--verbose|-v] switch port
3686
3687This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3688So you need to give the switch name and a port on the command line.
3689See L</ABBREVIATION FOR PORT>.
3690
3691Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3692You need to have the SNMP write access on the switch in order to modify it's configuration.
3693
3694
3695=head2 poe-disable
3696
3697 klask poe-disable [--verbose|-v] switch port
3698
3699This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3700So you need to give the switch name and a port on the command line.
3701See L</ABBREVIATION FOR PORT>.
3702
3703Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3704You need to have the SNMP write access on the switch in order to modify it's configuration.
3705
3706
3707=head2 poe-status
3708
3709 klask poe-status  [--verbose|-v] switch port
3710
3711This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3712The return value could be C<enable> or C<disable> word.
3713So you need to give the switch name and a port on the command line.
3714See L</ABBREVIATION FOR PORT>.
3715
3716If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3717(SNMP community read write access),
3718it's always possible to have the PoE port status.
3719
3720Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3721
3722
3723=head1 CONFIGURATION
3724
3725Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
3726The configuration is done in a F</etc/klask/klask.conf> YAML file.
3727This format have many advantage over XML, it's easier to read and to write !
3728
3729Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3730
3731 default:
3732   community: public
3733   community-rw: private
3734   snmpport: 161
3735   float-regex: '(?^msx: ^float )'
3736   scan-mode: active
3737
3738 network:
3739   labnet:
3740     ip-subnet:
3741       - add: 192.168.1.0/24
3742       - add: 192.168.2.0/24
3743     interface: eth0
3744     vlan-id: 12
3745     main-router: gw1.labnet.local
3746
3747   schoolnet:
3748     ip-subnet:
3749       - add: 192.168.3.0/24
3750       - add: 192.168.4.0/24
3751     interface: eth0.38
3752     vlan-id: 13
3753     main-router: gw2.schoolnet.local
3754     scan-mode: passive
3755
3756   etunet:
3757     ip-subnet:
3758       - add: 192.168.5.0/24
3759     interface: eth2
3760     vlan-id: 14
3761     main-router: gw3.etunet.local
3762     scan-mode: passive
3763
3764 switch:
3765   - hostname: sw1.klask.local
3766     location: BatY / 1 floor / K004
3767     portignore:
3768       - 1
3769       - 2
3770
3771   - hostname: sw2.klask.local
3772     location: BatY / 2 floor / K203
3773     type: HP2424
3774     portignore:
3775       - 1
3776       - 2
3777     fake-ip: 192.168.9.14
3778
3779   - hostname: sw3.klask.local
3780     location: BatY / 2 floor / K203
3781
3782I think it's pretty easy to understand.
3783The default section can be overide in any section, if parameter mean something in theses sections.
3784Network to be scan are define in the network section. You must put an add by network.
3785Maybe I will make a delete line to suppress specific computers.
3786The switch section define your switch.
3787You have to write the port number to ignore, this was important if your switchs are cascades
3788(right now, method C<updatesw> find them automatically)
3789and is still important if you have loop (with PVST or MSTP).
3790Just put the ports numbers between switch.
3791
3792The C<community> parameter is use to get SNMP data on switch.
3793It could be overload for each switch.
3794By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3795Some few command change the switch state as the commands L</enable> and L</disable>.
3796In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3797Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
3798
3799
3800=head1 ABBREVIATION FOR PORT
3801
3802HP Procurve and Nexans switches have a simplistic numbering scheme.
3803It's just number: 1, 2, 3... 24.
3804On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3805Nothing is done on theses ports names.
3806
3807On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3808followed by tree number.
3809In order to have short name,
3810we made the following rules:
3811
3812 Bridge-Aggregation     -> Br
3813 FastEthernet           -> Fa
3814 Forty-GigabitEthernet  -> Fo
3815 FortyGigabitEthernet   -> Fo
3816 GigabitEthernet        -> Gi
3817 Giga                   -> Gi
3818 Port-Channel           -> Po
3819 Ten-GigabitEthernet    -> Te
3820 TenGigabitEthernet     -> Te
3821 Ten                    -> Te
3822
3823All Klask command automatically normalize the port name on standart output
3824and also on input command line.
3825
3826In the case of use an aggregator port (Po, Tk, Br ...),
3827the real ports used are also return.
3828
3829
3830=head1 SWITCH SUPPORTED
3831
3832Here is a list of switches where Klask gives or gave (for old switches) good results.
3833We have only a few manageable switches to actually test Klask.
3834It is quite possible that switches from other brands will work just as well.
3835You just have to do a test on it and add the line of description that goes well in the source code.
3836Contact us for any additional information.
3837
3838In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3839This makes it possible to adjust the code names of the different manufacturers!
3840
3841HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3842J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3843J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F),
3844J4121A(HP4000M), J9145A(HP2910-24G), J3298A(HP212M), J9625A(HP2620-24P).
3845
3846BayStack: BayStack 350T HW(BS350T)
3847
3848Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3849
3850DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3851
3852H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3853
3854H3C: H3C5500
3855
38563COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
38573CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
38583CR17572-91, 3C17702-US, 3C17700.
3859
3860
3861=head1 FILES
3862
3863 /etc/klask/klask.conf
3864 /var/lib/klask/klaskdb
3865 /var/lib/klask/switchdb
3866
3867
3868=head1 SEE ALSO
3869
3870Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3871
3872=over
3873
3874=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
3875
3876=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3877
3878=back
3879
3880
3881=head1 VERSION
3882
3883$Id: klask 389 2018-01-05 10:41:47Z g7moreau $
3884
3885
3886=head1 AUTHOR
3887
3888Written by Gabriel Moreau, Grenoble - France
3889
3890=head1 SPECIAL THANKS
3891
3892The list of people below did not directly contribute to Klask's source code
3893but provided me with some data, returned bugs
3894or helped me in another small task like having new ideas ...
3895Maybe I forgot your contribution in recent years,
3896please forgive me in advance and send me an e-mail to correct this.
3897
3898Kevin Reverchon, Olivier De Marchi, Patrick Begou, Herve Colasuonno.
3899
3900
3901=head1 LICENSE AND COPYRIGHT
3902
3903Licence GNU GPL version 2 or later and Perl equivalent
3904
3905Copyright (C) 2005-2018 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.