source: trunk/klask @ 66

Last change on this file since 66 was 66, checked in by g7moreau, 15 years ago
  • Faster implementation of removedb command if the parameter is an IP address
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 53.2 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2008 Gabriel Moreau.
4#
5# $Id: klask 66 2009-09-09 15:24:55Z 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;
20
21# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
22# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
23# arping fping bind9-host arpwatch
24
25my $KLASK_VAR      = '/var/cache/klask';
26my $KLASK_CFG_FILE = '/etc/klask.conf';
27my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
28my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
29
30test_running_environnement();
31
32my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
33
34my %DEFAULT = %{ $KLASK_CFG->{default} };
35my @SWITCH  = @{ $KLASK_CFG->{switch}  };
36
37my %switch_level = ();
38my %SWITCH_DB    = ();
39LEVEL_OF_EACH_SWITCH:
40for my $sw (@SWITCH){
41   $switch_level{$sw->{hostname}} = $sw->{level} || $DEFAULT{switch_level}  || 2;
42   $SWITCH_DB{$sw->{hostname}} = $sw;
43   }
44@SWITCH = reverse sort { $switch_level{$a->{hostname}} <=> $switch_level{$b->{hostname}} } @{$KLASK_CFG->{switch}};
45
46my %SWITCH_PORT_COUNT = ();
47
48my %CMD_DB = (
49   help       => \&cmd_help,
50   version    => \&cmd_version,
51   exportdb   => \&cmd_exportdb,
52   updatedb   => \&cmd_updatedb,
53   searchdb   => \&cmd_searchdb,
54   removedb   => \&cmd_removedb,
55   search     => \&cmd_search,
56   enable     => \&cmd_enable,
57   disable    => \&cmd_disable,
58   status     => \&cmd_status,
59   updatesw   => \&cmd_updatesw,
60   exportsw   => \&cmd_exportsw,
61   iplocation => \&cmd_iplocation,
62   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
63   );
64
65Readonly my %INTERNAL_PORT_MAP => (
66   0 => 'A',
67   1 => 'B',
68   2 => 'C',
69   3 => 'D',
70   4 => 'E',
71   5 => 'F',
72   6 => 'G',
73   7 => 'H',
74   );
75Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
76
77Readonly my %SWITCH_KIND => (
78   J3299A => { model => 'HP224M',     match => 'HP J3299A ProCurve Switch 224M'  },
79   J4120A => { model => 'HP1600M',    match => 'HP J4120A ProCurve Switch 1600M' },
80   J9029A => { model => 'HP1800-8G',  match => 'PROCURVE J9029A'                 },
81   J4093A => { model => 'HP2424M',    match => 'HP J4093A ProCurve Switch 2424M' },
82   J4813A => { model => 'HP2524',     match => 'HP J4813A ProCurve Switch 2524'  },
83   J4900A => { model => 'HP2626A',    match => 'HP J4900A ProCurve Switch 2626'  },
84   J4900B => { model => 'HP2626B',    match => 'J4900B.+?Switch 2626'            },# ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
85   J4899B => { model => 'HP2650',     match => 'ProCurve J4899B Switch 2650'     },
86   J9021A => { model => 'HP2810-24G', match => 'ProCurve J9021A Switch 2810-24G' },
87   J9022A => { model => 'HP2810-48G', match => 'ProCurve J9022A Switch 2810-48G' },
88   J4903A => { model => 'HP2824',     match => 'J4903A.+?Switch 2824,'           },
89   J4110A => { model => 'HP8000M',    match => 'HP J4110A ProCurve Switch 8000M' },
90   BS350T => { model => 'BS350T',     match => 'BayStack 350T HW'                },
91   );
92
93Readonly my %OID_NUMBER => (
94   sysDescription  => '1.3.6.1.2.1.1.1.0',
95   sysName         => '1.3.6.1.2.1.1.5.0',
96   sysContact      => '1.3.6.1.2.1.1.4.0',
97   sysLocation     => '1.3.6.1.2.1.1.6.0',
98   );
99
100Readonly 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;
101Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
102
103
104################
105# principal
106################
107
108my $cmd = shift @ARGV || 'help';
109if (defined $CMD_DB{$cmd}) {
110   $CMD_DB{$cmd}->(@ARGV);
111   }
112else {
113   print {*STDERR} "klask: command $cmd not found\n\n";
114   $CMD_DB{help}->();
115   exit 1;
116   }
117
118exit;
119
120sub test_running_environnement {
121   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
122   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
123   return;
124   }
125
126sub test_switchdb_environnement {
127   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
128   return;
129   }
130
131sub test_maindb_environnement {
132   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
133   return;
134   }
135
136###
137# fast ping dont l'objectif est de remplir la table arp de la machine
138sub fastping {
139   system "fping -c 1 @_ >/dev/null 2>&1";
140   return;
141   }
142
143sub shell_command {
144   my $cmd = shift;
145
146   my $fh     = new FileHandle;
147   my $result = '';
148   open $fh, q{-|}, "$cmd" or die "Can't exec $cmd\n";
149   $result .= <$fh>;
150   close $fh;
151   chomp $result;
152   return $result;
153   }
154
155###
156# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
157sub resolve_ip_arp_host {
158   my $param_ip_or_host = shift;
159   my $interface = shift || q{*};
160   my $type      = shift || q{fast};
161
162   my %ret = (
163      hostname_fq  => 'unknow',
164      ipv4_address => '0.0.0.0',
165      mac_address  => 'unknow',
166      );
167
168#   my $cmdarping  = `arping -c 1 -w 1 -rR $param 2>/dev/null`;
169   if ( not $param_ip_or_host =~ m/^\d+ \. \d+ \. \d+ \. \d+$/xms ) {
170      $param_ip_or_host =~ s/ \. .* //xms;
171      }
172
173   # controler que arpwatch tourne !
174   # resultat de la commande arpwatch
175   # /var/lib/arpwatch/arp.dat
176   # 0:13:d3:e1:92:d0        192.168.24.109  1163681980      theo8sv109
177   # my $cmd = "grep  -e '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/arp.dat | sort +2rn | head -1";
178   # my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/*.dat | sort +2rn | head -1";
179   my $cmd = q{grep  -he '\b} . $param_ip_or_host . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
180   my $cmd_arpwatch = shell_command $cmd;
181   my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
182
183   $ret{ipv4_address} = $ip        if $ip;
184   $ret{mac_address}  = $arp       if $arp;
185   $ret{timestamp}    = $timestamp if $timestamp;
186
187   my $nowtimestamp = time;
188
189   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 3 * 3600 ) ) ) {
190      $ret{mac_address} = 'unknow';
191      return %ret;
192      }
193
194   # resultat de la commande arp
195   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
196   # sw2-batF0-legi.hmg.priv (192.168.22.112) at 00:30:c1:76:9c:01 [ether] on eth0.37
197   my $cmd_arp  = shell_command "arp -a $param_ip_or_host";
198   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
199      ( $ret{hostname_fq}, $ret{ipv4_address}, $ret{mac_address} )  = ($1, $2, $3);
200      }
201
202   # resultat de la commande host si le parametre est ip
203   # 250.66.254.194.in-addr.arpa domain name pointer legihp2100.hmg.inpg.fr.
204   my $cmd_host = shell_command "host $param_ip_or_host";
205   if ( $cmd_host =~ m/domain \s name \s pointer \s (\S+) \.$/xms ) {
206      $ret{hostname_fq} = $1;
207      }
208
209   # resultat de la commande host si parametre est hostname
210   # tech7meylan.hmg.inpg.fr has address 194.254.66.240
211   if ( $cmd_host =~ m/(\S*) \s has \s address \s ( $RE_IPv4_ADDRESS )$/xms ) {
212      ( $ret{hostname_fq}, $ret{ipv4_address} ) = ($1, $2);
213      }
214
215   # Connerie !
216   # Inverse les IP !!
217   # if ( $cmd_host =~ m/ \b ( $RE_IPv4_ADDRESS ) \. in-addr \. arpa \s/xms ) {
218   #    $ret{ipv4_address} = $1;
219   #    }
220   #$ret{hostname_fq}  = $param_ip_or_host if not defined $1 and $ret{hostname_fq} eq 'unknow';
221
222   if ($ret{mac_address} ne 'unknow') {
223      my @paquets = ();
224      foreach ( split m/ : /xms, $ret{mac_address} ) {
225         my @chars = split m//xms, uc "00$_";
226         push @paquets, "$chars[-2]$chars[-1]";
227         }
228      $ret{mac_address} = join q{:}, @paquets;
229      }
230
231   return %ret;
232   }
233
234# Find Surname of a switch
235sub get_switch_model {
236   my $sw_snmp_description = shift || 'unknow';
237
238   for my $sw_kind (keys %SWITCH_KIND) {
239      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/ms; # option xms break search, why ?
240
241      return $SWITCH_KIND{$sw_kind}->{model};
242      }
243
244   return $sw_snmp_description;
245   }
246
247###
248# va rechercher le nom des switchs pour savoir qui est qui
249sub init_switch_names {
250   my $verbose = shift;
251
252   printf "%-25s                %-25s %s\n",'Switch','Description','Type';
253   print "-------------------------------------------------------------------------\n" if $verbose;
254
255   INIT_EACH_SWITCH:
256   for my $sw (@SWITCH) {
257      my %session = ( -hostname   => $sw->{hostname} );
258         $session{-version} = $sw->{version}   || 1;
259         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
260         if (exists $sw->{version} and $sw->{version} eq '3') {
261            $session{-username} = $sw->{username} || 'snmpadmin';
262            }
263         else {
264            $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
265            }
266
267      $sw->{local_session} = \%session;
268
269      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
270      print "$error \n" if $error;
271
272      my $result = $session->get_request(
273         -varbindlist => [
274            $OID_NUMBER{sysDescription},
275            $OID_NUMBER{sysName},
276            $OID_NUMBER{sysContact},
277            $OID_NUMBER{sysLocation},
278            ]
279         );
280      $sw->{description} = $result->{$OID_NUMBER{sysName}} || $sw->{hostname};
281      $sw->{model} = get_switch_model( $result->{$OID_NUMBER{sysDescription}});
282      #$sw->{location} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{hostname};
283      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{hostname};
284      $session->close;
285
286      # Ligne à virer car on récupère maintenant le modèle du switch
287      my ($desc, $type) = split m/ : /xms, $sw->{description}, 2;
288      printf "%-25s 0--------->>>> %-25s %s\n", $sw->{hostname}, $desc, $sw->{model} if $verbose;
289      }
290
291   print "\n" if $verbose;
292   return;
293   }
294
295###
296# convertit l'hexa (uniquement 2 chiffres) en decimal
297sub hex_to_dec {
298   #00:0F:1F:43:E4:2B
299   my $car = '00' . uc shift;
300
301   return '00' if $car eq '00UNKNOW';
302   my %table = (
303      '0'=>'0',  '1'=>'1',  '2'=>'2',  '3'=>'3',  '4'=>'4',
304      '5'=>'5',  '6'=>'6',  '7'=>'7',  '8'=>'8',  '9'=>'9',
305      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15',
306      );
307   my @chars = split m//xms, $car;
308   return $table{$chars[-2]}*16 + $table{$chars[-1]};
309   }
310
311###
312# convertit l'@ arp en decimal
313sub arp_hex_to_dec {
314   #00:0F:1F:43:E4:2B
315   my $arp = shift;
316
317   my @paquets = split m/ : /xms, $arp;
318   my $return = q{};
319   foreach(@paquets) {
320      $return .= q{.} . hex_to_dec($_);
321      }
322   return $return;
323   }
324
325###
326# va rechercher le port et le switch sur lequel est la machine
327sub find_switch_port {
328   my $arp             = shift;
329   my $switch_proposal = shift || q{};
330
331   my %ret;
332   $ret{switch_description} = 'unknow';
333   $ret{switch_port} = '0';
334
335   return %ret if $arp eq 'unknow';;
336
337   my @switch_search = @SWITCH;
338   if ($switch_proposal ne q{}) {
339      for my $sw (@SWITCH) {
340         next if $sw->{hostname} ne $switch_proposal;
341         unshift @switch_search, $sw;
342         last;
343         }
344      }
345
346   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($arp);
347
348   LOOP_ON_SWITCH:
349   for my $sw (@switch_search) {
350      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
351      print "$error \n" if $error;
352
353      my $result = $session->get_request(
354         -varbindlist => [$research]
355         );
356      if (not defined $result or $result->{$research} eq 'noSuchInstance') {
357         $session->close;
358         next LOOP_ON_SWITCH;
359         }
360
361         my $swport = $result->{$research};
362         $session->close;
363
364         # IMPORTANT !!
365         # ceci empeche la detection sur certains port ...
366         # en effet les switch sont relies entre eux par un cable reseau et du coup
367         # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
368         # cette partie est a ameliore, voir a configurer dans l'entete
369         # 21->24 45->48
370#         my $flag = 0;
371         SWITCH_PORT_IGNORE:
372         foreach my $p (@{$sw->{portignore}}) {
373            next SWITCH_PORT_IGNORE if $swport ne get_numerical_port($sw->{model},$p);
374#            $flag = 1;
375            next LOOP_ON_SWITCH;
376            }
377#         if ($flag == 0) {
378            $ret{switch_hostname}    = $sw->{hostname};
379            $ret{switch_description} = $sw->{description};
380            $ret{switch_port}        = get_human_readable_port($sw->{model}, $swport); # $swport;
381
382            last LOOP_ON_SWITCH;
383#            }
384#         }
385#      $session->close;
386      }
387   return %ret;
388   }
389
390###
391# va rechercher les port et les switch sur lequel est la machine
392sub find_all_switch_port {
393   my $arp = shift;
394
395   my $ret = {};
396
397   return $ret if $arp eq 'unknow';
398
399   for my $sw (@SWITCH) {
400      $SWITCH_PORT_COUNT{$sw->{hostname}} = {} if not exists $SWITCH_PORT_COUNT{$sw->{hostname}};
401      }
402
403   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($arp);
404   LOOP_ON_ALL_SWITCH:
405   for my $sw (@SWITCH) {
406      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
407      print "$error \n" if $error;
408
409      my $result = $session->get_request(
410         -varbindlist => [$research]
411         );
412
413      if(defined $result and $result->{$research} ne 'noSuchInstance'){
414         my $swport = $result->{$research};
415
416         $ret->{$sw->{hostname}} = {};
417         $ret->{$sw->{hostname}}{hostname}    = $sw->{hostname};
418         $ret->{$sw->{hostname}}{description} = $sw->{description};
419         $ret->{$sw->{hostname}}{port}        = get_human_readable_port($sw->{model}, $swport);
420
421         $SWITCH_PORT_COUNT{$sw->{hostname}}->{$swport}++;
422         }
423
424      $session->close;
425      }
426   return $ret;
427   }
428
429sub get_list_network {
430
431   return keys %{$KLASK_CFG->{network}};
432   }
433
434sub get_current_interface {
435   my $network = shift;
436
437   return $KLASK_CFG->{network}{$network}{interface};
438   }
439
440###
441# liste l'ensemble des adresses ip d'un réseau
442sub get_list_ip {
443   my @network = @_;
444
445   my $cidrlist = Net::CIDR::Lite->new;
446
447   for my $net (@network) {
448      my @line  = @{$KLASK_CFG->{network}{$net}{'ip-subnet'}};
449      for my $cmd (@line) {
450         for my $method (keys %{$cmd}){
451            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
452            }
453         }
454      }
455
456   my @res = ();
457
458   for my $cidr ($cidrlist->list()) {
459      my $net = new NetAddr::IP $cidr;
460      for my $ip (@{$net}) {
461         $ip =~ s{ /32 }{}xms;
462         push @res,  $ip;
463         }
464      }
465
466   return @res;
467   }
468
469# liste l'ensemble des routeurs du réseau
470sub get_list_main_router {
471   my @network = @_;
472
473   my @res = ();
474
475   for my $net (@network) {
476      push @res, $KLASK_CFG->{network}{$net}{'main-router'};
477      }
478
479   return @res;
480   }
481
482sub get_human_readable_port {
483   my $sw_model = shift;
484   my $sw_port  = shift;
485
486   if ($sw_model eq 'HP8000M') {
487
488      my $reste = (($sw_port - 1) % 8) + 1;
489      my $major = int (($sw_port - 1) / 8);
490      return "$INTERNAL_PORT_MAP{$major}$reste";
491      }
492
493   if ($sw_model eq 'HP2424M') {
494      if ($sw_port > 24) {
495         
496         my $reste = $sw_port - 24;
497         return "A$reste";
498         }
499      }
500
501   if ($sw_model eq 'HP1600M') {
502      if ($sw_port > 16) {
503         
504         my $reste = $sw_port - 16;
505         return "A$reste";
506         }
507      }
508
509   return $sw_port;
510   }
511
512sub get_numerical_port {
513   my $sw_model = shift;
514   my $sw_port  = shift;
515
516   if ($sw_model eq 'HP8000M') {
517
518      my $letter = substr $sw_port, 0, 1;
519      my $reste =  substr $sw_port, 1;
520
521      return $INTERNAL_PORT_MAP_REV{$letter} * 8 + $reste;
522      }
523
524   if ($sw_model eq 'HP2424M') {
525      if ($sw_port =~ m/^A/xms ) {
526
527         my $reste =  substr $sw_port, 1;
528
529         return 24 + $reste;
530         }
531      }
532
533   if ($sw_model eq 'HP1600M') {
534      if ($sw_port =~ m/^A/xms ) {
535
536         my $reste =  substr $sw_port, 1;
537
538         return 16 + $reste;
539         }
540      }
541
542   return $sw_port;
543   }
544
545################
546# Les commandes
547################
548
549sub cmd_help {
550
551print <<'END';
552klask - ports manager and finder for switch
553
554 klask updatedb
555 klask exportdb
556
557 klask updatesw
558 klask exportsw
559
560 klask searchdb computer
561 klask search   computer
562
563 klask enable  switch port
564 klask disable switch port
565 klask status  switch port
566END
567   return;
568   }
569
570sub cmd_version {
571
572print <<'END';
573Klask - ports manager and finder for switch
574Copyright (C) 2005-2008 Gabriel Moreau
575
576END
577   print ' $Rev: 66 $'."\n";
578   print ' $Date: 2009-09-09 15:24:55 +0000 (Wed, 09 Sep 2009) $'."\n";
579   print ' $Id: klask 66 2009-09-09 15:24:55Z g7moreau $'."\n";
580   return;
581   }
582
583sub cmd_search {
584   my @computer = @_;
585
586   init_switch_names();    #nomme les switchs
587   fastping(@computer);
588   for my $clientname (@computer) {
589      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
590      my %where     = find_switch_port($resol_arp{mac_address}); #retrouve l'emplacement
591      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"
592         unless $where{switch_description} eq 'unknow' and $resol_arp{hostname_fq} eq 'unknow' and $resol_arp{mac_address} eq 'unknow';
593      }
594   return;
595   }
596
597sub cmd_searchdb {
598   my @computer = @_;
599
600   fastping(@computer);
601   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
602
603   LOOP_ON_COMPUTER:
604   for my $clientname (@computer) {
605      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
606      my $ip = $resol_arp{ipv4_address};
607
608      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
609
610      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
611      $year += 1900;
612      $mon++;
613      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
614
615      printf "%-22s %2s %-30s %-15s %-18s %s\n",
616         $computerdb->{$ip}{switch_name},
617         $computerdb->{$ip}{switch_port},
618         $computerdb->{$ip}{hostname_fq},
619         $ip,
620         $computerdb->{$ip}{mac_address},
621         $date;
622      }
623   return;
624   }
625
626sub cmd_updatedb {
627   my @network = @_;
628      @network = get_list_network() if not @network;
629
630   test_switchdb_environnement();
631
632   my $computerdb = {};
633      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
634   my $timestamp = time;
635
636   my %computer_not_detected = ();
637   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
638
639   my $number_of_computer = get_list_ip(@network); # + 1;
640   my $size_of_database   = keys %{$computerdb};
641      $size_of_database   = 1 if $size_of_database == 0;
642   my $i = 0;
643   my $detected_computer = 0;
644
645   init_switch_names('yes');    #nomme les switchs
646
647   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
648   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
649   my %db_switch_output_port       = %{$switch_connection->{output_port}};
650   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
651   my %db_switch_chained_port = ();
652   for my $swport (keys %db_switch_connected_on_port) {
653      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
654      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
655      }
656   for my $sw (@SWITCH){
657      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{hostname}}  if exists $db_switch_output_port{$sw->{hostname}};
658      if ( exists $db_switch_chained_port{$sw->{hostname}} ) {
659         chop $db_switch_chained_port{$sw->{hostname}};
660         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{hostname}};
661         }
662#      print "$sw->{hostname} ++ @{$sw->{portignore}}\n";
663      }
664   }
665
666   my %router_mac_ip = ();
667   DETECT_ALL_ROUTER:
668#   for my $one_router ('194.254.66.254') {
669   for my $one_router ( get_list_main_router(@network) ) {
670      my %resol_arp = resolve_ip_arp_host($one_router);
671      $router_mac_ip{ $resol_arp{mac_address} } = $resol_arp{ipv4_address};
672      }
673
674   ALL_NETWORK:
675   for my $net (@network) {
676
677      my @computer = get_list_ip($net);
678      my $current_interface = get_current_interface($net);
679
680      fastping(@computer);
681
682      LOOP_ON_COMPUTER:
683      for my $one_computer (@computer) {
684         $i++;
685
686         my $total_percent = int (($i*100)/$number_of_computer);
687
688         my $localtime = time - $timestamp;
689         my ($sec,$min) = localtime $localtime;
690
691         my $time_elapse = 0;
692            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
693         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
694
695         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
696#         printf ", Computer detected: %4i/%i (%2i%%)", $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
697         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
698         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
699#         printf '  [%02i:%02i/%02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
700         printf ' %-14s', $one_computer;
701
702         my %resol_arp = resolve_ip_arp_host($one_computer,$current_interface);
703
704         # do not search on router connection (why ?)
705         if ( exists $router_mac_ip{$resol_arp{mac_address}}) {
706            $computer_not_detected{$one_computer} = $current_interface;
707            next LOOP_ON_COMPUTER;
708            }
709
710         # do not search on switch inter-connection
711         if (exists $switch_level{$resol_arp{hostname_fq}}) {
712            $computer_not_detected{$one_computer} = $current_interface;
713            next LOOP_ON_COMPUTER;
714            }
715
716         my $switch_proposal = q{};
717         if (exists $computerdb->{$resol_arp{ipv4_address}} and exists $computerdb->{$resol_arp{ipv4_address}}{switch_hostname}) {
718            $switch_proposal = $computerdb->{$resol_arp{ipv4_address}}{switch_hostname};
719            }
720
721         # do not have a mac address
722         if ($resol_arp{mac_address} eq 'unknow' or (exists $resol_arp{timestamps} and $resol_arp{timestamps} < ($timestamp - 3 * 3600))) {
723            $computer_not_detected{$one_computer} = $current_interface;
724            next LOOP_ON_COMPUTER;
725            }
726
727         my %where = find_switch_port($resol_arp{mac_address},$switch_proposal);
728
729         #192.168.24.156:
730         #  arp: 00:0B:DB:D5:F6:65
731         #  hostname: pcroyon.hmg.priv
732         #  port: 5
733         #  switch: sw-batH-legi:hp2524
734         #  timestamp: 1164355525
735
736         # do not have a mac address
737#         if ($resol_arp{mac_address} eq 'unknow') {
738#            $computer_not_detected{$one_computer} = $current_interface;
739#            next LOOP_ON_COMPUTER;
740#            }
741
742         # detected on a switch
743         if ($where{switch_description} ne 'unknow') {
744            $detected_computer++;
745            $computerdb->{$resol_arp{ipv4_address}} = {
746               hostname_fq        => $resol_arp{hostname_fq},
747               mac_address        => $resol_arp{mac_address},
748               switch_hostname    => $where{switch_hostname},
749               switch_description => $where{switch_description},
750               switch_port        => $where{switch_port},
751               timestamp          => $timestamp,
752               network            => $net,
753               };
754            next LOOP_ON_COMPUTER;
755            }
756
757         # new in the database but where it is ?
758         if (not exists $computerdb->{$resol_arp{ipv4_address}}) {
759            $detected_computer++;
760            $computerdb->{$resol_arp{ipv4_address}} = {
761               hostname_fq        => $resol_arp{hostname_fq},
762               mac_address        => $resol_arp{mac_address},
763               switch_hostname    => $where{switch_hostname},
764               switch_description => $where{switch_description},
765               switch_port        => $where{switch_port},
766               timestamp          => $resol_arp{timestamp},
767               network            => $net,
768               };
769            }
770
771         # mise a jour du nom de la machine si modification dans le dns
772         $computerdb->{$resol_arp{ipv4_address}}{hostname_fq} = $resol_arp{hostname_fq};
773
774         # mise à jour de la date de détection si détection plus récente par arpwatch
775         $computerdb->{$resol_arp{ipv4_address}}{timestamp}   = $resol_arp{timestamp} if exists $resol_arp{timestamp} and $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $resol_arp{timestamp};
776
777         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
778#         push @computer_not_detected, $resol_arp{ipv4_address} if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
779         $computer_not_detected{$resol_arp{ipv4_address}} = $current_interface if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
780
781         }
782      }
783
784   # final end of line at the end of the loop
785   printf "\n";
786
787   my $dirdb = $KLASK_DB_FILE;
788      $dirdb =~ s{ / [^/]* $}{}xms;
789   mkdir "$dirdb", 0755 unless -d "$dirdb";
790   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
791
792   for my $one_computer (keys %computer_not_detected) {
793      my $interface = $computer_not_detected{$one_computer};
794      system "arping -c 1 -w 1 -rR -i $interface $one_computer &>/dev/null";
795#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
796      }
797   return;
798   }
799
800sub cmd_removedb {
801   my @computer = @_;
802
803   test_maindb_environnement();
804
805   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
806
807   LOOP_ON_COMPUTER:
808   for my $one_computer (@computer) {
809
810      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
811            and exists $computerdb->{$one_computer} ) {
812         delete $computerdb->{$one_computer};
813         next;
814         }
815
816      my %resol_arp = resolve_ip_arp_host($one_computer);
817
818      delete $computerdb->{$resol_arp{ipv4_address}} if exists $computerdb->{$resol_arp{ipv4_address}};
819      }
820
821   my $dirdb = $KLASK_DB_FILE;
822      $dirdb =~ s{ / [^/]* $}{}xms;
823   mkdir "$dirdb", 0755 unless -d "$dirdb";
824   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
825   return;
826   }
827
828sub cmd_exportdb {
829   my @ARGV   = @_;
830
831   my $format = 'txt';
832
833   my $ret = GetOptions(
834      'format|f=s'  => \$format,
835      );
836
837   my %possible_format = (
838      txt  => \&cmd_exportdb_txt,
839      html => \&cmd_exportdb_html,
840      );
841
842   $format = 'txt' if not defined $possible_format{$format};
843
844   $possible_format{$format}->(@ARGV);
845   return;
846   }
847
848sub cmd_exportdb_txt {
849   test_maindb_environnement();
850
851   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
852
853   printf "%-24s %-4s            %-30s %-15s %-18s %-s\n", qw(Switch Port Hostname IPv4-Address MAC-Address Date);
854   print "---------------------------------------------------------------------------------------------------------------------------\n";
855
856   LOOP_ON_IP_ADDRESS:
857   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
858
859#      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq 'unknow';
860
861      # to be improve in the future
862      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
863
864# dans le futur
865#      next if $computerdb->{$ip}{hostname_fq} eq 'unknow';
866
867      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
868      $year += 1900;
869      $mon++;
870      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
871
872      printf "%-25s  %2s  <-------  %-30s %-15s %-18s %s\n",
873         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
874         $computerdb->{$ip}{switch_port},
875         $computerdb->{$ip}{hostname_fq},
876         $ip,
877         $computerdb->{$ip}{mac_address},
878         $date;
879      }
880   return;
881   }
882
883sub cmd_exportdb_html {
884   test_maindb_environnement();
885
886   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
887
888#<link rel="stylesheet" type="text/css" href="style-klask.css" />
889#<script src="sorttable-klask.js"></script>
890
891   print <<'END_HTML';
892<table class="sortable" summary="Klask export database">
893 <caption>Klask database</caption>
894 <thead>
895  <tr>
896   <th scope="col" class="hklask-switch">Switch</th>
897   <th scope="col" class="sorttable_nosort">Port</th>
898   <th scope="col" class="sorttable_nosort">Link</th>
899   <th scope="col" class="sorttable_alpha">Hostname</th>
900   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
901   <th scope="col" class="hklask-mac">MAC-Address</th>
902   <th scope="col" class="hklask-date">Date</th>
903  </tr>
904 </thead>
905 <tfoot>
906  <tr>
907   <th scope="col" class="fklask-switch">Switch</th>
908   <th scope="col" class="fklask-port">Port</th>
909   <th scope="col" class="fklask-link">Link</th>
910   <th scope="col" class="fklask-hostname">Hostname</th>
911   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
912   <th scope="col" class="fklask-mac">MAC-Address</th>
913   <th scope="col" class="fklask-date">Date</th>
914  </tr>
915 </tfoot>
916 <tbody>
917END_HTML
918
919   my %mac_count = ();
920   LOOP_ON_IP_ADDRESS:
921   foreach my $ip (keys %{$computerdb}) {
922
923      # to be improve in the future
924      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
925
926      $mac_count{$computerdb->{$ip}{mac_address}}++;
927      }
928
929   my $typerow = 'even';
930
931   LOOP_ON_IP_ADDRESS:
932   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
933
934      # to be improve in the future
935      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
936
937      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
938      $year += 1900;
939      $mon++;
940      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
941
942#      $odd_or_even++;
943#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
944      $typerow = $typerow eq 'even' ? 'odd' : 'even';
945
946      my $switch_hostname = $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description} || 'unkown';
947      chomp $switch_hostname;
948      my $switch_hostname_sort = sprintf '%s %3s' ,$switch_hostname, $computerdb->{$ip}{switch_port};
949
950      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
951
952      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{mac_address}}, $computerdb->{$ip}{mac_address};
953
954      $computerdb->{$ip}{hostname_fq} = 'unknow' if $computerdb->{$ip}{hostname_fq} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
955      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{hostname_fq};
956
957      print <<"END_HTML";
958  <tr class="$typerow">
959   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
960   <td class="bklask-port">$computerdb->{$ip}{switch_port}</td>
961   <td><-------</td>
962   <td sorttable_customkey="$host_short">$computerdb->{$ip}{hostname_fq}</td>
963   <td sorttable_customkey="$ip_sort">$ip</td>
964   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{mac_address}</td>
965   <td>$date</td>
966  </tr>
967END_HTML
968      }
969
970   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
971
972   my %db_switch_output_port       = %{$switch_connection->{output_port}};
973   my %db_switch_parent            = %{$switch_connection->{parent}};
974   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
975   my %db_switch                   = %{$switch_connection->{switch_db}};
976
977   for my $sw (sort keys %db_switch_output_port) {
978
979      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
980
981      $typerow = $typerow eq 'even' ? 'odd' : 'even';
982
983      if (exists $db_switch_parent{$sw}) {
984
985      my $mac_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{mac_address};
986      my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{ipv4_address};
987      my $timestamp = $db_switch{$db_switch_parent{$sw}->{switch}}->{timestamp};
988
989      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
990      $year += 1900;
991      $mon++;
992      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
993
994      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
995
996      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
997
998      my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{switch}, 1), $db_switch_parent{$sw}->{port};
999
1000      print <<"END_HTML";
1001  <tr class="$typerow">
1002   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1003   <td class="bklask-port">$db_switch_output_port{$sw}</>
1004   <td>+--> $db_switch_parent{$sw}->{port}</td>
1005   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{switch}</>
1006   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1007   <td sorttable_customkey="$mac_sort">$mac_address</td>
1008   <td>$date</td>
1009  </tr>
1010END_HTML
1011         }
1012      else {
1013         print <<"END_HTML";
1014  <tr class="$typerow">
1015   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1016   <td class="bklask-port">$db_switch_output_port{$sw}</>
1017   <td>+--></td>
1018   <td sorttable_customkey="router">router</>
1019   <td sorttable_customkey="999999999999"></td>
1020   <td sorttable_customkey="99999"></td>
1021   <td></td>
1022  </tr>
1023END_HTML
1024         }
1025      }
1026
1027   for my $swport (sort keys %db_switch_connected_on_port) {
1028      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1029      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1030
1031         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1032
1033      my $mac_address = $db_switch{$sw}->{mac_address};
1034      my $ipv4_address = $db_switch{$sw}->{ipv4_address};
1035      my $timestamp = $db_switch{$sw}->{timestamp};
1036
1037      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1038      $year += 1900;
1039      $mon++;
1040      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1041
1042      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1043
1044      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1045
1046      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1047
1048         if (exists $db_switch_output_port{$sw}) {
1049
1050            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1051
1052            print <<"END_HTML";
1053  <tr class="$typerow">
1054   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1055   <td class="bklask-port">$port_connect</>
1056   <td>&lt;--+ $db_switch_output_port{$sw}</td>
1057   <td sorttable_customkey="$host_short">$sw</>
1058   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1059   <td sorttable_customkey="$mac_sort">$mac_address</td>
1060   <td>$date</td>
1061  </tr>
1062END_HTML
1063            }
1064         else {
1065            print <<"END_HTML";
1066  <tr class="$typerow">
1067   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1068   <td class="bklask-port">$port_connect</>
1069   <td>&lt;--+</td>
1070   <td sorttable_customkey="$sw">$sw</>
1071   <td sorttable_customkey="">$ipv4_address</td>
1072   <td sorttable_customkey="">$mac_address</td>
1073   <td>$date</td>
1074  </tr>
1075END_HTML
1076            }
1077         }
1078      }
1079
1080   print <<'END_HTML';
1081 </tbody>
1082</table>
1083END_HTML
1084   return;
1085   }
1086
1087sub cmd_iplocation {
1088   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1089
1090   LOOP_ON_IP_ADDRESS:
1091   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1092
1093      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1094
1095      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1096      next if $sw_hostname eq 'unknow';
1097
1098      my $sw_location = q{};
1099      for my $sw (@SWITCH) {
1100         next if $sw_hostname ne $sw->{hostname};
1101         $sw_location = $sw->{location};
1102         last;
1103         }
1104
1105      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1106      }
1107   return;
1108   }
1109
1110sub cmd_enable {
1111   my $switch = shift;
1112   my $port   = shift;
1113
1114   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
1115   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
1116   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
1117   return;
1118   }
1119
1120sub cmd_disable {
1121   my $switch = shift;
1122   my $port   = shift;
1123
1124   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
1125   return;
1126   }
1127
1128sub cmd_status {
1129   my $switch = shift;
1130   my $port   = shift;
1131
1132   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
1133   return;
1134   }
1135
1136sub cmd_search_mac_on_switch {
1137   my $switch_name = shift || q{};
1138   my $mac_address = shift || q{};
1139
1140   if ($switch_name eq q{} or $mac_address eq q{}) {
1141      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1142      }
1143
1144   if (not defined $SWITCH_DB{$switch_name}) {
1145      die "Switch $switch_name must be defined in klask configuration file\n";
1146      }
1147
1148   my $sw = $SWITCH_DB{$switch_name};
1149   my %session = ( -hostname => $sw->{hostname} );
1150      $session{-version} = $sw->{version}   || 1;
1151      $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1152   if (exists $sw->{version} and $sw->{version} eq '3') {
1153      $session{-username} = $sw->{username} || 'snmpadmin';
1154      }
1155   else {
1156      $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1157      }
1158
1159   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($mac_address);
1160   print "Klask search OID $research on switch $switch_name\n";
1161
1162   my ($session, $error) = Net::SNMP->session( %session );
1163   print "$error \n" if $error;
1164
1165   my $result = $session->get_request(
1166      -varbindlist => [$research]
1167      );
1168
1169   if (not defined $result or $result->{$research} eq 'noSuchInstance') {
1170      print "Klask do not find MAC $mac_address on switch $switch_name\n";
1171      $session->close;
1172      }
1173
1174   my $swport = $result->{$research};
1175   $session->close;
1176
1177   print "Klask find MAC $mac_address on switch $switch_name port $swport\n";
1178   return;
1179   }
1180
1181sub cmd_updatesw {
1182
1183   init_switch_names('yes');    #nomme les switchs
1184   print "\n";
1185
1186   my %where = ();
1187   my %db_switch_output_port = ();
1188   my %db_switch_ip_hostname = ();
1189
1190   DETECT_ALL_ROUTER:
1191#   for my $one_computer ('194.254.66.254') {
1192   for my $one_router ( get_list_main_router(get_list_network()) ) {
1193      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1194      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1195      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1196      }
1197
1198   ALL_ROUTER_IP_ADDRESS:
1199   for my $ip (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1200
1201      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip}; # /a priori/ idiot car ne sers à rien...
1202
1203      ALL_SWITCH_CONNECTED:
1204      for my $switch_detected ( keys %{$where{$ip}} ) {
1205
1206         my $switch = $where{$ip}->{$switch_detected};
1207
1208         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
1209
1210         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1211         }
1212      }
1213
1214   my %db_switch_link_with = ();
1215
1216   my @list_switch_ip = ();
1217   my @list_switch_ipv4 = ();
1218   for my $sw (@SWITCH){
1219      push @list_switch_ip, $sw->{hostname};
1220      }
1221
1222   my $timestamp = time;
1223
1224   ALL_SWITCH:
1225   for my $one_computer (@list_switch_ip) {
1226      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1227      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1228
1229      push @list_switch_ipv4,$resol_arp{ipv4_address};
1230
1231      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
1232
1233      $db_switch_ip_hostname{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
1234
1235      $SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
1236      $SWITCH_DB{$one_computer}->{mac_address}  = $resol_arp{mac_address};
1237      $SWITCH_DB{$one_computer}->{timestamp}    = $timestamp;
1238      }
1239
1240   ALL_SWITCH_IP_ADDRESS:
1241   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1242
1243      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1244
1245      DETECTED_SWITCH:
1246      for my $switch_detected ( keys %{$where{$ip}} ) {
1247
1248         next DETECTED_SWITCH if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostname{$ip}};
1249
1250         my $switch = $where{$ip}->{$switch_detected};
1251
1252         next if $switch->{port}     eq '0';
1253         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
1254         next if $switch->{hostname} eq $db_switch_ip_hostname{$ip}; # $computerdb->{$ip}{hostname};
1255
1256         $db_switch_link_with{ $db_switch_ip_hostname{$ip} } ||= {};
1257         $db_switch_link_with{ $db_switch_ip_hostname{$ip} }->{ $switch->{hostname} } = $switch->{port};
1258         }
1259
1260      }
1261
1262   my %db_switch_connected_on_port = ();
1263   my $maybe_more_than_one_switch_connected = 'yes';
1264
1265   while ($maybe_more_than_one_switch_connected eq 'yes') {
1266      for my $sw (keys %db_switch_link_with) {
1267         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1268
1269            my $port = $db_switch_link_with{$sw}->{$connect};
1270
1271            $db_switch_connected_on_port{"$connect:$port"} ||= {};
1272            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
1273            }
1274         }
1275
1276      $maybe_more_than_one_switch_connected  = 'no';
1277
1278      SWITCH_AND_PORT:
1279      for my $swport (keys %db_switch_connected_on_port) {
1280
1281         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1282
1283         $maybe_more_than_one_switch_connected = 'yes';
1284
1285         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1286         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1287
1288         CONNECTED:
1289         for my $sw_connected (@sw_on_same_port) {
1290
1291            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1292
1293            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1294
1295            for my $other_sw (@sw_on_same_port) {
1296               next if $other_sw eq $sw_connected;
1297
1298               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1299               }
1300
1301            # We can not do better for this switch for this loop
1302            next SWITCH_AND_PORT;
1303            }
1304         }
1305      }
1306
1307   my %db_switch_parent =();
1308
1309   for my $sw (keys %db_switch_link_with) {
1310      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1311
1312         my $port = $db_switch_link_with{$sw}->{$connect};
1313
1314         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1315         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1316
1317         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1318         }
1319      }
1320
1321   print "Switch output port and parent port connection\n";
1322   print "---------------------------------------------\n";
1323   for my $sw (sort keys %db_switch_output_port) {
1324      if (exists $db_switch_parent{$sw}) {
1325         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1326         }
1327      else {
1328         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1329         }
1330      }
1331   print "\n";
1332
1333   print "Switch parent and children port inter-connection\n";
1334   print "------------------------------------------------\n";
1335   for my $swport (sort keys %db_switch_connected_on_port) {
1336      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1337      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1338         if (exists $db_switch_output_port{$sw}) {
1339            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1340            }
1341         else {
1342            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1343            }
1344         }
1345      }
1346
1347   my $switch_connection = {
1348      output_port       => \%db_switch_output_port,
1349      parent            => \%db_switch_parent,
1350      connected_on_port => \%db_switch_connected_on_port,
1351      link_with         => \%db_switch_link_with,
1352      switch_db         => \%SWITCH_DB,
1353      };
1354
1355   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1356   return;
1357   }
1358
1359sub cmd_exportsw {
1360   my @ARGV   = @_;
1361
1362   test_switchdb_environnement();
1363
1364   my $format = 'txt';
1365
1366   my $ret = GetOptions(
1367      'format|f=s'  => \$format,
1368      );
1369
1370   my %possible_format = (
1371      txt => \&cmd_exportsw_txt,
1372      dot => \&cmd_exportsw_dot,
1373      );
1374
1375   $format = 'txt' if not defined $possible_format{$format};
1376
1377   $possible_format{$format}->(@ARGV);
1378   return;
1379   }
1380
1381sub cmd_exportsw_txt {
1382
1383   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1384
1385   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1386   my %db_switch_parent            = %{$switch_connection->{parent}};
1387   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1388
1389   print "Switch output port and parent port connection\n";
1390   print "---------------------------------------------\n";
1391   for my $sw (sort keys %db_switch_output_port) {
1392      if (exists $db_switch_parent{$sw}) {
1393         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1394         }
1395      else {
1396         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1397         }
1398      }
1399   print "\n";
1400
1401   print "Switch parent and children port inter-connection\n";
1402   print "------------------------------------------------\n";
1403   for my $swport (sort keys %db_switch_connected_on_port) {
1404      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1405      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1406         if (exists $db_switch_output_port{$sw}) {
1407            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1408            }
1409         else {
1410            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1411            }
1412         }
1413      }
1414   return;
1415   }
1416
1417sub cmd_exportsw_dot {
1418
1419   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1420
1421   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1422   my %db_switch_parent            = %{$switch_connection->{parent}};
1423   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1424   my %db_switch_link_with         = %{$switch_connection->{link_with}};
1425   my %db_switch_global            = %{$switch_connection->{switch_db}};
1426
1427   my %db_building= ();
1428   for my $sw (@SWITCH) {
1429      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
1430      $db_building{$building} ||= {};
1431      $db_building{$building}->{$location} ||= {};
1432      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1433      }
1434
1435
1436   print "digraph G {\n";
1437
1438   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
1439   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
1440
1441   my $b=0;
1442   for my $building (keys %db_building) {
1443      $b++;
1444
1445      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1446      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
1447
1448      my $l = 0;
1449      for my $loc (keys %{$db_building{$building}}) {
1450         $l++;
1451
1452         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
1453#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
1454         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
1455
1456         for my $sw (keys %{$db_building{$building}->{$loc}}) {
1457
1458            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
1459
1460            my $swname  = $sw;
1461               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
1462            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
1463            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
1464            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
1465
1466
1467            for my $swport (keys %db_switch_connected_on_port) {
1468               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1469               next if not $sw_connect eq $sw;
1470               next if $port_connect eq $db_switch_output_port{$sw};
1471               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
1472               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1473              }
1474            }
1475         }
1476      }
1477
1478#   print "Switch output port and parent port connection\n";
1479#   print "---------------------------------------------\n";
1480   for my $sw (sort keys %db_switch_output_port) {
1481      if (exists $db_switch_parent{$sw}) {
1482#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
1483         }
1484      else {
1485         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
1486         }
1487      }
1488   print "\n";
1489
1490#   print "Switch parent and children port inter-connection\n";
1491#   print "------------------------------------------------\n";
1492   for my $swport (sort keys %db_switch_connected_on_port) {
1493      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1494      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1495         if (exists $db_switch_output_port{$sw}) {
1496            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
1497            }
1498         else {
1499            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
1500            }
1501         }
1502      }
1503
1504print "}\n";
1505   return;
1506   }
1507
1508
1509__END__
1510
1511=head1 NAME
1512
1513klask - ports manager and finder for switch
1514
1515
1516=head1 USAGE
1517
1518 klask updatedb
1519 klask exportdb --format [txt|html]
1520
1521 klask updatesw
1522 klask exportsw --format [txt|dot]
1523
1524 klask searchdb computer
1525 klask search   computer
1526
1527 klask enable  switch port
1528 klask disable swith port
1529 klask status  swith port
1530
1531
1532=head1 DESCRIPTION
1533
1534klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1535
1536Klask has now a web site dedicated for it !
1537
1538 http://servforge.legi.inpg.fr/projects/klask
1539
1540
1541=head1 COMMANDS
1542
1543
1544=head2 search
1545
1546This 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.
1547
1548
1549=head2 enable
1550
1551This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1552
1553
1554=head2 disable
1555
1556This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1557
1558
1559=head2 status
1560
1561This 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.
1562
1563
1564=head2 updatedb
1565
1566This command will scan networks and update a database. To know which are the cmputer scan, you have to configure the file /etc/klask.conf This file is easy to read and write because klask use YAML format and not XML.
1567
1568
1569=head2 exportdb
1570
1571This 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...
1572
1573
1574=head2 updatesw
1575
1576This command build a map of your manageable switch on your network. The list of the switch must be given in the file /etc/klask.conf.
1577
1578
1579=head2 exportsw --format [txt|dot]
1580
1581This 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.
1582
1583 klask exportsw --format dot > /tmp/map.dot
1584 dot -Tpng /tmp/map.dot > /tmp/map.png
1585
1586
1587
1588=head1 CONFIGURATION
1589
1590Because klask need many parameters, it's not possible actually to use command line parameters. The configuration is done in a /etc/klask.conf YAML file. This format have many advantage over XML, it's easier to read and to write !
1591
1592Here an example, be aware with indent, it's important in YAML, do not use tabulation !
1593
1594 default:
1595   community: public
1596   snmpport: 161
1597
1598 network:
1599   labnet:
1600     ip-subnet:
1601       - add: 192.168.1.0/24
1602       - add: 192.168.2.0/24
1603     interface: eth0
1604     main-router: gw1.labnet.local
1605
1606   schoolnet:
1607     ip-subnet:
1608       - add: 192.168.6.0/24
1609       - add: 192.168.7.0/24
1610     interface: eth0.38
1611     main-router: gw2.schoolnet.local
1612
1613 switch:
1614   - hostname: sw1.klask.local
1615     portignore:
1616       - 1
1617       - 2
1618
1619   - hostname: sw2.klask.local
1620     location: BatK / 2 / K203
1621     type: HP2424
1622     portignore:
1623       - 1
1624       - 2
1625
1626I 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.
1627
1628
1629=head1 FILES
1630
1631 /etc/klask.conf
1632 /var/cache/klask/klaskdb
1633 /var/cache/klask/switchdb
1634
1635=head1 SEE ALSO
1636
1637Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1638
1639
1640=head1 VERSION
1641
1642$Id: klask 66 2009-09-09 15:24:55Z g7moreau $
1643
1644
1645=head1 AUTHOR
1646
1647Written by Gabriel Moreau, Grenoble - France
1648
1649
1650=head1 LICENSE AND COPYRIGHT
1651
1652GPL version 2 or later and Perl equivalent
1653
1654Copyright (C) 2005-2009 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.