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
RevLine 
[2]1#!/usr/bin/perl -w
[32]2#
3# Copyright (C) 2005-2008 Gabriel Moreau.
4#
5# $Id: klask 51 2009-07-23 22:22:32Z g7moreau $
[2]6
7use strict;
[3]8use warnings;
[2]9
[51]10use Readonly;
[2]11use Net::SNMP;
[44]12#use YAML;
13use YAML::Syck;
[2]14use Net::Netmask;
15use Net::CIDR::Lite;
16use NetAddr::IP;
[17]17use Getopt::Long;
[2]18
[8]19# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
[23]20# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
21# arping fping bind9-host arpwatch
[2]22
[26]23my $KLASK_VAR      = '/var/cache/klask';
[13]24my $KLASK_CFG_FILE = '/etc/klask.conf';
[28]25my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
26my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
[2]27
[26]28test_running_environnement();
29
[44]30my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
[2]31
[49]32my %DEFAULT = %{ $KLASK_CFG->{default} };
33my @SWITCH  = @{ $KLASK_CFG->{switch}  };
[2]34
[11]35my %switch_level = ();
[49]36my %SWITCH_DB    = ();
[2]37LEVEL_OF_EACH_SWITCH:
[11]38for my $sw (@SWITCH){
39   $switch_level{$sw->{hostname}} = $sw->{level} || $DEFAULT{switch_level}  || 2;
[25]40   $SWITCH_DB{$sw->{hostname}} = $sw;
[2]41   }
[49]42@SWITCH = sort { $switch_level{$b->{hostname}} <=> $switch_level{$a->{hostname}} } @{$KLASK_CFG->{switch}};
[2]43
[15]44my %SWITCH_PORT_COUNT = ();
[2]45
[11]46my %CMD_DB = (
[2]47   help       => \&cmd_help,
[36]48   version    => \&cmd_version,
[2]49   exportdb   => \&cmd_exportdb,
50   updatedb   => \&cmd_updatedb,
51   searchdb   => \&cmd_searchdb,
[4]52   removedb   => \&cmd_removedb,
[2]53   search     => \&cmd_search,
54   enable     => \&cmd_enable,
55   disable    => \&cmd_disable,
56   status     => \&cmd_status,
[4]57   updatesw   => \&cmd_updatesw,
58   exportsw   => \&cmd_exportsw,
[2]59   iplocation => \&cmd_iplocation,
[35]60   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
[2]61   );
62
[51]63Readonly my %INTERNAL_PORT_MAP => (
[2]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   );
[51]73Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
[2]74
[51]75Readonly my %SWITCH_KIND => (
[20]76   J3299A => { model => 'HP224M',     match => 'HP J3299A ProCurve Switch 224M'  },
77   J4120A => { model => 'HP1600M',    match => 'HP J4120A ProCurve Switch 1600M' },
[27]78   J9029A => { model => 'HP1800-8G',  match => 'PROCURVE J9029A'                 },
[20]79   J4093A => { model => 'HP2424M',    match => 'HP J4093A ProCurve Switch 2424M' },
80   J4813A => { model => 'HP2524',     match => 'HP J4813A ProCurve Switch 2524'  },
[21]81   J4900A => { model => 'HP2626A',    match => 'HP J4900A ProCurve Switch 2626'  },
[42]82   J4900B => { model => 'HP2626B',    match => 'J4900B.+?Switch 2626'            },# ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
[30]83   J4899B => { model => 'HP2650',     match => 'ProCurve J4899B Switch 2650'     },
[20]84   J9021A => { model => 'HP2810-24G', match => 'ProCurve J9021A Switch 2810-24G' },
[41]85   J9022A => { model => 'HP2810-48G', match => 'ProCurve J9022A Switch 2810-48G' },
[20]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'                },
[18]89   );
[49]90
[51]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',
[19]96   );
[18]97
[51]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
[2]102################
103# principal
104################
105
[11]106my $cmd = shift @ARGV || 'help';
107if (defined $CMD_DB{$cmd}) {
108   $CMD_DB{$cmd}->(@ARGV);
[2]109   }
110else {
111   print STDERR "klask: command $cmd not found\n\n";
[11]112   $CMD_DB{help}->();
[2]113   exit 1;
114   }
115
116exit;
117
[26]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";
[46]121   return;
[26]122   }
123
[34]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";
[46]126   return;
[34]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";
[46]131   return;
[34]132   }
133
[2]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";
[46]138   return;
[2]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 || '*';
[3]146   my $type      = shift || 'fast';
[2]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`;
[50]155   if ( not $param_ip_or_host =~ m/^\d+ \. \d+ \. \d+ \. \d+$/x ) {
156      $param_ip_or_host =~ s/ \. .* //x;
[44]157      }
[2]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
[44]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";
[2]166   my $cmd_arpwatch = `$cmd`;
167   chomp $cmd_arpwatch;
[50]168   my ($arp, $ip, $timestamp, $host) = split m/ \s+ /x, $cmd_arpwatch;
[44]169
[2]170   $ret{ipv4_address} = $ip        if $ip;
171   $ret{mac_address}  = $arp       if $arp;
172   $ret{timestamp}    = $timestamp if $timestamp;
173
[3]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
[49]181   # resultat de la commande arp
[2]182   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
[44]183   # sw2-batF0-legi.hmg.priv (192.168.22.112) at 00:30:c1:76:9c:01 [ether] on eth0.37
[51]184   my $cmd_arp  = `arp -a $param_ip_or_host 2> /dev/null`;
[2]185   chomp $cmd_arp;
[51]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      }
[2]189
[49]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.
[51]192   my $cmd_host = `host $param_ip_or_host 2> /dev/null`;
[49]193   chomp $cmd_host;
[51]194   if ( $cmd_host =~ m/domain \s name \s pointer \s (\S+) \.$/xms ) {
195      $ret{hostname_fq} = $1;
196      }
[2]197
[49]198   # resultat de la commande host si parametre est hostname
199   # tech7meylan.hmg.inpg.fr has address 194.254.66.240
[51]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      }
[2]203
[51]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';
[2]208
209   unless ($ret{mac_address} eq 'unknow') {
210      my @paquets = ();
[50]211      foreach ( split m/ : /x, $ret{mac_address} ) {
[51]212         my @chars = split m//xms, uc("00$_");
[2]213         push @paquets, "$chars[-2]$chars[-1]";
214         }
215      $ret{mac_address} = join ':', @paquets;
216      }
217
218   return %ret;
219   }
220
[20]221# Find Surname of a switch
222sub get_switch_model {
[22]223   my $sw_snmp_description = shift || 'unknow';
[49]224
[20]225   for my $sw_kind (keys %SWITCH_KIND) {
[50]226      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/x;
[49]227
[20]228      return $SWITCH_KIND{$sw_kind}->{model};
229      }
[49]230
[22]231   return $sw_snmp_description;
[20]232   }
233
[2]234###
235# va rechercher le nom des switchs pour savoir qui est qui
[4]236sub init_switch_names {
[2]237   my $verbose = shift;
[49]238
[14]239   printf "%-25s                %-25s %s\n",'Switch','Description','Type';
240#   print "Switch description\n" if $verbose;
241   print "-------------------------------------------------------------------------\n" if $verbose;
[2]242
243   INIT_EACH_SWITCH:
[11]244   for my $sw (@SWITCH) {
[3]245      my %session = ( -hostname   => $sw->{hostname} );
246         $session{-version} = $sw->{version}   || 1;
[11]247         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
[48]248         if (exists $sw->{version} and $sw->{version} eq '3') {
[3]249            $session{-username} = $sw->{username} || 'snmpadmin';
250            }
251         else {
[11]252            $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
[3]253            }
254
255      $sw->{local_session} = \%session;
256
257      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
[14]258      print "$error \n" if $error;
259
[2]260      my $result = $session->get_request(
[18]261         -varbindlist => [
[49]262            $OID_NUMBER{sysDescription},
[18]263            $OID_NUMBER{sysName},
264            $OID_NUMBER{sysContact},
265            $OID_NUMBER{sysLocation},
266            ]
[2]267         );
[18]268      $sw->{description} = $result->{$OID_NUMBER{sysName}} || $sw->{hostname};
[49]269      $sw->{model} = get_switch_model( $result->{$OID_NUMBER{sysDescription}});
[3]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};
[2]272      $session->close;
[22]273
[49]274      # Ligne à virer car on récupère maintenant le modèle du switch
[2]275      my ($desc, $type) = split ':', $sw->{description}, 2;
[20]276      printf "%-25s 0--------->>>> %-25s %s\n", $sw->{hostname}, $desc, $sw->{model} if $verbose;
[2]277      }
278
279   print "\n" if $verbose;
[46]280   return;
[2]281   }
282
283###
284# convertit l'hexa (uniquement 2 chiffres) en decimal
[4]285sub hex_to_dec {
[2]286   #00:0F:1F:43:E4:2B
[4]287   my $car = '00' . uc(shift);
[2]288
[4]289   return '00' if $car eq '00UNKNOW';
[2]290   my %table = (
[49]291      "0"=>"0",  "1"=>"1",  "2"=>"2",  "3"=>"3",  "4"=>"4",
292      "5"=>"5",  "6"=>"6",  "7"=>"7",  "8"=>"8",  "9"=>"9",
[2]293      "A"=>"10", "B"=>"11", "C"=>"12", "D"=>"13", "E"=>"14", "F"=>"15"
294      );
[50]295   my @chars = split m//x, $car;
[2]296   return $table{$chars[-2]}*16 + $table{$chars[-1]};
297   }
298
299###
300# convertit l'@ arp en decimal
[4]301sub arp_hex_to_dec {
[2]302   #00:0F:1F:43:E4:2B
303   my $arp = shift;
304
[50]305   my @paquets = split m/ : /x, $arp;
[2]306   my $return = '';
307   foreach(@paquets) {
[4]308      $return .= ".".hex_to_dec($_);
[2]309      }
310   return $return;
311   }
312
313###
314# va rechercher le port et le switch sur lequel est la machine
315sub find_switch_port {
[49]316   my $arp             = shift;
[2]317   my $switch_proposal = shift || '';
[49]318
[2]319   my %ret;
320   $ret{switch_description} = "unknow";
321   $ret{switch_port} = "0";
322
323   return %ret if $arp eq 'unknow';;
324
[22]325   my @switch_search = @SWITCH;
[2]326   if ($switch_proposal ne '') {
[11]327      for my $sw (@SWITCH) {
[2]328         next if $sw->{hostname} ne $switch_proposal;
[22]329         unshift @switch_search, $sw;
[2]330         last;
331         }
332      }
333
[4]334   my $research = "1.3.6.1.2.1.17.4.3.1.2".arp_hex_to_dec($arp);
[49]335
[2]336   LOOP_ON_SWITCH:
[22]337   for my $sw (@switch_search) {
[3]338      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
[22]339      print "$error \n" if $error;
340
[2]341      my $result = $session->get_request(
342         -varbindlist => [$research]
343         );
[3]344      if (not defined($result) or $result->{$research} eq 'noSuchInstance') {
[2]345         $session->close;
346         next LOOP_ON_SWITCH;
347         }
348
349         my $swport = $result->{$research};
350         $session->close;
351
352         # IMPORTANT !!
[49]353         # ceci empeche la detection sur certains port ...
[2]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}}) {
[22]361            next SWITCH_PORT_IGNORE if $swport ne get_numerical_port($sw->{model},$p);
[2]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};
[22]368            $ret{switch_port}        = get_human_readable_port($sw->{model}, $swport); # $swport;
[49]369
[2]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
[11]387   for my $sw (@SWITCH) {
[15]388      $SWITCH_PORT_COUNT{$sw->{hostname}} = {} if not exists $SWITCH_PORT_COUNT{$sw->{hostname}};
[2]389      }
390
[4]391   my $research = "1.3.6.1.2.1.17.4.3.1.2".arp_hex_to_dec($arp);
[2]392   LOOP_ON_ALL_SWITCH:
[11]393   for my $sw (@SWITCH) {
[3]394      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
[13]395      print "$error \n" if $error;
396
[2]397      my $result = $session->get_request(
398         -varbindlist => [$research]
399         );
[13]400
[3]401      if(defined($result) and $result->{$research} ne 'noSuchInstance'){
[2]402         my $swport = $result->{$research};
403
404         $ret->{$sw->{hostname}} = {};
405         $ret->{$sw->{hostname}}{hostname}    = $sw->{hostname};
406         $ret->{$sw->{hostname}}{description} = $sw->{description};
[22]407         $ret->{$sw->{hostname}}{port}        = get_human_readable_port($sw->{model}, $swport);
[2]408
[15]409         $SWITCH_PORT_COUNT{$sw->{hostname}}->{$swport}++;
[2]410         }
411
412      $session->close;
413      }
414   return $ret;
415   }
416
417sub get_list_network {
418
[12]419   return keys %{$KLASK_CFG->{network}};
[2]420   }
421
422sub get_current_interface {
423   my $network = shift;
424
[12]425   return $KLASK_CFG->{network}{$network}{interface};
[2]426   }
[49]427
[2]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) {
[12]436      my @line  = @{$KLASK_CFG->{network}{$net}{'ip-subnet'}};
[2]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
[4]444   my @res = ();
[2]445
446   for my $cidr ($cidrlist->list()) {
447      my $net = new NetAddr::IP $cidr;
448      for my $ip (@$net) {
[50]449         $ip =~ s{ /32 }{}x;
[2]450         push @res,  $ip;
451         }
452      }
453
454   return @res;
455   }
456
[9]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) {
[12]464      push @res, $KLASK_CFG->{network}{$net}{'main-router'};
[9]465      }
466
467   return @res;
468   }
469
[2]470sub get_human_readable_port {
[22]471   my $sw_model = shift;
472   my $sw_port  = shift;
[49]473
[22]474   return $sw_port if not $sw_model eq 'HP8000M';
[49]475
[22]476   my $reste = (($sw_port - 1) % 8) + 1;
477   my $major = int( ($sw_port - 1) / 8 );
[2]478
[11]479   return "$INTERNAL_PORT_MAP{$major}$reste";
[2]480   }
481
482sub get_numerical_port {
[22]483   my $sw_model = shift;
484   my $sw_port  = shift;
[49]485
[22]486   return $sw_port if not $sw_model eq 'HP8000';
[2]487
[22]488   my $letter = substr($sw_port, 0, 1);
[49]489
[2]490#   return $port if $letter =~ m/\d/;
[49]491
[22]492   my $reste =  substr($sw_port, 1);
[49]493
[11]494   return $INTERNAL_PORT_MAP_REV{$letter} * 8 + $reste;
[2]495   }
496
497################
498# Les commandes
499################
500
501sub cmd_help {
502
[47]503print <<'END';
[2]504klask - ports manager and finder for switch
505
506 klask updatedb
[45]507 klask exportdb
[2]508
[45]509 klask updatesw
510 klask exportsw
511
[2]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
[46]519   return;
[2]520   }
521
[36]522sub cmd_version {
523
[47]524print <<'END';
[36]525Klask - ports manager and finder for switch
526Copyright (C) 2005-2008 Gabriel Moreau
527
528END
[37]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";
[46]532   return;
[36]533   }
534
[2]535sub cmd_search {
536   my @computer = @_;
[49]537
[4]538   init_switch_names();    #nomme les switchs
[2]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      }
[46]546   return;
[2]547   }
548
549sub cmd_searchdb {
550   my @computer = @_;
551
552   fastping(@computer);
[44]553   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
[49]554
[2]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};
[49]559
[2]560      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
[49]561
[2]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      }
[46]575   return;
[2]576   }
577
578sub cmd_updatedb {
579   my @network = @_;
580      @network = get_list_network() if not @network;
581
[34]582   test_switchdb_environnement();
583
584   my $computerdb = {};
[44]585      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
[2]586   my $timestamp = time;
[22]587
[2]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;
[31]593      $size_of_database   = 1 if $size_of_database == 0;
[2]594   my $i = 0;
595   my $detected_computer = 0;
[22]596
[4]597   init_switch_names('yes');    #nomme les switchs
[2]598
[22]599   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
[44]600   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[22]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 = ();
[49]604   for my $swport (keys %db_switch_connected_on_port) {
[22]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
[2]618   my %router_mac_ip = ();
619   DETECT_ALL_ROUTER:
[9]620#   for my $one_router ('194.254.66.254') {
621   for my $one_router ( get_list_main_router(@network) ) {
[2]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++;
[49]637
[2]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);
[49]655
[9]656         # do not search on router connection (why ?)
[2]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
[9]662         # do not search on switch inter-connection
[2]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
[3]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
[2]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
[3]689#         if ($resol_arp{mac_address} eq 'unknow') {
690#            $computer_not_detected{$one_computer} = $current_interface;
691#            next LOOP_ON_COMPUTER;
692#            }
[2]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,
[45]704               network            => $net,
[2]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},
[45]719               network            => $net,
[2]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};
[49]725
[2]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;
[49]732
[2]733         }
734      }
735
736   # final end of line at the end of the loop
737   printf "\n";
738
[13]739   my $dirdb = $KLASK_DB_FILE;
[50]740      $dirdb =~ s{ / [^/]* $}{}x;
[2]741   mkdir "$dirdb", 0755 unless -d "$dirdb";
[44]742   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
[2]743
744   for my $one_computer (keys %computer_not_detected) {
745      my $interface = $computer_not_detected{$one_computer};
[16]746      system "arping -c 1 -w 1 -rR -i $interface $one_computer &>/dev/null";
[2]747#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
748      }
[46]749   return;
[2]750   }
751
752sub cmd_removedb {
753   my @computer = @_;
[34]754
755   test_maindb_environnement();
756
[44]757   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
[2]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
[13]767   my $dirdb = $KLASK_DB_FILE;
[50]768      $dirdb =~ s{ / [^/]* $}{}x;
[2]769   mkdir "$dirdb", 0755 unless -d "$dirdb";
[44]770   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
[46]771   return;
[2]772   }
773
774sub cmd_exportdb {
[45]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};
[49]789
[45]790   $possible_format{$format}->(@ARGV);
[46]791   return;
[45]792   }
793
794sub cmd_exportdb_txt {
[34]795   test_maindb_environnement();
796
[44]797   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
[2]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)) {
[49]804
[2]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';
[49]812
[2]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      }
[46]826   return;
[2]827   }
828
[45]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
[47]837   print <<'END_HTML';
[45]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>
[47]863END_HTML
[45]864
865   my %mac_count = ();
866   LOOP_ON_IP_ADDRESS:
867   foreach my $ip (keys %$computerdb) {
[49]868
[45]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 !
[49]871
[45]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)) {
[49]879
[45]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';
[49]890      $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]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
[50]896      my $ip_sort = sprintf "%03i%03i%03i%03i", split( m/ \. /x, $ip);
[45]897
898      my $mac_sort = sprintf "%04i-%s", 9999 - $mac_count{$computerdb->{$ip}{mac_address}}, $computerdb->{$ip}{mac_address};
899
[50]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};
[45]902
[47]903      print <<"END_HTML";
[45]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>
[47]913END_HTML
[45]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
[49]927      $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]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
[50]940      my $ip_sort = sprintf "%03i%03i%03i%03i", split( m/ \. /x, $ipv4_address);
[45]941
942      my $mac_sort = sprintf "%04i-%s", 9999, $mac_address;
943
[50]944      my ( $host_short ) = sprintf "%s %3s" , split(m/ \. /x, $db_switch_parent{$sw}->{switch}, 1), $db_switch_parent{$sw}->{port};
[45]945
[49]946      print <<"END_HTML";
[45]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>
[47]956END_HTML
[45]957         }
958      else {
[49]959         print <<"END_HTML";
[45]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>
[47]969END_HTML
[45]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
[50]988      my $ip_sort = sprintf "%03i%03i%03i%03i", split( m/ \. /x, $ipv4_address);
[45]989
990      my $mac_sort = sprintf "%04i-%s", 9999, $mac_address;
991
[49]992      $typerow = $typerow eq 'even' ? 'odd' : 'even';
[45]993
994         if (exists $db_switch_output_port{$sw}) {
995
[50]996            my ( $host_short ) = sprintf "%s %3s" , split( m/\./x , $sw, 1), $db_switch_output_port{$sw};
[45]997
[49]998            print <<"END_HTML";
[45]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>
[47]1008END_HTML
[45]1009            }
1010         else {
[49]1011            print <<"END_HTML";
[45]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>
[47]1021END_HTML
[45]1022            }
1023         }
1024      }
1025
[47]1026   print <<'END_HTML';
[45]1027 </tbody>
1028</table>
[47]1029END_HTML
[46]1030   return;
[45]1031   }
1032
[2]1033sub cmd_iplocation {
[44]1034   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
[2]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';
[49]1043
[2]1044      my $sw_location = '';
[11]1045      for my $sw (@SWITCH) {
[2]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      }
[46]1053   return;
[2]1054   }
1055
1056sub cmd_enable {
1057   my $switch = shift;
1058   my $port   = shift;
[49]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)
[2]1062   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
[46]1063   return;
[2]1064   }
1065
1066sub cmd_disable {
1067   my $switch = shift;
1068   my $port   = shift;
[49]1069
[2]1070   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
[46]1071   return;
[2]1072   }
1073
1074sub cmd_status {
1075   my $switch = shift;
1076   my $port   = shift;
[49]1077
[2]1078   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
[46]1079   return;
[2]1080   }
1081
[35]1082sub cmd_search_mac_on_switch {
[38]1083   my $switch_name = shift || '';
1084   my $mac_address = shift || '';
[49]1085
[38]1086   if ($switch_name eq '' or $mac_address eq '') {
[39]1087      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
[38]1088      }
[39]1089
1090   if (not defined $SWITCH_DB{$switch_name}) {
[49]1091      die "Switch $switch_name must be defined in klask configuration file\n";
[39]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;
[48]1098   if (exists $sw->{version} and $sw->{version} eq '3') {
[39]1099      $session{-username} = $sw->{username} || 'snmpadmin';
1100      }
1101   else {
1102      $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1103      }
1104
[35]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";
[2]1107
[39]1108   my ($session, $error) = Net::SNMP->session( %session );
[35]1109   print "$error \n" if $error;
1110
1111   my $result = $session->get_request(
1112      -varbindlist => [$research]
1113      );
[49]1114
[35]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";
[46]1124   return;
[35]1125   }
1126
[4]1127sub cmd_updatesw {
[2]1128
[4]1129   init_switch_names('yes');    #nomme les switchs
[2]1130   print "\n";
1131
1132   my %where = ();
1133   my %db_switch_output_port = ();
1134   my %db_switch_ip_hostname = ();
1135
1136   DETECT_ALL_ROUTER:
[9]1137#   for my $one_computer ('194.254.66.254') {
[11]1138   for my $one_router ( get_list_main_router(get_list_network()) ) {
[13]1139      my %resol_arp = resolve_ip_arp_host($one_router,'*','low');            # resolution arp
[2]1140      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
[13]1141      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
[2]1142      }
1143
1144   ALL_ROUTER_IP_ADDRESS:
[13]1145   for my $ip (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
[49]1146
[13]1147      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip}; # /a priori/ idiot car ne sers à rien...
[2]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';
[49]1155
[2]1156         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1157         }
[49]1158      }
[2]1159
1160   my %db_switch_link_with = ();
1161
1162   my @list_switch_ip = ();
1163   my @list_switch_ipv4 = ();
[11]1164   for my $sw (@SWITCH){
[2]1165      push @list_switch_ip, $sw->{hostname};
1166      }
1167
[45]1168   my $timestamp = time;
1169
[2]1170   ALL_SWITCH:
1171   for my $one_computer (@list_switch_ip) {
[3]1172      my %resol_arp = resolve_ip_arp_host($one_computer,'*','low'); # arp resolution
[2]1173      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
[49]1174
[2]1175      push @list_switch_ipv4,$resol_arp{ipv4_address};
[49]1176
[2]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};
[45]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;
[2]1184      }
[49]1185
[2]1186   ALL_SWITCH_IP_ADDRESS:
1187   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
[49]1188
[2]1189      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1190
1191      DETECTED_SWITCH:
1192      for my $switch_detected ( keys %{$where{$ip}} ) {
1193
[15]1194         next DETECTED_SWITCH if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostname{$ip}};
[2]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      }
[49]1207
[2]1208   my %db_switch_connected_on_port = ();
1209   my $maybe_more_than_one_switch_connected = 'yes';
[49]1210
[2]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}}) {
[49]1214
[2]1215            my $port = $db_switch_link_with{$sw}->{$connect};
[49]1216
[2]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) {
[49]1226
[2]1227         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
[49]1228
[2]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) {
[49]1236
[2]1237            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
[49]1238
[2]1239            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
[49]1240
[2]1241            for my $other_sw (@sw_on_same_port) {
1242               next if $other_sw eq $sw_connected;
[49]1243
[2]1244               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1245               }
[49]1246
[2]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}}) {
[49]1257
[2]1258         my $port = $db_switch_link_with{$sw}->{$connect};
[49]1259
[2]1260         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1261         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
[49]1262
[2]1263         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1264         }
1265      }
1266
[49]1267   print "Switch output port and parent port connection\n";
[2]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";
[49]1281   for my $swport (sort keys %db_switch_connected_on_port) {
[2]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,
[25]1298      switch_db         => \%SWITCH_DB,
[2]1299      };
[49]1300
[44]1301   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
[46]1302   return;
[2]1303   }
1304
[4]1305sub cmd_exportsw {
1306   my @ARGV   = @_;
[2]1307
[34]1308   test_switchdb_environnement();
1309
[4]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};
[49]1322
[4]1323   $possible_format{$format}->(@ARGV);
[46]1324   return;
[4]1325   }
1326
1327sub cmd_exportsw_txt {
1328
[44]1329   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[4]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
[49]1335   print "Switch output port and parent port connection\n";
[4]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";
[49]1349   for my $swport (sort keys %db_switch_connected_on_port) {
[4]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      }
[46]1360   return;
[4]1361   }
1362
1363sub cmd_exportsw_dot {
1364
[44]1365   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
[49]1366
[2]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}};
[25]1371   my %db_switch_global            = %{$switch_connection->{switch_db}};
1372
[2]1373   my %db_building= ();
[11]1374   for my $sw (@SWITCH) {
[50]1375      my ($building, $location) = split m/ \/ /x, $sw->{location}, 2;
[2]1376      $db_building{$building} ||= {};
1377      $db_building{$building}->{$location} ||= {};
1378      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1379      }
[49]1380
1381
[2]1382   print "digraph G {\n";
1383
[4]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";
[2]1386
1387   my $b=0;
1388   for my $building (keys %db_building) {
1389      $b++;
[49]1390
[4]1391      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1392      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
[2]1393
1394      my $l = 0;
1395      for my $loc (keys %{$db_building{$building}}) {
1396         $l++;
[49]1397
[33]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";
[4]1400         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
[2]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
[25]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";
[4]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";
[2]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";
[4]1418               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
[2]1419              }
1420            }
1421         }
1422      }
1423
[49]1424#   print "Switch output port and parent port connection\n";
[2]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";
[49]1438   for my $swport (sort keys %db_switch_connected_on_port) {
[2]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";
[46]1451   return;
[2]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
[45]1465 klask exportdb --format [txt|html]
[2]1466
[5]1467 klask updatesw
1468 klask exportsw --format [txt|dot]
1469
[2]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
[6]1480klask is a small tool to find where is a host in a big network. klask mean search in brittany.
[2]1481
[6]1482Klask has now a web site dedicated for it !
[2]1483
[6]1484 http://servforge.legi.inpg.fr/projects/klask
1485
1486
[2]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
[5]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
[2]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:
[5]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
[2]1551
[5]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
[2]1559 switch:
1560   - hostname: sw1.klask.local
1561     portignore:
1562       - 1
1563       - 2
1564
1565   - hostname: sw2.klask.local
[5]1566     location: BatK / 2 / K203
1567     type: HP2424
[2]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
[7]1579 /var/cache/klask/switchdb
[2]1580
1581=head1 SEE ALSO
1582
1583Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1584
1585
1586=head1 VERSION
1587
[36]1588$Id: klask 51 2009-07-23 22:22:32Z g7moreau $
[2]1589
1590
1591=head1 AUTHOR
1592
[5]1593Written by Gabriel Moreau, Grenoble - France
[2]1594
1595
1596=head1 COPYRIGHT
1597
[49]1598Copyright (C) 2005-2009 Gabriel Moreau.
[2]1599
[49]1600
[2]1601=head1 LICENCE
1602
1603GPL version 2 or later and Perl equivalent
[45]1604
Note: See TracBrowser for help on using the repository browser.