source: trunk/klask @ 67

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