source: trunk/klask @ 279

Last change on this file since 279 was 279, checked in by g7moreau, 7 years ago
  • double arrow for trunk port
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 122.1 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 279 2017-09-25 15:34:00Z g7moreau $
6
7use strict;
8use warnings;
9use version; our $VERSION = qv('0.6.6');
10
11use Readonly;
12use FileHandle;
13use Net::SNMP;
14#use YAML;
15use YAML::Syck;
16use Net::Netmask;
17use Net::CIDR::Lite;
18use NetAddr::IP;
19use Getopt::Long qw(GetOptions);
20use Socket;
21use List::Util 'shuffle';
22
23# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
24# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
25# arping net-tools fping bind9-host arpwatch
26
27################################################################
28# general initialization
29################################################################
30
31my $KLASK_VAR      = '/var/lib/klask';
32my $KLASK_CFG_FILE = '/etc/klask/klask.conf';
33my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
34my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
35
36test_running_environnement();
37
38my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
39
40my %DEFAULT     = %{$KLASK_CFG->{'default'}};
41my @SWITCH_LIST = @{$KLASK_CFG->{'switch'}};
42
43my %SWITCH_LEVEL = ();
44my %SWITCH_DB    = ();
45LEVEL_OF_EACH_SWITCH:
46for my $sw (@SWITCH_LIST) {
47   $SWITCH_LEVEL{$sw->{'hostname'}} = $sw->{'level'} || $DEFAULT{'switch_level'}  || 2;
48   $SWITCH_DB{$sw->{'hostname'}} = $sw;
49
50   # SNMP parameter initialisation
51   my %session = ( -hostname   => $sw->{'hostname'} );
52   $session{-version} = $sw->{'version'}  || 1;
53   $session{-port}    = $sw->{'snmpport'} || $DEFAULT{'snmpport'}  || 161;
54   if (exists $sw->{'version'} and $sw->{'version'} eq '3') {
55      $session{-username} = $sw->{'username'} || 'snmpadmin';
56      }
57   else {
58      $session{-community} = $sw->{'community'} || $DEFAULT{'community'} || 'public';
59      }
60   $sw->{'snmp_param_session'} = \%session;
61   }
62@SWITCH_LIST = reverse sort { $SWITCH_LEVEL{$a->{'hostname'}} <=> $SWITCH_LEVEL{$b->{'hostname'}} } @{$KLASK_CFG->{'switch'}};
63
64#my %SWITCH_PORT_COUNT = ();
65
66my %CMD_DB = (
67   'help'                 => \&cmd_help,
68   'version'              => \&cmd_version,
69   'exportdb'             => \&cmd_exportdb,
70   'updatedb'             => \&cmd_updatedb,
71   'searchdb'             => \&cmd_searchdb,
72   'removedb'             => \&cmd_removedb,
73   'cleandb'              => \&cmd_cleandb,
74   'search'               => \&cmd_search,
75   'enable'               => \&cmd_enable,
76   'disable'              => \&cmd_disable,
77   'status'               => \&cmd_status,
78   'updatesw'             => \&cmd_updatesw,
79   'exportsw'             => \&cmd_exportsw,
80   'iplocation'           => \&cmd_ip_location,
81   'ip-free'              => \&cmd_ip_free,
82   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
83   'bad-vlan-id'          => \&cmd_bad_vlan_id,
84   'poe-enable'           => \&cmd_poe_enable,
85   'poe-disable'          => \&cmd_poe_disable,
86   'poe-status'           => \&cmd_poe_status,
87   'port-setvlan'         => \&cmd_port_setvlan,
88   'port-getvlan'         => \&cmd_port_getvlan,
89   'vlan-setname'         => \&cmd_vlan_setname,
90   'vlan-getname'         => \&cmd_vlan_getname,
91   'vlan-list'            => \&cmd_vlan_list,
92   'rebootsw'             => \&cmd_rebootsw,
93   );
94
95#Readonly my %INTERNAL_PORT_MAP => (
96#   0 => 'A',
97#   1 => 'B',
98#   2 => 'C',
99#   3 => 'D',
100#   4 => 'E',
101#   5 => 'F',
102#   6 => 'G',
103#   7 => 'H',
104#   );
105#Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
106
107Readonly my %SWITCH_KIND => (
108   # HP
109   J3299A           => { type => 1, model => 'HP224M',         match => 'HP J3299A ProCurve Switch 224M'       },
110   J4120A           => { type => 1, model => 'HP1600M',        match => 'HP J4120A ProCurve Switch 1600M'      },
111   J9029A           => { type => 1, model => 'HP1800-8G',      match => 'PROCURVE J9029A'                      },
112   J9449A           => { type => 1, model => 'HP1810-8G',      match => 'HP ProCurve 1810G - 8 GE'             },
113   J4093A           => { type => 1, model => 'HP2424M',        match => 'HP J4093A ProCurve Switch 2424M'      },
114   J9279A           => { type => 1, model => 'HP2510G-24',     match => 'ProCurve J9279A Switch 2510G-24'      },
115   J9280A           => { type => 1, model => 'HP2510G-48',     match => 'ProCurve J9280A Switch 2510G-48'      },
116   J4813A           => { type => 1, model => 'HP2524',         match => 'HP J4813A ProCurve Switch 2524'       },
117   J4900A           => { type => 1, model => 'HP2626A',        match => 'HP J4900A ProCurve Switch 2626'       },
118   J4900B           => { type => 1, model => 'HP2626B',        match => 'J4900B.+?Switch 2626'                 }, # ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
119   J4899B           => { type => 1, model => 'HP2650',         match => 'ProCurve J4899B Switch 2650'          },
120   J9021A           => { type => 1, model => 'HP2810-24G',     match => 'ProCurve J9021A Switch 2810-24G'      },
121   J9022A           => { type => 1, model => 'HP2810-48G',     match => 'ProCurve J9022A Switch 2810-48G'      },
122   J8692A           => { type => 1, model => 'HP3500-24G',     match => 'J8692A Switch 3500yl-24G'             },
123   J4903A           => { type => 1, model => 'HP2824',         match => 'J4903A.+?Switch 2824,'                },
124   J4110A           => { type => 1, model => 'HP8000M',        match => 'HP J4110A ProCurve Switch 8000M'      },
125   JE074A           => { type => 2, model => 'HP5120-24G',     match => 'HP Comware.+?A5120-24G EI Switch'     },
126   JE069A           => { type => 2, model => 'HP5120-48G',     match => 'HP Comware.+?A5120-48G EI Switch'     },
127   JD377A           => { type => 2, model => 'HP5500-24G',     match => 'HP Comware.+?A5500-24G EI Switch'     },
128   JD374A           => { type => 2, model => 'HP5500-24F',     match => 'HP Comware.+?A5500-24G-SFP EI Switch' },
129   # BayStack
130   BS350T           => { type => 1, model => 'BS350T',         match => 'BayStack 350T HW'                     },
131   # Nexans
132   N3483G           => { type => 2, model => 'NA3483-6G',      match => 'GigaSwitch V3 TP SFP-I 48.+ ES3'      },
133   N3483P           => { type => 2, model => 'NA3483-6P',      match => 'GigaSwitch V3 TP.PSE.+ 48/54V ES3'    }, # GigaSwitch V3 TP(PSE+) SFP-I 48/54V ES3 (HW3/ENHANCED/SECURITY/V4.10C)
134   # DELL
135   PC7024           => { type => 2, model => 'DPC7024',        match => 'PowerConnect 7024,.+?VxWorks'         },
136   N2048            => { type => 2, model => 'DN2048',         match => 'Dell Networking N2048,'               },
137   N4032F           => { type => 2, model => 'DN4032F',        match => 'Dell Networking N4032F,'              },
138   N4064F           => { type => 2, model => 'DN4064F',        match => 'Dell Networking N4064F,'              },
139   # 3COM
140   'H3C5500'        => { type => 1, model => 'H3C5500',        match => 'H3C S5500-SI Series'                  },
141   '3C17203'        => { type => 1, model => '3C17203',        match => '3Com SuperStack 3 24-Port'            },
142   '3C17204'        => { type => 1, model => '3C17204',        match => '3Com SuperStack 3 48-Port'            },
143   '3CR17562-91'    => { type => 1, model => '3CR17562-91',    match => '3Com Switch 4500 50-Port'             },
144   '3CR17255-91'    => { type => 1, model => '3CR17255-91',    match => '3Com Switch 5500G-EI 48-Port'         },
145   '3CR17251-91'    => { type => 1, model => '3CR17251-91',    match => '3Com Switch 5500G-EI 48-Port'         },
146   '3CR17571-91'    => { type => 1, model => '3CR17571-91',    match => '3Com Switch 4500 PWR 26-Port'         },
147   '3CRWX220095A'   => { type => 1, model => '3CRWX220095A',   match => '3Com Wireless LAN Controller'         },
148   '3CR17254-91'    => { type => 1, model => '3CR17254-91',    match => '3Com Switch 5500G-EI 24-Port'         },
149   '3CRS48G-24S-91' => { type => 1, model => '3CRS48G-24S-91', match => '3Com Switch 4800G 24-Port'            },
150   '3CRS48G-48S-91' => { type => 1, model => '3CRS48G-48S-91', match => '3Com Switch 4800G 48-Port'            },
151   '3C17708'        => { type => 1, model => '3C17708',        match => '3Com Switch 4050'                     },
152   '3C17709'        => { type => 1, model => '3C17709',        match => '3Com Switch 4060'                     },
153   '3C17707'        => { type => 1, model => '3C17707',        match => '3Com Switch 4070'                     },
154   '3CR17258-91'    => { type => 1, model => '3CR17258-91',    match => '3Com Switch 5500G-EI 24-Port SFP'     },
155   '3CR17181-91'    => { type => 1, model => '3CR17181-91',    match => '3Com Switch 5500-EI 28-Port FX'       },
156   '3CR17252-91'    => { type => 1, model => '3CR17252-91',    match => '3Com Switch 5500G-EI PWR 24-Port'     },
157   '3CR17253-91'    => { type => 1, model => '3CR17253-91',    match => '3Com Switch 5500G-EI PWR 48-Port'     },
158   '3CR17250-91'    => { type => 1, model => '3CR17250-91',    match => '3Com Switch 5500G-EI 24-Port'         },
159   '3CR17561-91'    => { type => 1, model => '3CR17561-91',    match => '3Com Switch 4500 26-Port'             },
160   '3CR17572-91'    => { type => 1, model => '3CR17572-91',    match => '3Com Switch 4500 PWR 50-Port'         },
161   '3C17702-US'     => { type => 1, model => '3C17702-US',     match => '3Com Switch 4900 SX'                  },
162   '3C17700'        => { type => 1, model => '3C17700',        match => '3Com Switch 4900'                     },
163   );
164
165Readonly my %OID_NUMBER => (
166   sysDescription  => '1.3.6.1.2.1.1.1.0',
167   sysName         => '1.3.6.1.2.1.1.5.0',
168   sysContact      => '1.3.6.1.2.1.1.4.0',
169   sysLocation     => '1.3.6.1.2.1.1.6.0',
170   searchPort1     => '1.3.6.1.2.1.17.4.3.1.2',          # BRIDGE-MIB (802.1D).
171   searchPort2     => '1.3.6.1.2.1.17.7.1.2.2.1.2',      # Q-BRIDGE-MIB (802.1Q) add 0 if unknown vlan id
172   vlanPortDefault => '1.3.6.1.2.1.17.7.1.4.5.1.1',      # dot1qPvid
173   vlanStatus      => '1.3.6.1.2.1.17.7.1.4.3.1.5',      # integer 4 Create, 6 Destroy
174   vlanName        => '1.3.6.1.2.1.17.7.1.4.3.1.1',      # string
175   HPicfReset      => '1.3.6.1.4.1.11.2.14.11.1.4.1',    # HP reboot switch
176   ifIndex         => '1.3.6.1.2.1.17.1.4.1.2',          # dot1dBasePortIfIndex - Interface index redirection
177   ifName          => '1.3.6.1.2.1.31.1.1.1.1',          # Interface name (give port number)
178   portUpDown      => '1.3.6.1.2.1.2.2.1.7',             # 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)  = 2 (down)
179   poeState        => '1.3.6.1.2.1.105.1.1.1.3.1',       # 1.3.6.1.2.1.105.1.1.1.3.1.NoPort = 1 (poe up)  = 2 (poe down) - Cisco and Zyxel
180   NApoeState      => '1.3.6.1.4.1.266.20.3.1.1.21',     # .NoPort = 2 (poe off)  = 8 (poe atHighPower) - Nexans
181   ifAggregator    => '1.2.840.10006.300.43.1.2.1.1.12', # dot3adAggPortSelectedAggID - 0 not part of an  Aggregator - Ciso Dell HP Comware -  See https://stackoverflow.com/questions/14960157/how-to-map-portchannel-to-interfaces-via-snmp https://gist.github.com/bldewolf/6314435
182   );
183
184Readonly my %PORT_UPDOWN => (
185   1 => 'enable',
186   2 => 'disable',
187   );
188
189Readonly my $RE_MAC_ADDRESS  => qr{ [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} }xms;
190Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
191
192Readonly my $RE_FLOAT_HOSTNAME => $DEFAULT{'float-regex'} || qr{ ^float }xms;
193
194Readonly my $SEP_AGGREGATOR_PORT => ',';  # : is already use to join switch and port
195Readonly my $SEP_SWITCH_PORT     => ':';
196
197
198################################################################
199# main program
200################################################################
201
202my $cmd = shift @ARGV || 'help';
203if (defined $CMD_DB{$cmd}) {
204   $CMD_DB{$cmd}->(@ARGV);
205   }
206else {
207   print {*STDERR} "klask: command $cmd not found\n\n";
208   $CMD_DB{help}->();
209   exit 1;
210   }
211
212exit;
213
214################################################################
215# subroutine
216################################################################
217
218#---------------------------------------------------------------
219sub test_running_environnement {
220   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
221   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
222   return;
223   }
224
225#---------------------------------------------------------------
226sub test_switchdb_environnement {
227   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
228   return;
229   }
230
231#---------------------------------------------------------------
232sub test_maindb_environnement {
233   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
234   return;
235   }
236
237#---------------------------------------------------------------
238# fast ping dont l'objectif est de remplir la table arp de la machine
239sub fast_ping {
240   # Launch this command without waiting...
241   system "fping -q -c 1 @_ > /dev/null 2>&1 &";
242   return;
243   }
244
245#---------------------------------------------------------------
246sub shell_command {
247   my $cmd = shift;
248
249   my $fh     = new FileHandle;
250   my $result = '';
251   open $fh, q{-|}, "LANG=C $cmd" or die "Can't exec $cmd\n";
252   $result .= <$fh>;
253   close $fh;
254   chomp $result;
255   return $result;
256   }
257
258#---------------------------------------------------------------
259# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
260sub resolve_ip_arp_host {
261   my $param_ip_or_host = shift;
262   my $interface = shift || q{*};
263   my $type      = shift || q{fast};
264   my $already   = shift || q{yes};
265
266   my %ret = (
267      hostname_fq  => 'unknow',
268      ipv4_address => '0.0.0.0',
269      mac_address  => 'unknow',
270      );
271
272   # perl -MSocket -E 'say inet_ntoa(scalar gethostbyname("tech7meylan.hmg.inpg.fr"))'
273   my $packed_ip = scalar gethostbyname($param_ip_or_host);
274   return %ret if not defined $packed_ip;
275   $ret{'ipv4_address'} = inet_ntoa($packed_ip);
276   #if ($ret{'ipv4_address'} !~ m/$RE_IPv4_ADDRESS/) {
277   #   print "Error: for computer $param_ip_or_host on interface $interface, IP $ret{'ipv4_address'} is not valide\n";
278   #   return %ret;
279   #   }
280
281   # perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("194.254.66.240"), AF_INET)'
282   my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) if $already eq 'yes';
283   $ret{'hostname_fq'} = $hostname_fq if defined $hostname_fq;
284
285   # my $cmd = q{grep  -he '\b} . $param_ip_or_host . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
286   #my $cmd = q{grep  -he '\b} . $ret{'ipv4_address'} . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
287   my $cmd = q{grep  -He '\b} . $ret{'ipv4_address'} . q{\b' } . "/var/lib/arpwatch/$interface.dat" . '| sed -e \'s|^/var/lib/arpwatch/\(.*\)\.dat:|\1 |;\' | sort -rn -k 4,4 | head -1';
288   #grep -He 194.254.66.252 /var/lib/arpwatch/*.dat | sed -e 's|^/var/lib/arpwatch/\(.*\)\.dat:|\1\t|;' | sort -rn -k 4,4 | head -1
289
290   my $cmd_arpwatch = shell_command $cmd;
291   #my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
292   my ($interface2, $arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
293
294   $ret{'interface'}    = $interface2 || $interface;
295   $ret{'mac_address'}  = $arp       if $arp;
296   $ret{'timestamp'}    = $timestamp if $timestamp;
297
298   my $nowtimestamp = time;
299
300   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 45 * 60 ) ) ) { # 45 min
301      $ret{'mac_address'} = 'unknow';
302      return %ret;
303      }
304
305   # ARP result
306   #
307   # LANG=C arp -a 194.254.66.62 -i eth331
308   # gw66-62.legi.grenoble-inp.fr (194.254.66.62) at 00:08:7c:bb:0f:c0 [ether] on eth331
309   #
310   # LANG=C ip neigh show to 194.254.66.62 dev eth331
311   # 194.254.66.62 lladdr 00:08:7c:bb:0f:c0 REACHABLE
312   # LANG=C ip neigh show to 194.254.66.62
313   # 194.254.66.62 dev eth331 lladdr 00:08:7c:bb:0f:c0 REACHABLE
314#   my $cmd_arp  = shell_command "arp -a $param_ip_or_host -i $ret{'interface'}";
315#   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
316#      ( $ret{'hostname_fq'}, $ret{'ipv4_address'}, $ret{'mac_address'} )  = ($1, $2, $3);
317#      }
318   if ($ret{'mac_address'} eq 'unknow') {
319      # Last chance to have the mac_address
320      if ($ret{'interface'} eq '*') {
321         my $cmd_arp  = shell_command "ip neigh show to $ret{'ipv4_address'}";
322         if ( $cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s dev \s ([\w\d\.\:]+) \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms ) {
323            ($ret{'interface'}, $ret{'mac_address'}) = ($1, $2);
324            }
325         }
326      else {
327         my $cmd_arp  = shell_command "ip neigh show to $ret{'ipv4_address'} dev $ret{'interface'}";
328         if ( $cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms ) {
329            $ret{'mac_address'} = $1;
330            }
331         }
332      }
333
334   # Normalize MAC Address
335   if ($ret{'mac_address'} ne 'unknow') {
336      my @paquets = ();
337      for ( split m/ : /xms, $ret{'mac_address'} ) {
338         my @chars = split m//xms, uc "00$_";
339         push @paquets, "$chars[-2]$chars[-1]";
340         }
341      $ret{'mac_address'} = join q{:}, @paquets;
342      }
343
344   return %ret;
345   }
346
347#---------------------------------------------------------------
348# Find Surname of a switch
349sub get_switch_model {
350   my $sw_snmp_description = shift || 'unknow';
351   $sw_snmp_description =~ s/[\n\r]/ /g;
352
353   for my $sw_kind (keys %SWITCH_KIND) {
354      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/ms; # option xms break search, why ?
355
356      return $SWITCH_KIND{$sw_kind}->{model};
357      }
358
359   return $sw_snmp_description;
360   }
361
362#---------------------------------------------------------------
363# va rechercher le nom des switchs pour savoir qui est qui
364sub init_switch_names {
365   my ($verbose, $verb_description, $check_hostname, $check_location) = @_;
366
367   printf "%-26s                %-25s %s\n",'Switch','Description','Type' if $verbose;
368   print "------------------------------------------------------------------------------\n" if $verbose;
369
370   INIT_EACH_SWITCH:
371   for my $sw (@SWITCH_LIST) {
372      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
373      print "$error \n" if $error;
374
375      my $result = $session->get_request(
376         -varbindlist => [
377            $OID_NUMBER{'sysDescription'},
378            $OID_NUMBER{'sysName'},
379            $OID_NUMBER{'sysContact'},
380            $OID_NUMBER{'sysLocation'},
381            ]
382         );
383      if (!defined $result) {
384         printf {*STDERR} "ERROR: %s.\n", $session->error();
385         $session->close();
386         # Remove bad switch
387         @SWITCH_LIST = grep { $_->{'hostname'} ne $sw->{'hostname'} } @SWITCH_LIST;
388         delete $SWITCH_LEVEL{$sw->{'hostname'}} if exists $SWITCH_LEVEL{$sw->{'hostname'}};
389         delete $SWITCH_DB{$sw->{'hostname'}}    if exists $SWITCH_DB{$sw->{'hostname'}};
390         next INIT_EACH_SWITCH;
391         }
392
393      $sw->{'description'} = $result->{$OID_NUMBER{'sysName'}} || $sw->{'hostname'};
394      $sw->{model} = get_switch_model($result->{$OID_NUMBER{'sysDescription'}});
395      if ($verb_description) {
396         my $desc = $result->{$OID_NUMBER{'sysDescription'}};
397         $desc =~ s/[\n\r]/ /g;
398         print "   description: $desc\n"
399         }
400      if ($check_hostname) {
401         my ($hostname) = split /\./, $sw->{'hostname'}, 2;
402         print " $hostname - error internal hostname: $sw->{'hostname'}\n" if $result->{$OID_NUMBER{'sysName'}} ne $hostname;
403         }
404      if ($check_location) {
405         my $location = $result->{$OID_NUMBER{'sysLocation'}};
406         $location =~ s/^"(.+)"$/$1/;
407         print " $sw->{'hostname'} - error location: '$location' -> '$sw->{'location'}'\n" if $location ne $sw->{'location'};
408         }
409      #$sw->{'location'} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{'hostname'};
410      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{'hostname'};
411      $session->close;
412
413      # Ligne à virer car on récupère maintenant le modèle du switch
414      #my ($desc, $type) = split m/ : /xms, $sw->{'description'}, 2;
415      printf "%-26s 0--------->>>> %-25s %s\n", $sw->{'hostname'}, $sw->{'description'}, $sw->{model} if $verbose;
416      }
417
418   print "\n" if $verbose;
419   return;
420   }
421
422#---------------------------------------------------------------
423# convert hexa (only 2 digits) to decimal
424sub digit_hex2dec {
425   #00:0F:1F:43:E4:2B
426   my $car = '00' . uc shift;
427
428   return '00' if $car eq '00UNKNOW';
429   my %table = (
430      '0'=>'0',  '1'=>'1',  '2'=>'2',  '3'=>'3',  '4'=>'4',
431      '5'=>'5',  '6'=>'6',  '7'=>'7',  '8'=>'8',  '9'=>'9',
432      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15',
433      );
434   my @chars = split m//xms, $car;
435   return $table{$chars[-2]}*16 + $table{$chars[-1]};
436   }
437
438#---------------------------------------------------------------
439
440sub normalize_mac_address {
441   my $mac_address = shift;
442
443   # D07E-28D1-7AB8 or D07E.28D1.7AB8 or d07e28-d17ab8
444   if ($mac_address =~ m{^ (?: [0-9A-Fa-f]{4} [-\.]){2} [0-9A-Fa-f]{4} $}xms
445      or $mac_address =~ m{^ [0-9A-Fa-f]{6} - [0-9A-Fa-f]{6} $}xms
446      ) {
447      $mac_address =~ s/[-\.]//g;
448      return join q{:}, unpack('(A2)*', uc($mac_address));
449      }
450
451   return join q{:}, map { substr( uc("00$_"), -2) } split m/ [:-] /xms, $mac_address;
452   }
453
454#---------------------------------------------------------------
455# convert MAC hex address to decimal
456sub mac_address_hex2dec {
457   #00:0F:1F:43:E4:2B
458   my $mac_address = shift;
459
460   my @paquets = split m/ : /xms, $mac_address;
461   my $return = q{};
462   for (@paquets) {
463      $return .= q{.} . digit_hex2dec($_);
464      }
465   return $return;
466   }
467
468#---------------------------------------------------------------
469sub format_aggregator4html {
470   my $port_hr = shift;
471   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/: /; # First occurence
472   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ /g; # Other occurence
473   return $port_hr;
474   }
475
476#---------------------------------------------------------------
477sub format_aggregator4dot {
478   my $port_hr = shift;
479   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ - /; # First occurence
480   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ /g; # Other occurence
481   return $port_hr;
482   }
483
484#---------------------------------------------------------------
485# return the port and the switch where the computer is connected
486sub find_switch_port {
487   my $mac_address     = shift;
488   my $switch_proposal = shift || q{};
489   my $vlan_id = shift || 0;
490
491   my %ret;
492   $ret{'switch_description'} = 'unknow';
493   $ret{'switch_port_id'} = '0';
494
495   return %ret if $mac_address eq 'unknow';;
496
497   my @switch_search = @SWITCH_LIST;
498   if ($switch_proposal ne q{}) {
499      for my $sw (@SWITCH_LIST) {
500         next if $sw->{'hostname'} ne $switch_proposal;
501         unshift @switch_search, $sw;
502         last;
503         }
504      }
505
506   my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
507   my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
508
509   LOOP_ON_SWITCH:
510   for my $sw (@switch_search) {
511      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
512      print "$error \n" if $error;
513
514      my $result = $session->get_request(
515         -varbindlist => [$oid_search_port1]
516         );
517      if (not defined $result) {
518         $result = $session->get_request(
519            -varbindlist => [$oid_search_port2]
520            );
521         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
522         }
523
524      if (not (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance')) {
525         $session->close;
526         next LOOP_ON_SWITCH;
527         }
528
529      my $swport_id = $result->{$oid_search_port1};
530      my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
531
532      $session->close;
533
534      # IMPORTANT !!
535      # ceci empeche la detection sur certains port ...
536      # en effet les switch sont relies entre eux par un cable reseau et du coup
537      # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
538      # cette partie est a ameliore, voir a configurer dans l'entete
539      # 21->24 45->48
540      SWITCH_PORT_IGNORE:
541      for my $portignore (@{$sw->{portignore}}) {
542         next LOOP_ON_SWITCH if $swport_hr eq $portignore;
543         my ($swport_hr_limited) = split /$SEP_AGGREGATOR_PORT/, $swport_hr; # Beginning of the swith port (Aggregator)
544         next LOOP_ON_SWITCH if $swport_hr_limited eq $portignore;
545         }
546
547      $ret{'switch_hostname'}    = $sw->{'hostname'};
548      $ret{'switch_description'} = $sw->{'description'};
549      $ret{'switch_port_id'}     = $swport_id;
550      $ret{'switch_port_hr'}     = $swport_hr; # human readable
551
552      last LOOP_ON_SWITCH;
553      }
554   return %ret;
555   }
556
557#---------------------------------------------------------------
558# search all the port on all the switches where the computer is detected
559sub find_all_switch_port {
560   my $mac_address = shift;
561   my $vlan_id     = shift || 0;
562
563   my $ret = {};
564
565   return $ret if $mac_address eq 'unknow';
566
567   my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
568   my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
569   LOOP_ON_ALL_SWITCH:
570   for my $sw (@SWITCH_LIST) {
571      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
572      print "$error \n" if $error;
573
574      my $result = $session->get_request(
575         -varbindlist => [$oid_search_port1]
576         );
577      if (not defined $result) {
578         $result = $session->get_request(
579            -varbindlist => [$oid_search_port2]
580            );
581         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
582         }
583
584      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
585         my $swport_id = $result->{$oid_search_port1};
586         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
587
588         SWITCH_PORT_IGNORE:
589         for my $portignore (@{$sw->{portignore}}) {
590            if ($swport_hr eq $portignore) {
591               $session->close;
592               next LOOP_ON_ALL_SWITCH
593               }
594            }
595
596         $ret->{$sw->{'hostname'}} = {};
597         $ret->{$sw->{'hostname'}}{'hostname'}    = $sw->{'hostname'};
598         $ret->{$sw->{'hostname'}}{'description'} = $sw->{'description'};
599         $ret->{$sw->{'hostname'}}{'port_id'}     = $swport_id;
600         $ret->{$sw->{'hostname'}}{'port_hr'}     = $swport_hr;
601         }
602
603      $session->close;
604      }
605   return $ret;
606   }
607
608#---------------------------------------------------------------
609sub get_list_network {
610
611   return keys %{$KLASK_CFG->{'network'}};
612   }
613
614#---------------------------------------------------------------
615sub get_current_interface {
616   my $vlan_name = shift;
617
618   return $KLASK_CFG->{'network'}{$vlan_name}{'interface'};
619   }
620
621#---------------------------------------------------------------
622sub get_current_vlan_id {
623   my $vlan_name = shift;
624
625   return 0 if not exists $KLASK_CFG->{'network'}{$vlan_name};
626   return $KLASK_CFG->{'network'}{$vlan_name}{'vlan-id'};
627   }
628
629#---------------------------------------------------------------
630sub get_current_scan_mode {
631   my $vlan_name = shift;
632
633   return $KLASK_CFG->{'network'}{$vlan_name}{'scan-mode'} || $DEFAULT{'scan-mode'} || 'active';
634   }
635
636#---------------------------------------------------------------
637sub get_current_vlan_name_for_interface {
638   my $interface = shift;
639
640   for my $vlan_name (keys %{$KLASK_CFG->{'network'}}) {
641      next if $KLASK_CFG->{'network'}{$vlan_name}{'interface'} ne $interface;
642      return $vlan_name;
643      }
644   }
645
646#---------------------------------------------------------------
647# liste l'ensemble des adresses ip d'un réseau
648sub get_list_ip {
649   my @vlan_name = @_;
650
651   my $cidrlist = Net::CIDR::Lite->new;
652
653   for my $net (@vlan_name) {
654      my @line  = @{$KLASK_CFG->{'network'}{$net}{'ip-subnet'}};
655      for my $cmd (@line) {
656         for my $method (keys %{$cmd}) {
657            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
658            }
659         }
660      }
661
662   my @res = ();
663
664   for my $cidr ($cidrlist->list()) {
665      my $net = new NetAddr::IP $cidr;
666      for my $ip (@{$net}) {
667         $ip =~ s{ /32 }{}xms;
668         push @res,  $ip;
669         }
670      }
671
672   return @res;
673   }
674
675#---------------------------------------------------------------
676# liste l'ensemble des routeurs du réseau
677sub get_list_main_router {
678   my @vlan_name = @_;
679
680   my @res = ();
681
682   for my $net (@vlan_name) {
683      push @res, $KLASK_CFG->{'network'}{$net}{'main-router'};
684      }
685
686   return @res;
687   }
688
689#---------------------------------------------------------------
690sub normalize_port_human_readable {
691   my $sw_port_hr  = shift;
692
693   # Manufacturer abbreviation
694   $sw_port_hr =~ s/^Bridge-Aggregation/Br/i;
695   $sw_port_hr =~ s/^Port-Channel/Po/i;
696   $sw_port_hr =~ s/^Forty-?GigabitEthernet/Fo/i;
697   $sw_port_hr =~ s/^Ten-?GigabitEthernet/Te/i;
698   $sw_port_hr =~ s/^GigabitEthernet/Gi/i;
699   $sw_port_hr =~ s/^FastEthernet/Fa/i;
700
701   # Customer abbreviation
702   $sw_port_hr =~ s/^Ten/Te/i;
703   $sw_port_hr =~ s/^Giga/Gi/i;
704
705   return ucfirst $sw_port_hr;
706   }
707
708#---------------------------------------------------------------
709sub snmp_get_rwsession {
710   my ($sw) = @_;
711
712   my %session = %{$sw->{'snmp_param_session'}};
713   $session{-community} = $sw->{'community-rw'} || $DEFAULT{'community-rw'} || 'private';
714   return %session;
715   }
716
717#---------------------------------------------------------------
718sub snmp_get_switchport_id2hr {
719   my ($snmp_session, $swport_id) = @_;
720
721   # On H3C, port id (port_id) and port index (port_ix) are not the same
722   # Double SNMP request to get the name
723   # First get the index, second get the name (port_hr)
724
725   my $oid_search_ix = $OID_NUMBER{'ifIndex'} .'.'. $swport_id;
726   my $result_ix = $snmp_session->get_request(
727      -varbindlist => [$oid_search_ix]
728      );
729
730   my $swport_ix = $swport_id;
731   $swport_ix = $result_ix->{$oid_search_ix} if defined $result_ix;
732
733   return snmp_get_switchport_ix2hr($snmp_session, $swport_ix);
734   }
735
736#---------------------------------------------------------------
737sub snmp_get_switchport_ix2hr {
738   my ($snmp_session, $swport_ix) = @_;
739
740   my $oid_search_hr = $OID_NUMBER{'ifName'} .'.'. $swport_ix;
741   my $result_hr = $snmp_session->get_request(
742      -varbindlist => [$oid_search_hr]
743      );
744   my $swport_hr = $swport_ix;
745   $swport_hr = normalize_port_human_readable($result_hr->{$oid_search_hr}) if defined $result_hr;
746
747   # Aggregator port
748   if ($swport_hr =~ m/^(Trk|Br|Po)/) {
749      my $oid_search_index = $OID_NUMBER{'ifAggregator'}; # base OID
750      my @args = ( -varbindlist =>  [$oid_search_index]);
751      LOOP_ON_OID_PORT:
752      while ( defined $snmp_session->get_next_request(@args) ) {
753         my ($oid_current) = $snmp_session->var_bind_names;
754         last LOOP_ON_OID_PORT if  not Net::SNMP::oid_base_match($oid_search_index, $oid_current);
755
756         # IEEE8023-LAG-MIB::dot3adAggPortSelectedAggID.28 = INTEGER: 337
757         # IEEE8023-LAG-MIB::dot3adAggPortAttachedAggID.28 = INTEGER: 337
758         my $port_aggregator_index = $snmp_session->var_bind_list->{$oid_current};
759         my ($current_port_ix) = reverse split /\./, $oid_current; # last number
760
761         # prepare next loop item
762         @args = (-varbindlist => [$oid_current]);
763
764         next LOOP_ON_OID_PORT if $port_aggregator_index == 0;
765         next LOOP_ON_OID_PORT if not $port_aggregator_index == $swport_ix;
766
767         my $current_port_name = snmp_get_switchport_ix2hr($snmp_session, $current_port_ix);
768         $swport_hr .= "$SEP_AGGREGATOR_PORT$current_port_name";
769         }
770      }
771   return $swport_hr;
772   }
773
774#---------------------------------------------------------------
775# Reverse search port number
776sub snmp_get_switchport_hr2id {
777   my ($snmp_session, $swport_hr, $verbose) = @_;
778
779   # Split for Aggregator port
780   # Keep only the Aggregator part
781   ($swport_hr) = split /$SEP_AGGREGATOR_PORT/, $swport_hr;
782
783   my $swport_id = $swport_hr;
784   # direct return if already numeric (next loop is expensive) / old or simple switch
785   return $swport_id if $swport_id =~ m/^\d+$/;
786
787   my $oid_search_ix = $OID_NUMBER{'ifIndex'}; # base OID
788   my @args = ( -varbindlist =>  [$oid_search_ix]);
789   LOOP_ON_OID_PORT:
790   while ( defined $snmp_session->get_next_request(@args) ) {
791      my ($oid_current) = $snmp_session->var_bind_names;
792      last LOOP_ON_OID_PORT if  not Net::SNMP::oid_base_match($oid_search_ix, $oid_current);
793     
794      my $port_ifIndex = $snmp_session->var_bind_list->{$oid_current};
795      my ($port_ix) = reverse split /\./, $oid_current; # last number
796      printf "PORT1: %s => %s\n", $oid_current, $port_ifIndex if $verbose;
797
798      # prepare next loop item
799      @args = (-varbindlist => [$oid_current]);
800     
801      my $oid_search_ifName = $OID_NUMBER{'ifName'} .'.'. $port_ifIndex;
802      my $result = $snmp_session->get_request(-varbindlist => [$oid_search_ifName]);
803      next LOOP_ON_OID_PORT if not defined $result;
804     
805      my $current_port_hr = normalize_port_human_readable($result->{$oid_search_ifName});
806      printf "PORT2: $oid_search_ifName => $current_port_hr\n" if $verbose;
807      if ($current_port_hr eq $swport_hr) {
808         print "PORT3: $current_port_hr <-> $port_ix\n" if $verbose;
809         
810         # return port number ifIndex need by OID portUpDown
811         $swport_id = $port_ifIndex; # other possible value could be $port_ix
812         last LOOP_ON_OID_PORT;
813         }
814      }
815   return $swport_id;
816   }
817
818#---------------------------------------------------------------
819# Get the list of all the VLAN define on a switch
820sub snmp_get_vlan_list {
821   my ($snmp_session, $verbose) = @_;
822
823   my %vlandb = (); # Hash vlan number => vlan name
824
825   my $oid_search_index = $OID_NUMBER{'vlanName'}; # base OID
826   my @args = ( -varbindlist =>  [$oid_search_index]);
827   LOOP_ON_VLAN:
828   while ( defined $snmp_session->get_next_request(@args) ) {
829      my ($oid_current) = $snmp_session->var_bind_names;
830      last LOOP_ON_VLAN if not Net::SNMP::oid_base_match($oid_search_index, $oid_current);
831
832      my $vlan_name = $snmp_session->var_bind_list->{$oid_current};
833      my ($vlan_id) = reverse split /\./, $oid_current; # last number
834      printf "VLAN: %s => %s\n", $oid_current, $vlan_name if $verbose;
835
836      $vlandb{$vlan_id} = $vlan_name;
837
838      # prepare next loop item
839      @args = (-varbindlist => [$oid_current]);
840      }
841   return %vlandb;
842   }
843
844#---------------------------------------------------------------
845# Load computer database
846sub computerdb_load {
847   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
848
849   LOOP_ON_IP_ADDRESS:
850   for my $ip (keys %{$computerdb}) {
851
852      # Rename switch_port -> switch_port_id (2017/09/15)
853      if (not exists $computerdb->{$ip}{'switch_port_id' and exists $computerdb->{$ip}{'switch_port'}}) {
854         $computerdb->{$ip}{'switch_port_id'} = $computerdb->{$ip}{'switch_port'} if defined $computerdb->{$ip}{'switch_port'};
855         $computerdb->{$ip}{'switch_port_id'} = 0 if $computerdb->{$ip}{'switch_port_id'} !~ m/^\d+$/; # force numeric
856         }
857      delete $computerdb->{$ip}{'switch_port'} if exists $computerdb->{$ip}{'switch_port'};
858
859      next LOOP_ON_IP_ADDRESS if exists $computerdb->{$ip}{'switch_port_hr'} and defined $computerdb->{$ip}{'switch_port_hr'};
860
861      $computerdb->{$ip}{'switch_port_hr'} = $computerdb->{$ip}{'switch_port_id'};
862      }
863
864   return $computerdb;
865   }
866
867################################################################
868# command
869################################################################
870
871#---------------------------------------------------------------
872sub cmd_help {
873
874print <<'END';
875klask - port and search manager for switches, map management
876
877 klask version
878 klask help
879
880 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
881 klask exportdb [--format|-f txt|html]
882 klask removedb IP* computer*
883 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
884
885 klask updatesw [--verbose|-v]
886 klask exportsw [--format|-f txt|dot]
887
888 klask searchdb [--kind|-k host|mac] computer [mac-address]
889 klask search   computer
890 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
891
892 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
893
894 klask bad-vlan-id [--day|-d days_before_alert]
895
896 klask enable  [--verbose|-v] switch port
897 klask disable [--verbose|-v] switch port
898 klask status  [--verbose|-v] switch port
899
900 klask poe-enable  [--verbose|-v] switch port
901 klask poe-disable [--verbose|-v] switch port
902 klask poe-status  [--verbose|-v] switch port
903
904 klask vlan-getname switch vlan-id
905 klask vlan-list switch
906END
907   return;
908   }
909
910#---------------------------------------------------------------
911sub cmd_version {
912
913print <<'END';
914klask - port and search manager for switches, map management
915Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
916
917END
918   print ' $Id: klask 279 2017-09-25 15:34:00Z g7moreau $'."\n";
919   return;
920   }
921
922#---------------------------------------------------------------
923sub cmd_search {
924   my @computer = @_;
925
926   init_switch_names();    #nomme les switchs
927   fast_ping(@computer);
928
929   LOOP_ON_COMPUTER:
930   for my $clientname (@computer) {
931      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
932      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
933      my $vlan_id   = get_current_vlan_id($vlan_name);
934      my %where     = find_switch_port($resol_arp{'mac_address'}, '', $vlan_id); #retrouve l'emplacement
935
936      next LOOP_ON_COMPUTER if $where{'switch_description'} eq 'unknow' or $resol_arp{'hostname_fq'} eq 'unknow' or $resol_arp{'mac_address'} eq 'unknow';
937
938      printf '%-22s %2s %-30s %-15s %18s',
939         $where{'switch_hostname'},
940         $where{'switch_port_hr'},
941         $resol_arp{'hostname_fq'},
942         $resol_arp{'ipv4_address'},
943         $resol_arp{'mac_address'}."\n";
944      }
945   return;
946   }
947
948#---------------------------------------------------------------
949sub cmd_searchdb {
950   my @ARGV  = @_;
951
952   my $kind;
953
954   GetOptions(
955      'kind=s'   => \$kind,
956      );
957
958   my %possible_search = (
959      host  => \&cmd_searchdb_host,
960      mac   => \&cmd_searchdb_mac,
961      );
962
963   $kind = 'host' if not defined $possible_search{$kind};
964
965   $possible_search{$kind}->(@ARGV);
966   return;
967   }
968
969
970#---------------------------------------------------------------
971sub cmd_searchdb_host {
972   my @computer = @_;
973
974   fast_ping(@computer);
975   my $computerdb = computerdb_load();
976
977   LOOP_ON_COMPUTER:
978   for my $clientname (@computer) {
979      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
980      my $ip = $resol_arp{'ipv4_address'};
981
982      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
983
984      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
985      $year += 1900;
986      $mon++;
987      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
988
989      printf "%-22s %2s %-30s %-15s %-18s %s\n",
990         $computerdb->{$ip}{'switch_hostname'},
991         $computerdb->{$ip}{'switch_port_hr'},
992         $computerdb->{$ip}{'hostname_fq'},
993         $ip,
994         $computerdb->{$ip}{'mac_address'},
995         $date;
996      }
997   return;
998   }
999
1000#---------------------------------------------------------------
1001sub cmd_searchdb_mac {
1002   my @mac = map { normalize_mac_address($_) } @_;
1003
1004   my $computerdb = computerdb_load();
1005
1006   LOOP_ON_MAC:
1007   for my $mac (@mac) {
1008      LOOP_ON_COMPUTER:
1009      for my $ip (keys %{$computerdb}) {
1010         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{'mac_address'};
1011
1012         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1013         $year += 1900;
1014         $mon++;
1015         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1016
1017         printf "%-22s %2s %-30s %-15s %-18s %s\n",
1018            $computerdb->{$ip}{'switch_hostname'},
1019            $computerdb->{$ip}{'switch_port_hr'},
1020            $computerdb->{$ip}{'hostname_fq'},
1021            $ip,
1022            $computerdb->{$ip}{'mac_address'},
1023            $date;
1024         #next LOOP_ON_MAC;
1025         }
1026
1027      }
1028   return;
1029   }
1030
1031#---------------------------------------------------------------
1032sub cmd_updatedb {
1033   @ARGV = @_;
1034
1035   my ($verbose, $verb_description, $check_hostname, $check_location);
1036
1037   GetOptions(
1038      'verbose|v'          => \$verbose,
1039      'verb-description|d' => \$verb_description,
1040      'chk-hostname|h'     => \$check_hostname,
1041      'chk-location|l'     => \$check_location,
1042      );
1043
1044   my @network = @ARGV;
1045      @network = get_list_network() if not @network;
1046
1047   test_switchdb_environnement();
1048
1049   my $computerdb = {};
1050      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
1051   my $timestamp = time;
1052
1053   my %computer_not_detected = ();
1054   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
1055
1056   my $number_of_computer = get_list_ip(@network); # + 1;
1057   my $size_of_database   = keys %{$computerdb};
1058      $size_of_database   = 1 if $size_of_database == 0;
1059   my $i = 0;
1060   my $detected_computer = 0;
1061
1062   init_switch_names('yes', $verb_description, $check_hostname, $check_location);    #nomme les switchs
1063
1064   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
1065   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1066   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1067   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1068   my %db_switch_chained_port = ();
1069   for my $swport (keys %db_switch_connected_on_port) {
1070      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1071      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
1072      }
1073   for my $sw (@SWITCH_LIST) {
1074      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{'hostname'}}  if exists $db_switch_output_port{$sw->{'hostname'}};
1075      if ( exists $db_switch_chained_port{$sw->{'hostname'}} ) {
1076         chop $db_switch_chained_port{$sw->{'hostname'}};
1077         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{'hostname'}};
1078         }
1079#      print "$sw->{'hostname'} ++ @{$sw->{portignore}}\n";
1080      }
1081   }
1082
1083   my %router_mac_ip = ();
1084   DETECT_ALL_ROUTER:
1085#   for my $one_router ('194.254.66.254') {
1086   for my $one_router ( get_list_main_router(@network) ) {
1087      my %resol_arp = resolve_ip_arp_host($one_router);
1088      $router_mac_ip{ $resol_arp{'mac_address'} } = $resol_arp{'ipv4_address'};
1089      }
1090
1091   ALL_NETWORK:
1092   for my $current_net (@network) {
1093
1094      my @computer = get_list_ip($current_net);
1095      my $current_interface = get_current_interface($current_net);
1096
1097      fast_ping(@computer) if get_current_scan_mode($current_net) eq 'active';
1098
1099      LOOP_ON_COMPUTER:
1100      for my $one_computer (@computer) {
1101         $i++;
1102
1103         my $total_percent = int (($i*100)/$number_of_computer);
1104
1105         my $localtime = time - $timestamp;
1106         my ($sec,$min) = localtime $localtime;
1107
1108         my $time_elapse = 0;
1109            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
1110         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
1111
1112         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
1113         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
1114         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
1115         printf ' %-8s %-14s', $current_interface, $one_computer;
1116
1117         my $already_exist = exists $computerdb->{$one_computer} ? 'yes' : 'no';
1118         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface, 'fast', $already_exist);
1119
1120         # do not search on router connection (why ?)
1121         if ( exists $router_mac_ip{$resol_arp{'mac_address'}}) {
1122            $computer_not_detected{$one_computer} = $current_net;
1123            next LOOP_ON_COMPUTER;
1124            }
1125
1126         # do not search on switch inter-connection
1127         if (exists $SWITCH_LEVEL{$resol_arp{'hostname_fq'}}) {
1128            $computer_not_detected{$one_computer} = $current_net;
1129            next LOOP_ON_COMPUTER;
1130            }
1131
1132         my $switch_proposal = q{};
1133         if (exists $computerdb->{$resol_arp{'ipv4_address'}} and exists $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'}) {
1134            $switch_proposal = $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'};
1135            }
1136
1137         # do not have a mac address
1138         if ($resol_arp{'mac_address'} eq 'unknow' or (exists $resol_arp{'timestamps'} and $resol_arp{'timestamps'} < ($timestamp - 3 * 3600))) {
1139            $computer_not_detected{$one_computer} = $current_net;
1140            next LOOP_ON_COMPUTER;
1141            }
1142
1143         my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1144         my $vlan_id   = get_current_vlan_id($vlan_name);
1145         my %where = find_switch_port($resol_arp{'mac_address'}, $switch_proposal, $vlan_id);
1146
1147         #192.168.24.156:
1148         #  arp: 00:0B:DB:D5:F6:65
1149         #  hostname: pcroyon.hmg.priv
1150         #  port: 5
1151         #  switch: sw-batH-legi:hp2524
1152         #  timestamp: 1164355525
1153
1154         # do not have a mac address
1155#         if ($resol_arp{'mac_address'} eq 'unknow') {
1156#            $computer_not_detected{$one_computer} = $current_interface;
1157#            next LOOP_ON_COMPUTER;
1158#            }
1159
1160         # detected on a switch
1161         if ($where{'switch_description'} ne 'unknow') {
1162            $detected_computer++;
1163            $computerdb->{$resol_arp{'ipv4_address'}} = {
1164               hostname_fq        => $resol_arp{'hostname_fq'},
1165               mac_address        => $resol_arp{'mac_address'},
1166               switch_hostname    => $where{'switch_hostname'},
1167               switch_description => $where{'switch_description'},
1168               switch_port_id     => $where{'switch_port_id'},
1169               switch_port_hr     => $where{'switch_port_hr'},
1170               timestamp          => $timestamp,
1171               network            => $current_net,
1172               };
1173            next LOOP_ON_COMPUTER;
1174            }
1175
1176         # new in the database but where it is ?
1177         if (not exists $computerdb->{$resol_arp{'ipv4_address'}}) {
1178            $detected_computer++;
1179            $computerdb->{$resol_arp{'ipv4_address'}} = {
1180               hostname_fq        => $resol_arp{'hostname_fq'},
1181               mac_address        => $resol_arp{'mac_address'},
1182               switch_hostname    => $where{'switch_hostname'},
1183               switch_description => $where{'switch_description'},
1184               switch_port_id     => $where{'switch_port_id'},
1185               switch_port_hr     => $where{'switch_port_hr'},
1186               timestamp          => $resol_arp{'timestamp'},
1187               network            => $current_net,
1188               };
1189            }
1190
1191         # mise a jour du nom de la machine si modification dans le dns
1192         $computerdb->{$resol_arp{'ipv4_address'}}{'hostname_fq'} = $resol_arp{'hostname_fq'};
1193
1194         # mise à jour de la date de détection si détection plus récente par arpwatch
1195         $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'}   = $resol_arp{'timestamp'} if exists $resol_arp{'timestamp'} and $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $resol_arp{'timestamp'};
1196
1197         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
1198#         push @computer_not_detected, $resol_arp{'ipv4_address'} if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1199         $computer_not_detected{$resol_arp{'ipv4_address'}} = $current_net if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1200
1201         }
1202      }
1203
1204   # final end of line at the end of the loop
1205   printf "\n";
1206
1207   my $dirdb = $KLASK_DB_FILE;
1208      $dirdb =~ s{ / [^/]* $}{}xms;
1209   mkdir "$dirdb", 0755 unless -d "$dirdb";
1210   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1211
1212   for my $one_computer (keys %computer_not_detected) {
1213      my $current_net = $computer_not_detected{$one_computer};
1214      my $current_interface = get_current_interface($current_net);
1215      system "arping -c 1 -w 1 -rR -i $current_interface $one_computer > /dev/null 2>&1" if get_current_scan_mode($current_net) eq 'active';
1216      }
1217   return;
1218   }
1219
1220#---------------------------------------------------------------
1221sub cmd_removedb {
1222   my @computer = @_;
1223
1224   test_maindb_environnement();
1225
1226   my $computerdb = computerdb_load();
1227
1228   LOOP_ON_COMPUTER:
1229   for my $one_computer (@computer) {
1230
1231      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
1232            and exists $computerdb->{$one_computer} ) {
1233         delete $computerdb->{$one_computer};
1234         next;
1235         }
1236
1237      my %resol_arp = resolve_ip_arp_host($one_computer);
1238
1239      delete $computerdb->{$resol_arp{'ipv4_address'}} if exists $computerdb->{$resol_arp{'ipv4_address'}};
1240      }
1241
1242   my $dirdb = $KLASK_DB_FILE;
1243      $dirdb =~ s{ / [^/]* $}{}xms;
1244   mkdir "$dirdb", 0755 unless -d "$dirdb";
1245   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1246   return;
1247   }
1248
1249#---------------------------------------------------------------
1250sub cmd_cleandb {
1251   my @ARGV  = @_;
1252
1253   my $days_to_clean = 15;
1254   my $repairdns;
1255   my $verbose;
1256   my $database_has_changed;
1257
1258   GetOptions(
1259      'day|d=i'   => \$days_to_clean,
1260      'verbose|v' => \$verbose,
1261      'repair-dns|r' => \$repairdns,
1262      );
1263
1264   my @vlan_name = get_list_network();
1265
1266   my $computerdb = computerdb_load();
1267   my $timestamp = time;
1268
1269   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
1270   my $timestamp_3month  = 3600 * 24 * 90;
1271
1272   my %mactimedb = ();
1273   ALL_VLAN:
1274   for my $vlan (shuffle @vlan_name) {
1275
1276      my @ip_list   = shuffle get_list_ip($vlan);
1277
1278      LOOP_ON_IP_ADDRESS:
1279      for my $ip (@ip_list) {
1280
1281         next LOOP_ON_IP_ADDRESS if
1282            not exists $computerdb->{$ip};
1283
1284            #&& $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
1285         my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1286         my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1287         my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1288
1289         $mactimedb{$ip_mac} ||= {
1290            ip          => $ip,
1291            timestamp   => $ip_timestamp,
1292            vlan        => $vlan,
1293            hostname_fq => $ip_hostname_fq,
1294            };
1295
1296         if (
1297            ( $mactimedb{$ip_mac}->{'timestamp'} - $ip_timestamp > $timestamp_barrier
1298               or (
1299                  $mactimedb{$ip_mac}->{'timestamp'} > $ip_timestamp
1300                  and $timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_3month
1301                  )
1302            )
1303            and (
1304               not $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1305               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1306               )) {
1307            print "remove ip $ip\n" if $verbose;
1308            delete $computerdb->{$ip};
1309            $database_has_changed++;
1310            }
1311
1312         elsif (
1313            ( $ip_timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_barrier
1314               or (
1315                  $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}
1316                  and $timestamp - $ip_timestamp > $timestamp_3month
1317                  )
1318            )
1319            and (
1320               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1321               or $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1322               )) {
1323            print "remove ip ".$mactimedb{$ip_mac}->{ip}."\n" if $verbose;
1324            delete $computerdb->{$mactimedb{$ip_mac}->{ip}};
1325            $database_has_changed++;
1326            }
1327
1328         if ( $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}) {
1329            $mactimedb{$ip_mac} = {
1330               ip          => $ip,
1331               timestamp   => $ip_timestamp,
1332               vlan        => $vlan,
1333               hostname_fq => $ip_hostname_fq,
1334               };
1335            }
1336         }
1337      }
1338
1339   if ($repairdns) { # Search and update unkown computer in reverse DNS
1340      LOOP_ON_IP_ADDRESS:
1341      for my $ip (keys %{$computerdb}) {
1342         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} ne 'unknow';
1343
1344         my $packed_ip = scalar gethostbyname($ip);
1345         next LOOP_ON_IP_ADDRESS if not defined $packed_ip;
1346
1347         my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
1348         next LOOP_ON_IP_ADDRESS if not defined $hostname_fq;
1349
1350         $computerdb->{$ip}{'hostname_fq'} = $hostname_fq;
1351         $database_has_changed++;
1352         }
1353      }
1354
1355   if ( $database_has_changed ) {
1356      my $dirdb = $KLASK_DB_FILE;
1357         $dirdb =~ s{ / [^/]* $}{}xms;
1358      mkdir "$dirdb", 0755 unless -d "$dirdb";
1359      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1360      }
1361   return;
1362   }
1363
1364#---------------------------------------------------------------
1365sub cmd_exportdb {
1366   @ARGV = @_;
1367
1368   my $format = 'txt';
1369
1370   GetOptions(
1371      'format|f=s'  => \$format,
1372      );
1373
1374   my %possible_format = (
1375      txt  => \&cmd_exportdb_txt,
1376      html => \&cmd_exportdb_html,
1377      );
1378
1379   $format = 'txt' if not defined $possible_format{$format};
1380
1381   $possible_format{$format}->(@ARGV);
1382   return;
1383   }
1384
1385#---------------------------------------------------------------
1386sub cmd_exportdb_txt {
1387   test_maindb_environnement();
1388
1389   my $computerdb = computerdb_load();
1390
1391   printf "%-28s %8s              %-40s %-15s %-18s %-16s %s\n", qw(Switch Port Hostname-FQ IPv4-Address MAC-Address Date VLAN);
1392   print "--------------------------------------------------------------------------------------------------------------------------------------------\n";
1393
1394   LOOP_ON_IP_ADDRESS:
1395   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1396
1397      # to be improve in the future
1398      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1399
1400      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1401      $year += 1900;
1402      $mon++;
1403      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1404
1405      my $vlan = '';
1406      $vlan = $computerdb->{$ip}{'network'}.'('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1407
1408      my $arrow ='<-----------';
1409         $arrow ='<===========' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1410
1411      printf "%-28s %8s %12s %-40s %-15s %-18s %-16s %s\n",
1412         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1413         $computerdb->{$ip}{'switch_port_hr'},
1414         $arrow,
1415         $computerdb->{$ip}{'hostname_fq'},
1416         $ip,
1417         $computerdb->{$ip}{'mac_address'},
1418         $date,
1419         $vlan;
1420      }
1421   return;
1422   }
1423
1424#---------------------------------------------------------------
1425sub cmd_exportdb_html {
1426   test_maindb_environnement();
1427
1428   my $computerdb = computerdb_load();
1429
1430#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1431#<script src="sorttable-klask.js"></script>
1432
1433   print <<'END_HTML';
1434<table class="sortable" summary="Klask Host Database">
1435 <caption>Klask Host Database</caption>
1436 <thead>
1437  <tr>
1438   <th scope="col" class="klask-header-left">Switch</th>
1439   <th scope="col" class="sorttable_nosort">Port</th>
1440   <th scope="col" class="sorttable_nosort">Link</th>
1441   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1442   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1443   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1444   <th scope="col" class="sorttable_alpha">VLAN</th>
1445   <th scope="col" class="klask-header-right">Date</th>
1446  </tr>
1447 </thead>
1448 <tfoot>
1449  <tr>
1450   <th scope="col" class="klask-footer-left">Switch</th>
1451   <th scope="col" class="fklask-port">Port</th>
1452   <th scope="col" class="fklask-link">Link</th>
1453   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1454   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1455   <th scope="col" class="fklask-mac">MAC-Address</th>
1456   <th scope="col" class="fklask-vlan">VLAN</th>
1457   <th scope="col" class="klask-footer-right">Date</th>
1458  </tr>
1459 </tfoot>
1460 <tbody>
1461END_HTML
1462
1463   my %mac_count = ();
1464   LOOP_ON_IP_ADDRESS:
1465   for my $ip (keys %{$computerdb}) {
1466
1467      # to be improve in the future
1468      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1469
1470      $mac_count{$computerdb->{$ip}{'mac_address'}}++;
1471      }
1472
1473   my $typerow = 'even';
1474
1475   LOOP_ON_IP_ADDRESS:
1476   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1477
1478      # to be improve in the future
1479      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1480
1481      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1482      $year += 1900;
1483      $mon++;
1484      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1485
1486#      $odd_or_even++;
1487#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1488      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1489
1490      #my $arrow ='&#8592;';
1491      #   $arrow ='&#8656;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1492      my $arrow ='&#10229;';
1493         $arrow ='&#10232;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1494
1495      my $switch_hostname = $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'} || 'unkown';
1496      chomp $switch_hostname;
1497      my $switch_hostname_sort = sprintf '%s %06i' ,$switch_hostname, $computerdb->{$ip}{'switch_port_id'}; # Take switch index
1498
1499      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1500
1501      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{'mac_address'}}, $computerdb->{$ip}{'mac_address'};
1502
1503      $computerdb->{$ip}{'hostname_fq'} = 'unknow' if $computerdb->{$ip}{'hostname_fq'} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1504      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{'hostname_fq'};
1505
1506      my $vlan = '';
1507      $vlan = $computerdb->{$ip}{'network'}.' ('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1508
1509      my $parent_port_hr = format_aggregator4html($computerdb->{$ip}{'switch_port_hr'});
1510
1511      print <<"END_HTML";
1512  <tr class="$typerow">
1513   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1514   <td class="bklask-port">$parent_port_hr</td>
1515   <td>$arrow</td>
1516   <td sorttable_customkey="$host_short">$computerdb->{$ip}{'hostname_fq'}</td>
1517   <td sorttable_customkey="$ip_sort">$ip</td>
1518   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{'mac_address'}</td>
1519   <td>$vlan</td>
1520   <td>$date</td>
1521  </tr>
1522END_HTML
1523#   <td colspan="2">$arrow</td>
1524      }
1525
1526   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1527
1528   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1529   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1530   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1531   my %db_switch                   = %{$switch_connection->{'switch_db'}};
1532
1533   # Output switch connection
1534   LOOP_ON_OUTPUT_SWITCH:
1535   for my $sw (sort keys %db_switch_output_port) {
1536
1537      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1538
1539      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1540
1541      #my $arrow ='&#8702;';
1542      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1543      my $arrow ='&#10236;';
1544         $arrow ='&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1545
1546      if (exists $db_switch_parent{$sw}) {
1547         # Link to uplink switch
1548         next LOOP_ON_OUTPUT_SWITCH;
1549         
1550         # Do not print anymore
1551         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1552         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1553         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
1554
1555         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1556         $year += 1900;
1557         $mon++;
1558         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1559
1560         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1561
1562         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1563
1564         my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'};
1565
1566         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1567         $vlan .= ' ('.get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}).')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1568
1569         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1570         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
1571
1572         print <<"END_HTML";
1573  <tr class="$typerow">
1574   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1575   <td class="bklask-port">$parent_port_hr</td>
1576   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
1577   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{'switch'}</td>
1578   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1579   <td sorttable_customkey="$mac_sort">$mac_address</td>
1580   <td>$vlan</td>
1581   <td>$date</td>
1582  </tr>
1583END_HTML
1584         }
1585      else {
1586         # Router
1587         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1588
1589         my $host_short = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1590
1591         my $mac_address = $db_switch{$sw}->{'mac_address'};
1592         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1593         my $timestamp = $db_switch{$sw}->{'timestamp'};
1594
1595         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1596         $year += 1900;
1597         $mon++;
1598         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1599
1600         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1601
1602         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1603
1604         my $vlan = $db_switch{$sw}->{'network'};
1605         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1606
1607         my $arrow ='&#10235;';
1608            $arrow ='&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1609
1610         print <<"END_HTML";
1611  <tr class="$typerow">
1612   <td sorttable_customkey="router">router</td>
1613   <td class="bklask-port"></td>
1614   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$parent_port_hr</td></tr></table></td>
1615   <td sorttable_customkey="$host_short">$sw</td>
1616   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1617   <td sorttable_customkey="$mac_sort">$mac_address</td>
1618   <td>$vlan</td>
1619   <td>$date</td>
1620  </tr>
1621END_HTML
1622         #<td>$arrow</td><td>$parent_port_hr</td>
1623
1624         next LOOP_ON_OUTPUT_SWITCH;
1625
1626         # Old print
1627         print <<"END_HTML";
1628  <tr class="$typerow">
1629   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1630   <td class="bklask-port">$parent_port_hr</td>
1631   <td>$arrow</td><td></td>
1632   <td sorttable_customkey="router">router</td>
1633   <td sorttable_customkey="999999999999"></td>
1634   <td sorttable_customkey="99999"></td>
1635   <td></td>
1636   <td></td>
1637  </tr>
1638END_HTML
1639         }
1640      }
1641
1642   # Child switch connection : parent <- child
1643   for my $swport (sort keys %db_switch_connected_on_port) {
1644      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1645      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1646
1647         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1648
1649         my $mac_address = $db_switch{$sw}->{'mac_address'};
1650         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1651         my $timestamp = $db_switch{$sw}->{'timestamp'};
1652
1653         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1654         $year += 1900;
1655         $mon++;
1656         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1657
1658         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1659
1660         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1661
1662         $typerow = $typerow eq 'even' ? 'odd' : 'even';
1663
1664         #my $arrow ='&#8701;';
1665         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
1666         my $arrow ='&#10235;';
1667            $arrow ='&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
1668
1669         my $vlan = $db_switch{$sw}->{'network'};
1670         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1671
1672         if (exists $db_switch_output_port{$sw}) {
1673
1674            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1675
1676            my $parent_port_hr = format_aggregator4html($port_connect);
1677            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
1678
1679            print <<"END_HTML";
1680  <tr class="$typerow">
1681   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1682   <td class="bklask-port">$parent_port_hr</td>
1683   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
1684   <td sorttable_customkey="$host_short">$sw</td>
1685   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1686   <td sorttable_customkey="$mac_sort">$mac_address</td>
1687   <td>$vlan</td>
1688   <td>$date</td>
1689  </tr>
1690END_HTML
1691            }
1692         else {
1693            my $parent_port_hr = format_aggregator4html($port_connect);
1694
1695            print <<"END_HTML";
1696  <tr class="$typerow">
1697   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1698   <td class="bklask-port">$parent_port_hr</td>
1699   <td>$arrow</td>
1700   <td sorttable_customkey="$sw">$sw</td>
1701   <td sorttable_customkey="">$ipv4_address</td>
1702   <td sorttable_customkey="">$mac_address</td>
1703   <td>$vlan</td>
1704   <td>$date</td>
1705  </tr>
1706END_HTML
1707            }
1708         }
1709      }
1710
1711   print <<'END_HTML';
1712 </tbody>
1713</table>
1714END_HTML
1715   return;
1716   }
1717
1718#---------------------------------------------------------------
1719sub cmd_bad_vlan_id {
1720   @ARGV = @_;
1721
1722   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1723   my $verbose;
1724
1725   GetOptions(
1726      'day|d=i'   => \$days_before_alert,
1727      );
1728
1729   test_maindb_environnement();
1730
1731   my $computerdb = computerdb_load();
1732
1733   # create a database with the most recent computer by switch port
1734   my %switchportdb = ();
1735   LOOP_ON_IP_ADDRESS:
1736   for my $ip (keys %{$computerdb}) {
1737      # to be improve in the future
1738      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1739      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
1740      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port_id'} eq '0';
1741
1742      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1743      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1744      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1745
1746      my $swpt = sprintf "%-28s  %2s",
1747         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1748         $computerdb->{$ip}{'switch_port_hr'};
1749      $switchportdb{$swpt} ||= {
1750         ip          => $ip,
1751         timestamp   => $ip_timestamp,
1752         vlan        => $computerdb->{$ip}{'network'},
1753         hostname_fq => $ip_hostname_fq,
1754         mac_address => $ip_mac,
1755         };
1756
1757      # if float computer, set date 15 day before warning...
1758      my $ip_timestamp_mod = $ip_timestamp;
1759      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
1760      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1761      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1762
1763      if ($ip_timestamp_mod > $ip_timestamp_ref) {
1764         $switchportdb{$swpt} = {
1765            ip          => $ip,
1766            timestamp   => $ip_timestamp,
1767            vlan        => $computerdb->{$ip}{'network'},
1768            hostname_fq => $ip_hostname_fq,
1769            mac_address => $ip_mac,
1770            };
1771         }
1772      }
1773
1774   LOOP_ON_RECENT_COMPUTER:
1775   for my $swpt (keys %switchportdb) {
1776      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
1777      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
1778
1779      my $src_ip = $switchportdb{$swpt}->{ip};
1780      my $src_timestamp = 0;
1781      LOOP_ON_IP_ADDRESS:
1782      for my $ip (keys %{$computerdb}) {
1783         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne  $switchportdb{$swpt}->{'mac_address'};
1784         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1785         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
1786
1787         $src_ip = $ip;
1788         $src_timestamp = $computerdb->{$ip}{'timestamp'};
1789         }
1790
1791      # keep only if float computer is the most recent
1792      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
1793      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
1794
1795      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
1796      $year += 1900;
1797      $mon++;
1798      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1799
1800      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
1801      $year += 1900;
1802      $mon++;
1803      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1804
1805      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
1806
1807      printf "%s / %-10s +-> %-10s(%i)  %s %s %s %s\n",
1808         $swpt, $switchportdb{$swpt}->{'vlan'}, $computerdb->{$src_ip}{'network'}, $vlan_id,
1809         $date,
1810         $src_date,
1811         $computerdb->{$src_ip}{'mac_address'},
1812         $computerdb->{$src_ip}{'hostname_fq'};
1813      }
1814   }
1815
1816#---------------------------------------------------------------
1817sub cmd_poe_enable {
1818   @ARGV = @_;
1819
1820   my $verbose;
1821   GetOptions(
1822      'verbose|v' => \$verbose,
1823      );
1824
1825   my $switch_name = shift @ARGV || q{};
1826   my $switch_port = shift @ARGV || q{};
1827
1828   if ($switch_name eq q{} or $switch_port eq q{}) {
1829      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
1830      }
1831
1832   for my $sw_name (split /,/, $switch_name) {
1833      if (not defined $SWITCH_DB{$sw_name}) {
1834         die "Switch $sw_name must be defined in klask configuration file\n";
1835         }
1836
1837      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1838
1839      my $sw = $SWITCH_DB{$sw_name};
1840      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1841      print "$error \n" if $error;
1842
1843      my $result = $session->set_request(
1844         -varbindlist => [$oid_search, INTEGER, 8], # Only NEXANS
1845         );
1846      print $session->error()."\n" if $session->error_status();
1847
1848      $session->close;
1849      }
1850   cmd_poe_status($switch_name, $switch_port);
1851   return;
1852   }
1853
1854#---------------------------------------------------------------
1855sub cmd_poe_disable {
1856   @ARGV = @_;
1857
1858   my $verbose;
1859   GetOptions(
1860      'verbose|v' => \$verbose,
1861      );
1862
1863   my $switch_name = shift @ARGV || q{};
1864   my $switch_port = shift @ARGV || q{};
1865
1866   if ($switch_name eq q{} or $switch_port eq q{}) {
1867      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
1868      }
1869
1870   for my $sw_name (split /,/, $switch_name) {
1871      if (not defined $SWITCH_DB{$sw_name}) {
1872         die "Switch $sw_name must be defined in klask configuration file\n";
1873         }
1874
1875      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1876
1877      my $sw = $SWITCH_DB{$sw_name};
1878      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1879      print "$error \n" if $error;
1880
1881      my $result = $session->set_request(
1882         -varbindlist => [$oid_search, INTEGER, 2], # Only NEXANS
1883         );
1884      print $session->error()."\n" if $session->error_status();
1885
1886      $session->close;
1887      }
1888   cmd_poe_status($switch_name, $switch_port);
1889   return;
1890   }
1891
1892#---------------------------------------------------------------
1893sub cmd_poe_status {
1894   @ARGV = @_;
1895
1896   my $verbose;
1897   GetOptions(
1898      'verbose|v' => \$verbose,
1899      );
1900
1901   my $switch_name = shift @ARGV || q{};
1902   my $switch_port = shift @ARGV || q{};
1903
1904   if ($switch_name eq q{} or $switch_port eq q{}) {
1905      die "Usage: klask poe-status SWITCH_NAME PORT\n";
1906      }
1907
1908   for my $sw_name (split /,/, $switch_name) {
1909      if (not defined $SWITCH_DB{$sw_name}) {
1910         die "Switch $sw_name must be defined in klask configuration file\n";
1911         }
1912
1913      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1914
1915      my $sw = $SWITCH_DB{$sw_name};
1916      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1917      print "$error \n" if $error;
1918
1919      my $result = $session->get_request(
1920         -varbindlist => [$oid_search],
1921         );
1922
1923      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
1924         my $poe_status = $result->{$oid_search} || 'empty';
1925         $poe_status =~ s/8/enable/;
1926         $poe_status =~ s/2/disable/;
1927         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe_status;
1928         }
1929      else {
1930         print "Klask do not find PoE status on switch $sw_name on port $switch_port\n";
1931         }
1932
1933      $session->close;
1934      }
1935   return;
1936   }
1937
1938#---------------------------------------------------------------
1939# not finish - do not use
1940sub cmd_port_setvlan {
1941   my $switch_name = shift || q{};
1942   my $mac_address = shift || q{};
1943
1944   if ($switch_name eq q{} or $mac_address eq q{}) {
1945      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1946      }
1947
1948   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
1949
1950   for my $sw_name (split /,/, $switch_name) {
1951      if (not defined $SWITCH_DB{$sw_name}) {
1952         die "Switch $sw_name must be defined in klask configuration file\n";
1953         }
1954
1955      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
1956      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. 0 . mac_address_hex2dec($mac_address);
1957      print "Klask search OID $oid_search_port1 on switch $sw_name\n";
1958      print "Klask search OID $oid_search_port2 on switch $sw_name\n";
1959
1960      my $sw = $SWITCH_DB{$sw_name};
1961      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1962      print "$error \n" if $error;
1963
1964      my $result = $session->get_request(
1965         -varbindlist => [$oid_search_port1]
1966         );
1967      if (not defined $result) {
1968         $result = $session->get_request(
1969            -varbindlist => [$oid_search_port2]
1970            );
1971         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
1972         }
1973
1974      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
1975         my $swport = $result->{$oid_search_port1};
1976         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1977         }
1978      else {
1979         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1980         }
1981
1982      $session->close;
1983      }
1984   return;
1985   }
1986
1987#---------------------------------------------------------------
1988sub cmd_port_getvlan {
1989   @ARGV = @_;
1990
1991   my $verbose;
1992   GetOptions(
1993      'verbose|v' => \$verbose,
1994      );
1995
1996   my $switch_name = shift @ARGV || q{};
1997   my $switch_port = shift @ARGV || q{};
1998
1999   if ($switch_name eq q{} or $switch_port eq q{}) {
2000      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
2001      }
2002
2003   for my $sw_name (split /,/, $switch_name) {
2004      if (not defined $SWITCH_DB{$sw_name}) {
2005         die "Switch $sw_name must be defined in klask configuration file\n";
2006         }
2007
2008      my $oid_search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
2009
2010      my $sw = $SWITCH_DB{$sw_name};
2011      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2012      print "$error \n" if $error;
2013
2014      my $result = $session->get_request(
2015         -varbindlist => [$oid_search],
2016         );
2017
2018      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2019         my $vlan_id = $result->{$oid_search} || 'empty';
2020         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2021         }
2022      else {
2023         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
2024         }
2025
2026      $session->close;
2027      }
2028   return;
2029   }
2030
2031#---------------------------------------------------------------
2032sub cmd_vlan_setname {
2033   }
2034
2035#---------------------------------------------------------------
2036# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
2037sub cmd_rebootsw {
2038   @ARGV = @_;
2039
2040   my $verbose;
2041   GetOptions(
2042      'verbose|v' => \$verbose,
2043      );
2044
2045   my $switch_name = shift @ARGV || q{};
2046
2047   if ($switch_name eq q{}) {
2048      die "Usage: klask rebootsw SWITCH_NAME\n";
2049      }
2050
2051   for my $sw_name (split /,/, $switch_name) {
2052      if (not defined $SWITCH_DB{$sw_name}) {
2053         die "Switch $sw_name must be defined in klask configuration file\n";
2054         }
2055
2056      my $sw = $SWITCH_DB{$sw_name};
2057      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2058      print "$error \n" if $error;
2059
2060      my $result = $session->set_request(
2061         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
2062         );
2063
2064      $session->close;
2065      }
2066   return;
2067   }
2068
2069#---------------------------------------------------------------
2070sub cmd_vlan_getname {
2071   my $switch_name = shift || q{};
2072   my $vlan_id     = shift || q{};
2073
2074   if ($switch_name eq q{} or $vlan_id eq q{}) {
2075      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
2076      }
2077
2078   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2079
2080   for my $sw_name (split /,/, $switch_name) {
2081      if (not defined $SWITCH_DB{$sw_name}) {
2082         die "Switch $sw_name must be defined in klask configuration file\n";
2083         }
2084
2085      my $oid_search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
2086
2087      my $sw = $SWITCH_DB{$sw_name};
2088      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2089      print "$error \n" if $error;
2090
2091      my $result = $session->get_request(
2092         -varbindlist => [$oid_search_vlan_name]
2093         );
2094
2095      if (defined $result and $result->{$oid_search_vlan_name} ne 'noSuchInstance') {
2096         my $vlan_name = $result->{$oid_search_vlan_name} || 'empty';
2097         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
2098         }
2099      else {
2100         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2101         }
2102
2103      $session->close;
2104      }
2105   return;
2106   }
2107
2108#---------------------------------------------------------------
2109sub cmd_vlan_list {
2110   my $switch_name = shift || q{};
2111
2112   if ($switch_name eq q{}) {
2113      die "Usage: klask vlan-list SWITCH_NAME\n";
2114      }
2115
2116   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2117
2118   for my $sw_name (split /,/, $switch_name) {
2119      if (not defined $SWITCH_DB{$sw_name}) {
2120         die "Switch $sw_name must be defined in klask configuration file\n";
2121         }
2122
2123      my $sw = $SWITCH_DB{$sw_name};
2124      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2125      print "$error \n" if $error;
2126
2127      my %vlandb = snmp_get_vlan_list($session);
2128      $session->close;
2129     
2130      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2131      for my $vlan_id (keys %vlandb) {
2132         printf "%7i - %s\n", $vlan_id, $vlandb{$vlan_id};
2133         }
2134      }
2135   return;
2136   }
2137
2138#---------------------------------------------------------------
2139sub cmd_ip_location {
2140   my $computerdb = computerdb_load();
2141
2142   LOOP_ON_IP_ADDRESS:
2143   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
2144
2145      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2146
2147      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
2148      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
2149
2150      my $sw_location = q{};
2151      LOOP_ON_ALL_SWITCH:
2152      for my $sw (@SWITCH_LIST) {
2153         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2154         $sw_location = $sw->{'location'};
2155         last;
2156         }
2157
2158      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
2159      }
2160   return;
2161   }
2162
2163#---------------------------------------------------------------
2164sub cmd_ip_free {
2165   @ARGV = @_;
2166
2167   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
2168   my $format = 'txt';
2169   my $verbose;
2170
2171   GetOptions(
2172      'day|d=i'      => \$days_to_death,
2173      'format|f=s'   => \$format,
2174      'verbose|v'    => \$verbose,
2175      );
2176
2177   my %possible_format = (
2178      txt  => \&cmd_ip_free_txt,
2179      html => \&cmd_ip_free_html,
2180      none => sub {},
2181      );
2182   $format = 'txt' if not defined $possible_format{$format};
2183
2184   my @vlan_name = @ARGV;
2185   @vlan_name = get_list_network() if not @vlan_name;
2186
2187   my $computerdb = {};
2188      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
2189   my $timestamp = time;
2190
2191   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
2192
2193   my %result_ip = ();
2194
2195   ALL_NETWORK:
2196   for my $vlan (@vlan_name) {
2197
2198      my @ip_list = get_list_ip($vlan);
2199
2200      LOOP_ON_IP_ADDRESS:
2201      for my $ip (@ip_list) {
2202
2203         if (exists $computerdb->{$ip}) {
2204            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
2205
2206            my $mac_address = $computerdb->{$ip}{'mac_address'};
2207            LOOP_ON_DATABASE:
2208            for my $ip_db (keys %{$computerdb}) {
2209               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2210               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
2211               }
2212            }
2213
2214         my $ip_date_last_detection = '';
2215         if (exists $computerdb->{$ip}) {
2216            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
2217            $year += 1900;
2218            $mon++;
2219            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2220            }
2221
2222         my $packed_ip = scalar gethostbyname($ip);
2223         my $hostname_fq = 'unknown';
2224            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
2225
2226         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2227
2228         $result_ip{$ip} ||= {};
2229         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2230         $result_ip{$ip}->{'hostname_fq'} = $hostname_fq;
2231         $result_ip{$ip}->{'vlan'} = $vlan;
2232
2233         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
2234         }
2235      }
2236
2237   $possible_format{$format}->(%result_ip);
2238   }
2239
2240#---------------------------------------------------------------
2241sub cmd_ip_free_txt {
2242   my %result_ip = @_;
2243
2244   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2245   print "-------------------------------------------------------------------------------\n";
2246   LOOP_ON_IP_ADDRESS:
2247   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2248         my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2249         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
2250      }
2251   }
2252
2253#---------------------------------------------------------------
2254sub cmd_ip_free_html {
2255   my %result_ip = @_;
2256
2257   print <<'END_HTML';
2258<table class="sortable" summary="Klask Free IP Database">
2259 <caption>Klask Free IP Database</caption>
2260 <thead>
2261  <tr>
2262   <th scope="col" class="klask-header-left">IPv4-Address</th>
2263   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2264   <th scope="col" class="sorttable_alpha">VLAN</th>
2265   <th scope="col" class="klask-header-right">Date</th>
2266  </tr>
2267 </thead>
2268 <tfoot>
2269  <tr>
2270   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2271   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2272   <th scope="col" class="fklask-vlan">VLAN</th>
2273   <th scope="col" class="klask-footer-right">Date</th>
2274  </tr>
2275 </tfoot>
2276 <tbody>
2277END_HTML
2278
2279   my $typerow = 'even';
2280
2281   LOOP_ON_IP_ADDRESS:
2282   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2283
2284      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2285
2286      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
2287      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
2288
2289      my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2290
2291      print <<"END_HTML";
2292  <tr class="$typerow">
2293   <td sorttable_customkey="$ip_sort">$ip</td>
2294   <td sorttable_customkey="$host_short">$result_ip{$ip}->{'hostname_fq'}</td>
2295   <td>$vlan_nameid</td>
2296   <td>$result_ip{$ip}->{'date_last_detection'}</td>
2297  </tr>
2298END_HTML
2299      }
2300   print <<'END_HTML';
2301 </tbody>
2302</table>
2303END_HTML
2304   }
2305
2306#---------------------------------------------------------------
2307sub cmd_enable {
2308   @ARGV = @_;
2309
2310   my $verbose;
2311
2312   GetOptions(
2313      'verbose|v' => \$verbose,
2314      );
2315
2316   my $switch_name = shift @ARGV || q{};
2317   my $port_hr     = shift @ARGV || q{};
2318
2319   if ($switch_name eq q{} or $port_hr eq q{}) {
2320      die "Usage: klask disable SWITCH_NAME PORT\n";
2321      }
2322
2323   if (not defined $SWITCH_DB{$switch_name}) {
2324      die "Switch $switch_name must be defined in klask configuration file\n";
2325      }
2326
2327   my $sw = $SWITCH_DB{$switch_name};
2328   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2329   print "$error \n" if $error;
2330
2331   # Retrieve numeric port value
2332   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2333   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2334
2335   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2336   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2337
2338   my $result = $session->set_request(
2339      -varbindlist => [$oid_search_portstatus, INTEGER, 1],
2340      );
2341   print $session->error()."\n" if $session->error_status();
2342
2343   $session->close;
2344
2345   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2346   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2347   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2348   return;
2349   }
2350
2351#---------------------------------------------------------------
2352sub cmd_disable {
2353   @ARGV = @_;
2354
2355   my $verbose;
2356
2357   GetOptions(
2358      'verbose|v' => \$verbose,
2359      );
2360
2361   my $switch_name = shift @ARGV || q{};
2362   my $port_hr     = shift @ARGV || q{};
2363
2364   if ($switch_name eq q{} or $port_hr eq q{}) {
2365      die "Usage: klask disable SWITCH_NAME PORT\n";
2366      }
2367
2368   if (not defined $SWITCH_DB{$switch_name}) {
2369      die "Switch $switch_name must be defined in klask configuration file\n";
2370      }
2371
2372   my $sw = $SWITCH_DB{$switch_name};
2373   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2374   print "$error \n" if $error;
2375
2376   # Retrieve numeric port value
2377   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2378   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2379
2380   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2381   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2382
2383   my $result = $session->set_request(
2384      -varbindlist => [$oid_search_portstatus, INTEGER, 2],
2385      );
2386   print $session->error()."\n" if $session->error_status();
2387
2388   $session->close;
2389
2390   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2391   return;
2392   }
2393
2394#---------------------------------------------------------------
2395sub cmd_status {
2396   @ARGV = @_;
2397
2398   my $verbose;
2399
2400   GetOptions(
2401      'verbose|v' => \$verbose,
2402      );
2403
2404   my $switch_name = shift @ARGV || q{};
2405   my $port_hr     = shift @ARGV || q{};
2406
2407   if ($switch_name eq q{} or $port_hr eq q{}) {
2408      die "Usage: klask status SWITCH_NAME PORT\n";
2409      }
2410
2411   if (not defined $SWITCH_DB{$switch_name}) {
2412      die "Switch $switch_name must be defined in klask configuration file\n";
2413      }
2414
2415   my $sw = $SWITCH_DB{$switch_name};
2416   my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2417   print "$error \n" if $error;
2418
2419   # Retrieve numeric port value
2420   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2421   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2422
2423   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2424   print "Info: switch $switch_name port $port_hr ($port_id) SNMP OID $oid_search_portstatus\n" if $verbose;
2425
2426   my $result = $session->get_request(
2427      -varbindlist => [$oid_search_portstatus]
2428      );
2429   print $session->error()."\n" if $session->error_status();
2430   if (defined $result) {
2431      print "$PORT_UPDOWN{$result->{$oid_search_portstatus}}\n";
2432      }
2433
2434   $session->close;
2435
2436   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
2437   return;
2438   }
2439
2440#---------------------------------------------------------------
2441sub cmd_search_mac_on_switch {
2442   @ARGV = @_;
2443
2444   my $verbose;
2445   my $vlan_id = 0;
2446
2447   GetOptions(
2448      'verbose|v' => \$verbose,
2449      'vlan|l=i'  => \$vlan_id,
2450      );
2451
2452   my $switch_name = shift @ARGV || q{};
2453   my $mac_address = shift @ARGV || q{};
2454
2455   if ($switch_name eq q{} or $mac_address eq q{}) {
2456      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2457      }
2458
2459   $mac_address = normalize_mac_address($mac_address);
2460   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*} or $switch_name eq q{all};
2461
2462   for my $sw_name (split /,/, $switch_name) {
2463      if (not defined $SWITCH_DB{$sw_name}) {
2464         die "Switch $sw_name must be defined in klask configuration file\n";
2465         }
2466
2467      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2468      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
2469      print "Klask search OID $oid_search_port1 on switch $sw_name\n" if $verbose;
2470      print "Klask search OID $oid_search_port2 on switch $sw_name\n" if $verbose;
2471
2472      my $sw = $SWITCH_DB{$sw_name};
2473      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2474      print "$error \n" if $error;
2475
2476      my $result = $session->get_request(
2477         -varbindlist => [$oid_search_port1]
2478         );
2479      if (not defined $result) {
2480         $result = $session->get_request(
2481            -varbindlist => [$oid_search_port2]
2482            );
2483         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
2484         }
2485
2486      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
2487         my $swport_id = $result->{$oid_search_port1};
2488         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
2489         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
2490         }
2491      else {
2492         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
2493         }
2494
2495      $session->close;
2496      }
2497   return;
2498   }
2499
2500#---------------------------------------------------------------
2501sub cmd_updatesw {
2502   @ARGV = @_;
2503
2504   my $verbose;
2505
2506   GetOptions(
2507      'verbose|v' => \$verbose,
2508      );
2509
2510   init_switch_names('yes');    #nomme les switchs
2511   print "\n";
2512
2513   my %where = ();
2514   my %db_switch_output_port = ();
2515   my %db_switch_ip_hostnamefq = ();
2516
2517   DETECT_ALL_ROUTER:
2518   for my $one_router ( get_list_main_router(get_list_network()) ) {
2519      print "Info: router loop $one_router\n" if $verbose;
2520      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
2521
2522      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
2523      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $verbose;
2524
2525      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
2526      my $vlan_id   = get_current_vlan_id($vlan_name);
2527      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
2528      }
2529
2530   ALL_ROUTER_IP_ADDRESS:
2531   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
2532
2533      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
2534
2535      ALL_SWITCH_CONNECTED:
2536      for my $switch_detected ( keys %{$where{$ip_router}} ) {
2537
2538         my $switch = $where{$ip_router}->{$switch_detected};
2539
2540         next ALL_SWITCH_CONNECTED if $switch->{'port_id'} eq '0';
2541
2542         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
2543         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
2544         }
2545      }
2546
2547   my %db_switch_link_with = ();
2548
2549   my @list_all_switch = ();
2550   my @list_switch_ipv4 = ();
2551   for my $sw (@SWITCH_LIST) {
2552      push @list_all_switch, $sw->{'hostname'};
2553      }
2554
2555   my $timestamp = time;
2556
2557   ALL_SWITCH:
2558   for my $one_switch (@list_all_switch) {
2559      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
2560      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
2561         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
2562         fast_ping($fake_ip);
2563         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $verbose;
2564         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
2565         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
2566            $resol_arp{'mac_address'}   = $resol_arp_alt{'mac_address'};
2567            $resol_arp{'interface'}     = $resol_arp_alt{'interface'};
2568            $resol_arp{'ipv4_address'} .= '*';
2569            # Force a MAC trace on switch
2570            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
2571            }
2572         }
2573      print "Info: switch loop $one_switch\n" if $verbose;
2574      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
2575
2576      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
2577
2578      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
2579      my $vlan_id   = get_current_vlan_id($vlan_name);
2580      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
2581
2582      if ($verbose) {
2583         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
2584         print "VERBOSE_3: $one_switch --- ",
2585            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
2586            "\n";
2587         }
2588
2589      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
2590      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $verbose;
2591
2592      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
2593      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
2594      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
2595      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
2596      }
2597
2598   ALL_SWITCH_IP_ADDRESS:
2599   for my $ip (@list_switch_ipv4) {
2600#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
2601
2602      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2603
2604      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
2605#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
2606
2607      DETECTED_SWITCH:
2608      for my $switch_detected ( keys %{$where{$ip}} ) {
2609
2610         my $switch = $where{$ip}->{$switch_detected};
2611         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
2612
2613         next if $switch->{'port_id'}  eq '0';
2614         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
2615         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
2616
2617         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
2618         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{'hostname'} } = $switch->{'port_hr'};
2619         print "VERBOSE_7: +++++\n" if $verbose;
2620         }
2621
2622      }
2623
2624   my %db_switch_connected_on_port = ();
2625   my $maybe_more_than_one_switch_connected = 'yes';
2626   my $cloop = 0;
2627
2628   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2629      $cloop++;
2630      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
2631      for my $sw (keys %db_switch_link_with) {
2632         for my $connect (keys %{$db_switch_link_with{$sw}}) {
2633
2634            my $port_hr = $db_switch_link_with{$sw}->{$connect};
2635
2636            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2637            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
2638            }
2639         }
2640
2641      $maybe_more_than_one_switch_connected  = 'no';
2642
2643      SWITCH_AND_PORT:
2644      for my $swport (keys %db_switch_connected_on_port) {
2645
2646         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
2647
2648         $maybe_more_than_one_switch_connected = 'yes';
2649
2650         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2651         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
2652         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
2653
2654         CONNECTED:
2655         for my $sw_connected (@sw_on_same_port) {
2656
2657            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
2658
2659            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
2660
2661            for my $other_sw (@sw_on_same_port) {
2662               next if $other_sw eq $sw_connected;
2663
2664               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2665               }
2666
2667            # We can not do better for this switch for this loop
2668            next SWITCH_AND_PORT;
2669            }
2670         }
2671      }
2672
2673   my %db_switch_parent =();
2674
2675   for my $sw (keys %db_switch_link_with) {
2676      for my $connect (keys %{$db_switch_link_with{$sw}}) {
2677
2678         my $port_hr = $db_switch_link_with{$sw}->{$connect};
2679
2680         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2681         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
2682
2683         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
2684         }
2685      }
2686
2687   print "Switch output port and parent port connection\n";
2688   print "---------------------------------------------\n";
2689   for my $sw (sort keys %db_switch_output_port) {
2690      if (exists $db_switch_parent{$sw}) {
2691         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
2692         }
2693      else {
2694         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2695         }
2696      }
2697   print "\n";
2698
2699   print "Switch parent and children port inter-connection\n";
2700   print "------------------------------------------------\n";
2701   for my $swport (sort keys %db_switch_connected_on_port) {
2702      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2703      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2704         if (exists $db_switch_output_port{$sw}) {
2705            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2706            }
2707         else {
2708            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2709            }
2710         }
2711      }
2712
2713   my $switch_connection = {
2714      output_port       => \%db_switch_output_port,
2715      parent            => \%db_switch_parent,
2716      connected_on_port => \%db_switch_connected_on_port,
2717      link_with         => \%db_switch_link_with,
2718      switch_db         => \%SWITCH_DB,
2719      timestamp         => $timestamp,
2720      };
2721
2722   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
2723   return;
2724   }
2725
2726#---------------------------------------------------------------
2727sub cmd_exportsw {
2728   @ARGV = @_;
2729
2730   test_switchdb_environnement();
2731
2732   my $format = 'txt';
2733
2734   GetOptions(
2735      'format|f=s'  => \$format,
2736      );
2737
2738   my %possible_format = (
2739      txt => \&cmd_exportsw_txt,
2740      dot => \&cmd_exportsw_dot,
2741      );
2742
2743   $format = 'txt' if not defined $possible_format{$format};
2744
2745   $possible_format{$format}->(@ARGV);
2746   return;
2747   }
2748
2749#---------------------------------------------------------------
2750sub cmd_exportsw_txt {
2751
2752   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2753
2754   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2755   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2756   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2757
2758   print "Switch output port and parent port connection\n";
2759   print "---------------------------------------------\n";
2760   for my $sw (sort keys %db_switch_output_port) {
2761      my $arrow ='-->';
2762         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2763      if (exists $db_switch_parent{$sw}) {
2764         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
2765         }
2766      else {
2767         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
2768         }
2769      }
2770   print "\n";
2771
2772   print "Switch parent and children port inter-connection\n";
2773   print "------------------------------------------------\n";
2774   for my $swport (sort keys %db_switch_connected_on_port) {
2775      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2776      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2777         my $arrow ='<--';
2778            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
2779         if (exists $db_switch_output_port{$sw}) {
2780            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
2781            }
2782         else {
2783            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
2784            }
2785         }
2786      }
2787   return;
2788   }
2789
2790#---------------------------------------------------------------
2791sub cmd_exportsw_dot {
2792
2793   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2794
2795   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2796   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2797   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2798   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
2799   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
2800   my $timestamp                   =   $switch_connection->{'timestamp'};
2801
2802   my %db_building= ();
2803   for my $sw (@SWITCH_LIST) {
2804      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
2805      $db_building{$building} ||= {};
2806      $db_building{$building}->{$location} ||= {};
2807      $db_building{$building}->{$location}{ $sw->{'hostname'} } = 'y';
2808      }
2809
2810
2811   print "digraph G {\n";
2812   print "rankdir = LR;\n";
2813   #print "splines=polyline;\n";
2814
2815   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2816   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
2817
2818   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
2819   $year += 1900;
2820   $mon++;
2821   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2822   print "\"$date\" [ color = white, fillcolor = black, shape = polygon, sides=14, style = filled, fontcolor = white ]\n";
2823   print "site -> \"$date\" [color = white];\n";
2824
2825   my $b=0;
2826   for my $building (keys %db_building) {
2827      $b++;
2828
2829      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2830      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
2831
2832      my $l = 0;
2833      for my $loc (keys %{$db_building{$building}}) {
2834         $l++;
2835
2836         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
2837#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
2838         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
2839
2840         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2841
2842            my $peripheries = 1;
2843            my $color = 'black';
2844            if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2845               $peripheries = 2;
2846               $color = "\"$color:invis:$color\"";
2847               }
2848            print "\"$sw:$db_switch_output_port{$sw}\" [label = \"".format_aggregator4dot($db_switch_output_port{$sw})."\", color = black, fillcolor = lightblue,  peripheries = $peripheries, style = filled];\n";
2849
2850            my $swname  = $sw;
2851               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2852            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
2853            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2854            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, color = $color, arrowhead = normal, arrowtail = invdot]\n";
2855
2856
2857            for my $swport (keys %db_switch_connected_on_port) {
2858               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2859               next if not $sw_connect eq $sw;
2860               next if $port_connect eq $db_switch_output_port{$sw};
2861               my $peripheries = 1;
2862               $peripheries = 2 if $port_connect =~ m/^(Trk|Br|Po)/;
2863               print "\"$sw:$port_connect\" [label = \"".format_aggregator4dot($port_connect)."\", color = black, fillcolor = plum,  peripheries = $peripheries, style = filled];\n";
2864               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, color = $color, arrowhead= normal, arrowtail = inv]\n";
2865              }
2866            }
2867         }
2868      }
2869
2870#   print "Switch output port and parent port connection\n";
2871#   print "---------------------------------------------\n";
2872   for my $sw (sort keys %db_switch_output_port) {
2873      if (exists $db_switch_parent{$sw}) {
2874#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port_id'};
2875         }
2876      else {
2877         my $style = 'solid';
2878         my $color = 'navyblue';
2879         if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2880            $style = 'bold';
2881            $color = "\"$color:invis:$color\"";
2882            }
2883         printf "   \"%s:%s\" -> internet [style=$style, color = $color]\n", $sw, $db_switch_output_port{$sw};
2884         }
2885      }
2886   print "\n";
2887
2888#   print "Switch parent and children port inter-connection\n";
2889#   print "------------------------------------------------\n";
2890   for my $swport (sort keys %db_switch_connected_on_port) {
2891      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2892      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2893         my $style = 'solid';
2894         my $color = 'navyblue';
2895         if ($port_connect =~ m/^(Trk|Br|Po)/) {
2896            $style = 'bold';
2897            $color = "\"$color:invis:$color\"";
2898            }
2899         if (exists $db_switch_output_port{$sw}) {
2900            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color = $color]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2901            }
2902         else {
2903            printf "   \"%s\"   -> \"%s:%s\" [style=$style]\n", $sw, $sw_connect, $port_connect;
2904            }
2905         }
2906      }
2907
2908print "}\n";
2909   return;
2910   }
2911
2912
2913################################################################
2914# documentation
2915################################################################
2916
2917__END__
2918
2919=head1 NAME
2920
2921klask - port and search manager for switches, map management
2922
2923
2924=head1 USAGE
2925
2926 klask version
2927 klask help
2928
2929 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2930 klask exportdb [--format|-f txt|html]
2931 klask removedb IP* computer*
2932 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
2933
2934 klask updatesw [--verbose|-v]
2935 klask exportsw [--format|-f txt|dot]
2936
2937 klask searchdb [--kind|-k host|mac] computer [mac-address]
2938 klask search   computer
2939 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2940
2941 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
2942
2943 klask bad-vlan-id [--day|-d days_before_alert]
2944
2945 klask enable  [--verbose|-v] switch port
2946 klask disable [--verbose|-v] switch port
2947 klask status  [--verbose|-v] switch port
2948
2949 klask poe-enable  [--verbose|-v] switch port
2950 klask poe-disable [--verbose|-v] switch port
2951 klask poe-status  [--verbose|-v] switch port
2952
2953 klask vlan-getname switch vlan-id
2954 klask vlan-list switch
2955
2956
2957=head1 DESCRIPTION
2958
2959Klask is a small tool to find where is connected a host in a big network
2960and on which VLAN.
2961Klask mean search in brittany.
2962No hight level protocol like CDL, LLDP are use.
2963Everything is just done with SNMP request on MAC address.
2964
2965Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
2966If you use PVST or MSTP and create loop between VLAN,
2967you have to use C<portignore> functionality on switch port to cut manually loop
2968(see config file below).
2969
2970When you use a management port to administrate a switch,
2971it's not possible to create the map with this switch because it does not have a MAC address,
2972so other switch cannot find the real downlink port...
2973One way to work around this problem is, if you have a computer directly connected on the switch,
2974to put this IP as the fake ip for the switch.
2975The MAC address associated will be use just for the map detection.
2976The C<fake-ip> parameter is defined in the config file.
2977
2978Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
2979
2980
2981=head1 COMMANDS
2982
2983Some command are defined in the source code but are not documented here.
2984Theses could be not well defined, not finished, not well tested...
2985You can read the source code and use them at your own risk
2986(like for all the Klask code).
2987
2988=head2 search
2989
2990 klask search   computer
2991
2992This command takes one or more computer in argument.
2993It search a computer on the network and give the port and the switch on which the computer is connected.
2994
2995=head2 search-mac-on-switch
2996
2997 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2998
2999This command search a MAC address on a switch.
3000To search on all switch, you could put C<'*'> or C<all>.
3001The VLAN parameter could help.
3002
3003
3004=head2 enable
3005
3006 klask enable  [--verbose|-v] switch port
3007
3008This command activate a port (or an agrregate bridge port) on a switch by SNMP.
3009So you need to give the switch name and a port on the command line.
3010See L</ABBREVIATION FOR PORT>.
3011
3012Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3013
3014
3015=head2 disable
3016
3017 klask disable [--verbose|-v] switch port
3018
3019This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
3020So you need to give the switch name and a port on the command line.
3021See L</ABBREVIATION FOR PORT>.
3022
3023Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3024
3025
3026=head2 status
3027
3028 klask status  [--verbose|-v] switch port
3029
3030This command return the status of a port number on a switch by SNMP.
3031The return value could be C<enable> or C<disable> word.
3032So you need to give the switch name and a port on the command line.
3033See L</ABBREVIATION FOR PORT>.
3034
3035If it's not possible to change port status with command L</enable> and L</disable>
3036(SNMP community read write access),
3037it's always possible to have the port status even for bridge agrregate port.
3038
3039
3040=head2 updatedb
3041
3042 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
3043
3044This command will scan networks and update the computer database.
3045To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3046This file is easy to read and write because Klask use YAML format and not XML
3047(see L</CONFIGURATION>).
3048
3049Option are not stable and could be use manually when you have a new kind of switch.
3050Maybe some option will be transfered in a future C<checksw> command!
3051
3052The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3053By default, a network is C<active>.
3054This means that an C<fping> command is done at the beginning on all the IP of the network
3055and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3056will have an C<arping>
3057(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3058In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3059It's good for big subnet with few computer (telephone...).
3060The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3061
3062=head2 exportdb
3063
3064 klask exportdb [--format|-f txt|html]
3065
3066This command print the content of the computer database.
3067There is actually only two format : TXT and HTML.
3068By default, format is TXT.
3069It's very easy to have more format, it's just need times...
3070
3071=head2 removedb
3072
3073 klask removedb IP* computer*
3074
3075This command remove an entry in the database.
3076There is only one kind of parameter, the IP of the computers to be removed.
3077You can put as many IP as you want...
3078
3079Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
3080
3081=head2 cleandb
3082
3083 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3084
3085Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3086Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3087This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3088This functionality could be use when computer define in VLAN 1
3089could have a float IP when they are connected on VLAN 2.
3090In the Klask database, the float DNS entries are less important.
3091
3092When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
3093
3094=head2 updatesw
3095
3096 klask updatesw [--verbose|-v]
3097
3098This command build a map of your manageable switch on your network.
3099The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3100
3101
3102=head2 exportsw
3103
3104 klask exportsw [--format|-f txt|dot]
3105
3106This command print the content of the switch database. There is actually two format.
3107One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3108By default, format is TXT.
3109
3110 klask exportsw --format dot > /tmp/map.dot
3111 dot -Tpng /tmp/map.dot > /tmp/map.png
3112
3113
3114=head2 ip-free
3115
3116 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3117
3118This command return IP address that was not use (detected by Klask) at this time.
3119The list returned could be limited to just one VLAN.
3120IP returned could have been never used or no computer have been detected since the number of days specified
3121(2 years by default).
3122This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3123
3124 default:
3125   days-to-death: 730
3126
3127Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3128
3129
3130=head2 bad-vlan-id
3131
3132 klask bad-vlan-id [--day|-d days_before_alert]
3133
3134This command return a list of switch port that are not configure with the good VLAN.
3135Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3136and another prior trace where they had the good IP (good DNS name).
3137The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
3138This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3139
3140 default:
3141   days-before-alert: 15
3142
3143This functionality is not need if your switch use RADIUS 802.1X configuration...
3144
3145
3146=head2 poe-enable
3147
3148 klask poe-enable  [--verbose|-v] switch port
3149
3150This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3151So you need to give the switch name and a port on the command line.
3152See L</ABBREVIATION FOR PORT>.
3153
3154Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3155You need to have the SNMP write access on the switch in order to modify it's configuration.
3156
3157
3158=head2 poe-disable
3159
3160 klask poe-disable [--verbose|-v] switch port
3161
3162This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3163So you need to give the switch name and a port on the command line.
3164See L</ABBREVIATION FOR PORT>.
3165
3166Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3167You need to have the SNMP write access on the switch in order to modify it's configuration.
3168
3169
3170=head2 poe-status
3171
3172 klask poe-status  [--verbose|-v] switch port
3173
3174This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3175The return value could be C<enable> or C<disable> word.
3176So you need to give the switch name and a port on the command line.
3177See L</ABBREVIATION FOR PORT>.
3178
3179If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3180(SNMP community read write access),
3181it's always possible to have the PoE port status.
3182
3183Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3184
3185
3186=head1 CONFIGURATION
3187
3188Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
3189The configuration is done in a F</etc/klask/klask.conf> YAML file.
3190This format have many advantage over XML, it's easier to read and to write !
3191
3192Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3193
3194 default:
3195   community: public
3196   community-rw: private
3197   snmpport: 161
3198   float-regex: '(?^msx: ^float )'
3199   scan-mode: active
3200
3201 network:
3202   labnet:
3203     ip-subnet:
3204       - add: 192.168.1.0/24
3205       - add: 192.168.2.0/24
3206     interface: eth0
3207     vlan-id: 12
3208     main-router: gw1.labnet.local
3209
3210   schoolnet:
3211     ip-subnet:
3212       - add: 192.168.3.0/24
3213       - add: 192.168.4.0/24
3214     interface: eth0.38
3215     vlan-id: 13
3216     main-router: gw2.schoolnet.local
3217     scan-mode: passive
3218
3219   etunet:
3220     ip-subnet:
3221       - add: 192.168.5.0/24
3222     interface: eth2
3223     vlan-id: 14
3224     main-router: gw3.etunet.local
3225     scan-mode: passive
3226
3227 switch:
3228   - hostname: sw1.klask.local
3229     location: BatY / 1 floor / K004
3230     portignore:
3231       - 1
3232       - 2
3233
3234   - hostname: sw2.klask.local
3235     location: BatY / 2 floor / K203
3236     type: HP2424
3237     portignore:
3238       - 1
3239       - 2
3240     fake-ip: 192.168.9.14
3241
3242   - hostname: sw3.klask.local
3243     location: BatY / 2 floor / K203
3244
3245I think it's pretty easy to understand.
3246The default section can be overide in any section, if parameter mean something in theses sections.
3247Network to be scan are define in the network section. You must put an add by network.
3248Maybe I will make a delete line to suppress specific computers.
3249The switch section define your switch.
3250You have to write the port number to ignore, this was important if your switchs are cascades
3251(right now, method C<updatesw> find them automatically)
3252and is still important if you have loop (with PVST or MSTP).
3253Just put the ports numbers between switch.
3254
3255The C<community> parameter is use to get SNMP data on switch.
3256It could be overload for each switch.
3257By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3258Some few command change the switch state as the commands L</enable> and L</disable>.
3259In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3260Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
3261
3262
3263=head1 ABBREVIATION FOR PORT
3264
3265HP Procurve and Nexans switches have a simplistic numbering scheme.
3266It's just number: 1, 2, 3... 24.
3267On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3268Nothing is done on theses ports names.
3269
3270On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3271followed by tree number.
3272In order to have short name,
3273we made the following rules:
3274
3275 Bridge-Aggregation     -> Br
3276 FastEthernet           -> Fa
3277 Forty-GigabitEthernet  -> Fo
3278 FortyGigabitEthernet   -> Fo
3279 GigabitEthernet        -> Gi
3280 Giga                   -> Gi
3281 Port-Channel           -> Po
3282 Ten-GigabitEthernet    -> Te
3283 TenGigabitEthernet     -> Te
3284 Ten                    -> Te
3285
3286All Klask command automatically normalize the port name on standart output
3287and also on input command line.
3288
3289In the case of use an aggregator port (Po, Tk, Br ...),
3290the real ports used are also return.
3291
3292
3293=head1 SWITCH SUPPORTED
3294
3295Here is a list of switches where Klask gives or gave (for old switches) good results.
3296We have only a few manageable switches to actually test Klask.
3297It is quite possible that switches from other brands will work just as well.
3298You just have to do a test on it and add the line of description that goes well in the source code.
3299Contact us for any additional information.
3300
3301In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3302This makes it possible to adjust the code names of the different manufacturers!
3303
3304HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3305J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3306J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F).
3307
3308BayStack: BayStack 350T HW(BS350T)
3309
3310Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3311
3312DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3313
3314H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3315
3316H3C: H3C5500
3317
33183COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
33193CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
33203CR17572-91, 3C17702-US, 3C17700.
3321
3322
3323=head1 FILES
3324
3325 /etc/klask/klask.conf
3326 /var/lib/klask/klaskdb
3327 /var/lib/klask/switchdb
3328
3329
3330=head1 SEE ALSO
3331
3332Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3333
3334=over
3335
3336=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
3337
3338=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3339
3340=back
3341
3342
3343=head1 VERSION
3344
3345$Id: klask 279 2017-09-25 15:34:00Z g7moreau $
3346
3347
3348=head1 AUTHOR
3349
3350Written by Gabriel Moreau, Grenoble - France
3351
3352
3353=head1 LICENSE AND COPYRIGHT
3354
3355GPL version 2 or later and Perl equivalent
3356
3357Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.