source: trunk/klask @ 221

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