source: trunk/klask @ 65

Last change on this file since 65 was 65, checked in by g7moreau, 15 years ago
  • Big error, IP was reverse IP !
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 53.0 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2008 Gabriel Moreau.
4#
5# $Id: klask 65 2009-09-09 14:49:27Z 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: 65 $'."\n";
578   print ' $Date: 2009-09-09 14:49:27 +0000 (Wed, 09 Sep 2009) $'."\n";
579   print ' $Id: klask 65 2009-09-09 14:49:27Z 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      my %resol_arp = resolve_ip_arp_host($one_computer);
811
812      delete $computerdb->{$resol_arp{ipv4_address}} if exists $computerdb->{$resol_arp{ipv4_address}};
813      }
814
815   my $dirdb = $KLASK_DB_FILE;
816      $dirdb =~ s{ / [^/]* $}{}xms;
817   mkdir "$dirdb", 0755 unless -d "$dirdb";
818   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
819   return;
820   }
821
822sub cmd_exportdb {
823   my @ARGV   = @_;
824
825   my $format = 'txt';
826
827   my $ret = GetOptions(
828      'format|f=s'  => \$format,
829      );
830
831   my %possible_format = (
832      txt  => \&cmd_exportdb_txt,
833      html => \&cmd_exportdb_html,
834      );
835
836   $format = 'txt' if not defined $possible_format{$format};
837
838   $possible_format{$format}->(@ARGV);
839   return;
840   }
841
842sub cmd_exportdb_txt {
843   test_maindb_environnement();
844
845   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
846
847   printf "%-24s %-4s            %-30s %-15s %-18s %-s\n", qw(Switch Port Hostname IPv4-Address MAC-Address Date);
848   print "---------------------------------------------------------------------------------------------------------------------------\n";
849
850   LOOP_ON_IP_ADDRESS:
851   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
852
853#      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq 'unknow';
854
855      # to be improve in the future
856      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
857
858# dans le futur
859#      next if $computerdb->{$ip}{hostname_fq} eq 'unknow';
860
861      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
862      $year += 1900;
863      $mon++;
864      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
865
866      printf "%-25s  %2s  <-------  %-30s %-15s %-18s %s\n",
867         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
868         $computerdb->{$ip}{switch_port},
869         $computerdb->{$ip}{hostname_fq},
870         $ip,
871         $computerdb->{$ip}{mac_address},
872         $date;
873      }
874   return;
875   }
876
877sub cmd_exportdb_html {
878   test_maindb_environnement();
879
880   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
881
882#<link rel="stylesheet" type="text/css" href="style-klask.css" />
883#<script src="sorttable-klask.js"></script>
884
885   print <<'END_HTML';
886<table class="sortable" summary="Klask export database">
887 <caption>Klask database</caption>
888 <thead>
889  <tr>
890   <th scope="col" class="hklask-switch">Switch</th>
891   <th scope="col" class="sorttable_nosort">Port</th>
892   <th scope="col" class="sorttable_nosort">Link</th>
893   <th scope="col" class="sorttable_alpha">Hostname</th>
894   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
895   <th scope="col" class="hklask-mac">MAC-Address</th>
896   <th scope="col" class="hklask-date">Date</th>
897  </tr>
898 </thead>
899 <tfoot>
900  <tr>
901   <th scope="col" class="fklask-switch">Switch</th>
902   <th scope="col" class="fklask-port">Port</th>
903   <th scope="col" class="fklask-link">Link</th>
904   <th scope="col" class="fklask-hostname">Hostname</th>
905   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
906   <th scope="col" class="fklask-mac">MAC-Address</th>
907   <th scope="col" class="fklask-date">Date</th>
908  </tr>
909 </tfoot>
910 <tbody>
911END_HTML
912
913   my %mac_count = ();
914   LOOP_ON_IP_ADDRESS:
915   foreach my $ip (keys %{$computerdb}) {
916
917      # to be improve in the future
918      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
919
920      $mac_count{$computerdb->{$ip}{mac_address}}++;
921      }
922
923   my $typerow = 'even';
924
925   LOOP_ON_IP_ADDRESS:
926   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
927
928      # to be improve in the future
929      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
930
931      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
932      $year += 1900;
933      $mon++;
934      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
935
936#      $odd_or_even++;
937#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
938      $typerow = $typerow eq 'even' ? 'odd' : 'even';
939
940      my $switch_hostname = $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description} || 'unkown';
941      chomp $switch_hostname;
942      my $switch_hostname_sort = sprintf '%s %3s' ,$switch_hostname, $computerdb->{$ip}{switch_port};
943
944      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
945
946      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{mac_address}}, $computerdb->{$ip}{mac_address};
947
948      $computerdb->{$ip}{hostname_fq} = 'unknow' if $computerdb->{$ip}{hostname_fq} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
949      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{hostname_fq};
950
951      print <<"END_HTML";
952  <tr class="$typerow">
953   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
954   <td class="bklask-port">$computerdb->{$ip}{switch_port}</td>
955   <td><-------</td>
956   <td sorttable_customkey="$host_short">$computerdb->{$ip}{hostname_fq}</td>
957   <td sorttable_customkey="$ip_sort">$ip</td>
958   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{mac_address}</td>
959   <td>$date</td>
960  </tr>
961END_HTML
962      }
963
964   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
965
966   my %db_switch_output_port       = %{$switch_connection->{output_port}};
967   my %db_switch_parent            = %{$switch_connection->{parent}};
968   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
969   my %db_switch                   = %{$switch_connection->{switch_db}};
970
971   for my $sw (sort keys %db_switch_output_port) {
972
973      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
974
975      $typerow = $typerow eq 'even' ? 'odd' : 'even';
976
977      if (exists $db_switch_parent{$sw}) {
978
979      my $mac_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{mac_address};
980      my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{ipv4_address};
981      my $timestamp = $db_switch{$db_switch_parent{$sw}->{switch}}->{timestamp};
982
983      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
984      $year += 1900;
985      $mon++;
986      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
987
988      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
989
990      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
991
992      my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{switch}, 1), $db_switch_parent{$sw}->{port};
993
994      print <<"END_HTML";
995  <tr class="$typerow">
996   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
997   <td class="bklask-port">$db_switch_output_port{$sw}</>
998   <td>+--> $db_switch_parent{$sw}->{port}</td>
999   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{switch}</>
1000   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1001   <td sorttable_customkey="$mac_sort">$mac_address</td>
1002   <td>$date</td>
1003  </tr>
1004END_HTML
1005         }
1006      else {
1007         print <<"END_HTML";
1008  <tr class="$typerow">
1009   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1010   <td class="bklask-port">$db_switch_output_port{$sw}</>
1011   <td>+--></td>
1012   <td sorttable_customkey="router">router</>
1013   <td sorttable_customkey="999999999999"></td>
1014   <td sorttable_customkey="99999"></td>
1015   <td></td>
1016  </tr>
1017END_HTML
1018         }
1019      }
1020
1021   for my $swport (sort keys %db_switch_connected_on_port) {
1022      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1023      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1024
1025         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1026
1027      my $mac_address = $db_switch{$sw}->{mac_address};
1028      my $ipv4_address = $db_switch{$sw}->{ipv4_address};
1029      my $timestamp = $db_switch{$sw}->{timestamp};
1030
1031      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1032      $year += 1900;
1033      $mon++;
1034      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1035
1036      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1037
1038      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1039
1040      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1041
1042         if (exists $db_switch_output_port{$sw}) {
1043
1044            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1045
1046            print <<"END_HTML";
1047  <tr class="$typerow">
1048   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1049   <td class="bklask-port">$port_connect</>
1050   <td>&lt;--+ $db_switch_output_port{$sw}</td>
1051   <td sorttable_customkey="$host_short">$sw</>
1052   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1053   <td sorttable_customkey="$mac_sort">$mac_address</td>
1054   <td>$date</td>
1055  </tr>
1056END_HTML
1057            }
1058         else {
1059            print <<"END_HTML";
1060  <tr class="$typerow">
1061   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1062   <td class="bklask-port">$port_connect</>
1063   <td>&lt;--+</td>
1064   <td sorttable_customkey="$sw">$sw</>
1065   <td sorttable_customkey="">$ipv4_address</td>
1066   <td sorttable_customkey="">$mac_address</td>
1067   <td>$date</td>
1068  </tr>
1069END_HTML
1070            }
1071         }
1072      }
1073
1074   print <<'END_HTML';
1075 </tbody>
1076</table>
1077END_HTML
1078   return;
1079   }
1080
1081sub cmd_iplocation {
1082   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1083
1084   LOOP_ON_IP_ADDRESS:
1085   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1086
1087      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1088
1089      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1090      next if $sw_hostname eq 'unknow';
1091
1092      my $sw_location = q{};
1093      for my $sw (@SWITCH) {
1094         next if $sw_hostname ne $sw->{hostname};
1095         $sw_location = $sw->{location};
1096         last;
1097         }
1098
1099      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1100      }
1101   return;
1102   }
1103
1104sub cmd_enable {
1105   my $switch = shift;
1106   my $port   = shift;
1107
1108   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
1109   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
1110   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
1111   return;
1112   }
1113
1114sub cmd_disable {
1115   my $switch = shift;
1116   my $port   = shift;
1117
1118   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
1119   return;
1120   }
1121
1122sub cmd_status {
1123   my $switch = shift;
1124   my $port   = shift;
1125
1126   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
1127   return;
1128   }
1129
1130sub cmd_search_mac_on_switch {
1131   my $switch_name = shift || q{};
1132   my $mac_address = shift || q{};
1133
1134   if ($switch_name eq q{} or $mac_address eq q{}) {
1135      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1136      }
1137
1138   if (not defined $SWITCH_DB{$switch_name}) {
1139      die "Switch $switch_name must be defined in klask configuration file\n";
1140      }
1141
1142   my $sw = $SWITCH_DB{$switch_name};
1143   my %session = ( -hostname => $sw->{hostname} );
1144      $session{-version} = $sw->{version}   || 1;
1145      $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1146   if (exists $sw->{version} and $sw->{version} eq '3') {
1147      $session{-username} = $sw->{username} || 'snmpadmin';
1148      }
1149   else {
1150      $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1151      }
1152
1153   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($mac_address);
1154   print "Klask search OID $research on switch $switch_name\n";
1155
1156   my ($session, $error) = Net::SNMP->session( %session );
1157   print "$error \n" if $error;
1158
1159   my $result = $session->get_request(
1160      -varbindlist => [$research]
1161      );
1162
1163   if (not defined $result or $result->{$research} eq 'noSuchInstance') {
1164      print "Klask do not find MAC $mac_address on switch $switch_name\n";
1165      $session->close;
1166      }
1167
1168   my $swport = $result->{$research};
1169   $session->close;
1170
1171   print "Klask find MAC $mac_address on switch $switch_name port $swport\n";
1172   return;
1173   }
1174
1175sub cmd_updatesw {
1176
1177   init_switch_names('yes');    #nomme les switchs
1178   print "\n";
1179
1180   my %where = ();
1181   my %db_switch_output_port = ();
1182   my %db_switch_ip_hostname = ();
1183
1184   DETECT_ALL_ROUTER:
1185#   for my $one_computer ('194.254.66.254') {
1186   for my $one_router ( get_list_main_router(get_list_network()) ) {
1187      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1188      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1189      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1190      }
1191
1192   ALL_ROUTER_IP_ADDRESS:
1193   for my $ip (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1194
1195      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip}; # /a priori/ idiot car ne sers à rien...
1196
1197      ALL_SWITCH_CONNECTED:
1198      for my $switch_detected ( keys %{$where{$ip}} ) {
1199
1200         my $switch = $where{$ip}->{$switch_detected};
1201
1202         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
1203
1204         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1205         }
1206      }
1207
1208   my %db_switch_link_with = ();
1209
1210   my @list_switch_ip = ();
1211   my @list_switch_ipv4 = ();
1212   for my $sw (@SWITCH){
1213      push @list_switch_ip, $sw->{hostname};
1214      }
1215
1216   my $timestamp = time;
1217
1218   ALL_SWITCH:
1219   for my $one_computer (@list_switch_ip) {
1220      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1221      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1222
1223      push @list_switch_ipv4,$resol_arp{ipv4_address};
1224
1225      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
1226
1227      $db_switch_ip_hostname{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
1228
1229      $SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
1230      $SWITCH_DB{$one_computer}->{mac_address}  = $resol_arp{mac_address};
1231      $SWITCH_DB{$one_computer}->{timestamp}    = $timestamp;
1232      }
1233
1234   ALL_SWITCH_IP_ADDRESS:
1235   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1236
1237      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1238
1239      DETECTED_SWITCH:
1240      for my $switch_detected ( keys %{$where{$ip}} ) {
1241
1242         next DETECTED_SWITCH if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostname{$ip}};
1243
1244         my $switch = $where{$ip}->{$switch_detected};
1245
1246         next if $switch->{port}     eq '0';
1247         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
1248         next if $switch->{hostname} eq $db_switch_ip_hostname{$ip}; # $computerdb->{$ip}{hostname};
1249
1250         $db_switch_link_with{ $db_switch_ip_hostname{$ip} } ||= {};
1251         $db_switch_link_with{ $db_switch_ip_hostname{$ip} }->{ $switch->{hostname} } = $switch->{port};
1252         }
1253
1254      }
1255
1256   my %db_switch_connected_on_port = ();
1257   my $maybe_more_than_one_switch_connected = 'yes';
1258
1259   while ($maybe_more_than_one_switch_connected eq 'yes') {
1260      for my $sw (keys %db_switch_link_with) {
1261         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1262
1263            my $port = $db_switch_link_with{$sw}->{$connect};
1264
1265            $db_switch_connected_on_port{"$connect:$port"} ||= {};
1266            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
1267            }
1268         }
1269
1270      $maybe_more_than_one_switch_connected  = 'no';
1271
1272      SWITCH_AND_PORT:
1273      for my $swport (keys %db_switch_connected_on_port) {
1274
1275         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1276
1277         $maybe_more_than_one_switch_connected = 'yes';
1278
1279         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1280         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1281
1282         CONNECTED:
1283         for my $sw_connected (@sw_on_same_port) {
1284
1285            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1286
1287            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1288
1289            for my $other_sw (@sw_on_same_port) {
1290               next if $other_sw eq $sw_connected;
1291
1292               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1293               }
1294
1295            # We can not do better for this switch for this loop
1296            next SWITCH_AND_PORT;
1297            }
1298         }
1299      }
1300
1301   my %db_switch_parent =();
1302
1303   for my $sw (keys %db_switch_link_with) {
1304      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1305
1306         my $port = $db_switch_link_with{$sw}->{$connect};
1307
1308         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1309         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1310
1311         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1312         }
1313      }
1314
1315   print "Switch output port and parent port connection\n";
1316   print "---------------------------------------------\n";
1317   for my $sw (sort keys %db_switch_output_port) {
1318      if (exists $db_switch_parent{$sw}) {
1319         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1320         }
1321      else {
1322         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1323         }
1324      }
1325   print "\n";
1326
1327   print "Switch parent and children port inter-connection\n";
1328   print "------------------------------------------------\n";
1329   for my $swport (sort keys %db_switch_connected_on_port) {
1330      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1331      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1332         if (exists $db_switch_output_port{$sw}) {
1333            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1334            }
1335         else {
1336            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1337            }
1338         }
1339      }
1340
1341   my $switch_connection = {
1342      output_port       => \%db_switch_output_port,
1343      parent            => \%db_switch_parent,
1344      connected_on_port => \%db_switch_connected_on_port,
1345      link_with         => \%db_switch_link_with,
1346      switch_db         => \%SWITCH_DB,
1347      };
1348
1349   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1350   return;
1351   }
1352
1353sub cmd_exportsw {
1354   my @ARGV   = @_;
1355
1356   test_switchdb_environnement();
1357
1358   my $format = 'txt';
1359
1360   my $ret = GetOptions(
1361      'format|f=s'  => \$format,
1362      );
1363
1364   my %possible_format = (
1365      txt => \&cmd_exportsw_txt,
1366      dot => \&cmd_exportsw_dot,
1367      );
1368
1369   $format = 'txt' if not defined $possible_format{$format};
1370
1371   $possible_format{$format}->(@ARGV);
1372   return;
1373   }
1374
1375sub cmd_exportsw_txt {
1376
1377   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1378
1379   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1380   my %db_switch_parent            = %{$switch_connection->{parent}};
1381   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1382
1383   print "Switch output port and parent port connection\n";
1384   print "---------------------------------------------\n";
1385   for my $sw (sort keys %db_switch_output_port) {
1386      if (exists $db_switch_parent{$sw}) {
1387         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1388         }
1389      else {
1390         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1391         }
1392      }
1393   print "\n";
1394
1395   print "Switch parent and children port inter-connection\n";
1396   print "------------------------------------------------\n";
1397   for my $swport (sort keys %db_switch_connected_on_port) {
1398      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1399      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1400         if (exists $db_switch_output_port{$sw}) {
1401            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1402            }
1403         else {
1404            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1405            }
1406         }
1407      }
1408   return;
1409   }
1410
1411sub cmd_exportsw_dot {
1412
1413   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1414
1415   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1416   my %db_switch_parent            = %{$switch_connection->{parent}};
1417   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1418   my %db_switch_link_with         = %{$switch_connection->{link_with}};
1419   my %db_switch_global            = %{$switch_connection->{switch_db}};
1420
1421   my %db_building= ();
1422   for my $sw (@SWITCH) {
1423      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
1424      $db_building{$building} ||= {};
1425      $db_building{$building}->{$location} ||= {};
1426      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1427      }
1428
1429
1430   print "digraph G {\n";
1431
1432   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
1433   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
1434
1435   my $b=0;
1436   for my $building (keys %db_building) {
1437      $b++;
1438
1439      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1440      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
1441
1442      my $l = 0;
1443      for my $loc (keys %{$db_building{$building}}) {
1444         $l++;
1445
1446         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
1447#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
1448         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
1449
1450         for my $sw (keys %{$db_building{$building}->{$loc}}) {
1451
1452            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
1453
1454            my $swname  = $sw;
1455               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
1456            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
1457            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
1458            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
1459
1460
1461            for my $swport (keys %db_switch_connected_on_port) {
1462               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1463               next if not $sw_connect eq $sw;
1464               next if $port_connect eq $db_switch_output_port{$sw};
1465               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
1466               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1467              }
1468            }
1469         }
1470      }
1471
1472#   print "Switch output port and parent port connection\n";
1473#   print "---------------------------------------------\n";
1474   for my $sw (sort keys %db_switch_output_port) {
1475      if (exists $db_switch_parent{$sw}) {
1476#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
1477         }
1478      else {
1479         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
1480         }
1481      }
1482   print "\n";
1483
1484#   print "Switch parent and children port inter-connection\n";
1485#   print "------------------------------------------------\n";
1486   for my $swport (sort keys %db_switch_connected_on_port) {
1487      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1488      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1489         if (exists $db_switch_output_port{$sw}) {
1490            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
1491            }
1492         else {
1493            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
1494            }
1495         }
1496      }
1497
1498print "}\n";
1499   return;
1500   }
1501
1502
1503__END__
1504
1505=head1 NAME
1506
1507klask - ports manager and finder for switch
1508
1509
1510=head1 USAGE
1511
1512 klask updatedb
1513 klask exportdb --format [txt|html]
1514
1515 klask updatesw
1516 klask exportsw --format [txt|dot]
1517
1518 klask searchdb computer
1519 klask search   computer
1520
1521 klask enable  switch port
1522 klask disable swith port
1523 klask status  swith port
1524
1525
1526=head1 DESCRIPTION
1527
1528klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1529
1530Klask has now a web site dedicated for it !
1531
1532 http://servforge.legi.inpg.fr/projects/klask
1533
1534
1535=head1 COMMANDS
1536
1537
1538=head2 search
1539
1540This 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.
1541
1542
1543=head2 enable
1544
1545This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1546
1547
1548=head2 disable
1549
1550This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1551
1552
1553=head2 status
1554
1555This 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.
1556
1557
1558=head2 updatedb
1559
1560This 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.
1561
1562
1563=head2 exportdb
1564
1565This 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...
1566
1567
1568=head2 updatesw
1569
1570This 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.
1571
1572
1573=head2 exportsw --format [txt|dot]
1574
1575This 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.
1576
1577 klask exportsw --format dot > /tmp/map.dot
1578 dot -Tpng /tmp/map.dot > /tmp/map.png
1579
1580
1581
1582=head1 CONFIGURATION
1583
1584Because 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 !
1585
1586Here an example, be aware with indent, it's important in YAML, do not use tabulation !
1587
1588 default:
1589   community: public
1590   snmpport: 161
1591
1592 network:
1593   labnet:
1594     ip-subnet:
1595       - add: 192.168.1.0/24
1596       - add: 192.168.2.0/24
1597     interface: eth0
1598     main-router: gw1.labnet.local
1599
1600   schoolnet:
1601     ip-subnet:
1602       - add: 192.168.6.0/24
1603       - add: 192.168.7.0/24
1604     interface: eth0.38
1605     main-router: gw2.schoolnet.local
1606
1607 switch:
1608   - hostname: sw1.klask.local
1609     portignore:
1610       - 1
1611       - 2
1612
1613   - hostname: sw2.klask.local
1614     location: BatK / 2 / K203
1615     type: HP2424
1616     portignore:
1617       - 1
1618       - 2
1619
1620I 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.
1621
1622
1623=head1 FILES
1624
1625 /etc/klask.conf
1626 /var/cache/klask/klaskdb
1627 /var/cache/klask/switchdb
1628
1629=head1 SEE ALSO
1630
1631Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1632
1633
1634=head1 VERSION
1635
1636$Id: klask 65 2009-09-09 14:49:27Z g7moreau $
1637
1638
1639=head1 AUTHOR
1640
1641Written by Gabriel Moreau, Grenoble - France
1642
1643
1644=head1 LICENSE AND COPYRIGHT
1645
1646GPL version 2 or later and Perl equivalent
1647
1648Copyright (C) 2005-2009 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.