source: trunk/klask @ 238

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