source: trunk/klask @ 299

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