source: trunk/klask @ 189

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