source: trunk/klask @ 64

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