source: trunk/klask @ 295

Last change on this file since 295 was 295, checked in by g7moreau, 7 years ago
  • Begin Digest::SHA implementation in order to checksum switch database...
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 124.5 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 295 2017-10-04 15:57:55Z 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################################################################
869# command
870################################################################
871
872#---------------------------------------------------------------
873sub cmd_help {
874
875print <<'END';
876klask - port and search manager for switches, map management
877
878 klask version
879 klask help
880
881 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
882 klask exportdb [--format|-f txt|html]
883 klask removedb IP* computer*
884 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
885
886 klask updatesw [--verbose|-v]
887 klask exportsw [--format|-f txt|dot]
888
889 klask searchdb [--kind|-k host|mac] computer [mac-address]
890 klask search   computer
891 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
892
893 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
894
895 klask bad-vlan-id [--day|-d days_before_alert]
896
897 klask enable  [--verbose|-v] switch port
898 klask disable [--verbose|-v] switch port
899 klask status  [--verbose|-v] switch port
900
901 klask poe-enable  [--verbose|-v] switch port
902 klask poe-disable [--verbose|-v] switch port
903 klask poe-status  [--verbose|-v] switch port
904
905 klask vlan-getname switch vlan-id
906 klask vlan-list switch
907END
908   return;
909   }
910
911#---------------------------------------------------------------
912sub cmd_version {
913
914print <<'END';
915klask - port and search manager for switches, map management
916Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
917
918END
919   print ' $Id: klask 295 2017-10-04 15:57:55Z g7moreau $'."\n";
920   return;
921   }
922
923#---------------------------------------------------------------
924sub cmd_search {
925   my @computer = @_;
926
927   init_switch_names();    #nomme les switchs
928   fast_ping(@computer);
929
930   LOOP_ON_COMPUTER:
931   for my $clientname (@computer) {
932      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
933      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
934      my $vlan_id   = get_current_vlan_id($vlan_name);
935      my %where     = find_switch_port($resol_arp{'mac_address'}, '', $vlan_id); #retrouve l'emplacement
936
937      next LOOP_ON_COMPUTER if $where{'switch_description'} eq 'unknow' or $resol_arp{'hostname_fq'} eq 'unknow' or $resol_arp{'mac_address'} eq 'unknow';
938
939      printf '%-22s %2s %-30s %-15s %18s',
940         $where{'switch_hostname'},
941         $where{'switch_port_hr'},
942         $resol_arp{'hostname_fq'},
943         $resol_arp{'ipv4_address'},
944         $resol_arp{'mac_address'}."\n";
945      }
946   return;
947   }
948
949#---------------------------------------------------------------
950sub cmd_searchdb {
951   my @ARGV  = @_;
952
953   my $kind;
954
955   GetOptions(
956      'kind=s'   => \$kind,
957      );
958
959   my %possible_search = (
960      host  => \&cmd_searchdb_host,
961      mac   => \&cmd_searchdb_mac,
962      );
963
964   $kind = 'host' if not defined $possible_search{$kind};
965
966   $possible_search{$kind}->(@ARGV);
967   return;
968   }
969
970
971#---------------------------------------------------------------
972sub cmd_searchdb_host {
973   my @computer = @_;
974
975   fast_ping(@computer);
976   my $computerdb = computerdb_load();
977
978   LOOP_ON_COMPUTER:
979   for my $clientname (@computer) {
980      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
981      my $ip = $resol_arp{'ipv4_address'};
982
983      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
984
985      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
986      $year += 1900;
987      $mon++;
988      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
989
990      printf "%-22s %2s %-30s %-15s %-18s %s\n",
991         $computerdb->{$ip}{'switch_hostname'},
992         $computerdb->{$ip}{'switch_port_hr'},
993         $computerdb->{$ip}{'hostname_fq'},
994         $ip,
995         $computerdb->{$ip}{'mac_address'},
996         $date;
997      }
998   return;
999   }
1000
1001#---------------------------------------------------------------
1002sub cmd_searchdb_mac {
1003   my @mac = map { normalize_mac_address($_) } @_;
1004
1005   my $computerdb = computerdb_load();
1006
1007   LOOP_ON_MAC:
1008   for my $mac (@mac) {
1009      LOOP_ON_COMPUTER:
1010      for my $ip (keys %{$computerdb}) {
1011         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{'mac_address'};
1012
1013         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1014         $year += 1900;
1015         $mon++;
1016         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1017
1018         printf "%-22s %2s %-30s %-15s %-18s %s\n",
1019            $computerdb->{$ip}{'switch_hostname'},
1020            $computerdb->{$ip}{'switch_port_hr'},
1021            $computerdb->{$ip}{'hostname_fq'},
1022            $ip,
1023            $computerdb->{$ip}{'mac_address'},
1024            $date;
1025         #next LOOP_ON_MAC;
1026         }
1027
1028      }
1029   return;
1030   }
1031
1032#---------------------------------------------------------------
1033sub cmd_updatedb {
1034   @ARGV = @_;
1035
1036   my ($verbose, $verb_description, $check_hostname, $check_location);
1037
1038   GetOptions(
1039      'verbose|v'          => \$verbose,
1040      'verb-description|d' => \$verb_description,
1041      'chk-hostname|h'     => \$check_hostname,
1042      'chk-location|l'     => \$check_location,
1043      );
1044
1045   my @network = @ARGV;
1046      @network = get_list_network() if not @network;
1047
1048   test_switchdb_environnement();
1049
1050   my $computerdb = {};
1051      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
1052   my $timestamp = time;
1053
1054   my %computer_not_detected = ();
1055   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
1056
1057   my $number_of_computer = get_list_ip(@network); # + 1;
1058   my $size_of_database   = keys %{$computerdb};
1059      $size_of_database   = 1 if $size_of_database == 0;
1060   my $i = 0;
1061   my $detected_computer = 0;
1062
1063   init_switch_names('yes', $verb_description, $check_hostname, $check_location);    #nomme les switchs
1064
1065   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
1066   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1067   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1068   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1069   my %db_switch_chained_port = ();
1070   for my $swport (keys %db_switch_connected_on_port) {
1071      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1072      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
1073      }
1074   for my $sw (@SWITCH_LIST) {
1075      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{'hostname'}}  if exists $db_switch_output_port{$sw->{'hostname'}};
1076      if ( exists $db_switch_chained_port{$sw->{'hostname'}} ) {
1077         chop $db_switch_chained_port{$sw->{'hostname'}};
1078         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{'hostname'}};
1079         }
1080#      print "$sw->{'hostname'} ++ @{$sw->{portignore}}\n";
1081      }
1082   }
1083
1084   my %router_mac_ip = ();
1085   DETECT_ALL_ROUTER:
1086#   for my $one_router ('194.254.66.254') {
1087   for my $one_router ( get_list_main_router(@network) ) {
1088      my %resol_arp = resolve_ip_arp_host($one_router);
1089      $router_mac_ip{ $resol_arp{'mac_address'} } = $resol_arp{'ipv4_address'};
1090      }
1091
1092   ALL_NETWORK:
1093   for my $current_net (@network) {
1094
1095      my @computer = get_list_ip($current_net);
1096      my $current_interface = get_current_interface($current_net);
1097
1098      fast_ping(@computer) if get_current_scan_mode($current_net) eq 'active';
1099
1100      LOOP_ON_COMPUTER:
1101      for my $one_computer (@computer) {
1102         $i++;
1103
1104         my $total_percent = int (($i*100)/$number_of_computer);
1105
1106         my $localtime = time - $timestamp;
1107         my ($sec,$min) = localtime $localtime;
1108
1109         my $time_elapse = 0;
1110            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
1111         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
1112
1113         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
1114         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
1115         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
1116         printf ' %-8s %-14s', $current_interface, $one_computer;
1117
1118         my $already_exist = exists $computerdb->{$one_computer} ? 'yes' : 'no';
1119         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface, 'fast', $already_exist);
1120
1121         # do not search on router connection (why ?)
1122         if ( exists $router_mac_ip{$resol_arp{'mac_address'}}) {
1123            $computer_not_detected{$one_computer} = $current_net;
1124            next LOOP_ON_COMPUTER;
1125            }
1126
1127         # do not search on switch inter-connection
1128         if (exists $SWITCH_LEVEL{$resol_arp{'hostname_fq'}}) {
1129            $computer_not_detected{$one_computer} = $current_net;
1130            next LOOP_ON_COMPUTER;
1131            }
1132
1133         my $switch_proposal = q{};
1134         if (exists $computerdb->{$resol_arp{'ipv4_address'}} and exists $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'}) {
1135            $switch_proposal = $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'};
1136            }
1137
1138         # do not have a mac address
1139         if ($resol_arp{'mac_address'} eq 'unknow' or (exists $resol_arp{'timestamps'} and $resol_arp{'timestamps'} < ($timestamp - 3 * 3600))) {
1140            $computer_not_detected{$one_computer} = $current_net;
1141            next LOOP_ON_COMPUTER;
1142            }
1143
1144         my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1145         my $vlan_id   = get_current_vlan_id($vlan_name);
1146         my %where = find_switch_port($resol_arp{'mac_address'}, $switch_proposal, $vlan_id);
1147
1148         #192.168.24.156:
1149         #  arp: 00:0B:DB:D5:F6:65
1150         #  hostname: pcroyon.hmg.priv
1151         #  port: 5
1152         #  switch: sw-batH-legi:hp2524
1153         #  timestamp: 1164355525
1154
1155         # do not have a mac address
1156#         if ($resol_arp{'mac_address'} eq 'unknow') {
1157#            $computer_not_detected{$one_computer} = $current_interface;
1158#            next LOOP_ON_COMPUTER;
1159#            }
1160
1161         # detected on a switch
1162         if ($where{'switch_description'} ne 'unknow') {
1163            $detected_computer++;
1164            $computerdb->{$resol_arp{'ipv4_address'}} = {
1165               hostname_fq        => $resol_arp{'hostname_fq'},
1166               mac_address        => $resol_arp{'mac_address'},
1167               switch_hostname    => $where{'switch_hostname'},
1168               switch_description => $where{'switch_description'},
1169               switch_port_id     => $where{'switch_port_id'},
1170               switch_port_hr     => $where{'switch_port_hr'},
1171               timestamp          => $timestamp,
1172               network            => $current_net,
1173               };
1174            next LOOP_ON_COMPUTER;
1175            }
1176
1177         # new in the database but where it is ?
1178         if (not exists $computerdb->{$resol_arp{'ipv4_address'}}) {
1179            $detected_computer++;
1180            $computerdb->{$resol_arp{'ipv4_address'}} = {
1181               hostname_fq        => $resol_arp{'hostname_fq'},
1182               mac_address        => $resol_arp{'mac_address'},
1183               switch_hostname    => $where{'switch_hostname'},
1184               switch_description => $where{'switch_description'},
1185               switch_port_id     => $where{'switch_port_id'},
1186               switch_port_hr     => $where{'switch_port_hr'},
1187               timestamp          => $resol_arp{'timestamp'},
1188               network            => $current_net,
1189               };
1190            }
1191
1192         # mise a jour du nom de la machine si modification dans le dns
1193         $computerdb->{$resol_arp{'ipv4_address'}}{'hostname_fq'} = $resol_arp{'hostname_fq'};
1194
1195         # mise à jour de la date de détection si détection plus récente par arpwatch
1196         $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'}   = $resol_arp{'timestamp'} if exists $resol_arp{'timestamp'} and $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $resol_arp{'timestamp'};
1197
1198         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
1199#         push @computer_not_detected, $resol_arp{'ipv4_address'} if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1200         $computer_not_detected{$resol_arp{'ipv4_address'}} = $current_net if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1201
1202         }
1203      }
1204
1205   # final end of line at the end of the loop
1206   printf "\n";
1207
1208   my $dirdb = $KLASK_DB_FILE;
1209      $dirdb =~ s{ / [^/]* $}{}xms;
1210   mkdir "$dirdb", 0755 unless -d "$dirdb";
1211   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1212
1213   for my $one_computer (keys %computer_not_detected) {
1214      my $current_net = $computer_not_detected{$one_computer};
1215      my $current_interface = get_current_interface($current_net);
1216      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';
1217      }
1218   return;
1219   }
1220
1221#---------------------------------------------------------------
1222sub cmd_removedb {
1223   my @computer = @_;
1224
1225   test_maindb_environnement();
1226
1227   my $computerdb = computerdb_load();
1228
1229   LOOP_ON_COMPUTER:
1230   for my $one_computer (@computer) {
1231
1232      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
1233            and exists $computerdb->{$one_computer} ) {
1234         delete $computerdb->{$one_computer};
1235         next;
1236         }
1237
1238      my %resol_arp = resolve_ip_arp_host($one_computer);
1239
1240      delete $computerdb->{$resol_arp{'ipv4_address'}} if exists $computerdb->{$resol_arp{'ipv4_address'}};
1241      }
1242
1243   my $dirdb = $KLASK_DB_FILE;
1244      $dirdb =~ s{ / [^/]* $}{}xms;
1245   mkdir "$dirdb", 0755 unless -d "$dirdb";
1246   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1247   return;
1248   }
1249
1250#---------------------------------------------------------------
1251sub cmd_cleandb {
1252   my @ARGV  = @_;
1253
1254   my $days_to_clean = 15;
1255   my $repairdns;
1256   my $verbose;
1257   my $database_has_changed;
1258
1259   GetOptions(
1260      'day|d=i'   => \$days_to_clean,
1261      'verbose|v' => \$verbose,
1262      'repair-dns|r' => \$repairdns,
1263      );
1264
1265   my @vlan_name = get_list_network();
1266
1267   my $computerdb = computerdb_load();
1268   my $timestamp = time;
1269
1270   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
1271   my $timestamp_3month  = 3600 * 24 * 90;
1272
1273   my %mactimedb = ();
1274   ALL_VLAN:
1275   for my $vlan (shuffle @vlan_name) {
1276
1277      my @ip_list   = shuffle get_list_ip($vlan);
1278
1279      LOOP_ON_IP_ADDRESS:
1280      for my $ip (@ip_list) {
1281
1282         next LOOP_ON_IP_ADDRESS if
1283            not exists $computerdb->{$ip};
1284
1285            #&& $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
1286         my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1287         my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1288         my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1289
1290         $mactimedb{$ip_mac} ||= {
1291            ip          => $ip,
1292            timestamp   => $ip_timestamp,
1293            vlan        => $vlan,
1294            hostname_fq => $ip_hostname_fq,
1295            };
1296
1297         if (
1298            ( $mactimedb{$ip_mac}->{'timestamp'} - $ip_timestamp > $timestamp_barrier
1299               or (
1300                  $mactimedb{$ip_mac}->{'timestamp'} > $ip_timestamp
1301                  and $timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_3month
1302                  )
1303            )
1304            and (
1305               not $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1306               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1307               )) {
1308            print "remove ip $ip\n" if $verbose;
1309            delete $computerdb->{$ip};
1310            $database_has_changed++;
1311            }
1312
1313         elsif (
1314            ( $ip_timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_barrier
1315               or (
1316                  $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}
1317                  and $timestamp - $ip_timestamp > $timestamp_3month
1318                  )
1319            )
1320            and (
1321               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1322               or $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1323               )) {
1324            print "remove ip ".$mactimedb{$ip_mac}->{ip}."\n" if $verbose;
1325            delete $computerdb->{$mactimedb{$ip_mac}->{ip}};
1326            $database_has_changed++;
1327            }
1328
1329         if ( $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}) {
1330            $mactimedb{$ip_mac} = {
1331               ip          => $ip,
1332               timestamp   => $ip_timestamp,
1333               vlan        => $vlan,
1334               hostname_fq => $ip_hostname_fq,
1335               };
1336            }
1337         }
1338      }
1339
1340   if ($repairdns) { # Search and update unkown computer in reverse DNS
1341      LOOP_ON_IP_ADDRESS:
1342      for my $ip (keys %{$computerdb}) {
1343         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} ne 'unknow';
1344
1345         my $packed_ip = scalar gethostbyname($ip);
1346         next LOOP_ON_IP_ADDRESS if not defined $packed_ip;
1347
1348         my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
1349         next LOOP_ON_IP_ADDRESS if not defined $hostname_fq;
1350
1351         $computerdb->{$ip}{'hostname_fq'} = $hostname_fq;
1352         $database_has_changed++;
1353         }
1354      }
1355
1356   if ( $database_has_changed ) {
1357      my $dirdb = $KLASK_DB_FILE;
1358         $dirdb =~ s{ / [^/]* $}{}xms;
1359      mkdir "$dirdb", 0755 unless -d "$dirdb";
1360      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1361      }
1362   return;
1363   }
1364
1365#---------------------------------------------------------------
1366sub cmd_exportdb {
1367   @ARGV = @_;
1368
1369   my $format = 'txt';
1370
1371   GetOptions(
1372      'format|f=s'  => \$format,
1373      );
1374
1375   my %possible_format = (
1376      txt  => \&cmd_exportdb_txt,
1377      html => \&cmd_exportdb_html,
1378      );
1379
1380   $format = 'txt' if not defined $possible_format{$format};
1381
1382   $possible_format{$format}->(@ARGV);
1383   return;
1384   }
1385
1386#---------------------------------------------------------------
1387sub cmd_exportdb_txt {
1388   test_maindb_environnement();
1389
1390   my $computerdb = computerdb_load();
1391
1392   printf "%-28s %8s              %-40s %-15s %-18s %-16s %s\n", qw(Switch Port Hostname-FQ IPv4-Address MAC-Address Date VLAN);
1393   print "--------------------------------------------------------------------------------------------------------------------------------------------\n";
1394
1395   LOOP_ON_IP_ADDRESS:
1396   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1397
1398      # to be improve in the future
1399      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1400
1401      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1402      $year += 1900;
1403      $mon++;
1404      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1405
1406      my $vlan = '';
1407      $vlan = $computerdb->{$ip}{'network'}.'('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1408
1409      my $arrow ='<-----------';
1410         $arrow ='<===========' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1411
1412      printf "%-28s %8s %12s %-40s %-15s %-18s %-16s %s\n",
1413         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1414         $computerdb->{$ip}{'switch_port_hr'},
1415         $arrow,
1416         $computerdb->{$ip}{'hostname_fq'},
1417         $ip,
1418         $computerdb->{$ip}{'mac_address'},
1419         $date,
1420         $vlan;
1421      }
1422   return;
1423   }
1424
1425#---------------------------------------------------------------
1426sub cmd_exportdb_html {
1427   test_maindb_environnement();
1428
1429   my $computerdb = computerdb_load();
1430
1431#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1432#<script src="sorttable-klask.js"></script>
1433
1434   print <<'END_HTML';
1435<table class="sortable" summary="Klask Host Database">
1436 <caption>Klask Host Database</caption>
1437 <thead>
1438  <tr>
1439   <th scope="col" class="klask-header-left">Switch</th>
1440   <th scope="col" class="sorttable_nosort">Port</th>
1441   <th scope="col" class="sorttable_nosort">Link</th>
1442   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1443   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1444   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1445   <th scope="col" class="sorttable_alpha">VLAN</th>
1446   <th scope="col" class="klask-header-right">Date</th>
1447  </tr>
1448 </thead>
1449 <tfoot>
1450  <tr>
1451   <th scope="col" class="klask-footer-left">Switch</th>
1452   <th scope="col" class="fklask-port">Port</th>
1453   <th scope="col" class="fklask-link">Link</th>
1454   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1455   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1456   <th scope="col" class="fklask-mac">MAC-Address</th>
1457   <th scope="col" class="fklask-vlan">VLAN</th>
1458   <th scope="col" class="klask-footer-right">Date</th>
1459  </tr>
1460 </tfoot>
1461 <tbody>
1462END_HTML
1463
1464   my %mac_count = ();
1465   LOOP_ON_IP_ADDRESS:
1466   for my $ip (keys %{$computerdb}) {
1467
1468      # to be improve in the future
1469      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1470
1471      $mac_count{$computerdb->{$ip}{'mac_address'}}++;
1472      }
1473
1474   my $typerow = 'even';
1475
1476   LOOP_ON_IP_ADDRESS:
1477   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1478
1479      # to be improve in the future
1480      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1481
1482      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1483      $year += 1900;
1484      $mon++;
1485      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1486
1487#      $odd_or_even++;
1488#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1489      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1490
1491      #my $arrow ='&#8592;';
1492      #   $arrow ='&#8656;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1493      my $arrow ='&#10229;';
1494         $arrow ='&#10232;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1495
1496      my $switch_hostname = $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'} || 'unkown';
1497      chomp $switch_hostname;
1498      my $switch_hostname_sort = sprintf '%s %06i' ,$switch_hostname, $computerdb->{$ip}{'switch_port_id'}; # Take switch index
1499
1500      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1501
1502      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{'mac_address'}}, $computerdb->{$ip}{'mac_address'};
1503
1504      $computerdb->{$ip}{'hostname_fq'} = 'unknow' if $computerdb->{$ip}{'hostname_fq'} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1505      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{'hostname_fq'};
1506
1507      my $vlan = '';
1508      $vlan = $computerdb->{$ip}{'network'}.' ('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1509
1510      my $parent_port_hr = format_aggregator4html($computerdb->{$ip}{'switch_port_hr'});
1511
1512      print <<"END_HTML";
1513  <tr class="$typerow">
1514   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1515   <td class="bklask-port">$parent_port_hr</td>
1516   <td>$arrow</td>
1517   <td sorttable_customkey="$host_short">$computerdb->{$ip}{'hostname_fq'}</td>
1518   <td sorttable_customkey="$ip_sort">$ip</td>
1519   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{'mac_address'}</td>
1520   <td>$vlan</td>
1521   <td>$date</td>
1522  </tr>
1523END_HTML
1524#   <td colspan="2">$arrow</td>
1525      }
1526
1527   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1528
1529   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1530   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1531   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1532   my %db_switch                   = %{$switch_connection->{'switch_db'}};
1533
1534   # Output switch connection
1535   LOOP_ON_OUTPUT_SWITCH:
1536   for my $sw (sort keys %db_switch_output_port) {
1537
1538      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1539
1540      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1541
1542      #my $arrow ='&#8702;';
1543      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1544      my $arrow ='&#10236;';
1545         $arrow ='&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1546
1547      if (exists $db_switch_parent{$sw}) {
1548         # Link to uplink switch
1549         next LOOP_ON_OUTPUT_SWITCH;
1550
1551         # Do not print anymore
1552         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1553         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1554         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
1555
1556         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1557         $year += 1900;
1558         $mon++;
1559         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1560
1561         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1562
1563         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1564
1565         my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'};
1566
1567         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1568         $vlan .= ' ('.get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}).')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1569
1570         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1571         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
1572
1573         print <<"END_HTML";
1574  <tr class="$typerow">
1575   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1576   <td class="bklask-port">$parent_port_hr</td>
1577   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
1578   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{'switch'}</td>
1579   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1580   <td sorttable_customkey="$mac_sort">$mac_address</td>
1581   <td>$vlan</td>
1582   <td>$date</td>
1583  </tr>
1584END_HTML
1585         }
1586      else {
1587         # Router
1588         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1589
1590         my $host_short = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1591
1592         my $mac_address = $db_switch{$sw}->{'mac_address'};
1593         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1594         my $timestamp = $db_switch{$sw}->{'timestamp'};
1595
1596         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1597         $year += 1900;
1598         $mon++;
1599         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1600
1601         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1602
1603         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1604
1605         my $vlan = $db_switch{$sw}->{'network'};
1606         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1607
1608         my $arrow ='&#10235;';
1609            $arrow ='&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1610
1611         print <<"END_HTML";
1612  <tr class="$typerow">
1613   <td sorttable_customkey="router">router</td>
1614   <td class="bklask-port"></td>
1615   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$parent_port_hr</td></tr></table></td>
1616   <td sorttable_customkey="$host_short">$sw</td>
1617   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1618   <td sorttable_customkey="$mac_sort">$mac_address</td>
1619   <td>$vlan</td>
1620   <td>$date</td>
1621  </tr>
1622END_HTML
1623         #<td>$arrow</td><td>$parent_port_hr</td>
1624
1625         next LOOP_ON_OUTPUT_SWITCH;
1626
1627         # Old print
1628         print <<"END_HTML";
1629  <tr class="$typerow">
1630   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1631   <td class="bklask-port">$parent_port_hr</td>
1632   <td>$arrow</td><td></td>
1633   <td sorttable_customkey="router">router</td>
1634   <td sorttable_customkey="999999999999"></td>
1635   <td sorttable_customkey="99999"></td>
1636   <td></td>
1637   <td></td>
1638  </tr>
1639END_HTML
1640         }
1641      }
1642
1643   # Child switch connection : parent <- child
1644   for my $swport (sort keys %db_switch_connected_on_port) {
1645      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1646      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1647
1648         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1649
1650         my $mac_address = $db_switch{$sw}->{'mac_address'};
1651         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1652         my $timestamp = $db_switch{$sw}->{'timestamp'};
1653
1654         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1655         $year += 1900;
1656         $mon++;
1657         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1658
1659         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1660
1661         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1662
1663         $typerow = $typerow eq 'even' ? 'odd' : 'even';
1664
1665         #my $arrow ='&#8701;';
1666         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
1667         my $arrow ='&#10235;';
1668            $arrow ='&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
1669
1670         my $vlan = $db_switch{$sw}->{'network'};
1671         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1672
1673         if (exists $db_switch_output_port{$sw}) {
1674
1675            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1676
1677            my $parent_port_hr = format_aggregator4html($port_connect);
1678            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
1679
1680            print <<"END_HTML";
1681  <tr class="$typerow">
1682   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1683   <td class="bklask-port">$parent_port_hr</td>
1684   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
1685   <td sorttable_customkey="$host_short">$sw</td>
1686   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1687   <td sorttable_customkey="$mac_sort">$mac_address</td>
1688   <td>$vlan</td>
1689   <td>$date</td>
1690  </tr>
1691END_HTML
1692            }
1693         else {
1694            my $parent_port_hr = format_aggregator4html($port_connect);
1695
1696            print <<"END_HTML";
1697  <tr class="$typerow">
1698   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1699   <td class="bklask-port">$parent_port_hr</td>
1700   <td>$arrow</td>
1701   <td sorttable_customkey="$sw">$sw</td>
1702   <td sorttable_customkey="">$ipv4_address</td>
1703   <td sorttable_customkey="">$mac_address</td>
1704   <td>$vlan</td>
1705   <td>$date</td>
1706  </tr>
1707END_HTML
1708            }
1709         }
1710      }
1711
1712   print <<'END_HTML';
1713 </tbody>
1714</table>
1715END_HTML
1716   return;
1717   }
1718
1719#---------------------------------------------------------------
1720sub cmd_bad_vlan_id {
1721   @ARGV = @_;
1722
1723   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1724   my $verbose;
1725
1726   GetOptions(
1727      'day|d=i'   => \$days_before_alert,
1728      );
1729
1730   test_maindb_environnement();
1731
1732   my $computerdb = computerdb_load();
1733
1734   # create a database with the most recent computer by switch port
1735   my %switchportdb = ();
1736   LOOP_ON_IP_ADDRESS:
1737   for my $ip (keys %{$computerdb}) {
1738      # to be improve in the future
1739      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1740      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
1741      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port_id'} eq '0';
1742
1743      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1744      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1745      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1746
1747      my $swpt = sprintf "%-28s  %2s",
1748         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1749         $computerdb->{$ip}{'switch_port_hr'};
1750      $switchportdb{$swpt} ||= {
1751         ip          => $ip,
1752         timestamp   => $ip_timestamp,
1753         vlan        => $computerdb->{$ip}{'network'},
1754         hostname_fq => $ip_hostname_fq,
1755         mac_address => $ip_mac,
1756         };
1757
1758      # if float computer, set date 15 day before warning...
1759      my $ip_timestamp_mod = $ip_timestamp;
1760      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
1761      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1762      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1763
1764      if ($ip_timestamp_mod > $ip_timestamp_ref) {
1765         $switchportdb{$swpt} = {
1766            ip          => $ip,
1767            timestamp   => $ip_timestamp,
1768            vlan        => $computerdb->{$ip}{'network'},
1769            hostname_fq => $ip_hostname_fq,
1770            mac_address => $ip_mac,
1771            };
1772         }
1773      }
1774
1775   LOOP_ON_RECENT_COMPUTER:
1776   for my $swpt (keys %switchportdb) {
1777      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
1778      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
1779
1780      my $src_ip = $switchportdb{$swpt}->{ip};
1781      my $src_timestamp = 0;
1782      LOOP_ON_IP_ADDRESS:
1783      for my $ip (keys %{$computerdb}) {
1784         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne  $switchportdb{$swpt}->{'mac_address'};
1785         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1786         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
1787
1788         $src_ip = $ip;
1789         $src_timestamp = $computerdb->{$ip}{'timestamp'};
1790         }
1791
1792      # keep only if float computer is the most recent
1793      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
1794      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
1795
1796      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
1797      $year += 1900;
1798      $mon++;
1799      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1800
1801      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
1802      $year += 1900;
1803      $mon++;
1804      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1805
1806      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
1807
1808      printf "%s / %-10s +-> %-10s(%i)  %s %s %s %s\n",
1809         $swpt, $switchportdb{$swpt}->{'vlan'}, $computerdb->{$src_ip}{'network'}, $vlan_id,
1810         $date,
1811         $src_date,
1812         $computerdb->{$src_ip}{'mac_address'},
1813         $computerdb->{$src_ip}{'hostname_fq'};
1814      }
1815   }
1816
1817#---------------------------------------------------------------
1818sub cmd_poe_enable {
1819   @ARGV = @_;
1820
1821   my $verbose;
1822   GetOptions(
1823      'verbose|v' => \$verbose,
1824      );
1825
1826   my $switch_name = shift @ARGV || q{};
1827   my $switch_port = shift @ARGV || q{};
1828
1829   if ($switch_name eq q{} or $switch_port eq q{}) {
1830      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
1831      }
1832
1833   for my $sw_name (split /,/, $switch_name) {
1834      if (not defined $SWITCH_DB{$sw_name}) {
1835         die "Switch $sw_name must be defined in klask configuration file\n";
1836         }
1837
1838      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1839
1840      my $sw = $SWITCH_DB{$sw_name};
1841      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1842      print "$error \n" if $error;
1843
1844      my $result = $session->set_request(
1845         -varbindlist => [$oid_search, INTEGER, 8], # Only NEXANS
1846         );
1847      print $session->error()."\n" if $session->error_status();
1848
1849      $session->close;
1850      }
1851   cmd_poe_status($switch_name, $switch_port);
1852   return;
1853   }
1854
1855#---------------------------------------------------------------
1856sub cmd_poe_disable {
1857   @ARGV = @_;
1858
1859   my $verbose;
1860   GetOptions(
1861      'verbose|v' => \$verbose,
1862      );
1863
1864   my $switch_name = shift @ARGV || q{};
1865   my $switch_port = shift @ARGV || q{};
1866
1867   if ($switch_name eq q{} or $switch_port eq q{}) {
1868      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
1869      }
1870
1871   for my $sw_name (split /,/, $switch_name) {
1872      if (not defined $SWITCH_DB{$sw_name}) {
1873         die "Switch $sw_name must be defined in klask configuration file\n";
1874         }
1875
1876      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1877
1878      my $sw = $SWITCH_DB{$sw_name};
1879      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1880      print "$error \n" if $error;
1881
1882      my $result = $session->set_request(
1883         -varbindlist => [$oid_search, INTEGER, 2], # Only NEXANS
1884         );
1885      print $session->error()."\n" if $session->error_status();
1886
1887      $session->close;
1888      }
1889   cmd_poe_status($switch_name, $switch_port);
1890   return;
1891   }
1892
1893#---------------------------------------------------------------
1894sub cmd_poe_status {
1895   @ARGV = @_;
1896
1897   my $verbose;
1898   GetOptions(
1899      'verbose|v' => \$verbose,
1900      );
1901
1902   my $switch_name = shift @ARGV || q{};
1903   my $switch_port = shift @ARGV || q{};
1904
1905   if ($switch_name eq q{} or $switch_port eq q{}) {
1906      die "Usage: klask poe-status SWITCH_NAME PORT\n";
1907      }
1908
1909   for my $sw_name (split /,/, $switch_name) {
1910      if (not defined $SWITCH_DB{$sw_name}) {
1911         die "Switch $sw_name must be defined in klask configuration file\n";
1912         }
1913
1914      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1915
1916      my $sw = $SWITCH_DB{$sw_name};
1917      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1918      print "$error \n" if $error;
1919
1920      my $result = $session->get_request(
1921         -varbindlist => [$oid_search],
1922         );
1923
1924      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
1925         my $poe_status = $result->{$oid_search} || 'empty';
1926         $poe_status =~ s/8/enable/;
1927         $poe_status =~ s/2/disable/;
1928         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe_status;
1929         }
1930      else {
1931         print "Klask do not find PoE status on switch $sw_name on port $switch_port\n";
1932         }
1933
1934      $session->close;
1935      }
1936   return;
1937   }
1938
1939#---------------------------------------------------------------
1940# not finish - do not use
1941sub cmd_port_setvlan {
1942   my $switch_name = shift || q{};
1943   my $mac_address = shift || q{};
1944
1945   if ($switch_name eq q{} or $mac_address eq q{}) {
1946      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1947      }
1948
1949   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
1950
1951   for my $sw_name (split /,/, $switch_name) {
1952      if (not defined $SWITCH_DB{$sw_name}) {
1953         die "Switch $sw_name must be defined in klask configuration file\n";
1954         }
1955
1956      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
1957      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. 0 . mac_address_hex2dec($mac_address);
1958      print "Klask search OID $oid_search_port1 on switch $sw_name\n";
1959      print "Klask search OID $oid_search_port2 on switch $sw_name\n";
1960
1961      my $sw = $SWITCH_DB{$sw_name};
1962      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1963      print "$error \n" if $error;
1964
1965      my $result = $session->get_request(
1966         -varbindlist => [$oid_search_port1]
1967         );
1968      if (not defined $result) {
1969         $result = $session->get_request(
1970            -varbindlist => [$oid_search_port2]
1971            );
1972         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
1973         }
1974
1975      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
1976         my $swport = $result->{$oid_search_port1};
1977         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1978         }
1979      else {
1980         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1981         }
1982
1983      $session->close;
1984      }
1985   return;
1986   }
1987
1988#---------------------------------------------------------------
1989sub cmd_port_getvlan {
1990   @ARGV = @_;
1991
1992   my $verbose;
1993   GetOptions(
1994      'verbose|v' => \$verbose,
1995      );
1996
1997   my $switch_name = shift @ARGV || q{};
1998   my $switch_port = shift @ARGV || q{};
1999
2000   if ($switch_name eq q{} or $switch_port eq q{}) {
2001      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
2002      }
2003
2004   for my $sw_name (split /,/, $switch_name) {
2005      if (not defined $SWITCH_DB{$sw_name}) {
2006         die "Switch $sw_name must be defined in klask configuration file\n";
2007         }
2008
2009      my $oid_search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
2010
2011      my $sw = $SWITCH_DB{$sw_name};
2012      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2013      print "$error \n" if $error;
2014
2015      my $result = $session->get_request(
2016         -varbindlist => [$oid_search],
2017         );
2018
2019      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2020         my $vlan_id = $result->{$oid_search} || 'empty';
2021         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2022         }
2023      else {
2024         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
2025         }
2026
2027      $session->close;
2028      }
2029   return;
2030   }
2031
2032#---------------------------------------------------------------
2033sub cmd_vlan_setname {
2034   }
2035
2036#---------------------------------------------------------------
2037# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
2038sub cmd_rebootsw {
2039   @ARGV = @_;
2040
2041   my $verbose;
2042   GetOptions(
2043      'verbose|v' => \$verbose,
2044      );
2045
2046   my $switch_name = shift @ARGV || q{};
2047
2048   if ($switch_name eq q{}) {
2049      die "Usage: klask rebootsw SWITCH_NAME\n";
2050      }
2051
2052   for my $sw_name (split /,/, $switch_name) {
2053      if (not defined $SWITCH_DB{$sw_name}) {
2054         die "Switch $sw_name must be defined in klask configuration file\n";
2055         }
2056
2057      my $sw = $SWITCH_DB{$sw_name};
2058      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2059      print "$error \n" if $error;
2060
2061      my $result = $session->set_request(
2062         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
2063         );
2064
2065      $session->close;
2066      }
2067   return;
2068   }
2069
2070#---------------------------------------------------------------
2071sub cmd_vlan_getname {
2072   my $switch_name = shift || q{};
2073   my $vlan_id     = shift || q{};
2074
2075   if ($switch_name eq q{} or $vlan_id eq q{}) {
2076      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
2077      }
2078
2079   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2080
2081   for my $sw_name (split /,/, $switch_name) {
2082      if (not defined $SWITCH_DB{$sw_name}) {
2083         die "Switch $sw_name must be defined in klask configuration file\n";
2084         }
2085
2086      my $oid_search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
2087
2088      my $sw = $SWITCH_DB{$sw_name};
2089      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2090      print "$error \n" if $error;
2091
2092      my $result = $session->get_request(
2093         -varbindlist => [$oid_search_vlan_name]
2094         );
2095
2096      if (defined $result and $result->{$oid_search_vlan_name} ne 'noSuchInstance') {
2097         my $vlan_name = $result->{$oid_search_vlan_name} || 'empty';
2098         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
2099         }
2100      else {
2101         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2102         }
2103
2104      $session->close;
2105      }
2106   return;
2107   }
2108
2109#---------------------------------------------------------------
2110sub cmd_vlan_list {
2111   my $switch_name = shift || q{};
2112
2113   if ($switch_name eq q{}) {
2114      die "Usage: klask vlan-list SWITCH_NAME\n";
2115      }
2116
2117   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2118
2119   for my $sw_name (split /,/, $switch_name) {
2120      if (not defined $SWITCH_DB{$sw_name}) {
2121         die "Switch $sw_name must be defined in klask configuration file\n";
2122         }
2123
2124      my $sw = $SWITCH_DB{$sw_name};
2125      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2126      print "$error \n" if $error;
2127
2128      my %vlandb = snmp_get_vlan_list($session);
2129      $session->close;
2130
2131      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2132      for my $vlan_id (keys %vlandb) {
2133         printf "%7i - %s\n", $vlan_id, $vlandb{$vlan_id};
2134         }
2135      }
2136   return;
2137   }
2138
2139#---------------------------------------------------------------
2140sub cmd_ip_location {
2141   my $computerdb = computerdb_load();
2142
2143   LOOP_ON_IP_ADDRESS:
2144   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
2145
2146      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2147
2148      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
2149      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
2150
2151      my $sw_location = q{};
2152      LOOP_ON_ALL_SWITCH:
2153      for my $sw (@SWITCH_LIST) {
2154         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2155         $sw_location = $sw->{'location'};
2156         last;
2157         }
2158
2159      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
2160      }
2161   return;
2162   }
2163
2164#---------------------------------------------------------------
2165sub cmd_ip_free {
2166   @ARGV = @_;
2167
2168   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
2169   my $format = 'txt';
2170   my $verbose;
2171
2172   GetOptions(
2173      'day|d=i'      => \$days_to_death,
2174      'format|f=s'   => \$format,
2175      'verbose|v'    => \$verbose,
2176      );
2177
2178   my %possible_format = (
2179      txt  => \&cmd_ip_free_txt,
2180      html => \&cmd_ip_free_html,
2181      none => sub {},
2182      );
2183   $format = 'txt' if not defined $possible_format{$format};
2184
2185   my @vlan_name = @ARGV;
2186   @vlan_name = get_list_network() if not @vlan_name;
2187
2188   my $computerdb = {};
2189      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
2190   my $timestamp = time;
2191
2192   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
2193
2194   my %result_ip = ();
2195
2196   ALL_NETWORK:
2197   for my $vlan (@vlan_name) {
2198
2199      my @ip_list = get_list_ip($vlan);
2200
2201      LOOP_ON_IP_ADDRESS:
2202      for my $ip (@ip_list) {
2203
2204         if (exists $computerdb->{$ip}) {
2205            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
2206
2207            my $mac_address = $computerdb->{$ip}{'mac_address'};
2208            LOOP_ON_DATABASE:
2209            for my $ip_db (keys %{$computerdb}) {
2210               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2211               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
2212               }
2213            }
2214
2215         my $ip_date_last_detection = '';
2216         if (exists $computerdb->{$ip}) {
2217            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
2218            $year += 1900;
2219            $mon++;
2220            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2221            }
2222
2223         my $packed_ip = scalar gethostbyname($ip);
2224         my $hostname_fq = 'unknown';
2225            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
2226
2227         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2228
2229         $result_ip{$ip} ||= {};
2230         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2231         $result_ip{$ip}->{'hostname_fq'} = $hostname_fq;
2232         $result_ip{$ip}->{'vlan'} = $vlan;
2233
2234         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
2235         }
2236      }
2237
2238   $possible_format{$format}->(%result_ip);
2239   }
2240
2241#---------------------------------------------------------------
2242sub cmd_ip_free_txt {
2243   my %result_ip = @_;
2244
2245   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2246   print "-------------------------------------------------------------------------------\n";
2247   LOOP_ON_IP_ADDRESS:
2248   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2249         my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2250         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
2251      }
2252   }
2253
2254#---------------------------------------------------------------
2255sub cmd_ip_free_html {
2256   my %result_ip = @_;
2257
2258   print <<'END_HTML';
2259<table class="sortable" summary="Klask Free IP Database">
2260 <caption>Klask Free IP Database</caption>
2261 <thead>
2262  <tr>
2263   <th scope="col" class="klask-header-left">IPv4-Address</th>
2264   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2265   <th scope="col" class="sorttable_alpha">VLAN</th>
2266   <th scope="col" class="klask-header-right">Date</th>
2267  </tr>
2268 </thead>
2269 <tfoot>
2270  <tr>
2271   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2272   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2273   <th scope="col" class="fklask-vlan">VLAN</th>
2274   <th scope="col" class="klask-footer-right">Date</th>
2275  </tr>
2276 </tfoot>
2277 <tbody>
2278END_HTML
2279
2280   my $typerow = 'even';
2281
2282   LOOP_ON_IP_ADDRESS:
2283   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2284
2285      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2286
2287      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
2288      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
2289
2290      my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2291
2292      print <<"END_HTML";
2293  <tr class="$typerow">
2294   <td sorttable_customkey="$ip_sort">$ip</td>
2295   <td sorttable_customkey="$host_short">$result_ip{$ip}->{'hostname_fq'}</td>
2296   <td>$vlan_nameid</td>
2297   <td>$result_ip{$ip}->{'date_last_detection'}</td>
2298  </tr>
2299END_HTML
2300      }
2301   print <<'END_HTML';
2302 </tbody>
2303</table>
2304END_HTML
2305   }
2306
2307#---------------------------------------------------------------
2308sub cmd_enable {
2309   @ARGV = @_;
2310
2311   my $verbose;
2312
2313   GetOptions(
2314      'verbose|v' => \$verbose,
2315      );
2316
2317   my $switch_name = shift @ARGV || q{};
2318   my $port_hr     = shift @ARGV || q{};
2319
2320   if ($switch_name eq q{} or $port_hr eq q{}) {
2321      die "Usage: klask disable SWITCH_NAME PORT\n";
2322      }
2323
2324   if (not defined $SWITCH_DB{$switch_name}) {
2325      die "Switch $switch_name must be defined in klask configuration file\n";
2326      }
2327
2328   my $sw = $SWITCH_DB{$switch_name};
2329   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2330   print "$error \n" if $error;
2331
2332   # Retrieve numeric port value
2333   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2334   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2335
2336   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2337   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2338
2339   my $result = $session->set_request(
2340      -varbindlist => [$oid_search_portstatus, INTEGER, 1],
2341      );
2342   print $session->error()."\n" if $session->error_status();
2343
2344   $session->close;
2345
2346   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2347   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2348   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2349   return;
2350   }
2351
2352#---------------------------------------------------------------
2353sub cmd_disable {
2354   @ARGV = @_;
2355
2356   my $verbose;
2357
2358   GetOptions(
2359      'verbose|v' => \$verbose,
2360      );
2361
2362   my $switch_name = shift @ARGV || q{};
2363   my $port_hr     = shift @ARGV || q{};
2364
2365   if ($switch_name eq q{} or $port_hr eq q{}) {
2366      die "Usage: klask disable SWITCH_NAME PORT\n";
2367      }
2368
2369   if (not defined $SWITCH_DB{$switch_name}) {
2370      die "Switch $switch_name must be defined in klask configuration file\n";
2371      }
2372
2373   my $sw = $SWITCH_DB{$switch_name};
2374   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2375   print "$error \n" if $error;
2376
2377   # Retrieve numeric port value
2378   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2379   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2380
2381   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2382   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2383
2384   my $result = $session->set_request(
2385      -varbindlist => [$oid_search_portstatus, INTEGER, 2],
2386      );
2387   print $session->error()."\n" if $session->error_status();
2388
2389   $session->close;
2390
2391   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2392   return;
2393   }
2394
2395#---------------------------------------------------------------
2396sub cmd_status {
2397   @ARGV = @_;
2398
2399   my $verbose;
2400
2401   GetOptions(
2402      'verbose|v' => \$verbose,
2403      );
2404
2405   my $switch_name = shift @ARGV || q{};
2406   my $port_hr     = shift @ARGV || q{};
2407
2408   if ($switch_name eq q{} or $port_hr eq q{}) {
2409      die "Usage: klask status SWITCH_NAME PORT\n";
2410      }
2411
2412   if (not defined $SWITCH_DB{$switch_name}) {
2413      die "Switch $switch_name must be defined in klask configuration file\n";
2414      }
2415
2416   my $sw = $SWITCH_DB{$switch_name};
2417   my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2418   print "$error \n" if $error;
2419
2420   # Retrieve numeric port value
2421   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2422   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2423
2424   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2425   print "Info: switch $switch_name port $port_hr ($port_id) SNMP OID $oid_search_portstatus\n" if $verbose;
2426
2427   my $result = $session->get_request(
2428      -varbindlist => [$oid_search_portstatus]
2429      );
2430   print $session->error()."\n" if $session->error_status();
2431   if (defined $result) {
2432      print "$PORT_UPDOWN{$result->{$oid_search_portstatus}}\n";
2433      }
2434
2435   $session->close;
2436
2437   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
2438   return;
2439   }
2440
2441#---------------------------------------------------------------
2442sub cmd_search_mac_on_switch {
2443   @ARGV = @_;
2444
2445   my $verbose;
2446   my $vlan_id = 0;
2447
2448   GetOptions(
2449      'verbose|v' => \$verbose,
2450      'vlan|l=i'  => \$vlan_id,
2451      );
2452
2453   my $switch_name = shift @ARGV || q{};
2454   my $mac_address = shift @ARGV || q{};
2455
2456   if ($switch_name eq q{} or $mac_address eq q{}) {
2457      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2458      }
2459
2460   $mac_address = normalize_mac_address($mac_address);
2461   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*} or $switch_name eq q{all};
2462
2463   for my $sw_name (split /,/, $switch_name) {
2464      if (not defined $SWITCH_DB{$sw_name}) {
2465         die "Switch $sw_name must be defined in klask configuration file\n";
2466         }
2467
2468      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2469      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
2470      print "Klask search OID $oid_search_port1 on switch $sw_name\n" if $verbose;
2471      print "Klask search OID $oid_search_port2 on switch $sw_name\n" if $verbose;
2472
2473      my $sw = $SWITCH_DB{$sw_name};
2474      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2475      print "$error \n" if $error;
2476
2477      my $result = $session->get_request(
2478         -varbindlist => [$oid_search_port1]
2479         );
2480      if (not defined $result) {
2481         $result = $session->get_request(
2482            -varbindlist => [$oid_search_port2]
2483            );
2484         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
2485         }
2486
2487      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
2488         my $swport_id = $result->{$oid_search_port1};
2489         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
2490         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
2491         }
2492      else {
2493         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
2494         }
2495
2496      $session->close;
2497      }
2498   return;
2499   }
2500
2501#---------------------------------------------------------------
2502sub cmd_updatesw {
2503   @ARGV = @_;
2504
2505   my $verbose;
2506
2507   GetOptions(
2508      'verbose|v' => \$verbose,
2509      );
2510
2511   init_switch_names('yes');    #nomme les switchs
2512   print "\n";
2513
2514   my %where = ();
2515   my %db_switch_output_port = ();
2516   my %db_switch_ip_hostnamefq = ();
2517
2518   DETECT_ALL_ROUTER:
2519   for my $one_router ( get_list_main_router(get_list_network()) ) {
2520      print "Info: router loop $one_router\n" if $verbose;
2521      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
2522
2523      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
2524      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $verbose;
2525
2526      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
2527      my $vlan_id   = get_current_vlan_id($vlan_name);
2528      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
2529      }
2530
2531   ALL_ROUTER_IP_ADDRESS:
2532   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
2533
2534      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
2535
2536      ALL_SWITCH_CONNECTED:
2537      for my $switch_detected ( keys %{$where{$ip_router}} ) {
2538
2539         my $switch = $where{$ip_router}->{$switch_detected};
2540
2541         next ALL_SWITCH_CONNECTED if $switch->{'port_id'} eq '0';
2542
2543         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
2544         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
2545         }
2546      }
2547
2548   my %db_switch_link_with = ();
2549
2550   my @list_all_switch = ();
2551   my @list_switch_ipv4 = ();
2552   for my $sw (@SWITCH_LIST) {
2553      push @list_all_switch, $sw->{'hostname'};
2554      }
2555
2556   my $timestamp = time;
2557
2558   ALL_SWITCH:
2559   for my $one_switch (@list_all_switch) {
2560      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
2561      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
2562         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
2563         fast_ping($fake_ip);
2564         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $verbose;
2565         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
2566         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
2567            $resol_arp{'mac_address'}   = $resol_arp_alt{'mac_address'};
2568            $resol_arp{'interface'}     = $resol_arp_alt{'interface'};
2569            $resol_arp{'ipv4_address'} .= '*';
2570            # Force a MAC trace on switch
2571            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
2572            }
2573         }
2574      print "Info: switch loop $one_switch\n" if $verbose;
2575      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
2576
2577      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
2578
2579      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
2580      my $vlan_id   = get_current_vlan_id($vlan_name);
2581      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
2582
2583      if ($verbose) {
2584         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
2585         print "VERBOSE_3: $one_switch --- ",
2586            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
2587            "\n";
2588         }
2589
2590      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
2591      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $verbose;
2592
2593      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
2594      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
2595      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
2596      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
2597      }
2598
2599   ALL_SWITCH_IP_ADDRESS:
2600   for my $ip (@list_switch_ipv4) {
2601#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
2602
2603      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2604
2605      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
2606#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
2607
2608      DETECTED_SWITCH:
2609      for my $switch_detected ( keys %{$where{$ip}} ) {
2610
2611         my $switch = $where{$ip}->{$switch_detected};
2612         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
2613
2614         next if $switch->{'port_id'}  eq '0';
2615         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
2616         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
2617
2618         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
2619         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{'hostname'} } = $switch->{'port_hr'};
2620         print "VERBOSE_7: +++++\n" if $verbose;
2621         }
2622
2623      }
2624
2625   my %db_switch_connected_on_port = ();
2626   my $maybe_more_than_one_switch_connected = 'yes';
2627   my $cloop = 0;
2628
2629   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2630      $cloop++;
2631      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
2632      for my $sw (keys %db_switch_link_with) {
2633         for my $connect (keys %{$db_switch_link_with{$sw}}) {
2634
2635            my $port_hr = $db_switch_link_with{$sw}->{$connect};
2636
2637            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2638            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
2639            }
2640         }
2641
2642      $maybe_more_than_one_switch_connected  = 'no';
2643
2644      SWITCH_AND_PORT:
2645      for my $swport (keys %db_switch_connected_on_port) {
2646
2647         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
2648
2649         $maybe_more_than_one_switch_connected = 'yes';
2650
2651         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2652         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
2653         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
2654
2655         CONNECTED:
2656         for my $sw_connected (@sw_on_same_port) {
2657
2658            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
2659
2660            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
2661
2662            for my $other_sw (@sw_on_same_port) {
2663               next if $other_sw eq $sw_connected;
2664
2665               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2666               }
2667
2668            # We can not do better for this switch for this loop
2669            next SWITCH_AND_PORT;
2670            }
2671         }
2672      }
2673
2674   my %db_switch_parent =();
2675
2676   for my $sw (keys %db_switch_link_with) {
2677      for my $connect (keys %{$db_switch_link_with{$sw}}) {
2678
2679         my $port_hr = $db_switch_link_with{$sw}->{$connect};
2680
2681         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2682         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
2683
2684         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
2685         }
2686      }
2687
2688   print "Switch output port and parent port connection\n";
2689   print "---------------------------------------------\n";
2690   for my $sw (sort keys %db_switch_output_port) {
2691      if (exists $db_switch_parent{$sw}) {
2692         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
2693         }
2694      else {
2695         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2696         }
2697      }
2698   print "\n";
2699
2700   print "Switch parent and children port inter-connection\n";
2701   print "------------------------------------------------\n";
2702   for my $swport (sort keys %db_switch_connected_on_port) {
2703      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2704      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2705         if (exists $db_switch_output_port{$sw}) {
2706            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2707            }
2708         else {
2709            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2710            }
2711         }
2712      }
2713
2714   my $checksum_data = ''
2715   for my $sw_name (keys %SWITCH_DB) {
2716      $checksum_data .= join ':', $SWITCH_DB{$sw_name}->{'description'},
2717         $SWITCH_DB{$sw_name}->{model},
2718         $SWITCH_DB{$sw_name}->{'hostname'},
2719         "\n";
2720      }
2721
2722   my $switch_connection = {
2723      output_port       => \%db_switch_output_port,
2724      parent            => \%db_switch_parent,
2725      connected_on_port => \%db_switch_connected_on_port,
2726      link_with         => \%db_switch_link_with,
2727      switch_db         => \%SWITCH_DB,
2728      timestamp         => $timestamp,
2729      checksum          => sha512_base64($checksum_data),
2730      };
2731
2732   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
2733   return;
2734   }
2735
2736#---------------------------------------------------------------
2737sub cmd_exportsw {
2738   @ARGV = @_;
2739
2740   test_switchdb_environnement();
2741
2742   my $format = 'txt';
2743   my $graph_modulo = 0;
2744   my $graph_shift  = 1;
2745
2746   GetOptions(
2747      'format|f=s'  => \$format,
2748      'modulo|m=i'  => \$graph_modulo,
2749      'shift|s=i'   => \$graph_shift,
2750      );
2751
2752   my %possible_format = (
2753      txt => \&cmd_exportsw_txt,
2754      dot => \&cmd_exportsw_dot,
2755      );
2756
2757   $format = 'txt' if not defined $possible_format{$format};
2758
2759   $possible_format{$format}->($graph_modulo, $graph_shift, @ARGV);
2760   return;
2761   }
2762
2763#---------------------------------------------------------------
2764sub cmd_exportsw_txt {
2765
2766   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2767
2768   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2769   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2770   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2771
2772   print "Switch output port and parent port connection\n";
2773   print "---------------------------------------------\n";
2774   for my $sw (sort keys %db_switch_output_port) {
2775      my $arrow ='-->';
2776         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2777      if (exists $db_switch_parent{$sw}) {
2778         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'};
2779         }
2780      else {
2781         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
2782         }
2783      }
2784   print "\n";
2785
2786   print "Switch parent and children port inter-connection\n";
2787   print "------------------------------------------------\n";
2788   for my $swport (sort keys %db_switch_connected_on_port) {
2789      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2790      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2791         my $arrow ='<--';
2792            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
2793         if (exists $db_switch_output_port{$sw}) {
2794            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
2795            }
2796         else {
2797            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
2798            }
2799         }
2800      }
2801   return;
2802   }
2803
2804#---------------------------------------------------------------
2805sub cmd_exportsw_dot {
2806   my $graph_modulo = shift;
2807   my $graph_shift  = shift;
2808
2809   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2810
2811   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2812   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2813   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2814   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
2815   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
2816   my $timestamp                   =   $switch_connection->{'timestamp'};
2817
2818   my $invisible_node = 0; # Count number of invisible node
2819
2820   my %db_building= ();
2821   my %db_switch_line = (); # Number of line drawed on a switch
2822   for my $sw (values %db_switch_global) {
2823      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
2824      $db_building{$building} ||= {};
2825      $db_building{$building}->{$location} ||= {};
2826      $db_building{$building}->{$location}{ $sw->{'hostname'} } = 'y';
2827
2828      $db_switch_line{$sw} = 0;
2829      }
2830
2831
2832   print "digraph G {\n";
2833   print "rankdir=LR;\n";
2834   #print "splines=polyline;\n";
2835
2836   print "site [label=\"site\", color=black, fillcolor=gold, shape=invhouse, style=filled];\n";
2837   print "internet [label=\"internet\", color=black, fillcolor=cyan, shape=house, style=filled];\n";
2838
2839   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
2840   $year += 1900;
2841   $mon++;
2842   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2843   print "\"$date\" [label=\"MAP DATE\\n\\n$date\", color=white, fillcolor=black, shape=polygon, sides=14, style=filled, fontcolor=white];\n";
2844   print "site -> \"$date\" [style=invis];\n";
2845
2846   my $b=0;
2847   for my $building (keys %db_building) {
2848      $b++;
2849
2850      print "\"building$b\" [label=\"$building\", color=black, fillcolor=gold, style=filled];\n";
2851      print "site -> \"building$b\" [len=2, color=firebrick];\n";
2852
2853      my $l = 0;
2854      for my $loc (keys %{$db_building{$building}}) {
2855         $l++;
2856
2857         print "\"location$b-$l\" [label=\"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color=black, fillcolor=orange, style=filled];\n";
2858#         print "\"location$b-$l\" [label=\"$building / $loc\", color=black, fillcolor=orange, style=filled];\n";
2859         print "\"building$b\" -> \"location$b-$l\" [len=2, color=firebrick];\n";
2860
2861         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2862
2863            my $peripheries = 1;
2864            my $color = 'lightblue';
2865            if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2866               $peripheries = 2;
2867               $color = "\"$color:$color\"";
2868               }
2869            print "\"$sw:$db_switch_output_port{$sw}\" [label=\"".format_aggregator4dot($db_switch_output_port{$sw})."\", color=black, fillcolor=lightblue, peripheries=$peripheries, style=filled];\n";
2870
2871            my $swname  = $sw;
2872               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2873            print "\"$sw\" [label=\"$swname\", color=black, fillcolor=palegreen, shape=rect, style=filled];\n";
2874            print "\"location$b-$l\" -> \"$sw\" [len=2, color=firebrick, arrowtail=dot];\n";
2875            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=invdot];\n";
2876
2877
2878            for my $swport (keys %db_switch_connected_on_port) {
2879               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2880               next if not $sw_connect eq $sw;
2881               next if $port_connect eq $db_switch_output_port{$sw};
2882               my $peripheries = 1;
2883               my $color = 'plum';
2884               if ($port_connect =~ m/^(Trk|Br|Po)/) {
2885                  $peripheries = 2;
2886                  $color = "\"$color:$color\"";
2887                  }
2888               print "\"$sw:$port_connect\" [label=\"".format_aggregator4dot($port_connect)."\", color=black, fillcolor=plum, peripheries=$peripheries, style=filled];\n";
2889               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=inv];\n";
2890
2891               #$db_switch_line{$sw}++;
2892               #if ($db_switch_line{$sw} % 9 == 0) {
2893               #   # Create invisible node
2894               #   $invisible_node++;
2895               #   my $invisible = '__Invisible_' . $invisible_node;
2896               #   print "$invisible [shape=none, label=\"\"]\n";
2897               #   print "\"$sw:$port_connect\" -> $invisible [style=invis]\n";
2898               #   print "$invisible            -> \"$sw\"    [style=invis]\n";
2899               #   }
2900              }
2901            }
2902         }
2903      }
2904
2905#   print "Switch output port and parent port connection\n";
2906#   print "---------------------------------------------\n";
2907   for my $sw (sort keys %db_switch_output_port) {
2908      if (exists $db_switch_parent{$sw}) {
2909#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port_id'};
2910         }
2911      else {
2912         my $style = 'solid';
2913         my $color = 'black'; # navyblue
2914         if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2915            $style = 'bold';
2916            $color = "\"$color:invis:$color\"";
2917            }
2918         printf "   \"%s:%s\" -> internet [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw};
2919         }
2920      }
2921   print "\n";
2922
2923#   print "Switch parent and children port inter-connection\n";
2924#   print "------------------------------------------------\n";
2925   for my $swport (sort keys %db_switch_connected_on_port) {
2926      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2927      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2928         my $style = 'solid';
2929         my $color = 'black'; # navyblue
2930         if ($port_connect =~ m/^(Trk|Br|Po)/) {
2931            $style = 'bold';
2932            $color = "\"$color:invis:$color\"";
2933            }
2934         if (exists $db_switch_output_port{$sw}) {
2935            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2936
2937            next if $graph_modulo == 0; # No shift (invisible nodes) in graph
2938            $db_switch_line{$sw_connect}++;
2939            if ($db_switch_line{$sw_connect} % $graph_modulo == 0) {
2940               # Create invisible node
2941               $invisible_node++;
2942               my $invisible = '__Invisible_' . $invisible_node;
2943               print  "   \"$invisible.a\" [shape=none, label=\"\"];\n";
2944               printf "   \"%s:%s\"  -> \"$invisible.a\" [style=invis];\n", $sw, $db_switch_output_port{$sw};
2945               if ($graph_shift == 2) {
2946                  # Two invisible node
2947                  print  "   \"$invisible.b\" [shape=none, label=\"\"];\n";
2948                  print  "   \"$invisible.a\" -> \"$invisible.b\" [style=invis];\n";
2949                  printf "   \"$invisible.b\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
2950                  }
2951               else {
2952                  # One invisible node
2953                  printf "   \"$invisible.a\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
2954                  }
2955               }
2956            }
2957         else {
2958            printf "   \"%s\"   -> \"%s:%s\" [style=$style];\n", $sw, $sw_connect, $port_connect;
2959            }
2960         }
2961      }
2962
2963print "}\n";
2964   return;
2965   }
2966
2967
2968################################################################
2969# documentation
2970################################################################
2971
2972__END__
2973
2974=head1 NAME
2975
2976klask - port and search manager for switches, map management
2977
2978
2979=head1 USAGE
2980
2981 klask version
2982 klask help
2983
2984 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2985 klask exportdb [--format|-f txt|html]
2986 klask removedb IP* computer*
2987 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
2988
2989 klask updatesw [--verbose|-v]
2990 klask exportsw [--format|-f txt|dot]
2991
2992 klask searchdb [--kind|-k host|mac] computer [mac-address]
2993 klask search   computer
2994 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2995
2996 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
2997
2998 klask bad-vlan-id [--day|-d days_before_alert]
2999
3000 klask enable  [--verbose|-v] switch port
3001 klask disable [--verbose|-v] switch port
3002 klask status  [--verbose|-v] switch port
3003
3004 klask poe-enable  [--verbose|-v] switch port
3005 klask poe-disable [--verbose|-v] switch port
3006 klask poe-status  [--verbose|-v] switch port
3007
3008 klask vlan-getname switch vlan-id
3009 klask vlan-list switch
3010
3011
3012=head1 DESCRIPTION
3013
3014Klask is a small tool to find where is connected a host in a big network
3015and on which VLAN.
3016Klask mean search in brittany.
3017No hight level protocol like CDL, LLDP are use.
3018Everything is just done with SNMP request on MAC address.
3019
3020Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
3021If you use PVST or MSTP and create loop between VLAN,
3022you have to use C<portignore> functionality on switch port to cut manually loop
3023(see config file below).
3024
3025When you use a management port to administrate a switch,
3026it's not possible to create the map with this switch because it does not have a MAC address,
3027so other switch cannot find the real downlink port...
3028One way to work around this problem is, if you have a computer directly connected on the switch,
3029to put this IP as the fake ip for the switch.
3030The MAC address associated will be use just for the map detection.
3031The C<fake-ip> parameter is defined in the config file.
3032
3033Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
3034
3035
3036=head1 COMMANDS
3037
3038Some command are defined in the source code but are not documented here.
3039Theses could be not well defined, not finished, not well tested...
3040You can read the source code and use them at your own risk
3041(like for all the Klask code).
3042
3043=head2 search
3044
3045 klask search   computer
3046
3047This command takes one or more computer in argument.
3048It search a computer on the network and give the port and the switch on which the computer is connected.
3049
3050=head2 search-mac-on-switch
3051
3052 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3053
3054This command search a MAC address on a switch.
3055To search on all switch, you could put C<'*'> or C<all>.
3056The VLAN parameter could help.
3057
3058
3059=head2 enable
3060
3061 klask enable  [--verbose|-v] switch port
3062
3063This command activate a port (or an agrregate bridge port) on a switch by SNMP.
3064So you need to give the switch name and a port on the command line.
3065See L</ABBREVIATION FOR PORT>.
3066
3067Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3068
3069
3070=head2 disable
3071
3072 klask disable [--verbose|-v] switch port
3073
3074This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
3075So you need to give the switch name and a port on the command line.
3076See L</ABBREVIATION FOR PORT>.
3077
3078Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3079
3080
3081=head2 status
3082
3083 klask status  [--verbose|-v] switch port
3084
3085This command return the status of a port number on a switch by SNMP.
3086The return value could be C<enable> or C<disable> word.
3087So you need to give the switch name and a port on the command line.
3088See L</ABBREVIATION FOR PORT>.
3089
3090If it's not possible to change port status with command L</enable> and L</disable>
3091(SNMP community read write access),
3092it's always possible to have the port status even for bridge agrregate port.
3093
3094
3095=head2 updatedb
3096
3097 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
3098
3099This command will scan networks and update the computer database.
3100To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3101This file is easy to read and write because Klask use YAML format and not XML
3102(see L</CONFIGURATION>).
3103
3104Option are not stable and could be use manually when you have a new kind of switch.
3105Maybe some option will be transfered in a future C<checksw> command!
3106
3107The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3108By default, a network is C<active>.
3109This means that an C<fping> command is done at the beginning on all the IP of the network
3110and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3111will have an C<arping>
3112(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3113In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3114It's good for big subnet with few computer (telephone...).
3115The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3116
3117=head2 exportdb
3118
3119 klask exportdb [--format|-f txt|html]
3120
3121This command print the content of the computer database.
3122There is actually only two format : TXT and HTML.
3123By default, format is TXT.
3124It's very easy to have more format, it's just need times...
3125
3126=head2 removedb
3127
3128 klask removedb IP* computer*
3129
3130This command remove an entry in the database.
3131There is only one kind of parameter, the IP of the computers to be removed.
3132You can put as many IP as you want...
3133
3134Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
3135
3136=head2 cleandb
3137
3138 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3139
3140Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3141Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3142This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3143This functionality could be use when computer define in VLAN 1
3144could have a float IP when they are connected on VLAN 2.
3145In the Klask database, the float DNS entries are less important.
3146
3147When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
3148
3149=head2 updatesw
3150
3151 klask updatesw [--verbose|-v]
3152
3153This command build a map of your manageable switch on your network.
3154The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3155
3156
3157=head2 exportsw
3158
3159 klask exportsw [--format|-f txt|dot]
3160
3161This command print the content of the switch database. There is actually two format.
3162One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3163By default, format is TXT.
3164
3165 klask exportsw --format dot > /tmp/map.dot
3166 dot -Tpng /tmp/map.dot > /tmp/map.png
3167
3168
3169=head2 ip-free
3170
3171 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3172
3173This command return IP address that was not use (detected by Klask) at this time.
3174The list returned could be limited to just one VLAN.
3175IP returned could have been never used or no computer have been detected since the number of days specified
3176(2 years by default).
3177This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3178
3179 default:
3180   days-to-death: 730
3181
3182Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3183
3184
3185=head2 bad-vlan-id
3186
3187 klask bad-vlan-id [--day|-d days_before_alert]
3188
3189This command return a list of switch port that are not configure with the good VLAN.
3190Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3191and another prior trace where they had the good IP (good DNS name).
3192The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
3193This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3194
3195 default:
3196   days-before-alert: 15
3197
3198This functionality is not need if your switch use RADIUS 802.1X configuration...
3199
3200
3201=head2 poe-enable
3202
3203 klask poe-enable  [--verbose|-v] switch port
3204
3205This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3206So you need to give the switch name and a port on the command line.
3207See L</ABBREVIATION FOR PORT>.
3208
3209Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3210You need to have the SNMP write access on the switch in order to modify it's configuration.
3211
3212
3213=head2 poe-disable
3214
3215 klask poe-disable [--verbose|-v] switch port
3216
3217This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3218So you need to give the switch name and a port on the command line.
3219See L</ABBREVIATION FOR PORT>.
3220
3221Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3222You need to have the SNMP write access on the switch in order to modify it's configuration.
3223
3224
3225=head2 poe-status
3226
3227 klask poe-status  [--verbose|-v] switch port
3228
3229This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3230The return value could be C<enable> or C<disable> word.
3231So you need to give the switch name and a port on the command line.
3232See L</ABBREVIATION FOR PORT>.
3233
3234If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3235(SNMP community read write access),
3236it's always possible to have the PoE port status.
3237
3238Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3239
3240
3241=head1 CONFIGURATION
3242
3243Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
3244The configuration is done in a F</etc/klask/klask.conf> YAML file.
3245This format have many advantage over XML, it's easier to read and to write !
3246
3247Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3248
3249 default:
3250   community: public
3251   community-rw: private
3252   snmpport: 161
3253   float-regex: '(?^msx: ^float )'
3254   scan-mode: active
3255
3256 network:
3257   labnet:
3258     ip-subnet:
3259       - add: 192.168.1.0/24
3260       - add: 192.168.2.0/24
3261     interface: eth0
3262     vlan-id: 12
3263     main-router: gw1.labnet.local
3264
3265   schoolnet:
3266     ip-subnet:
3267       - add: 192.168.3.0/24
3268       - add: 192.168.4.0/24
3269     interface: eth0.38
3270     vlan-id: 13
3271     main-router: gw2.schoolnet.local
3272     scan-mode: passive
3273
3274   etunet:
3275     ip-subnet:
3276       - add: 192.168.5.0/24
3277     interface: eth2
3278     vlan-id: 14
3279     main-router: gw3.etunet.local
3280     scan-mode: passive
3281
3282 switch:
3283   - hostname: sw1.klask.local
3284     location: BatY / 1 floor / K004
3285     portignore:
3286       - 1
3287       - 2
3288
3289   - hostname: sw2.klask.local
3290     location: BatY / 2 floor / K203
3291     type: HP2424
3292     portignore:
3293       - 1
3294       - 2
3295     fake-ip: 192.168.9.14
3296
3297   - hostname: sw3.klask.local
3298     location: BatY / 2 floor / K203
3299
3300I think it's pretty easy to understand.
3301The default section can be overide in any section, if parameter mean something in theses sections.
3302Network to be scan are define in the network section. You must put an add by network.
3303Maybe I will make a delete line to suppress specific computers.
3304The switch section define your switch.
3305You have to write the port number to ignore, this was important if your switchs are cascades
3306(right now, method C<updatesw> find them automatically)
3307and is still important if you have loop (with PVST or MSTP).
3308Just put the ports numbers between switch.
3309
3310The C<community> parameter is use to get SNMP data on switch.
3311It could be overload for each switch.
3312By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3313Some few command change the switch state as the commands L</enable> and L</disable>.
3314In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3315Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
3316
3317
3318=head1 ABBREVIATION FOR PORT
3319
3320HP Procurve and Nexans switches have a simplistic numbering scheme.
3321It's just number: 1, 2, 3... 24.
3322On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3323Nothing is done on theses ports names.
3324
3325On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3326followed by tree number.
3327In order to have short name,
3328we made the following rules:
3329
3330 Bridge-Aggregation     -> Br
3331 FastEthernet           -> Fa
3332 Forty-GigabitEthernet  -> Fo
3333 FortyGigabitEthernet   -> Fo
3334 GigabitEthernet        -> Gi
3335 Giga                   -> Gi
3336 Port-Channel           -> Po
3337 Ten-GigabitEthernet    -> Te
3338 TenGigabitEthernet     -> Te
3339 Ten                    -> Te
3340
3341All Klask command automatically normalize the port name on standart output
3342and also on input command line.
3343
3344In the case of use an aggregator port (Po, Tk, Br ...),
3345the real ports used are also return.
3346
3347
3348=head1 SWITCH SUPPORTED
3349
3350Here is a list of switches where Klask gives or gave (for old switches) good results.
3351We have only a few manageable switches to actually test Klask.
3352It is quite possible that switches from other brands will work just as well.
3353You just have to do a test on it and add the line of description that goes well in the source code.
3354Contact us for any additional information.
3355
3356In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3357This makes it possible to adjust the code names of the different manufacturers!
3358
3359HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3360J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3361J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F).
3362
3363BayStack: BayStack 350T HW(BS350T)
3364
3365Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3366
3367DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3368
3369H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3370
3371H3C: H3C5500
3372
33733COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
33743CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
33753CR17572-91, 3C17702-US, 3C17700.
3376
3377
3378=head1 FILES
3379
3380 /etc/klask/klask.conf
3381 /var/lib/klask/klaskdb
3382 /var/lib/klask/switchdb
3383
3384
3385=head1 SEE ALSO
3386
3387Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3388
3389=over
3390
3391=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
3392
3393=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3394
3395=back
3396
3397
3398=head1 VERSION
3399
3400$Id: klask 295 2017-10-04 15:57:55Z g7moreau $
3401
3402
3403=head1 AUTHOR
3404
3405Written by Gabriel Moreau, Grenoble - France
3406
3407
3408=head1 LICENSE AND COPYRIGHT
3409
3410GPL version 2 or later and Perl equivalent
3411
3412Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.