source: trunk/klask @ 266

Last change on this file since 266 was 266, checked in by g7moreau, 7 years ago
  • All arrow on the same direction right to left
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 120.7 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 266 2017-09-15 07:27:56Z 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 266 2017-09-15 07:27:56Z 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 ='&#10229;';
1486         $arrow ='&#10232;' 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>$arrow</td><td></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#   <td colspan="2">$arrow</td>
1517      }
1518
1519   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1520
1521   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1522   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1523   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1524   my %db_switch                   = %{$switch_connection->{'switch_db'}};
1525
1526   # Output switch connection
1527   LOOP_ON_OUTPUT_SWITCH:
1528   for my $sw (sort keys %db_switch_output_port) {
1529
1530      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1531
1532      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1533
1534      #my $arrow ='&#8702;';
1535      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1536      my $arrow ='&#10236;';
1537         $arrow ='&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1538
1539      if (exists $db_switch_parent{$sw}) {
1540         # Link to uplink switch
1541         next LOOP_ON_OUTPUT_SWITCH;
1542         
1543         # Do not print anymore
1544         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1545         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1546         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
1547
1548         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1549         $year += 1900;
1550         $mon++;
1551         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1552
1553         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1554
1555         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1556
1557         my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'};
1558
1559         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1560         $vlan .= ' ('.get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}).')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1561
1562         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1563         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
1564
1565         print <<"END_HTML";
1566  <tr class="$typerow">
1567   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1568   <td class="bklask-port">$parent_port_hr</td>
1569   <td>$arrow</td><td>$child_port_hr</td>
1570   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{'switch'}</td>
1571   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1572   <td sorttable_customkey="$mac_sort">$mac_address</td>
1573   <td>$vlan</td>
1574   <td>$date</td>
1575  </tr>
1576END_HTML
1577         }
1578      else {
1579         # Router
1580         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1581
1582         my $host_short = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1583
1584         my $mac_address = $db_switch{$sw}->{'mac_address'};
1585         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1586         my $timestamp = $db_switch{$sw}->{'timestamp'};
1587
1588         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1589         $year += 1900;
1590         $mon++;
1591         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1592
1593         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1594
1595         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1596
1597         my $vlan = $db_switch{$sw}->{'network'};
1598         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1599
1600         my $arrow ='&#10235;';
1601            $arrow ='&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1602
1603         print <<"END_HTML";
1604  <tr class="$typerow">
1605   <td sorttable_customkey="router">router</td>
1606   <td class="bklask-port"></td>
1607   <td>$arrow</td><td>$parent_port_hr</td>
1608   <td sorttable_customkey="$host_short">$sw</td>
1609   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1610   <td sorttable_customkey="$mac_sort">$mac_address</td>
1611   <td>$vlan</td>
1612   <td>$date</td>
1613  </tr>
1614END_HTML
1615
1616         next LOOP_ON_OUTPUT_SWITCH;
1617
1618         # Old print
1619         print <<"END_HTML";
1620  <tr class="$typerow">
1621   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1622   <td class="bklask-port">$parent_port_hr</td>
1623   <td>$arrow</td><td></td>
1624   <td sorttable_customkey="router">router</td>
1625   <td sorttable_customkey="999999999999"></td>
1626   <td sorttable_customkey="99999"></td>
1627   <td></td>
1628   <td></td>
1629  </tr>
1630END_HTML
1631         }
1632      }
1633
1634   # Child switch connection : parent <- child
1635   for my $swport (sort keys %db_switch_connected_on_port) {
1636      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1637      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1638
1639         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1640
1641         my $mac_address = $db_switch{$sw}->{'mac_address'};
1642         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1643         my $timestamp = $db_switch{$sw}->{'timestamp'};
1644
1645         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1646         $year += 1900;
1647         $mon++;
1648         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1649
1650         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1651
1652         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1653
1654         $typerow = $typerow eq 'even' ? 'odd' : 'even';
1655
1656         #my $arrow ='&#8701;';
1657         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
1658         my $arrow ='&#10235;';
1659            $arrow ='&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
1660
1661         my $vlan = $db_switch{$sw}->{'network'};
1662         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1663
1664         if (exists $db_switch_output_port{$sw}) {
1665
1666            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1667
1668            my $parent_port_hr = format_aggregator4html($port_connect);
1669            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
1670
1671            print <<"END_HTML";
1672  <tr class="$typerow">
1673   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1674   <td class="bklask-port">$parent_port_hr</td>
1675   <td>$arrow</td><td>$child_port_hr</td>
1676   <td sorttable_customkey="$host_short">$sw</td>
1677   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1678   <td sorttable_customkey="$mac_sort">$mac_address</td>
1679   <td>$vlan</td>
1680   <td>$date</td>
1681  </tr>
1682END_HTML
1683            }
1684         else {
1685            my $parent_port_hr = format_aggregator4html($port_connect);
1686
1687            print <<"END_HTML";
1688  <tr class="$typerow">
1689   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1690   <td class="bklask-port">$parent_port_hr</td>
1691   <td>$arrow</td><td></td>
1692   <td sorttable_customkey="$sw">$sw</td>
1693   <td sorttable_customkey="">$ipv4_address</td>
1694   <td sorttable_customkey="">$mac_address</td>
1695   <td>$vlan</td>
1696   <td>$date</td>
1697  </tr>
1698END_HTML
1699            }
1700         }
1701      }
1702
1703   print <<'END_HTML';
1704 </tbody>
1705</table>
1706END_HTML
1707   return;
1708   }
1709
1710#---------------------------------------------------------------
1711sub cmd_bad_vlan_id {
1712   @ARGV = @_;
1713
1714   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1715   my $verbose;
1716
1717   GetOptions(
1718      'day|d=i'   => \$days_before_alert,
1719      );
1720
1721   test_maindb_environnement();
1722
1723   my $computerdb = computerdb_load();
1724
1725   # create a database with the most recent computer by switch port
1726   my %switchportdb = ();
1727   LOOP_ON_IP_ADDRESS:
1728   for my $ip (keys %{$computerdb}) {
1729      # to be improve in the future
1730      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1731      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
1732      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port'} eq '0';
1733
1734      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1735      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1736      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1737
1738      my $swpt = sprintf "%-28s  %2s",
1739         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1740         $computerdb->{$ip}{'switch_port_hr'};
1741      $switchportdb{$swpt} ||= {
1742         ip          => $ip,
1743         timestamp   => $ip_timestamp,
1744         vlan        => $computerdb->{$ip}{'network'},
1745         hostname_fq => $ip_hostname_fq,
1746         mac_address => $ip_mac,
1747         };
1748
1749      # if float computer, set date 15 day before warning...
1750      my $ip_timestamp_mod = $ip_timestamp;
1751      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
1752      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1753      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1754
1755      if ($ip_timestamp_mod > $ip_timestamp_ref) {
1756         $switchportdb{$swpt} = {
1757            ip          => $ip,
1758            timestamp   => $ip_timestamp,
1759            vlan        => $computerdb->{$ip}{'network'},
1760            hostname_fq => $ip_hostname_fq,
1761            mac_address => $ip_mac,
1762            };
1763         }
1764      }
1765
1766   LOOP_ON_RECENT_COMPUTER:
1767   for my $swpt (keys %switchportdb) {
1768      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
1769      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
1770
1771      my $src_ip = $switchportdb{$swpt}->{ip};
1772      my $src_timestamp = 0;
1773      LOOP_ON_IP_ADDRESS:
1774      for my $ip (keys %{$computerdb}) {
1775         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne  $switchportdb{$swpt}->{'mac_address'};
1776         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1777         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
1778
1779         $src_ip = $ip;
1780         $src_timestamp = $computerdb->{$ip}{'timestamp'};
1781         }
1782
1783      # keep only if float computer is the most recent
1784      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
1785      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
1786
1787      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
1788      $year += 1900;
1789      $mon++;
1790      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1791
1792      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
1793      $year += 1900;
1794      $mon++;
1795      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1796
1797      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
1798
1799      printf "%s / %-10s +-> %-10s(%i)  %s %s %s %s\n",
1800         $swpt, $switchportdb{$swpt}->{'vlan'}, $computerdb->{$src_ip}{'network'}, $vlan_id,
1801         $date,
1802         $src_date,
1803         $computerdb->{$src_ip}{'mac_address'},
1804         $computerdb->{$src_ip}{'hostname_fq'};
1805      }
1806   }
1807
1808#---------------------------------------------------------------
1809sub cmd_poe_enable {
1810   @ARGV = @_;
1811
1812   my $verbose;
1813   GetOptions(
1814      'verbose|v' => \$verbose,
1815      );
1816
1817   my $switch_name = shift @ARGV || q{};
1818   my $switch_port = shift @ARGV || q{};
1819
1820   if ($switch_name eq q{} or $switch_port eq q{}) {
1821      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
1822      }
1823
1824   for my $sw_name (split /,/, $switch_name) {
1825      if (not defined $SWITCH_DB{$sw_name}) {
1826         die "Switch $sw_name must be defined in klask configuration file\n";
1827         }
1828
1829      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1830
1831      my $sw = $SWITCH_DB{$sw_name};
1832      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1833      print "$error \n" if $error;
1834
1835      my $result = $session->set_request(
1836         -varbindlist => [$search, INTEGER, 8], # Only NEXANS
1837         );
1838      print $session->error()."\n" if $session->error_status();
1839
1840      $session->close;
1841      }
1842   cmd_poe_status($switch_name, $switch_port);
1843   return;
1844   }
1845
1846#---------------------------------------------------------------
1847sub cmd_poe_disable {
1848   @ARGV = @_;
1849
1850   my $verbose;
1851   GetOptions(
1852      'verbose|v' => \$verbose,
1853      );
1854
1855   my $switch_name = shift @ARGV || q{};
1856   my $switch_port = shift @ARGV || q{};
1857
1858   if ($switch_name eq q{} or $switch_port eq q{}) {
1859      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
1860      }
1861
1862   for my $sw_name (split /,/, $switch_name) {
1863      if (not defined $SWITCH_DB{$sw_name}) {
1864         die "Switch $sw_name must be defined in klask configuration file\n";
1865         }
1866
1867      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1868
1869      my $sw = $SWITCH_DB{$sw_name};
1870      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1871      print "$error \n" if $error;
1872
1873      my $result = $session->set_request(
1874         -varbindlist => [$search, INTEGER, 2], # Only NEXANS
1875         );
1876      print $session->error()."\n" if $session->error_status();
1877
1878      $session->close;
1879      }
1880   cmd_poe_status($switch_name, $switch_port);
1881   return;
1882   }
1883
1884#---------------------------------------------------------------
1885sub cmd_poe_status {
1886   @ARGV = @_;
1887
1888   my $verbose;
1889   GetOptions(
1890      'verbose|v' => \$verbose,
1891      );
1892
1893   my $switch_name = shift @ARGV || q{};
1894   my $switch_port = shift @ARGV || q{};
1895
1896   if ($switch_name eq q{} or $switch_port eq q{}) {
1897      die "Usage: klask poe-status SWITCH_NAME PORT\n";
1898      }
1899
1900   for my $sw_name (split /,/, $switch_name) {
1901      if (not defined $SWITCH_DB{$sw_name}) {
1902         die "Switch $sw_name must be defined in klask configuration file\n";
1903         }
1904
1905      my $search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS Switch and low port number
1906
1907      my $sw = $SWITCH_DB{$sw_name};
1908      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1909      print "$error \n" if $error;
1910
1911      my $result = $session->get_request(
1912         -varbindlist => [$search],
1913         );
1914
1915      if (defined $result and $result->{$search} ne 'noSuchInstance') {
1916         my $poe = $result->{$search} || 'empty';
1917         $poe =~ s/8/enable/;
1918         $poe =~ s/2/disable/;
1919         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe;
1920         }
1921      else {
1922         print "Klask do not find PoE Status on switch $sw_name on port $switch_port\n";
1923         }
1924
1925      $session->close;
1926      }
1927   return;
1928   }
1929
1930#---------------------------------------------------------------
1931# not finish - do not use
1932sub cmd_port_setvlan {
1933   my $switch_name = shift || q{};
1934   my $mac_address = shift || q{};
1935
1936   if ($switch_name eq q{} or $mac_address eq q{}) {
1937      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1938      }
1939
1940   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
1941
1942   for my $sw_name (split /,/, $switch_name) {
1943      if (not defined $SWITCH_DB{$sw_name}) {
1944         die "Switch $sw_name must be defined in klask configuration file\n";
1945         }
1946
1947      my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
1948      my $research2 = $OID_NUMBER{'searchPort2'} .'.'. 0 . mac_address_hex2dec($mac_address);
1949      print "Klask search OID $research1 on switch $sw_name\n";
1950      print "Klask search OID $research2 on switch $sw_name\n";
1951
1952      my $sw = $SWITCH_DB{$sw_name};
1953      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1954      print "$error \n" if $error;
1955
1956      my $result = $session->get_request(
1957         -varbindlist => [$research1]
1958         );
1959      if (not defined $result) {
1960         $result = $session->get_request(
1961            -varbindlist => [$research2]
1962            );
1963         $result->{$research1} = $result->{$research2} if defined $result;
1964         }
1965
1966      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1967         my $swport = $result->{$research1};
1968         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1969         }
1970      else {
1971         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1972         }
1973
1974      $session->close;
1975      }
1976   return;
1977   }
1978
1979#---------------------------------------------------------------
1980sub cmd_port_getvlan {
1981   @ARGV = @_;
1982
1983   my $verbose;
1984   GetOptions(
1985      'verbose|v' => \$verbose,
1986      );
1987
1988   my $switch_name = shift @ARGV || q{};
1989   my $switch_port = shift @ARGV || q{};
1990
1991   if ($switch_name eq q{} or $switch_port eq q{}) {
1992      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
1993      }
1994
1995   for my $sw_name (split /,/, $switch_name) {
1996      if (not defined $SWITCH_DB{$sw_name}) {
1997         die "Switch $sw_name must be defined in klask configuration file\n";
1998         }
1999
2000      my $search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
2001
2002      my $sw = $SWITCH_DB{$sw_name};
2003      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2004      print "$error \n" if $error;
2005
2006      my $result = $session->get_request(
2007         -varbindlist => [$search],
2008         );
2009
2010      if (defined $result and $result->{$search} ne 'noSuchInstance') {
2011         my $vlan_id = $result->{$search} || 'empty';
2012         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2013         }
2014      else {
2015         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
2016         }
2017
2018      $session->close;
2019      }
2020   return;
2021   }
2022
2023#---------------------------------------------------------------
2024sub cmd_vlan_setname {
2025   }
2026
2027#---------------------------------------------------------------
2028# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
2029sub cmd_rebootsw {
2030   @ARGV = @_;
2031
2032   my $verbose;
2033   GetOptions(
2034      'verbose|v' => \$verbose,
2035      );
2036
2037   my $switch_name = shift @ARGV || q{};
2038
2039   if ($switch_name eq q{}) {
2040      die "Usage: klask rebootsw SWITCH_NAME\n";
2041      }
2042
2043   for my $sw_name (split /,/, $switch_name) {
2044      if (not defined $SWITCH_DB{$sw_name}) {
2045         die "Switch $sw_name must be defined in klask configuration file\n";
2046         }
2047
2048      my $sw = $SWITCH_DB{$sw_name};
2049      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2050      print "$error \n" if $error;
2051
2052      my $result = $session->set_request(
2053         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
2054         );
2055
2056      $session->close;
2057      }
2058   return;
2059   }
2060
2061#---------------------------------------------------------------
2062sub cmd_vlan_getname {
2063   my $switch_name = shift || q{};
2064   my $vlan_id     = shift || q{};
2065
2066   if ($switch_name eq q{} or $vlan_id eq q{}) {
2067      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
2068      }
2069
2070   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2071
2072   for my $sw_name (split /,/, $switch_name) {
2073      if (not defined $SWITCH_DB{$sw_name}) {
2074         die "Switch $sw_name must be defined in klask configuration file\n";
2075         }
2076
2077      my $search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
2078
2079      my $sw = $SWITCH_DB{$sw_name};
2080      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2081      print "$error \n" if $error;
2082
2083      my $result = $session->get_request(
2084         -varbindlist => [$search_vlan_name]
2085         );
2086
2087      if (defined $result and $result->{$search_vlan_name} ne 'noSuchInstance') {
2088         my $vlan_name = $result->{$search_vlan_name} || 'empty';
2089         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
2090         }
2091      else {
2092         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2093         }
2094
2095      $session->close;
2096      }
2097   return;
2098   }
2099
2100#---------------------------------------------------------------
2101sub cmd_vlan_list {
2102   my $switch_name = shift || q{};
2103
2104   if ($switch_name eq q{}) {
2105      die "Usage: klask vlan-list SWITCH_NAME\n";
2106      }
2107
2108   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2109
2110   for my $sw_name (split /,/, $switch_name) {
2111      if (not defined $SWITCH_DB{$sw_name}) {
2112         die "Switch $sw_name must be defined in klask configuration file\n";
2113         }
2114
2115      my $sw = $SWITCH_DB{$sw_name};
2116      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2117      print "$error \n" if $error;
2118
2119      my %vlandb = snmp_get_vlan_list($session);
2120      $session->close;
2121     
2122      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2123      for my $vlanid (keys %vlandb) {
2124         printf "%7i - %s\n", $vlanid, $vlandb{$vlanid};
2125         }
2126      }
2127   return;
2128   }
2129
2130#---------------------------------------------------------------
2131sub cmd_ip_location {
2132   my $computerdb = computerdb_load();
2133
2134   LOOP_ON_IP_ADDRESS:
2135   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
2136
2137      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2138
2139      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
2140      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
2141
2142      my $sw_location = q{};
2143      LOOP_ON_ALL_SWITCH:
2144      for my $sw (@SWITCH_LIST) {
2145         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2146         $sw_location = $sw->{'location'};
2147         last;
2148         }
2149
2150      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
2151      }
2152   return;
2153   }
2154
2155#---------------------------------------------------------------
2156sub cmd_ip_free {
2157   @ARGV = @_;
2158
2159   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
2160   my $format = 'txt';
2161   my $verbose;
2162
2163   GetOptions(
2164      'day|d=i'      => \$days_to_death,
2165      'format|f=s'   => \$format,
2166      'verbose|v'    => \$verbose,
2167      );
2168
2169   my %possible_format = (
2170      txt  => \&cmd_ip_free_txt,
2171      html => \&cmd_ip_free_html,
2172      none => sub {},
2173      );
2174   $format = 'txt' if not defined $possible_format{$format};
2175
2176   my @vlan_name = @ARGV;
2177   @vlan_name = get_list_network() if not @vlan_name;
2178
2179   my $computerdb = {};
2180      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
2181   my $timestamp = time;
2182
2183   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
2184
2185   my %result_ip = ();
2186
2187   ALL_NETWORK:
2188   for my $vlan (@vlan_name) {
2189
2190      my @ip_list = get_list_ip($vlan);
2191
2192      LOOP_ON_IP_ADDRESS:
2193      for my $ip (@ip_list) {
2194
2195         if (exists $computerdb->{$ip}) {
2196            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
2197
2198            my $mac_address = $computerdb->{$ip}{'mac_address'};
2199            LOOP_ON_DATABASE:
2200            for my $ip_db (keys %{$computerdb}) {
2201               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2202               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
2203               }
2204            }
2205
2206         my $ip_date_last_detection = '';
2207         if (exists $computerdb->{$ip}) {
2208            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
2209            $year += 1900;
2210            $mon++;
2211            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2212            }
2213
2214         my $packed_ip = scalar gethostbyname($ip);
2215         my $hostname_fq = 'unknown';
2216            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
2217
2218         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2219
2220         $result_ip{$ip} ||= {};
2221         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2222         $result_ip{$ip}->{'hostname_fq'} = $hostname_fq;
2223         $result_ip{$ip}->{'vlan'} = $vlan;
2224
2225         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
2226         }
2227      }
2228
2229   $possible_format{$format}->(%result_ip);
2230   }
2231
2232#---------------------------------------------------------------
2233sub cmd_ip_free_txt {
2234   my %result_ip = @_;
2235
2236   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2237   print "-------------------------------------------------------------------------------\n";
2238   LOOP_ON_IP_ADDRESS:
2239   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2240         my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2241         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
2242      }
2243   }
2244
2245#---------------------------------------------------------------
2246sub cmd_ip_free_html {
2247   my %result_ip = @_;
2248
2249   print <<'END_HTML';
2250<table class="sortable" summary="Klask Free IP Database">
2251 <caption>Klask Free IP Database</caption>
2252 <thead>
2253  <tr>
2254   <th scope="col" class="klask-header-left">IPv4-Address</th>
2255   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2256   <th scope="col" class="sorttable_alpha">VLAN</th>
2257   <th scope="col" class="klask-header-right">Date</th>
2258  </tr>
2259 </thead>
2260 <tfoot>
2261  <tr>
2262   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2263   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2264   <th scope="col" class="fklask-vlan">VLAN</th>
2265   <th scope="col" class="klask-footer-right">Date</th>
2266  </tr>
2267 </tfoot>
2268 <tbody>
2269END_HTML
2270
2271   my $typerow = 'even';
2272
2273   LOOP_ON_IP_ADDRESS:
2274   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2275
2276      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2277
2278      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
2279      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
2280
2281      my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2282
2283      print <<"END_HTML";
2284  <tr class="$typerow">
2285   <td sorttable_customkey="$ip_sort">$ip</td>
2286   <td sorttable_customkey="$host_short">$result_ip{$ip}->{'hostname_fq'}</td>
2287   <td>$vlan_nameid</td>
2288   <td>$result_ip{$ip}->{'date_last_detection'}</td>
2289  </tr>
2290END_HTML
2291      }
2292   print <<'END_HTML';
2293 </tbody>
2294</table>
2295END_HTML
2296   }
2297
2298#---------------------------------------------------------------
2299sub cmd_enable {
2300   @ARGV = @_;
2301
2302   my $verbose;
2303
2304   GetOptions(
2305      'verbose|v' => \$verbose,
2306      );
2307
2308   my $switch_name = shift @ARGV || q{};
2309   my $port        = shift @ARGV || q{};
2310
2311   if ($switch_name eq q{} or $port eq q{}) {
2312      die "Usage: klask disable SWITCH_NAME PORT\n";
2313      }
2314
2315   if (not defined $SWITCH_DB{$switch_name}) {
2316      die "Switch $switch_name must be defined in klask configuration file\n";
2317      }
2318
2319   my $sw = $SWITCH_DB{$switch_name};
2320   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2321   print "$error \n" if $error;
2322
2323   # Retrieve numeric port value
2324   my $port_num = snmp_get_switchport_hr2num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2325   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
2326
2327   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2328   print "Info: switch $switch_name port $port SNMP OID $search_portstatus\n" if $verbose;
2329
2330   my $result = $session->set_request(
2331      -varbindlist => [$search_portstatus, INTEGER, 1],
2332      );
2333   print $session->error()."\n" if $session->error_status();
2334
2335   $session->close;
2336
2337   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2338   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2339   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2340   return;
2341   }
2342
2343#---------------------------------------------------------------
2344sub cmd_disable {
2345   @ARGV = @_;
2346
2347   my $verbose;
2348
2349   GetOptions(
2350      'verbose|v' => \$verbose,
2351      );
2352
2353   my $switch_name = shift @ARGV || q{};
2354   my $port        = shift @ARGV || q{};
2355
2356   if ($switch_name eq q{} or $port eq q{}) {
2357      die "Usage: klask disable SWITCH_NAME PORT\n";
2358      }
2359
2360   if (not defined $SWITCH_DB{$switch_name}) {
2361      die "Switch $switch_name must be defined in klask configuration file\n";
2362      }
2363
2364   my $sw = $SWITCH_DB{$switch_name};
2365   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2366   print "$error \n" if $error;
2367
2368   # Retrieve numeric port value
2369   my $port_num = snmp_get_switchport_hr2num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2370   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
2371
2372   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2373   print "Info: switch $switch_name port $port SNMP OID $search_portstatus\n" if $verbose;
2374
2375   my $result = $session->set_request(
2376      -varbindlist => [$search_portstatus, INTEGER, 2],
2377      );
2378   print $session->error()."\n" if $session->error_status();
2379
2380   $session->close;
2381
2382   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2383   return;
2384   }
2385
2386#---------------------------------------------------------------
2387sub cmd_status {
2388   @ARGV = @_;
2389
2390   my $verbose;
2391
2392   GetOptions(
2393      'verbose|v' => \$verbose,
2394      );
2395
2396   my $switch_name = shift @ARGV || q{};
2397   my $port        = shift @ARGV || q{};
2398
2399   if ($switch_name eq q{} or $port eq q{}) {
2400      die "Usage: klask status SWITCH_NAME PORT\n";
2401      }
2402
2403   if (not defined $SWITCH_DB{$switch_name}) {
2404      die "Switch $switch_name must be defined in klask configuration file\n";
2405      }
2406
2407   my $sw = $SWITCH_DB{$switch_name};
2408   my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2409   print "$error \n" if $error;
2410
2411   # Retrieve numeric port value
2412   my $port_num = snmp_get_switchport_hr2num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2413   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
2414
2415   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2416   print "Info: switch $switch_name port $port ($port_num) SNMP OID $search_portstatus\n" if $verbose;
2417
2418   my $result = $session->get_request(
2419      -varbindlist => [$search_portstatus]
2420      );
2421   print $session->error()."\n" if $session->error_status();
2422   if (defined $result) {
2423      print "$PORT_UPDOWN{$result->{$search_portstatus}}\n";
2424      }
2425
2426   $session->close;
2427
2428   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
2429   return;
2430   }
2431
2432#---------------------------------------------------------------
2433sub cmd_search_mac_on_switch {
2434   @ARGV = @_;
2435
2436   my $verbose;
2437   my $vlan_id = 0;
2438
2439   GetOptions(
2440      'verbose|v' => \$verbose,
2441      'vlan|l=i'  => \$vlan_id,
2442      );
2443
2444   my $switch_name = shift @ARGV || q{};
2445   my $mac_address = shift @ARGV || q{};
2446
2447   if ($switch_name eq q{} or $mac_address eq q{}) {
2448      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2449      }
2450
2451   $mac_address = normalize_mac_address($mac_address);
2452   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*} or $switch_name eq q{all};
2453
2454   for my $sw_name (split /,/, $switch_name) {
2455      if (not defined $SWITCH_DB{$sw_name}) {
2456         die "Switch $sw_name must be defined in klask configuration file\n";
2457         }
2458
2459      my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2460      my $research2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
2461      print "Klask search OID $research1 on switch $sw_name\n" if $verbose;
2462      print "Klask search OID $research2 on switch $sw_name\n" if $verbose;
2463
2464      my $sw = $SWITCH_DB{$sw_name};
2465      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2466      print "$error \n" if $error;
2467
2468      my $result = $session->get_request(
2469         -varbindlist => [$research1]
2470         );
2471      if (not defined $result) {
2472         $result = $session->get_request(
2473            -varbindlist => [$research2]
2474            );
2475         $result->{$research1} = $result->{$research2} if defined $result;
2476         }
2477
2478      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
2479         my $swport_num = $result->{$research1};
2480         my $swport_hr = snmp_get_switchport_num2hr($session, $swport_num);
2481         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
2482         }
2483      else {
2484         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
2485         }
2486
2487      $session->close;
2488      }
2489   return;
2490   }
2491
2492#---------------------------------------------------------------
2493sub cmd_updatesw {
2494   @ARGV = @_;
2495
2496   my $verbose;
2497
2498   GetOptions(
2499      'verbose|v' => \$verbose,
2500      );
2501
2502   init_switch_names('yes');    #nomme les switchs
2503   print "\n";
2504
2505   my %where = ();
2506   my %db_switch_output_port = ();
2507   my %db_switch_ip_hostnamefq = ();
2508
2509   DETECT_ALL_ROUTER:
2510   for my $one_router ( get_list_main_router(get_list_network()) ) {
2511      print "Info: router loop $one_router\n" if $verbose;
2512      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
2513
2514      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
2515      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $verbose;
2516
2517      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
2518      my $vlan_id   = get_current_vlan_id($vlan_name);
2519      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
2520      }
2521
2522   ALL_ROUTER_IP_ADDRESS:
2523   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
2524
2525      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
2526
2527      ALL_SWITCH_CONNECTED:
2528      for my $switch_detected ( keys %{$where{$ip_router}} ) {
2529
2530         my $switch = $where{$ip_router}->{$switch_detected};
2531
2532         next ALL_SWITCH_CONNECTED if $switch->{'port'} eq '0';
2533
2534         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
2535         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
2536         }
2537      }
2538
2539   my %db_switch_link_with = ();
2540
2541   my @list_all_switch = ();
2542   my @list_switch_ipv4 = ();
2543   for my $sw (@SWITCH_LIST) {
2544      push @list_all_switch, $sw->{'hostname'};
2545      }
2546
2547   my $timestamp = time;
2548
2549   ALL_SWITCH:
2550   for my $one_switch (@list_all_switch) {
2551      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
2552      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
2553         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
2554         fast_ping($fake_ip);
2555         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $verbose;
2556         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
2557         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
2558            $resol_arp{'mac_address'}   = $resol_arp_alt{'mac_address'};
2559            $resol_arp{'interface'}     = $resol_arp_alt{'interface'};
2560            $resol_arp{'ipv4_address'} .= '*';
2561            # Force a MAC trace on switch
2562            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
2563            }
2564         }
2565      print "Info: switch loop $one_switch\n" if $verbose;
2566      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
2567
2568      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
2569
2570      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
2571      my $vlan_id   = get_current_vlan_id($vlan_name);
2572      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
2573
2574      if ($verbose) {
2575         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
2576         print "VERBOSE_3: $one_switch --- ",
2577            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
2578            "\n";
2579         }
2580
2581      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
2582      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $verbose;
2583
2584      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
2585      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
2586      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
2587      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
2588      }
2589
2590   ALL_SWITCH_IP_ADDRESS:
2591   for my $ip (@list_switch_ipv4) {
2592#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
2593
2594      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2595
2596      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
2597#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
2598
2599      DETECTED_SWITCH:
2600      for my $switch_detected ( keys %{$where{$ip}} ) {
2601
2602         my $switch = $where{$ip}->{$switch_detected};
2603         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
2604
2605         next if $switch->{'port'}     eq '0';
2606         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
2607         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
2608
2609         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
2610         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{'hostname'} } = $switch->{'port_hr'};
2611         print "VERBOSE_7: +++++\n" if $verbose;
2612         }
2613
2614      }
2615
2616   my %db_switch_connected_on_port = ();
2617   my $maybe_more_than_one_switch_connected = 'yes';
2618   my $cloop = 0;
2619
2620   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2621      $cloop++;
2622      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
2623      for my $sw (keys %db_switch_link_with) {
2624         for my $connect (keys %{$db_switch_link_with{$sw}}) {
2625
2626            my $port_hr = $db_switch_link_with{$sw}->{$connect};
2627
2628            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2629            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
2630            }
2631         }
2632
2633      $maybe_more_than_one_switch_connected  = 'no';
2634
2635      SWITCH_AND_PORT:
2636      for my $swport (keys %db_switch_connected_on_port) {
2637
2638         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
2639
2640         $maybe_more_than_one_switch_connected = 'yes';
2641
2642         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2643         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
2644         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
2645
2646         CONNECTED:
2647         for my $sw_connected (@sw_on_same_port) {
2648
2649            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
2650
2651            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
2652
2653            for my $other_sw (@sw_on_same_port) {
2654               next if $other_sw eq $sw_connected;
2655
2656               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2657               }
2658
2659            # We can not do better for this switch for this loop
2660            next SWITCH_AND_PORT;
2661            }
2662         }
2663      }
2664
2665   my %db_switch_parent =();
2666
2667   for my $sw (keys %db_switch_link_with) {
2668      for my $connect (keys %{$db_switch_link_with{$sw}}) {
2669
2670         my $port_hr = $db_switch_link_with{$sw}->{$connect};
2671
2672         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2673         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
2674
2675         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
2676         }
2677      }
2678
2679   print "Switch output port and parent port connection\n";
2680   print "---------------------------------------------\n";
2681   for my $sw (sort keys %db_switch_output_port) {
2682      if (exists $db_switch_parent{$sw}) {
2683         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
2684         }
2685      else {
2686         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2687         }
2688      }
2689   print "\n";
2690
2691   print "Switch parent and children port inter-connection\n";
2692   print "------------------------------------------------\n";
2693   for my $swport (sort keys %db_switch_connected_on_port) {
2694      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2695      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2696         if (exists $db_switch_output_port{$sw}) {
2697            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2698            }
2699         else {
2700            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2701            }
2702         }
2703      }
2704
2705   my $switch_connection = {
2706      output_port       => \%db_switch_output_port,
2707      parent            => \%db_switch_parent,
2708      connected_on_port => \%db_switch_connected_on_port,
2709      link_with         => \%db_switch_link_with,
2710      switch_db         => \%SWITCH_DB,
2711      timestamp         => $timestamp,
2712      };
2713
2714   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
2715   return;
2716   }
2717
2718#---------------------------------------------------------------
2719sub cmd_exportsw {
2720   @ARGV = @_;
2721
2722   test_switchdb_environnement();
2723
2724   my $format = 'txt';
2725
2726   GetOptions(
2727      'format|f=s'  => \$format,
2728      );
2729
2730   my %possible_format = (
2731      txt => \&cmd_exportsw_txt,
2732      dot => \&cmd_exportsw_dot,
2733      );
2734
2735   $format = 'txt' if not defined $possible_format{$format};
2736
2737   $possible_format{$format}->(@ARGV);
2738   return;
2739   }
2740
2741#---------------------------------------------------------------
2742sub cmd_exportsw_txt {
2743
2744   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2745
2746   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2747   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2748   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2749
2750   print "Switch output port and parent port connection\n";
2751   print "---------------------------------------------\n";
2752   for my $sw (sort keys %db_switch_output_port) {
2753      my $arrow ='-->';
2754         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2755      if (exists $db_switch_parent{$sw}) {
2756         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'};
2757         }
2758      else {
2759         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
2760         }
2761      }
2762   print "\n";
2763
2764   print "Switch parent and children port inter-connection\n";
2765   print "------------------------------------------------\n";
2766   for my $swport (sort keys %db_switch_connected_on_port) {
2767      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2768      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2769         my $arrow ='<--';
2770            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
2771         if (exists $db_switch_output_port{$sw}) {
2772            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
2773            }
2774         else {
2775            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
2776            }
2777         }
2778      }
2779   return;
2780   }
2781
2782#---------------------------------------------------------------
2783sub cmd_exportsw_dot {
2784
2785   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2786
2787   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2788   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2789   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2790   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
2791   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
2792   my $timestamp                   =   $switch_connection->{'timestamp'};
2793
2794   my %db_building= ();
2795   for my $sw (@SWITCH_LIST) {
2796      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
2797      $db_building{$building} ||= {};
2798      $db_building{$building}->{$location} ||= {};
2799      $db_building{$building}->{$location}{ $sw->{'hostname'} } = 'y';
2800      }
2801
2802
2803   print "digraph G {\n";
2804   print "rankdir = LR;\n";
2805   #print "splines=polyline;\n";
2806
2807   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2808   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
2809
2810   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
2811   $year += 1900;
2812   $mon++;
2813   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2814   print "\"$date\" [ color = white, fillcolor = black, shape = polygon, sides=14, style = filled, fontcolor = white ]\n";
2815   print "site -> \"$date\" [color = white];\n";
2816
2817   my $b=0;
2818   for my $building (keys %db_building) {
2819      $b++;
2820
2821      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2822      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
2823
2824      my $l = 0;
2825      for my $loc (keys %{$db_building{$building}}) {
2826         $l++;
2827
2828         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
2829#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
2830         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
2831
2832         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2833
2834            my $peripheries = 1;
2835            $peripheries = 2 if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2836            print "\"$sw:$db_switch_output_port{$sw}\" [label = \"".format_aggregator4dot($db_switch_output_port{$sw})."\", color = black, fillcolor = lightblue,  peripheries = $peripheries, style = filled];\n";
2837
2838            my $swname  = $sw;
2839               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2840            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
2841            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2842            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
2843
2844
2845            for my $swport (keys %db_switch_connected_on_port) {
2846               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2847               next if not $sw_connect eq $sw;
2848               next if $port_connect eq $db_switch_output_port{$sw};
2849               my $peripheries = 1;
2850               $peripheries = 2 if $port_connect =~ m/^(Trk|Br|Po)/;
2851               print "\"$sw:$port_connect\" [label = \"".format_aggregator4dot($port_connect)."\", color = black, fillcolor = plum,  peripheries = $peripheries, style = filled];\n";
2852               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
2853              }
2854            }
2855         }
2856      }
2857
2858#   print "Switch output port and parent port connection\n";
2859#   print "---------------------------------------------\n";
2860   for my $sw (sort keys %db_switch_output_port) {
2861      if (exists $db_switch_parent{$sw}) {
2862#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port'};
2863         }
2864      else {
2865         my $style = 'solid';
2866         $style = 'bold' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2867         printf "   \"%s:%s\" -> internet [style=$style, color = navyblue]\n", $sw, $db_switch_output_port{$sw};
2868         }
2869      }
2870   print "\n";
2871
2872#   print "Switch parent and children port inter-connection\n";
2873#   print "------------------------------------------------\n";
2874   for my $swport (sort keys %db_switch_connected_on_port) {
2875      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2876      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2877         my $style = 'solid';
2878         $style = 'bold' if $port_connect =~ m/^(Trk|Br|Po)/;
2879         if (exists $db_switch_output_port{$sw}) {
2880            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2881            }
2882         else {
2883            printf "   \"%s\"   -> \"%s:%s\" [style=$style]\n", $sw, $sw_connect, $port_connect;
2884            }
2885         }
2886      }
2887
2888print "}\n";
2889   return;
2890   }
2891
2892
2893################################################################
2894# documentation
2895################################################################
2896
2897__END__
2898
2899=head1 NAME
2900
2901klask - port and search manager for switches, map management
2902
2903
2904=head1 USAGE
2905
2906 klask version
2907 klask help
2908
2909 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2910 klask exportdb [--format|-f txt|html]
2911 klask removedb IP* computer*
2912 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
2913
2914 klask updatesw [--verbose|-v]
2915 klask exportsw [--format|-f txt|dot]
2916
2917 klask searchdb [--kind|-k host|mac] computer [mac-address]
2918 klask search   computer
2919 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2920
2921 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
2922
2923 klask bad-vlan-id [--day|-d days_before_alert]
2924
2925 klask enable  [--verbose|-v] switch port
2926 klask disable [--verbose|-v] switch port
2927 klask status  [--verbose|-v] switch port
2928
2929 klask poe-enable  [--verbose|-v] switch port
2930 klask poe-disable [--verbose|-v] switch port
2931 klask poe-status  [--verbose|-v] switch port
2932
2933 klask vlan-getname switch vlan-id
2934 klask vlan-list switch
2935
2936
2937=head1 DESCRIPTION
2938
2939Klask is a small tool to find where is connected a host in a big network
2940and on which VLAN.
2941Klask mean search in brittany.
2942No hight level protocol like CDL, LLDP are use.
2943Everything is just done with SNMP request on MAC address.
2944
2945Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
2946If you use PVST or MSTP and create loop between VLAN,
2947you have to use C<portignore> functionality on switch port to cut manually loop
2948(see config file below).
2949
2950When you use a management port to administrate a switch,
2951it's not possible to create the map with this switch because it does not have a MAC address,
2952so other switch cannot find the real downlink port...
2953One way to work around this problem is, if you have a computer directly connected on the switch,
2954to put this IP as the fake ip for the switch.
2955The MAC address associated will be use just for the map detection.
2956The C<fake-ip> parameter is defined in the config file.
2957
2958Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
2959
2960
2961=head1 COMMANDS
2962
2963Some command are defined in the source code but are not documented here.
2964Theses could be not well defined, not finished, not well tested...
2965You can read the source code and use them at your own risk
2966(like for all the Klask code).
2967
2968=head2 search
2969
2970 klask search   computer
2971
2972This command takes one or more computer in argument.
2973It search a computer on the network and give the port and the switch on which the computer is connected.
2974
2975=head2 search-mac-on-switch
2976
2977 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2978
2979This command search a MAC address on a switch.
2980To search on all switch, you could put C<'*'> or C<all>.
2981The VLAN parameter could help.
2982
2983
2984=head2 enable
2985
2986 klask enable  [--verbose|-v] switch port
2987
2988This command activate a port (or an agrregate bridge port) on a switch by SNMP.
2989So you need to give the switch name and a port on the command line.
2990See L</ABBREVIATION FOR PORT>.
2991
2992Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
2993
2994
2995=head2 disable
2996
2997 klask disable [--verbose|-v] switch port
2998
2999This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
3000So you need to give the switch name and a port on the command line.
3001See L</ABBREVIATION FOR PORT>.
3002
3003Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3004
3005
3006=head2 status
3007
3008 klask status  [--verbose|-v] switch port
3009
3010This command return the status of a port number on a switch by SNMP.
3011The return value could be C<enable> or C<disable> word.
3012So you need to give the switch name and a port on the command line.
3013See L</ABBREVIATION FOR PORT>.
3014
3015If it's not possible to change port status with command L</enable> and L</disable>
3016(SNMP community read write access),
3017it's always possible to have the port status even for bridge agrregate port.
3018
3019
3020=head2 updatedb
3021
3022 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
3023
3024This command will scan networks and update the computer database.
3025To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3026This file is easy to read and write because Klask use YAML format and not XML
3027(see L</CONFIGURATION>).
3028
3029Option are not stable and could be use manually when you have a new kind of switch.
3030Maybe some option will be transfered in a future C<checksw> command!
3031
3032The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3033By default, a network is C<active>.
3034This means that an C<fping> command is done at the beginning on all the IP of the network
3035and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3036will have an C<arping>
3037(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3038In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3039It's good for big subnet with few computer (telephone...).
3040The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3041
3042=head2 exportdb
3043
3044 klask exportdb [--format|-f txt|html]
3045
3046This command print the content of the computer database.
3047There is actually only two format : TXT and HTML.
3048By default, format is TXT.
3049It's very easy to have more format, it's just need times...
3050
3051=head2 removedb
3052
3053 klask removedb IP* computer*
3054
3055This command remove an entry in the database.
3056There is only one kind of parameter, the IP of the computers to be removed.
3057You can put as many IP as you want...
3058
3059Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
3060
3061=head2 cleandb
3062
3063 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3064
3065Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3066Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3067This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3068This functionality could be use when computer define in VLAN 1
3069could have a float IP when they are connected on VLAN 2.
3070In the Klask database, the float DNS entries are less important.
3071
3072When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
3073
3074=head2 updatesw
3075
3076 klask updatesw [--verbose|-v]
3077
3078This command build a map of your manageable switch on your network.
3079The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3080
3081
3082=head2 exportsw
3083
3084 klask exportsw [--format|-f txt|dot]
3085
3086This command print the content of the switch database. There is actually two format.
3087One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3088By default, format is TXT.
3089
3090 klask exportsw --format dot > /tmp/map.dot
3091 dot -Tpng /tmp/map.dot > /tmp/map.png
3092
3093
3094=head2 ip-free
3095
3096 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3097
3098This command return IP address that was not use (detected by Klask) at this time.
3099The list returned could be limited to just one VLAN.
3100IP returned could have been never used or no computer have been detected since the number of days specified
3101(2 years by default).
3102This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3103
3104 default:
3105   days-to-death: 730
3106
3107Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3108
3109
3110=head2 bad-vlan-id
3111
3112 klask bad-vlan-id [--day|-d days_before_alert]
3113
3114This command return a list of switch port that are not configure with the good VLAN.
3115Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3116and another prior trace where they had the good IP (good DNS name).
3117The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
3118This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3119
3120 default:
3121   days-before-alert: 15
3122
3123This functionality is not need if your switch use RADIUS 802.1X configuration...
3124
3125
3126=head2 poe-enable
3127
3128 klask poe-enable  [--verbose|-v] switch port
3129
3130This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3131So you need to give the switch name and a port on the command line.
3132See L</ABBREVIATION FOR PORT>.
3133
3134Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3135You need to have the SNMP write access on the switch in order to modify it's configuration.
3136
3137
3138=head2 poe-disable
3139
3140 klask poe-disable [--verbose|-v] switch port
3141
3142This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3143So you need to give the switch name and a port on the command line.
3144See L</ABBREVIATION FOR PORT>.
3145
3146Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3147You need to have the SNMP write access on the switch in order to modify it's configuration.
3148
3149
3150=head2 poe-status
3151
3152 klask poe-status  [--verbose|-v] switch port
3153
3154This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3155The return value could be C<enable> or C<disable> word.
3156So you need to give the switch name and a port on the command line.
3157See L</ABBREVIATION FOR PORT>.
3158
3159If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3160(SNMP community read write access),
3161it's always possible to have the PoE port status.
3162
3163Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3164
3165
3166=head1 CONFIGURATION
3167
3168Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
3169The configuration is done in a F</etc/klask/klask.conf> YAML file.
3170This format have many advantage over XML, it's easier to read and to write !
3171
3172Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3173
3174 default:
3175   community: public
3176   community-rw: private
3177   snmpport: 161
3178   float-regex: '(?^msx: ^float )'
3179   scan-mode: active
3180
3181 network:
3182   labnet:
3183     ip-subnet:
3184       - add: 192.168.1.0/24
3185       - add: 192.168.2.0/24
3186     interface: eth0
3187     vlan-id: 12
3188     main-router: gw1.labnet.local
3189
3190   schoolnet:
3191     ip-subnet:
3192       - add: 192.168.3.0/24
3193       - add: 192.168.4.0/24
3194     interface: eth0.38
3195     vlan-id: 13
3196     main-router: gw2.schoolnet.local
3197     scan-mode: passive
3198
3199   etunet:
3200     ip-subnet:
3201       - add: 192.168.5.0/24
3202     interface: eth2
3203     vlan-id: 14
3204     main-router: gw3.etunet.local
3205     scan-mode: passive
3206
3207 switch:
3208   - hostname: sw1.klask.local
3209     location: BatY / 1 floor / K004
3210     portignore:
3211       - 1
3212       - 2
3213
3214   - hostname: sw2.klask.local
3215     location: BatY / 2 floor / K203
3216     type: HP2424
3217     portignore:
3218       - 1
3219       - 2
3220     fake-ip: 192.168.9.14
3221
3222   - hostname: sw3.klask.local
3223     location: BatY / 2 floor / K203
3224
3225I think it's pretty easy to understand.
3226The default section can be overide in any section, if parameter mean something in theses sections.
3227Network to be scan are define in the network section. You must put an add by network.
3228Maybe I will make a delete line to suppress specific computers.
3229The switch section define your switch.
3230You have to write the port number to ignore, this was important if your switchs are cascades
3231(right now, method C<updatesw> find them automatically)
3232and is still important if you have loop (with PVST or MSTP).
3233Just put the ports numbers between switch.
3234
3235The C<community> parameter is use to get SNMP data on switch.
3236It could be overload for each switch.
3237By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3238Some few command change the switch state as the commands L</enable> and L</disable>.
3239In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3240Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
3241
3242
3243=head1 ABBREVIATION FOR PORT
3244
3245HP Procurve and Nexans switches have a simplistic numbering scheme.
3246It's just number: 1, 2, 3... 24.
3247On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3248Nothing is done on theses ports names.
3249
3250On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3251followed by tree number.
3252In order to have short name,
3253we made the following rules:
3254
3255 Bridge-Aggregation     -> Br
3256 FastEthernet           -> Fa
3257 Forty-GigabitEthernet  -> Fo
3258 FortyGigabitEthernet   -> Fo
3259 GigabitEthernet        -> Gi
3260 Giga                   -> Gi
3261 Port-Channel           -> Po
3262 Ten-GigabitEthernet    -> Te
3263 TenGigabitEthernet     -> Te
3264 Ten                    -> Te
3265
3266All Klask command automatically normalize the port name on standart output
3267and also on input command line.
3268
3269In the case of use an aggregator port (Po, Tk, Br ...),
3270the real ports used are also return.
3271
3272
3273=head1 SWITCH SUPPORTED
3274
3275Here is a list of switches where Klask gives or gave (for old switches) good results.
3276We have only a few manageable switches to actually test Klask.
3277It is quite possible that switches from other brands will work just as well.
3278You just have to do a test on it and add the line of description that goes well in the source code.
3279Contact us for any additional information.
3280
3281In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3282This makes it possible to adjust the code names of the different manufacturers!
3283
3284HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3285J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3286J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F).
3287
3288BayStack: BayStack 350T HW(BS350T)
3289
3290Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3291
3292DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3293
3294H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3295
3296H3C: H3C5500
3297
32983COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
32993CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
33003CR17572-91, 3C17702-US, 3C17700.
3301
3302
3303=head1 FILES
3304
3305 /etc/klask/klask.conf
3306 /var/lib/klask/klaskdb
3307 /var/lib/klask/switchdb
3308
3309
3310=head1 SEE ALSO
3311
3312Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3313
3314=over
3315
3316=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
3317
3318=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3319
3320=back
3321
3322
3323=head1 VERSION
3324
3325$Id: klask 266 2017-09-15 07:27:56Z g7moreau $
3326
3327
3328=head1 AUTHOR
3329
3330Written by Gabriel Moreau, Grenoble - France
3331
3332
3333=head1 LICENSE AND COPYRIGHT
3334
3335GPL version 2 or later and Perl equivalent
3336
3337Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.