source: trunk/klask @ 265

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