source: trunk/klask @ 232

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