source: trunk/klask @ 229

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