source: trunk/klask @ 383

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