source: trunk/klask @ 310

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