source: trunk/klask @ 235

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