source: trunk/klask @ 226

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