source: trunk/klask @ 58

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