source: trunk/klask @ 132

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