source: trunk/klask @ 233

Last change on this file since 233 was 233, checked in by g7moreau, 7 years ago
  • Just export txt format aligment
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 106.0 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2017 Gabriel Moreau
4#
5# $Id: klask 233 2017-03-11 21:56:58Z 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 233 2017-03-11 21:56:58Z 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 "%-28s %8s              %-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      # to be improve in the future
1389      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1390
1391      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1392      $year += 1900;
1393      $mon++;
1394      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1395
1396      my $vlan = '';
1397      $vlan = $computerdb->{$ip}{network}.'('.get_current_vlan_id($computerdb->{$ip}{network}).')' if $computerdb->{$ip}{network};
1398
1399      my $arrow ='<-----------';
1400         $arrow ='<===========' if $computerdb->{$ip}{switch_port_hr} =~ m/^(Trk|Br|Po)/;
1401
1402      printf "%-28s %8s %12s %-40s %-15s %-18s %-16s %s\n",
1403         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
1404         $computerdb->{$ip}{switch_port_hr},
1405         $arrow,
1406         $computerdb->{$ip}{hostname_fq},
1407         $ip,
1408         $computerdb->{$ip}{mac_address},
1409         $date,
1410         $vlan;
1411      }
1412   return;
1413   }
1414
1415#---------------------------------------------------------------
1416sub cmd_exportdb_html {
1417   test_maindb_environnement();
1418
1419   my $computerdb = computerdb_load();
1420
1421#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1422#<script src="sorttable-klask.js"></script>
1423
1424   print <<'END_HTML';
1425<table class="sortable" summary="Klask Host Database">
1426 <caption>Klask Host Database</caption>
1427 <thead>
1428  <tr>
1429   <th scope="col" class="klask-header-left">Switch</th>
1430   <th scope="col" class="sorttable_nosort">Port</th>
1431   <th scope="col" class="sorttable_nosort" colspan="2">Link</th>
1432   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1433   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1434   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1435   <th scope="col" class="sorttable_alpha">VLAN</th>
1436   <th scope="col" class="klask-header-right">Date</th>
1437  </tr>
1438 </thead>
1439 <tfoot>
1440  <tr>
1441   <th scope="col" class="klask-footer-left">Switch</th>
1442   <th scope="col" class="fklask-port">Port</th>
1443   <th scope="col" class="fklask-link" colspan="2">Link</th>
1444   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1445   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1446   <th scope="col" class="fklask-mac">MAC-Address</th>
1447   <th scope="col" class="fklask-vlan">VLAN</th>
1448   <th scope="col" class="klask-footer-right">Date</th>
1449  </tr>
1450 </tfoot>
1451 <tbody>
1452END_HTML
1453
1454   my %mac_count = ();
1455   LOOP_ON_IP_ADDRESS:
1456   for my $ip (keys %{$computerdb}) {
1457
1458      # to be improve in the future
1459      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1460
1461      $mac_count{$computerdb->{$ip}{mac_address}}++;
1462      }
1463
1464   my $typerow = 'even';
1465
1466   LOOP_ON_IP_ADDRESS:
1467   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1468
1469      # to be improve in the future
1470      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1471
1472      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1473      $year += 1900;
1474      $mon++;
1475      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1476
1477#      $odd_or_even++;
1478#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1479      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1480
1481      my $arrow ='&#8592;';
1482         $arrow ='&#8656;' if $computerdb->{$ip}{switch_port_hr} =~ m/^(Trk|Br|Po)/;
1483
1484      my $switch_hostname = $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description} || 'unkown';
1485      chomp $switch_hostname;
1486      my $switch_hostname_sort = sprintf '%s %3s' ,$switch_hostname, $computerdb->{$ip}{switch_port_hr};
1487
1488      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1489
1490      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{mac_address}}, $computerdb->{$ip}{mac_address};
1491
1492      $computerdb->{$ip}{hostname_fq} = 'unknow' if $computerdb->{$ip}{hostname_fq} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1493      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{hostname_fq};
1494
1495      my $vlan = '';
1496      $vlan = $computerdb->{$ip}{network}.' ('.get_current_vlan_id($computerdb->{$ip}{network}).')' if $computerdb->{$ip}{network};
1497
1498      print <<"END_HTML";
1499  <tr class="$typerow">
1500   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1501   <td class="bklask-port">$computerdb->{$ip}{switch_port_hr}</td>
1502   <td colspan="2">$arrow</td>
1503   <td sorttable_customkey="$host_short">$computerdb->{$ip}{hostname_fq}</td>
1504   <td sorttable_customkey="$ip_sort">$ip</td>
1505   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{mac_address}</td>
1506   <td>$vlan</td>
1507   <td>$date</td>
1508  </tr>
1509END_HTML
1510      }
1511
1512   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1513
1514   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1515   my %db_switch_parent            = %{$switch_connection->{parent}};
1516   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1517   my %db_switch                   = %{$switch_connection->{switch_db}};
1518
1519   for my $sw (sort keys %db_switch_output_port) {
1520
1521      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1522
1523      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1524
1525      my $arrow ='&#8702;';
1526         $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1527
1528      if (exists $db_switch_parent{$sw}) {
1529         my $mac_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{mac_address};
1530         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{ipv4_address};
1531         my $timestamp = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{timestamp};
1532
1533         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1534         $year += 1900;
1535         $mon++;
1536         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1537
1538         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1539
1540         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1541
1542         my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{port_hr};
1543
1544         print <<"END_HTML";
1545  <tr class="$typerow">
1546   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1547   <td class="bklask-port">$db_switch_output_port{$sw}</td>
1548   <td>$arrow</td><td>$db_switch_parent{$sw}->{port_hr}</td>
1549   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{'switch'}</td>
1550   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1551   <td sorttable_customkey="$mac_sort">$mac_address</td>
1552   <td></td>
1553   <td>$date</td>
1554  </tr>
1555END_HTML
1556         }
1557      else {
1558         print <<"END_HTML";
1559  <tr class="$typerow">
1560   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1561   <td class="bklask-port">$db_switch_output_port{$sw}</td>
1562   <td>$arrow</td><td></td>
1563   <td sorttable_customkey="router">router</td>
1564   <td sorttable_customkey="999999999999"></td>
1565   <td sorttable_customkey="99999"></td>
1566   <td></td>
1567   <td></td>
1568  </tr>
1569END_HTML
1570         }
1571      }
1572
1573   for my $swport (sort keys %db_switch_connected_on_port) {
1574      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1575      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1576
1577         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1578
1579         my $mac_address = $db_switch{$sw}->{mac_address};
1580         my $ipv4_address = $db_switch{$sw}->{ipv4_address};
1581         my $timestamp = $db_switch{$sw}->{timestamp};
1582
1583         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1584         $year += 1900;
1585         $mon++;
1586         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1587
1588         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1589
1590         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1591
1592         $typerow = $typerow eq 'even' ? 'odd' : 'even';
1593
1594         my $arrow ='&#8701;';
1595            $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
1596
1597         if (exists $db_switch_output_port{$sw}) {
1598
1599            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1600
1601            print <<"END_HTML";
1602  <tr class="$typerow">
1603   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1604   <td class="bklask-port">$port_connect</td>
1605   <td>$arrow</td><td>$db_switch_output_port{$sw}</td>
1606   <td sorttable_customkey="$host_short">$sw</td>
1607   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1608   <td sorttable_customkey="$mac_sort">$mac_address</td>
1609   <td></td>
1610   <td>$date</td>
1611  </tr>
1612END_HTML
1613            }
1614         else {
1615            print <<"END_HTML";
1616  <tr class="$typerow">
1617   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1618   <td class="bklask-port">$port_connect</td>
1619   <td>$arrow</td><td></td>
1620   <td sorttable_customkey="$sw">$sw</td>
1621   <td sorttable_customkey="">$ipv4_address</td>
1622   <td sorttable_customkey="">$mac_address</td>
1623   <td></td>
1624   <td>$date</td>
1625  </tr>
1626END_HTML
1627            }
1628         }
1629      }
1630
1631   print <<'END_HTML';
1632 </tbody>
1633</table>
1634END_HTML
1635   return;
1636   }
1637
1638#---------------------------------------------------------------
1639sub cmd_bad_vlan_id {
1640   @ARGV = @_;
1641
1642   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1643   my $verbose;
1644
1645   GetOptions(
1646      'day|d=i'   => \$days_before_alert,
1647      );
1648
1649   test_maindb_environnement();
1650
1651   my $computerdb = computerdb_load();
1652
1653   # create a database with the most recent computer by switch port
1654   my %switchportdb = ();
1655   LOOP_ON_IP_ADDRESS:
1656   for my $ip (keys %{$computerdb}) {
1657      # to be improve in the future
1658      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1659      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}) eq 'unknow';
1660      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{switch_port} eq '0';
1661
1662      my $ip_timestamp   = $computerdb->{$ip}{timestamp};
1663      my $ip_mac         = $computerdb->{$ip}{mac_address};
1664      my $ip_hostname_fq = $computerdb->{$ip}{hostname_fq};
1665
1666      my $swpt = sprintf "%-28s  %2s",
1667         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
1668         $computerdb->{$ip}{switch_port_hr};
1669      $switchportdb{$swpt} ||= {
1670         ip          => $ip,
1671         timestamp   => $ip_timestamp,
1672         vlan        => $computerdb->{$ip}{network},
1673         hostname_fq => $ip_hostname_fq,
1674         mac_address => $ip_mac,
1675         };
1676
1677      # if float computer, set date 15 day before warning...
1678      my $ip_timestamp_mod = $ip_timestamp;
1679      my $ip_timestamp_ref = $switchportdb{$swpt}->{timestamp};
1680      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1681      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/;
1682
1683      if ($ip_timestamp_mod > $ip_timestamp_ref) {
1684         $switchportdb{$swpt} = {
1685            ip          => $ip,
1686            timestamp   => $ip_timestamp,
1687            vlan        => $computerdb->{$ip}{network},
1688            hostname_fq => $ip_hostname_fq,
1689            mac_address => $ip_mac,
1690            };
1691         }
1692      }
1693
1694   LOOP_ON_RECENT_COMPUTER:
1695   for my $swpt (keys %switchportdb) {
1696      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
1697      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{hostname_fq} !~ m/$RE_FLOAT_HOSTNAME/;
1698
1699      my $src_ip = $switchportdb{$swpt}->{ip};
1700      my $src_timestamp = 0;
1701      LOOP_ON_IP_ADDRESS:
1702      for my $ip (keys %{$computerdb}) {
1703         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{mac_address} ne  $switchportdb{$swpt}->{mac_address};
1704         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/;
1705         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{timestamp} < $src_timestamp;
1706
1707         $src_ip = $ip;
1708         $src_timestamp = $computerdb->{$ip}{timestamp};
1709         }
1710
1711      # keep only if float computer is the most recent
1712      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
1713      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{timestamp} < $src_timestamp;
1714
1715      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $switchportdb{$swpt}->{timestamp};
1716      $year += 1900;
1717      $mon++;
1718      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1719
1720      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{timestamp};
1721      $year += 1900;
1722      $mon++;
1723      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1724
1725      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{network});
1726
1727      printf "%s / %-10s +-> %-10s(%i)  %s %s %s %s\n",
1728         $swpt, $switchportdb{$swpt}->{vlan}, $computerdb->{$src_ip}{network}, $vlan_id,
1729         $date,
1730         $src_date,
1731         $computerdb->{$src_ip}{mac_address},
1732         $computerdb->{$src_ip}{hostname_fq};
1733      }
1734   }
1735
1736#---------------------------------------------------------------
1737# not finish - do not use
1738sub cmd_set_vlan_port {
1739   my $switch_name = shift || q{};
1740   my $mac_address = shift || q{};
1741
1742   if ($switch_name eq q{} or $mac_address eq q{}) {
1743      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1744      }
1745
1746   $switch_name = join(',', map {$_->{hostname}} @SWITCH_LIST ) if $switch_name eq q{*};
1747
1748   for my $sw_name (split /,/, $switch_name) {
1749      if (not defined $SWITCH_DB{$sw_name}) {
1750         die "Switch $sw_name must be defined in klask configuration file\n";
1751         }
1752
1753      my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex_to_dec($mac_address);
1754      my $research2 = $OID_NUMBER{'searchPort2'} .'.'. 0 . mac_address_hex_to_dec($mac_address);
1755      print "Klask search OID $research1 on switch $sw_name\n";
1756      print "Klask search OID $research2 on switch $sw_name\n";
1757
1758      my $sw = $SWITCH_DB{$sw_name};
1759      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1760      print "$error \n" if $error;
1761
1762      my $result = $session->get_request(
1763         -varbindlist => [$research1]
1764         );
1765      if (not defined $result) {
1766         $result = $session->get_request(
1767            -varbindlist => [$research2]
1768            );
1769         $result->{$research1} = $result->{$research2} if defined $result;
1770         }
1771
1772      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1773         my $swport = $result->{$research1};
1774         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1775         }
1776      else {
1777         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1778         }
1779
1780      $session->close;
1781      }
1782   return;
1783   }
1784
1785#---------------------------------------------------------------
1786sub cmd_get_vlan_port {
1787   @ARGV = @_;
1788
1789   my $verbose;
1790   GetOptions(
1791      'verbose|v' => \$verbose,
1792      );
1793
1794   my $switch_name = shift @ARGV || q{};
1795   my $switch_port = shift @ARGV || q{};
1796
1797   if ($switch_name eq q{} or $switch_port eq q{}) {
1798      die "Usage: klask get-vlan-port SWITCH_NAME PORT\n";
1799      }
1800
1801   for my $sw_name (split /,/, $switch_name) {
1802      if (not defined $SWITCH_DB{$sw_name}) {
1803         die "Switch $sw_name must be defined in klask configuration file\n";
1804         }
1805
1806      my $search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
1807
1808      my $sw = $SWITCH_DB{$sw_name};
1809      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1810      print "$error \n" if $error;
1811
1812      my $result = $session->get_request(
1813         -varbindlist => [$search],
1814         );
1815
1816      if (defined $result and $result->{$search} ne 'noSuchInstance') {
1817         my $vlan_id = $result->{$search} || 'empty';
1818         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
1819         }
1820      else {
1821         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
1822         }
1823
1824      $session->close;
1825      }
1826   return;
1827   }
1828
1829#---------------------------------------------------------------
1830sub cmd_set_vlan_name {
1831   }
1832
1833#---------------------------------------------------------------
1834# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'hpicfReset'}.0" i 2;
1835sub cmd_rebootsw {
1836   @ARGV = @_;
1837
1838   my $verbose;
1839   GetOptions(
1840      'verbose|v' => \$verbose,
1841      );
1842
1843   my $switch_name = shift @ARGV || q{};
1844
1845   if ($switch_name eq q{}) {
1846      die "Usage: klask rebootsw SWITCH_NAME\n";
1847      }
1848
1849   for my $sw_name (split /,/, $switch_name) {
1850      if (not defined $SWITCH_DB{$sw_name}) {
1851         die "Switch $sw_name must be defined in klask configuration file\n";
1852         }
1853
1854      my $sw = $SWITCH_DB{$sw_name};
1855      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1856      print "$error \n" if $error;
1857
1858      my $result = $session->set_request(
1859         -varbindlist => ["$OID_NUMBER{'hpicfReset'}.0", INTEGER, 2],
1860         );
1861
1862      $session->close;
1863      }
1864   return;
1865   }
1866
1867#---------------------------------------------------------------
1868sub cmd_get_vlan_name {
1869   my $switch_name = shift || q{};
1870   my $vlan_id     = shift || q{};
1871
1872   if ($switch_name eq q{} or $vlan_id eq q{}) {
1873      die "Usage: klask get-vlan-name SWITCH_NAME VLAN_ID\n";
1874      }
1875
1876   $switch_name = join(',', map {$_->{hostname}} @SWITCH_LIST ) if $switch_name eq q{*};
1877
1878   for my $sw_name (split /,/, $switch_name) {
1879      if (not defined $SWITCH_DB{$sw_name}) {
1880         die "Switch $sw_name must be defined in klask configuration file\n";
1881         }
1882
1883      my $search_vlan_name = $OID_NUMBER{vlanName} . ".$vlan_id";
1884
1885      my $sw = $SWITCH_DB{$sw_name};
1886      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1887      print "$error \n" if $error;
1888
1889      my $result = $session->get_request(
1890         -varbindlist => [$search_vlan_name]
1891         );
1892
1893      if (defined $result and $result->{$search_vlan_name} ne 'noSuchInstance') {
1894         my $vlan_name = $result->{$search_vlan_name} || 'empty';
1895         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
1896         }
1897      else {
1898         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
1899         }
1900
1901      $session->close;
1902      }
1903   return;
1904   }
1905
1906#---------------------------------------------------------------
1907sub cmd_ip_location {
1908   my $computerdb = computerdb_load();
1909
1910   LOOP_ON_IP_ADDRESS:
1911   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1912
1913      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1914
1915      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1916      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
1917
1918      my $sw_location = q{};
1919      LOOP_ON_ALL_SWITCH:
1920      for my $sw (@SWITCH_LIST) {
1921         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{hostname};
1922         $sw_location = $sw->{location};
1923         last;
1924         }
1925
1926      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1927      }
1928   return;
1929   }
1930
1931#---------------------------------------------------------------
1932sub cmd_ip_free {
1933   @ARGV = @_;
1934
1935   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
1936   my $format = 'txt';
1937   my $verbose;
1938
1939   GetOptions(
1940      'day|d=i'      => \$days_to_death,
1941      'format|f=s'   => \$format,
1942      'verbose|v'    => \$verbose,
1943      );
1944
1945   my %possible_format = (
1946      txt  => \&cmd_ip_free_txt,
1947      html => \&cmd_ip_free_html,
1948      none => sub {},
1949      );
1950   $format = 'txt' if not defined $possible_format{$format};
1951
1952   my @vlan_name = @ARGV;
1953   @vlan_name = get_list_network() if not @vlan_name;
1954
1955   my $computerdb = {};
1956      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
1957   my $timestamp = time;
1958
1959   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
1960
1961   my %result_ip = ();
1962
1963   ALL_NETWORK:
1964   for my $vlan (@vlan_name) {
1965
1966      my @ip_list = get_list_ip($vlan);
1967
1968      LOOP_ON_IP_ADDRESS:
1969      for my $ip (@ip_list) {
1970
1971         if (exists $computerdb->{$ip}) {
1972            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{timestamp} > $timestamp_barrier;
1973
1974            my $mac_address = $computerdb->{$ip}{mac_address};
1975            LOOP_ON_DATABASE:
1976            for my $ip_db (keys %{$computerdb}) {
1977               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{mac_address} ne $mac_address;
1978               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{timestamp} > $timestamp_barrier;
1979               }
1980            }
1981
1982         my $ip_date_last_detection = '';
1983         if (exists $computerdb->{$ip}) {
1984            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1985            $year += 1900;
1986            $mon++;
1987            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1988            }
1989
1990         my $packed_ip = scalar gethostbyname($ip);
1991         my $hostname_fq = 'unknown';
1992            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
1993
1994         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1995
1996         $result_ip{$ip} ||= {};
1997         $result_ip{$ip}->{date_last_detection} = $ip_date_last_detection;
1998         $result_ip{$ip}->{hostname_fq} = $hostname_fq;
1999         $result_ip{$ip}->{vlan} = $vlan;
2000
2001         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
2002         }
2003      }
2004
2005   $possible_format{$format}->(%result_ip);
2006   }
2007
2008#---------------------------------------------------------------
2009sub cmd_ip_free_txt {
2010   my %result_ip = @_;
2011
2012   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2013   print "-------------------------------------------------------------------------------\n";
2014   LOOP_ON_IP_ADDRESS:
2015   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2016         my $vlan_nameid = $result_ip{$ip}->{vlan}.'('.get_current_vlan_id($result_ip{$ip}->{vlan}).')';
2017         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{hostname_fq}, $result_ip{$ip}->{date_last_detection}, $vlan_nameid;
2018      }
2019   }
2020
2021#---------------------------------------------------------------
2022sub cmd_ip_free_html {
2023   my %result_ip = @_;
2024
2025   print <<'END_HTML';
2026<table class="sortable" summary="Klask Free IP Database">
2027 <caption>Klask Free IP Database</caption>
2028 <thead>
2029  <tr>
2030   <th scope="col" class="klask-header-left">IPv4-Address</th>
2031   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2032   <th scope="col" class="sorttable_alpha">VLAN</th>
2033   <th scope="col" class="klask-header-right">Date</th>
2034  </tr>
2035 </thead>
2036 <tfoot>
2037  <tr>
2038   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2039   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2040   <th scope="col" class="fklask-vlan">VLAN</th>
2041   <th scope="col" class="klask-footer-right">Date</th>
2042  </tr>
2043 </tfoot>
2044 <tbody>
2045END_HTML
2046
2047   my $typerow = 'even';
2048
2049   LOOP_ON_IP_ADDRESS:
2050   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2051
2052      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2053
2054      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
2055      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{hostname_fq};
2056
2057      my $vlan_nameid = $result_ip{$ip}->{vlan}.'('.get_current_vlan_id($result_ip{$ip}->{vlan}).')';
2058
2059      print <<"END_HTML";
2060  <tr class="$typerow">
2061   <td sorttable_customkey="$ip_sort">$ip</td>
2062   <td sorttable_customkey="$host_short">$result_ip{$ip}->{hostname_fq}</td>
2063   <td>$vlan_nameid</td>
2064   <td>$result_ip{$ip}->{date_last_detection}</td>
2065  </tr>
2066END_HTML
2067      }
2068   print <<'END_HTML';
2069 </tbody>
2070</table>
2071END_HTML
2072   }
2073
2074#---------------------------------------------------------------
2075sub cmd_enable {
2076   @ARGV = @_;
2077
2078   my $verbose;
2079
2080   GetOptions(
2081      'verbose|v' => \$verbose,
2082      );
2083
2084   my $switch_name = shift @ARGV || q{};
2085   my $port        = shift @ARGV || q{};
2086
2087   if ($switch_name eq q{} or $port eq q{}) {
2088      die "Usage: klask disable SWITCH_NAME PORT\n";
2089      }
2090
2091   if (not defined $SWITCH_DB{$switch_name}) {
2092      die "Switch $switch_name must be defined in klask configuration file\n";
2093      }
2094
2095   my $sw = $SWITCH_DB{$switch_name};
2096   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2097   print "$error \n" if $error;
2098
2099   # Retrieve numeric port value
2100   my $port_num = snmp_get_switchport_num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2101   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
2102
2103   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2104   print "Info: switch $switch_name port $port SNMP OID $search_portstatus\n" if $verbose;
2105
2106   my $result = $session->set_request(
2107      -varbindlist => [$search_portstatus, INTEGER, 1],
2108      );
2109   print $session->error()."\n" if $session->error_status();
2110
2111   $session->close;
2112
2113   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2114   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2115   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2116   return;
2117   }
2118
2119#---------------------------------------------------------------
2120sub cmd_disable {
2121   @ARGV = @_;
2122
2123   my $verbose;
2124
2125   GetOptions(
2126      'verbose|v' => \$verbose,
2127      );
2128
2129   my $switch_name = shift @ARGV || q{};
2130   my $port        = shift @ARGV || q{};
2131
2132   if ($switch_name eq q{} or $port eq q{}) {
2133      die "Usage: klask disable SWITCH_NAME PORT\n";
2134      }
2135
2136   if (not defined $SWITCH_DB{$switch_name}) {
2137      die "Switch $switch_name must be defined in klask configuration file\n";
2138      }
2139
2140   my $sw = $SWITCH_DB{$switch_name};
2141   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2142   print "$error \n" if $error;
2143
2144   # Retrieve numeric port value
2145   my $port_num = snmp_get_switchport_num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2146   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
2147
2148   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2149   print "Info: switch $switch_name port $port SNMP OID $search_portstatus\n" if $verbose;
2150
2151   my $result = $session->set_request(
2152      -varbindlist => [$search_portstatus, INTEGER, 2],
2153      );
2154   print $session->error()."\n" if $session->error_status();
2155
2156   $session->close;
2157
2158   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2159   return;
2160   }
2161
2162#---------------------------------------------------------------
2163sub cmd_status {
2164   @ARGV = @_;
2165
2166   my $verbose;
2167
2168   GetOptions(
2169      'verbose|v' => \$verbose,
2170      );
2171
2172   my $switch_name = shift @ARGV || q{};
2173   my $port        = shift @ARGV || q{};
2174
2175   if ($switch_name eq q{} or $port eq q{}) {
2176      die "Usage: klask status SWITCH_NAME PORT\n";
2177      }
2178
2179   if (not defined $SWITCH_DB{$switch_name}) {
2180      die "Switch $switch_name must be defined in klask configuration file\n";
2181      }
2182
2183   my $sw = $SWITCH_DB{$switch_name};
2184   my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2185   print "$error \n" if $error;
2186
2187   # Retrieve numeric port value
2188   my $port_num = snmp_get_switchport_num($session, normalize_port_human_readable($port), $verbose ? 'yes' : '');
2189   die "Error : Port $port does not exist on switch $switch_name\n" if not $port_num =~ m/^\d+$/;
2190
2191   my $search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_num;
2192   print "Info: switch $switch_name port $port ($port_num) SNMP OID $search_portstatus\n" if $verbose;
2193
2194   my $result = $session->get_request(
2195      -varbindlist => [$search_portstatus]
2196      );
2197   print $session->error()."\n" if $session->error_status();
2198   if (defined $result) {
2199      print "$PORT_UPDOWN{$result->{$search_portstatus}}\n";
2200      }
2201
2202   $session->close;
2203
2204   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
2205   return;
2206   }
2207
2208#---------------------------------------------------------------
2209sub cmd_search_mac_on_switch {
2210   @ARGV = @_;
2211
2212   my $verbose;
2213   my $vlan_id = 0;
2214
2215   GetOptions(
2216      'verbose|v' => \$verbose,
2217      'vlan|l=i'  => \$vlan_id,
2218      );
2219
2220   my $switch_name = shift @ARGV || q{};
2221   my $mac_address = shift @ARGV || q{};
2222
2223   if ($switch_name eq q{} or $mac_address eq q{}) {
2224      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2225      }
2226
2227   $mac_address = normalize_mac_address($mac_address);
2228   $switch_name = join(',', map {$_->{hostname}} @SWITCH_LIST ) if $switch_name eq q{*} or $switch_name eq q{all};
2229
2230   for my $sw_name (split /,/, $switch_name) {
2231      if (not defined $SWITCH_DB{$sw_name}) {
2232         die "Switch $sw_name must be defined in klask configuration file\n";
2233         }
2234
2235      my $research1 = $OID_NUMBER{'searchPort1'} . mac_address_hex_to_dec($mac_address);
2236      my $research2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex_to_dec($mac_address);
2237      print "Klask search OID $research1 on switch $sw_name\n" if $verbose;
2238      print "Klask search OID $research2 on switch $sw_name\n" if $verbose;
2239
2240      my $sw = $SWITCH_DB{$sw_name};
2241      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2242      print "$error \n" if $error;
2243
2244      my $result = $session->get_request(
2245         -varbindlist => [$research1]
2246         );
2247      if (not defined $result) {
2248         $result = $session->get_request(
2249            -varbindlist => [$research2]
2250            );
2251         $result->{$research1} = $result->{$research2} if defined $result;
2252         }
2253
2254      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
2255         my $swport_num = $result->{$research1};
2256         my $swport_hr = get_human_readable_port($sw->{model}, snmp_get_switchport_hr($session, $swport_num));
2257         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
2258         }
2259      else {
2260         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
2261         }
2262
2263      $session->close;
2264      }
2265   return;
2266   }
2267
2268#---------------------------------------------------------------
2269sub cmd_updatesw {
2270   @ARGV = @_;
2271
2272   my $verbose;
2273
2274   GetOptions(
2275      'verbose|v' => \$verbose,
2276      );
2277
2278   init_switch_names('yes');    #nomme les switchs
2279   print "\n";
2280
2281   my %where = ();
2282   my %db_switch_output_port = ();
2283   my %db_switch_ip_hostnamefq = ();
2284
2285   DETECT_ALL_ROUTER:
2286   for my $one_router ( get_list_main_router(get_list_network()) ) {
2287      print "Info: router loop $one_router\n" if $verbose;
2288      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
2289
2290      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
2291      print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
2292
2293      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{interface});
2294      my $vlan_id   = get_current_vlan_id($vlan_name);
2295      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address},$vlan_id); # retrouve les emplacements des routeurs
2296      }
2297
2298   ALL_ROUTER_IP_ADDRESS:
2299   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
2300
2301      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
2302
2303      ALL_SWITCH_CONNECTED:
2304      for my $switch_detected ( keys %{$where{$ip_router}} ) {
2305
2306         my $switch = $where{$ip_router}->{$switch_detected};
2307
2308         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
2309
2310         $db_switch_output_port{$switch->{hostname}} = $switch->{port_hr};
2311         print "VERBOSE_2: output port $switch->{hostname} : $switch->{port_hr}\n" if $verbose;
2312         }
2313      }
2314
2315   my %db_switch_link_with = ();
2316
2317   my @list_all_switch = ();
2318   my @list_switch_ipv4 = ();
2319   for my $sw (@SWITCH_LIST) {
2320      push @list_all_switch, $sw->{hostname};
2321      }
2322
2323   my $timestamp = time;
2324
2325   ALL_SWITCH:
2326   for my $one_switch (@list_all_switch) {
2327      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
2328      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
2329         print "WARNING: fake ip on switch $one_switch -> $SWITCH_DB{$one_switch}{'fake-ip'} / $resol_arp{ipv4_address}\n" if $verbose;
2330         my %resol_arp_alt = resolve_ip_arp_host($SWITCH_DB{$one_switch}{'fake-ip'}, q{*}, q{low}); # arp resolution
2331         if ($resol_arp_alt{mac_address} ne 'unknow') {
2332            $resol_arp{mac_address}   = $resol_arp_alt{mac_address};
2333            $resol_arp{interface}     = $resol_arp_alt{interface};
2334            $resol_arp{ipv4_address} .= '*';
2335            }
2336         }
2337      print "Info: switch loop $one_switch\n" if $verbose;
2338      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
2339
2340      push @list_switch_ipv4, $resol_arp{ipv4_address};
2341
2342      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{interface});
2343      my $vlan_id   = get_current_vlan_id($vlan_name);
2344      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address},$vlan_id); # find port on all switch
2345
2346      if ($verbose) {
2347         print "VERBOSE_3: $one_switch $resol_arp{ipv4_address} $resol_arp{mac_address}\n";
2348         print "VERBOSE_3: $one_switch --- ",
2349            join(' + ', keys %{$where{$resol_arp{ipv4_address}}}),
2350            "\n";
2351         }
2352
2353      $db_switch_ip_hostnamefq{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
2354      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{ipv4_address} -> $resol_arp{hostname_fq}\n" if $verbose;
2355
2356      $SWITCH_DB{$one_switch}->{ipv4_address} = $resol_arp{ipv4_address};
2357      $SWITCH_DB{$one_switch}->{mac_address}  = $resol_arp{mac_address};
2358      $SWITCH_DB{$one_switch}->{timestamp}    = $timestamp;
2359      }
2360
2361   ALL_SWITCH_IP_ADDRESS:
2362   for my $ip (@list_switch_ipv4) {
2363#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
2364
2365      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2366
2367      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
2368#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
2369
2370      DETECTED_SWITCH:
2371      for my $switch_detected ( keys %{$where{$ip}} ) {
2372
2373         my $switch = $where{$ip}->{$switch_detected};
2374         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{hostname} : $switch->{port_hr}\n" if $verbose;
2375
2376         next if $switch->{port}     eq '0';
2377         next if $switch->{port_hr}  eq $db_switch_output_port{$switch->{hostname}};
2378         next if $switch->{hostname} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{hostname};
2379
2380         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
2381         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{hostname} } = $switch->{port_hr};
2382         print "VERBOSE_7: +++++\n" if $verbose;
2383         }
2384
2385      }
2386
2387   my %db_switch_connected_on_port = ();
2388   my $maybe_more_than_one_switch_connected = 'yes';
2389   my $cloop = 0;
2390
2391   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2392      $cloop++;
2393      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
2394      for my $sw (keys %db_switch_link_with) {
2395         for my $connect (keys %{$db_switch_link_with{$sw}}) {
2396
2397            my $port_hr = $db_switch_link_with{$sw}->{$connect};
2398
2399            $db_switch_connected_on_port{"$connect:$port_hr"} ||= {};
2400            $db_switch_connected_on_port{"$connect:$port_hr"}->{$sw}++; # Just to define the key
2401            }
2402         }
2403
2404      $maybe_more_than_one_switch_connected  = 'no';
2405
2406      SWITCH_AND_PORT:
2407      for my $swport (keys %db_switch_connected_on_port) {
2408
2409         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
2410
2411         $maybe_more_than_one_switch_connected = 'yes';
2412
2413         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2414         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
2415         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
2416
2417         CONNECTED:
2418         for my $sw_connected (@sw_on_same_port) {
2419
2420            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
2421
2422            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
2423
2424            for my $other_sw (@sw_on_same_port) {
2425               next if $other_sw eq $sw_connected;
2426
2427               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2428               }
2429
2430            # We can not do better for this switch for this loop
2431            next SWITCH_AND_PORT;
2432            }
2433         }
2434      }
2435
2436   my %db_switch_parent =();
2437
2438   for my $sw (keys %db_switch_link_with) {
2439      for my $connect (keys %{$db_switch_link_with{$sw}}) {
2440
2441         my $port_hr = $db_switch_link_with{$sw}->{$connect};
2442
2443         $db_switch_connected_on_port{"$connect:$port_hr"} ||= {};
2444         $db_switch_connected_on_port{"$connect:$port_hr"}->{$sw} = $port_hr;
2445
2446         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
2447         }
2448      }
2449
2450   print "Switch output port and parent port connection\n";
2451   print "---------------------------------------------\n";
2452   for my $sw (sort keys %db_switch_output_port) {
2453      if (exists $db_switch_parent{$sw}) {
2454         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port_hr}, $db_switch_parent{$sw}->{'switch'};
2455         }
2456      else {
2457         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2458         }
2459      }
2460   print "\n";
2461
2462   print "Switch parent and children port inter-connection\n";
2463   print "------------------------------------------------\n";
2464   for my $swport (sort keys %db_switch_connected_on_port) {
2465      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2466      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2467         if (exists $db_switch_output_port{$sw}) {
2468            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2469            }
2470         else {
2471            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2472            }
2473         }
2474      }
2475
2476   my $switch_connection = {
2477      output_port       => \%db_switch_output_port,
2478      parent            => \%db_switch_parent,
2479      connected_on_port => \%db_switch_connected_on_port,
2480      link_with         => \%db_switch_link_with,
2481      switch_db         => \%SWITCH_DB,
2482      };
2483
2484   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
2485   return;
2486   }
2487
2488#---------------------------------------------------------------
2489sub cmd_exportsw {
2490   @ARGV = @_;
2491
2492   test_switchdb_environnement();
2493
2494   my $format = 'txt';
2495
2496   GetOptions(
2497      'format|f=s'  => \$format,
2498      );
2499
2500   my %possible_format = (
2501      txt => \&cmd_exportsw_txt,
2502      dot => \&cmd_exportsw_dot,
2503      );
2504
2505   $format = 'txt' if not defined $possible_format{$format};
2506
2507   $possible_format{$format}->(@ARGV);
2508   return;
2509   }
2510
2511#---------------------------------------------------------------
2512sub cmd_exportsw_txt {
2513
2514   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2515
2516   my %db_switch_output_port       = %{$switch_connection->{output_port}};
2517   my %db_switch_parent            = %{$switch_connection->{parent}};
2518   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
2519
2520   print "Switch output port and parent port connection\n";
2521   print "---------------------------------------------\n";
2522   for my $sw (sort keys %db_switch_output_port) {
2523      my $arrow ='-->';
2524         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2525      if (exists $db_switch_parent{$sw}) {
2526         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, $db_switch_parent{$sw}->{port_hr}, $db_switch_parent{$sw}->{'switch'};
2527         }
2528      else {
2529         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
2530         }
2531      }
2532   print "\n";
2533
2534   print "Switch parent and children port inter-connection\n";
2535   print "------------------------------------------------\n";
2536   for my $swport (sort keys %db_switch_connected_on_port) {
2537      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2538      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2539         my $arrow ='<--';
2540            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
2541         if (exists $db_switch_output_port{$sw}) {
2542            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
2543            }
2544         else {
2545            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
2546            }
2547         }
2548      }
2549   return;
2550   }
2551
2552#---------------------------------------------------------------
2553sub cmd_exportsw_dot {
2554
2555   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2556
2557   my %db_switch_output_port       = %{$switch_connection->{output_port}};
2558   my %db_switch_parent            = %{$switch_connection->{parent}};
2559   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
2560   my %db_switch_link_with         = %{$switch_connection->{link_with}};
2561   my %db_switch_global            = %{$switch_connection->{switch_db}};
2562
2563   my %db_building= ();
2564   for my $sw (@SWITCH_LIST) {
2565      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
2566      $db_building{$building} ||= {};
2567      $db_building{$building}->{$location} ||= {};
2568      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
2569      }
2570
2571
2572   print "digraph G {\n";
2573   print "rankdir = LR;\n";
2574   #print "splines=polyline;\n";
2575
2576   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2577   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
2578
2579   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime time;
2580   $year += 1900;
2581   $mon++;
2582   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2583   print "\"$date\" [ color = white, fillcolor = black, shape = polygon, sides=14, style = filled, fontcolor = white ]\n";
2584   print "site -> \"$date\" [color = white];\n";
2585
2586   my $b=0;
2587   for my $building (keys %db_building) {
2588      $b++;
2589
2590      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2591      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
2592
2593      my $l = 0;
2594      for my $loc (keys %{$db_building{$building}}) {
2595         $l++;
2596
2597         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
2598#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
2599         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
2600
2601         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2602
2603            print "\"$sw:$db_switch_output_port{$sw}\" [label = \"$db_switch_output_port{$sw}\", color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
2604
2605            my $swname  = $sw;
2606               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2607            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
2608            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2609            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
2610
2611
2612            for my $swport (keys %db_switch_connected_on_port) {
2613               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2614               next if not $sw_connect eq $sw;
2615               next if $port_connect eq $db_switch_output_port{$sw};
2616               print "\"$sw:$port_connect\" [label = \"$port_connect\", color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
2617               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
2618              }
2619            }
2620         }
2621      }
2622
2623#   print "Switch output port and parent port connection\n";
2624#   print "---------------------------------------------\n";
2625   for my $sw (sort keys %db_switch_output_port) {
2626      if (exists $db_switch_parent{$sw}) {
2627#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{port};
2628         }
2629      else {
2630         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
2631         }
2632      }
2633   print "\n";
2634
2635#   print "Switch parent and children port inter-connection\n";
2636#   print "------------------------------------------------\n";
2637   for my $swport (sort keys %db_switch_connected_on_port) {
2638      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2639      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2640         if (exists $db_switch_output_port{$sw}) {
2641            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2642            }
2643         else {
2644            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
2645            }
2646         }
2647      }
2648
2649print "}\n";
2650   return;
2651   }
2652
2653
2654################################################################
2655# documentation
2656################################################################
2657
2658__END__
2659
2660=head1 NAME
2661
2662klask - port and search manager for switches, map management
2663
2664
2665=head1 USAGE
2666
2667 klask version
2668 klask help
2669
2670 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2671 klask exportdb [--format|-f txt|html]
2672 klask removedb IP* computer*
2673 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
2674
2675 klask updatesw [--verbose|-v]
2676 klask exportsw [--format|-f txt|dot]
2677
2678 klask searchdb [--kind|-k host|mac] computer [mac-address]
2679 klask search   computer
2680 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2681
2682 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
2683
2684 klask bad-vlan-id [--day|-d days_before_alert]
2685
2686 klask enable  [--verbose|-v] switch port
2687 klask disable [--verbose|-v] switch port
2688 klask status  [--verbose|-v] switch port
2689
2690
2691=head1 DESCRIPTION
2692
2693Klask is a small tool to find where is a host in a big network.
2694Klask mean search in brittany.
2695No hight level protocol like CDL, LLDP are use.
2696Everything is just done with SNMP request on MAC address.
2697
2698Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
2699If you use PVST or MSTP and create loop between VLAN,
2700you have to use C<portignore> functionality on switch port to cut manually loop
2701(see config file below).
2702
2703When you use a management port to administrate a switch,
2704it's not possible to create the map with this switch because it does not have a MAC address,
2705so other switch cannot find the real downlink port...
2706One way to work around this problem is, if you have a computer directly connected on the switch,
2707to put this IP as the fake ip for the switch.
2708The MAC address associated will be use just for the map detection.
2709The C<fake-ip> parameter is defined in the config file.
2710
2711Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
2712
2713
2714=head1 COMMANDS
2715
2716Some command are defined in the source code but are not documented here.
2717Theses could be not well defined, not finished, not well tested...
2718You can read the source code and use them at your own risk
2719(like for all the Klask code).
2720
2721=head2 search
2722
2723 klask search   computer
2724
2725This command takes one or more computer in argument.
2726It search a computer on the network and give the port and the switch on which the computer is connected.
2727
2728=head2 search-mac-on-switch
2729
2730 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
2731
2732This command search a MAC address on a switch.
2733To search on all switch, you could put C<'*'> or C<all>.
2734The VLAN parameter could help.
2735
2736
2737=head2 enable
2738
2739 klask enable  [--verbose|-v] switch port
2740
2741This command activate a port (or an agrregate bridge port) on a switch by SNMP.
2742So you need to give the switch name and a port on the command line.
2743See L</ABBREVIATION FOR PORT>.
2744
2745Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
2746
2747
2748=head2 disable
2749
2750 klask disable [--verbose|-v] switch port
2751
2752This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
2753So you need to give the switch name and a port on the command line.
2754See L</ABBREVIATION FOR PORT>.
2755
2756Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
2757
2758
2759=head2 status
2760
2761 klask status  [--verbose|-v] switch port
2762
2763This command return the status of a port number on a switch by SNMP.
2764The return value could be C<enable> or C<disable> word.
2765So you need to give the switch name and a port on the command line.
2766See L</ABBREVIATION FOR PORT>.
2767
2768If it's not possible to change port status with command L</enable> and L</disable>
2769(SNMP community read write access),
2770it's always possible to have the port status even for bridge agrregate port.
2771
2772
2773=head2 updatedb
2774
2775 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2776
2777This command will scan networks and update the computer database.
2778To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
2779This file is easy to read and write because Klask use YAML format and not XML
2780(see L</CONFIGURATION>).
2781
2782Option are not stable and could be use manually when you have a new kind of switch.
2783Maybe some option will be transfered in a future C<checksw> command!
2784
2785The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
2786By default, a network is C<active>.
2787This means that an C<fping> command is done at the beginning on all the IP of the network
2788and the computers that was not detected in this pass, but where their Klask entry is less than one week,
2789will have an C<arping>
2790(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
2791In the scan mode C<passive>, no C<fping> and no C<arping> are done.
2792It's good for big subnet with few computer (telephone...).
2793The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
2794
2795=head2 exportdb
2796
2797 klask exportdb [--format|-f txt|html]
2798
2799This command print the content of the computer database.
2800There is actually only two format : TXT and HTML.
2801By default, format is TXT.
2802It's very easy to have more format, it's just need times...
2803
2804=head2 removedb
2805
2806 klask removedb IP* computer*
2807
2808This command remove an entry in the database.
2809There is only one kind of parameter, the IP of the computers to be removed.
2810You can put as many IP as you want...
2811
2812Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
2813
2814=head2 cleandb
2815
2816 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
2817
2818Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
2819Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
2820This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
2821This functionality could be use when computer define in VLAN 1
2822could have a float IP when they are connected on VLAN 2.
2823In the Klask database, the float DNS entries are less important.
2824
2825When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
2826
2827=head2 updatesw
2828
2829 klask updatesw [--verbose|-v]
2830
2831This command build a map of your manageable switch on your network.
2832The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
2833
2834
2835=head2 exportsw
2836
2837 klask exportsw [--format|-f txt|dot]
2838
2839This command print the content of the switch database. There is actually two format.
2840One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
2841By default, format is TXT.
2842
2843 klask exportsw --format dot > /tmp/map.dot
2844 dot -Tpng /tmp/map.dot > /tmp/map.png
2845
2846
2847=head2 ip-free
2848
2849 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
2850
2851This command return IP address that was not use (detected by Klask) at this time.
2852The list returned could be limited to just one VLAN.
2853IP returned could have been never used or no computer have been detected since the number of days specified
2854(2 years by default).
2855This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
2856
2857 default:
2858   days-to-death: 730
2859
2860Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
2861
2862
2863=head2 bad-vlan-id
2864
2865 klask bad-vlan-id [--day|-d days_before_alert]
2866
2867This command return a list of switch port that are not configure with the good VLAN.
2868Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
2869and another prior trace where they had the good IP (good DNS name).
2870The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
2871This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
2872
2873 default:
2874   days-before-alert: 15
2875
2876This functionality is not need if your switch use RADIUS 802.1X configuration...
2877
2878
2879
2880=head1 CONFIGURATION
2881
2882Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
2883The configuration is done in a F</etc/klask/klask.conf> YAML file.
2884This format have many advantage over XML, it's easier to read and to write !
2885
2886Here an example, be aware with indent, it's important in YAML, do not use tabulation !
2887
2888 default:
2889   community: public
2890   community-rw: private
2891   snmpport: 161
2892   float-regex: '(?^msx: ^float )'
2893   scan-mode: active
2894
2895 network:
2896   labnet:
2897     ip-subnet:
2898       - add: 192.168.1.0/24
2899       - add: 192.168.2.0/24
2900     interface: eth0
2901     vlan-id: 12
2902     main-router: gw1.labnet.local
2903
2904   schoolnet:
2905     ip-subnet:
2906       - add: 192.168.3.0/24
2907       - add: 192.168.4.0/24
2908     interface: eth0.38
2909     vlan-id: 13
2910     main-router: gw2.schoolnet.local
2911     scan-mode: passive
2912
2913   etunet:
2914     ip-subnet:
2915       - add: 192.168.5.0/24
2916     interface: eth2
2917     vlan-id: 14
2918     main-router: gw3.etunet.local
2919     scan-mode: passive
2920
2921 switch:
2922   - hostname: sw1.klask.local
2923     location: BatY / 1 floor / K004
2924     portignore:
2925       - 1
2926       - 2
2927
2928   - hostname: sw2.klask.local
2929     location: BatY / 2 floor / K203
2930     type: HP2424
2931     portignore:
2932       - 1
2933       - 2
2934     fake-ip: 192.168.9.14
2935
2936   - hostname: sw3.klask.local
2937     location: BatY / 2 floor / K203
2938
2939I think it's pretty easy to understand.
2940The default section can be overide in any section, if parameter mean something in theses sections.
2941Network to be scan are define in the network section. You must put an add by network.
2942Maybe I will make a delete line to suppress specific computers.
2943The switch section define your switch.
2944You have to write the port number to ignore, this was important if your switchs are cascades
2945(right now, method C<updatesw> find them automatically)
2946and is still important if you have loop (with PVST or MSTP).
2947Just put the ports numbers between switch.
2948
2949The C<community> parameter is use to get SNMP data on switch.
2950It could be overload for each switch.
2951By default, it's value is C<public> and you have to configure a readonly word for safety reason.
2952Some few command change the switch state as the commands L</enable> and L</disable>.
2953In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
2954Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
2955
2956
2957=head1 ABBREVIATION FOR PORT
2958
2959HP Procurve and Nexans switches have a simplistic numbering scheme.
2960It's just number: 1, 2, 3... 24.
2961On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
2962Nothing is done on theses ports names.
2963
2964On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
2965followed by tree number.
2966In order to have short name,
2967we made the following rules:
2968
2969 Bridge-Aggregation     -> Br
2970 FastEthernet           -> Fa
2971 Forty-GigabitEthernet  -> Fo
2972 FortyGigabitEthernet   -> Fo
2973 GigabitEthernet        -> Gi
2974 Giga                   -> Gi
2975 Port-Channel           -> Po
2976 Ten-GigabitEthernet    -> Te
2977 TenGigabitEthernet     -> Te
2978 Ten                    -> Te
2979
2980All Klask command automatically normalize the port name on standart output
2981and also on input command line.
2982
2983
2984=head1 FILES
2985
2986 /etc/klask/klask.conf
2987 /var/lib/klask/klaskdb
2988 /var/lib/klask/switchdb
2989
2990
2991=head1 SEE ALSO
2992
2993Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
2994
2995=over
2996
2997=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
2998
2999=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3000
3001=back
3002
3003
3004=head1 VERSION
3005
3006$Id: klask 233 2017-03-11 21:56:58Z g7moreau $
3007
3008
3009=head1 AUTHOR
3010
3011Written by Gabriel Moreau, Grenoble - France
3012
3013
3014=head1 LICENSE AND COPYRIGHT
3015
3016GPL version 2 or later and Perl equivalent
3017
3018Copyright (C) 2005-2017 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.