source: trunk/klask @ 405

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