source: trunk/klask @ 64

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