source: trunk/klask @ 284

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