source: trunk/klask @ 51

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