source: trunk/klask @ 282

Last change on this file since 282 was 282, checked in by g7moreau, 7 years ago
  • Better dot file
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 123.3 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 282 2017-09-25 18:17:52Z g7moreau $
6
7use strict;
8use warnings;
9use version; our $VERSION = qv('0.6.6');
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 282 2017-09-25 18:17:52Z 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
2734   GetOptions(
2735      'format|f=s'  => \$format,
2736      );
2737
2738   my %possible_format = (
2739      txt => \&cmd_exportsw_txt,
2740      dot => \&cmd_exportsw_dot,
2741      );
2742
2743   $format = 'txt' if not defined $possible_format{$format};
2744
2745   $possible_format{$format}->(@ARGV);
2746   return;
2747   }
2748
2749#---------------------------------------------------------------
2750sub cmd_exportsw_txt {
2751
2752   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2753
2754   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2755   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2756   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2757
2758   print "Switch output port and parent port connection\n";
2759   print "---------------------------------------------\n";
2760   for my $sw (sort keys %db_switch_output_port) {
2761      my $arrow ='-->';
2762         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2763      if (exists $db_switch_parent{$sw}) {
2764         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'};
2765         }
2766      else {
2767         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
2768         }
2769      }
2770   print "\n";
2771
2772   print "Switch parent and children port inter-connection\n";
2773   print "------------------------------------------------\n";
2774   for my $swport (sort keys %db_switch_connected_on_port) {
2775      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2776      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2777         my $arrow ='<--';
2778            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
2779         if (exists $db_switch_output_port{$sw}) {
2780            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
2781            }
2782         else {
2783            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
2784            }
2785         }
2786      }
2787   return;
2788   }
2789
2790#---------------------------------------------------------------
2791sub cmd_exportsw_dot {
2792
2793   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2794
2795   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2796   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2797   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2798   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
2799   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
2800   my $timestamp                   =   $switch_connection->{'timestamp'};
2801
2802   my $invisible_node = 0; # Count number of invisible node
2803
2804   my %db_building= ();
2805   my %db_switch_line = (); # Number of line drawed on a switch
2806   for my $sw (@SWITCH_LIST) {
2807      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
2808      $db_building{$building} ||= {};
2809      $db_building{$building}->{$location} ||= {};
2810      $db_building{$building}->{$location}{ $sw->{'hostname'} } = 'y';
2811     
2812      $db_switch_line{$sw} = 0;
2813      }
2814
2815
2816   print "digraph G {\n";
2817   print "rankdir = LR;\n";
2818   #print "splines=polyline;\n";
2819
2820   print "site [label=\"site\", color=black, fillcolor=gold, shape=invhouse, style=filled];\n";
2821   print "internet [label=\"internet\", color=black, fillcolor=cyan, shape=house, style=filled];\n";
2822
2823   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
2824   $year += 1900;
2825   $mon++;
2826   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2827   print "\"$date\" [color=white, fillcolor=black, shape=polygon, sides=14, style=filled, fontcolor=white];\n";
2828   print "site -> \"$date\" [color = white];\n";
2829
2830   my $b=0;
2831   for my $building (keys %db_building) {
2832      $b++;
2833
2834      print "\"building$b\" [label=\"$building\", color=black, fillcolor=gold, style=filled];\n";
2835      print "site -> \"building$b\" [len=2, color=firebrick];\n";
2836
2837      my $l = 0;
2838      for my $loc (keys %{$db_building{$building}}) {
2839         $l++;
2840
2841         print "\"location$b-$l\" [label=\"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color=black, fillcolor=orange, style=filled];\n";
2842#         print "\"location$b-$l\" [label=\"$building / $loc\", color=black, fillcolor=orange, style=filled];\n";
2843         print "\"building$b\" -> \"location$b-$l\" [len=2, color=firebrick]\n";
2844
2845         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2846
2847            my $peripheries = 1;
2848            my $color = 'black';
2849            if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2850               $peripheries = 2;
2851               $color = "\"$color:invis:$color\"";
2852               }
2853            print "\"$sw:$db_switch_output_port{$sw}\" [label=\"".format_aggregator4dot($db_switch_output_port{$sw})."\", color=black, fillcolor=lightblue, peripheries=$peripheries, style=filled];\n";
2854
2855            my $swname  = $sw;
2856               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2857            print "\"$sw\" [label=\"$swname\", color=black, fillcolor=palegreen, shape=rect, style=filled];\n";
2858            print "\"location$b-$l\" -> \"$sw\" [len=2, color=firebrick, arrowtail=dot];\n";
2859            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=invdot];\n";
2860
2861
2862            for my $swport (keys %db_switch_connected_on_port) {
2863               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2864               next if not $sw_connect eq $sw;
2865               next if $port_connect eq $db_switch_output_port{$sw};
2866               my $peripheries = 1;
2867               my $color = 'black';
2868               if ($port_connect =~ m/^(Trk|Br|Po)/) {
2869                  $peripheries = 2;
2870                  $color = "\"$color:invis:$color\"";
2871                  }
2872               print "\"$sw:$port_connect\" [label=\"".format_aggregator4dot($port_connect)."\", color=black, fillcolor=plum, peripheries=$peripheries, style=filled];\n";
2873               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=inv];\n";
2874               
2875               $db_switch_line{$sw}++;
2876               #if ($db_switch_line{$sw} % 9 == 0) {
2877               #   # Create invisible node
2878               #   $invisible_node++;
2879               #   my $invisible = '__Invisible_' . $invisible_node;
2880               #   print "$invisible [shape=none, label=\"\"]\n";
2881               #   print "\"$sw:$port_connect\" -> $invisible [style=invis]\n";
2882               #   print "$invisible            -> \"$sw\"    [style=invis]\n";
2883               #   }
2884              }
2885            }
2886         }
2887      }
2888
2889#   print "Switch output port and parent port connection\n";
2890#   print "---------------------------------------------\n";
2891   for my $sw (sort keys %db_switch_output_port) {
2892      if (exists $db_switch_parent{$sw}) {
2893#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port_id'};
2894         }
2895      else {
2896         my $style = 'solid';
2897         my $color = 'navyblue';
2898         if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2899            $style = 'bold';
2900            $color = "\"$color:invis:$color\"";
2901            }
2902         printf "   \"%s:%s\" -> internet [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw};
2903         }
2904      }
2905   print "\n";
2906
2907#   print "Switch parent and children port inter-connection\n";
2908#   print "------------------------------------------------\n";
2909   for my $swport (sort keys %db_switch_connected_on_port) {
2910      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2911      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2912         my $style = 'solid';
2913         my $color = 'navyblue';
2914         if ($port_connect =~ m/^(Trk|Br|Po)/) {
2915            $style = 'bold';
2916            $color = "\"$color:invis:$color\"";
2917            }
2918         if (exists $db_switch_output_port{$sw}) {
2919            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2920
2921            if ($db_switch_line{$sw_connect} % 9 == 0) {
2922               # Create invisible node
2923               $invisible_node++;
2924               my $invisible = '__Invisible_' . $invisible_node;
2925               print  "   $invisible [shape=none, label=\"\"];\n";
2926               printf "   \"%s:%s\"  -> $invisible [style=invis];\n", $sw, $db_switch_output_port{$sw};
2927               printf "   $invisible -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
2928               }
2929            }
2930         else {
2931            printf "   \"%s\"   -> \"%s:%s\" [style=$style];\n", $sw, $sw_connect, $port_connect;
2932            }
2933         }
2934      }
2935
2936print "}\n";
2937   return;
2938   }
2939
2940
2941################################################################
2942# documentation
2943################################################################
2944
2945__END__
2946
2947=head1 NAME
2948
2949klask - port and search manager for switches, map management
2950
2951
2952=head1 USAGE
2953
2954 klask version
2955 klask help
2956
2957 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2958 klask exportdb [--format|-f txt|html]
2959 klask removedb IP* computer*
2960 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
2961
2962 klask updatesw [--verbose|-v]
2963 klask exportsw [--format|-f txt|dot]
2964
2965 klask searchdb [--kind|-k host|mac] computer [mac-address]
2966 klask search   computer
2967 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2968
2969 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
2970
2971 klask bad-vlan-id [--day|-d days_before_alert]
2972
2973 klask enable  [--verbose|-v] switch port
2974 klask disable [--verbose|-v] switch port
2975 klask status  [--verbose|-v] switch port
2976
2977 klask poe-enable  [--verbose|-v] switch port
2978 klask poe-disable [--verbose|-v] switch port
2979 klask poe-status  [--verbose|-v] switch port
2980
2981 klask vlan-getname switch vlan-id
2982 klask vlan-list switch
2983
2984
2985=head1 DESCRIPTION
2986
2987Klask is a small tool to find where is connected a host in a big network
2988and on which VLAN.
2989Klask mean search in brittany.
2990No hight level protocol like CDL, LLDP are use.
2991Everything is just done with SNMP request on MAC address.
2992
2993Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
2994If you use PVST or MSTP and create loop between VLAN,
2995you have to use C<portignore> functionality on switch port to cut manually loop
2996(see config file below).
2997
2998When you use a management port to administrate a switch,
2999it's not possible to create the map with this switch because it does not have a MAC address,
3000so other switch cannot find the real downlink port...
3001One way to work around this problem is, if you have a computer directly connected on the switch,
3002to put this IP as the fake ip for the switch.
3003The MAC address associated will be use just for the map detection.
3004The C<fake-ip> parameter is defined in the config file.
3005
3006Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
3007
3008
3009=head1 COMMANDS
3010
3011Some command are defined in the source code but are not documented here.
3012Theses could be not well defined, not finished, not well tested...
3013You can read the source code and use them at your own risk
3014(like for all the Klask code).
3015
3016=head2 search
3017
3018 klask search   computer
3019
3020This command takes one or more computer in argument.
3021It search a computer on the network and give the port and the switch on which the computer is connected.
3022
3023=head2 search-mac-on-switch
3024
3025 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3026
3027This command search a MAC address on a switch.
3028To search on all switch, you could put C<'*'> or C<all>.
3029The VLAN parameter could help.
3030
3031
3032=head2 enable
3033
3034 klask enable  [--verbose|-v] switch port
3035
3036This command activate a port (or an agrregate bridge port) on a switch by SNMP.
3037So you need to give the switch name and a port on the command line.
3038See L</ABBREVIATION FOR PORT>.
3039
3040Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3041
3042
3043=head2 disable
3044
3045 klask disable [--verbose|-v] switch port
3046
3047This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
3048So you need to give the switch name and a port on the command line.
3049See L</ABBREVIATION FOR PORT>.
3050
3051Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3052
3053
3054=head2 status
3055
3056 klask status  [--verbose|-v] switch port
3057
3058This command return the status of a port number on a switch by SNMP.
3059The return value could be C<enable> or C<disable> word.
3060So you need to give the switch name and a port on the command line.
3061See L</ABBREVIATION FOR PORT>.
3062
3063If it's not possible to change port status with command L</enable> and L</disable>
3064(SNMP community read write access),
3065it's always possible to have the port status even for bridge agrregate port.
3066
3067
3068=head2 updatedb
3069
3070 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
3071
3072This command will scan networks and update the computer database.
3073To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3074This file is easy to read and write because Klask use YAML format and not XML
3075(see L</CONFIGURATION>).
3076
3077Option are not stable and could be use manually when you have a new kind of switch.
3078Maybe some option will be transfered in a future C<checksw> command!
3079
3080The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3081By default, a network is C<active>.
3082This means that an C<fping> command is done at the beginning on all the IP of the network
3083and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3084will have an C<arping>
3085(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3086In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3087It's good for big subnet with few computer (telephone...).
3088The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3089
3090=head2 exportdb
3091
3092 klask exportdb [--format|-f txt|html]
3093
3094This command print the content of the computer database.
3095There is actually only two format : TXT and HTML.
3096By default, format is TXT.
3097It's very easy to have more format, it's just need times...
3098
3099=head2 removedb
3100
3101 klask removedb IP* computer*
3102
3103This command remove an entry in the database.
3104There is only one kind of parameter, the IP of the computers to be removed.
3105You can put as many IP as you want...
3106
3107Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
3108
3109=head2 cleandb
3110
3111 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3112
3113Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3114Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3115This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3116This functionality could be use when computer define in VLAN 1
3117could have a float IP when they are connected on VLAN 2.
3118In the Klask database, the float DNS entries are less important.
3119
3120When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
3121
3122=head2 updatesw
3123
3124 klask updatesw [--verbose|-v]
3125
3126This command build a map of your manageable switch on your network.
3127The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3128
3129
3130=head2 exportsw
3131
3132 klask exportsw [--format|-f txt|dot]
3133
3134This command print the content of the switch database. There is actually two format.
3135One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3136By default, format is TXT.
3137
3138 klask exportsw --format dot > /tmp/map.dot
3139 dot -Tpng /tmp/map.dot > /tmp/map.png
3140
3141
3142=head2 ip-free
3143
3144 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3145
3146This command return IP address that was not use (detected by Klask) at this time.
3147The list returned could be limited to just one VLAN.
3148IP returned could have been never used or no computer have been detected since the number of days specified
3149(2 years by default).
3150This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3151
3152 default:
3153   days-to-death: 730
3154
3155Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3156
3157
3158=head2 bad-vlan-id
3159
3160 klask bad-vlan-id [--day|-d days_before_alert]
3161
3162This command return a list of switch port that are not configure with the good VLAN.
3163Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3164and another prior trace where they had the good IP (good DNS name).
3165The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
3166This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3167
3168 default:
3169   days-before-alert: 15
3170
3171This functionality is not need if your switch use RADIUS 802.1X configuration...
3172
3173
3174=head2 poe-enable
3175
3176 klask poe-enable  [--verbose|-v] switch port
3177
3178This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3179So you need to give the switch name and a port on the command line.
3180See L</ABBREVIATION FOR PORT>.
3181
3182Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3183You need to have the SNMP write access on the switch in order to modify it's configuration.
3184
3185
3186=head2 poe-disable
3187
3188 klask poe-disable [--verbose|-v] switch port
3189
3190This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3191So you need to give the switch name and a port on the command line.
3192See L</ABBREVIATION FOR PORT>.
3193
3194Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3195You need to have the SNMP write access on the switch in order to modify it's configuration.
3196
3197
3198=head2 poe-status
3199
3200 klask poe-status  [--verbose|-v] switch port
3201
3202This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3203The return value could be C<enable> or C<disable> word.
3204So you need to give the switch name and a port on the command line.
3205See L</ABBREVIATION FOR PORT>.
3206
3207If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3208(SNMP community read write access),
3209it's always possible to have the PoE port status.
3210
3211Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3212
3213
3214=head1 CONFIGURATION
3215
3216Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
3217The configuration is done in a F</etc/klask/klask.conf> YAML file.
3218This format have many advantage over XML, it's easier to read and to write !
3219
3220Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3221
3222 default:
3223   community: public
3224   community-rw: private
3225   snmpport: 161
3226   float-regex: '(?^msx: ^float )'
3227   scan-mode: active
3228
3229 network:
3230   labnet:
3231     ip-subnet:
3232       - add: 192.168.1.0/24
3233       - add: 192.168.2.0/24
3234     interface: eth0
3235     vlan-id: 12
3236     main-router: gw1.labnet.local
3237
3238   schoolnet:
3239     ip-subnet:
3240       - add: 192.168.3.0/24
3241       - add: 192.168.4.0/24
3242     interface: eth0.38
3243     vlan-id: 13
3244     main-router: gw2.schoolnet.local
3245     scan-mode: passive
3246
3247   etunet:
3248     ip-subnet:
3249       - add: 192.168.5.0/24
3250     interface: eth2
3251     vlan-id: 14
3252     main-router: gw3.etunet.local
3253     scan-mode: passive
3254
3255 switch:
3256   - hostname: sw1.klask.local
3257     location: BatY / 1 floor / K004
3258     portignore:
3259       - 1
3260       - 2
3261
3262   - hostname: sw2.klask.local
3263     location: BatY / 2 floor / K203
3264     type: HP2424
3265     portignore:
3266       - 1
3267       - 2
3268     fake-ip: 192.168.9.14
3269
3270   - hostname: sw3.klask.local
3271     location: BatY / 2 floor / K203
3272
3273I think it's pretty easy to understand.
3274The default section can be overide in any section, if parameter mean something in theses sections.
3275Network to be scan are define in the network section. You must put an add by network.
3276Maybe I will make a delete line to suppress specific computers.
3277The switch section define your switch.
3278You have to write the port number to ignore, this was important if your switchs are cascades
3279(right now, method C<updatesw> find them automatically)
3280and is still important if you have loop (with PVST or MSTP).
3281Just put the ports numbers between switch.
3282
3283The C<community> parameter is use to get SNMP data on switch.
3284It could be overload for each switch.
3285By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3286Some few command change the switch state as the commands L</enable> and L</disable>.
3287In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3288Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
3289
3290
3291=head1 ABBREVIATION FOR PORT
3292
3293HP Procurve and Nexans switches have a simplistic numbering scheme.
3294It's just number: 1, 2, 3... 24.
3295On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3296Nothing is done on theses ports names.
3297
3298On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3299followed by tree number.
3300In order to have short name,
3301we made the following rules:
3302
3303 Bridge-Aggregation     -> Br
3304 FastEthernet           -> Fa
3305 Forty-GigabitEthernet  -> Fo
3306 FortyGigabitEthernet   -> Fo
3307 GigabitEthernet        -> Gi
3308 Giga                   -> Gi
3309 Port-Channel           -> Po
3310 Ten-GigabitEthernet    -> Te
3311 TenGigabitEthernet     -> Te
3312 Ten                    -> Te
3313
3314All Klask command automatically normalize the port name on standart output
3315and also on input command line.
3316
3317In the case of use an aggregator port (Po, Tk, Br ...),
3318the real ports used are also return.
3319
3320
3321=head1 SWITCH SUPPORTED
3322
3323Here is a list of switches where Klask gives or gave (for old switches) good results.
3324We have only a few manageable switches to actually test Klask.
3325It is quite possible that switches from other brands will work just as well.
3326You just have to do a test on it and add the line of description that goes well in the source code.
3327Contact us for any additional information.
3328
3329In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3330This makes it possible to adjust the code names of the different manufacturers!
3331
3332HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3333J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3334J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F).
3335
3336BayStack: BayStack 350T HW(BS350T)
3337
3338Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3339
3340DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3341
3342H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3343
3344H3C: H3C5500
3345
33463COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
33473CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
33483CR17572-91, 3C17702-US, 3C17700.
3349
3350
3351=head1 FILES
3352
3353 /etc/klask/klask.conf
3354 /var/lib/klask/klaskdb
3355 /var/lib/klask/switchdb
3356
3357
3358=head1 SEE ALSO
3359
3360Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3361
3362=over
3363
3364=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
3365
3366=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3367
3368=back
3369
3370
3371=head1 VERSION
3372
3373$Id: klask 282 2017-09-25 18:17:52Z g7moreau $
3374
3375
3376=head1 AUTHOR
3377
3378Written by Gabriel Moreau, Grenoble - France
3379
3380
3381=head1 LICENSE AND COPYRIGHT
3382
3383GPL version 2 or later and Perl equivalent
3384
3385Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.