source: trunk/klask @ 272

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