source: trunk/klask @ 388

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