source: trunk/klask @ 291

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