source: trunk/klask @ 48

Last change on this file since 48 was 48, checked in by g7moreau, 15 years ago
  • Suppress perlcritic warning at level 3

Mismatched operator at line XXX, column 55. Numeric/string operators and operands should match. (Severity: 3)

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