source: trunk/klask @ 376

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