source: trunk/klask @ 67

Last change on this file since 67 was 67, checked in by g7moreau, 13 years ago
  • Small typo
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 53.3 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2008 Gabriel Moreau.
4#
5# $Id: klask 67 2010-11-02 10:21:34Z 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 = reverse sort { $switch_level{$a->{hostname}} <=> $switch_level{$b->{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   ifMtu          => '1.3.6.1.2.1.2.2.1.4',
99   ifSpeed        => '1.3.6.1.2.1.2.2.1.5',
100   );
101
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
106################
107# principal
108################
109
110my $cmd = shift @ARGV || 'help';
111if (defined $CMD_DB{$cmd}) {
112   $CMD_DB{$cmd}->(@ARGV);
113   }
114else {
115   print {*STDERR} "klask: command $cmd not found\n\n";
116   $CMD_DB{help}->();
117   exit 1;
118   }
119
120exit;
121
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";
125   return;
126   }
127
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";
130   return;
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";
135   return;
136   }
137
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";
142   return;
143   }
144
145sub shell_command {
146   my $cmd = shift;
147
148   my $fh     = new FileHandle;
149   my $result = '';
150   open $fh, q{-|}, "$cmd" or die "Can't exec $cmd\n";
151   $result .= <$fh>;
152   close $fh;
153   chomp $result;
154   return $result;
155   }
156
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;
161   my $interface = shift || q{*};
162   my $type      = shift || q{fast};
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`;
171   if ( not $param_ip_or_host =~ m/^\d+ \. \d+ \. \d+ \. \d+$/xms ) {
172      $param_ip_or_host =~ s/ \. .* //xms;
173      }
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
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";
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";
182   my $cmd_arpwatch = shell_command $cmd;
183   my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
184
185   $ret{ipv4_address} = $ip        if $ip;
186   $ret{mac_address}  = $arp       if $arp;
187   $ret{timestamp}    = $timestamp if $timestamp;
188
189   my $nowtimestamp = time;
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
196   # resultat de la commande arp
197   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
198   # sw2-batF0-legi.hmg.priv (192.168.22.112) at 00:30:c1:76:9c:01 [ether] on eth0.37
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      }
203
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      }
210
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      }
216
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   #    }
222   #$ret{hostname_fq}  = $param_ip_or_host if not defined $1 and $ret{hostname_fq} eq 'unknow';
223
224   if ($ret{mac_address} ne 'unknow') {
225      my @paquets = ();
226      foreach ( split m/ : /xms, $ret{mac_address} ) {
227         my @chars = split m//xms, uc "00$_";
228         push @paquets, "$chars[-2]$chars[-1]";
229         }
230      $ret{mac_address} = join q{:}, @paquets;
231      }
232
233   return %ret;
234   }
235
236# Find Surname of a switch
237sub get_switch_model {
238   my $sw_snmp_description = shift || 'unknow';
239
240   for my $sw_kind (keys %SWITCH_KIND) {
241      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/ms; # option xms break search, why ?
242
243      return $SWITCH_KIND{$sw_kind}->{model};
244      }
245
246   return $sw_snmp_description;
247   }
248
249###
250# va rechercher le nom des switchs pour savoir qui est qui
251sub init_switch_names {
252   my $verbose = shift;
253
254   printf "%-25s                %-25s %s\n",'Switch','Description','Type';
255   print "-------------------------------------------------------------------------\n" if $verbose;
256
257   INIT_EACH_SWITCH:
258   for my $sw (@SWITCH) {
259      my %session = ( -hostname   => $sw->{hostname} );
260         $session{-version} = $sw->{version}   || 1;
261         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
262         if (exists $sw->{version} and $sw->{version} eq '3') {
263            $session{-username} = $sw->{username} || 'snmpadmin';
264            }
265         else {
266            $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
267            }
268
269      $sw->{local_session} = \%session;
270
271      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
272      print "$error \n" if $error;
273
274      my $result = $session->get_request(
275         -varbindlist => [
276            $OID_NUMBER{sysDescription},
277            $OID_NUMBER{sysName},
278            $OID_NUMBER{sysContact},
279            $OID_NUMBER{sysLocation},
280            ]
281         );
282      $sw->{description} = $result->{$OID_NUMBER{sysName}} || $sw->{hostname};
283      $sw->{model} = get_switch_model( $result->{$OID_NUMBER{sysDescription}});
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};
286      $session->close;
287
288      # Ligne à virer car on récupère maintenant le modèle du switch
289      my ($desc, $type) = split m/ : /xms, $sw->{description}, 2;
290      printf "%-25s 0--------->>>> %-25s %s\n", $sw->{hostname}, $desc, $sw->{model} if $verbose;
291      }
292
293   print "\n" if $verbose;
294   return;
295   }
296
297###
298# convertit l'hexa (uniquement 2 chiffres) en decimal
299sub hex_to_dec {
300   #00:0F:1F:43:E4:2B
301   my $car = '00' . uc shift;
302
303   return '00' if $car eq '00UNKNOW';
304   my %table = (
305      '0'=>'0',  '1'=>'1',  '2'=>'2',  '3'=>'3',  '4'=>'4',
306      '5'=>'5',  '6'=>'6',  '7'=>'7',  '8'=>'8',  '9'=>'9',
307      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15',
308      );
309   my @chars = split m//xms, $car;
310   return $table{$chars[-2]}*16 + $table{$chars[-1]};
311   }
312
313###
314# convertit l'@ arp en decimal
315sub arp_hex_to_dec {
316   #00:0F:1F:43:E4:2B
317   my $arp = shift;
318
319   my @paquets = split m/ : /xms, $arp;
320   my $return = q{};
321   foreach(@paquets) {
322      $return .= q{.} . hex_to_dec($_);
323      }
324   return $return;
325   }
326
327###
328# va rechercher le port et le switch sur lequel est la machine
329sub find_switch_port {
330   my $arp             = shift;
331   my $switch_proposal = shift || q{};
332
333   my %ret;
334   $ret{switch_description} = 'unknow';
335   $ret{switch_port} = '0';
336
337   return %ret if $arp eq 'unknow';;
338
339   my @switch_search = @SWITCH;
340   if ($switch_proposal ne q{}) {
341      for my $sw (@SWITCH) {
342         next if $sw->{hostname} ne $switch_proposal;
343         unshift @switch_search, $sw;
344         last;
345         }
346      }
347
348   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($arp);
349
350   LOOP_ON_SWITCH:
351   for my $sw (@switch_search) {
352      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
353      print "$error \n" if $error;
354
355      my $result = $session->get_request(
356         -varbindlist => [$research]
357         );
358      if (not defined $result or $result->{$research} eq 'noSuchInstance') {
359         $session->close;
360         next LOOP_ON_SWITCH;
361         }
362
363         my $swport = $result->{$research};
364         $session->close;
365
366         # IMPORTANT !!
367         # ceci empeche la detection sur certains port ...
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}}) {
375            next SWITCH_PORT_IGNORE if $swport ne get_numerical_port($sw->{model},$p);
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};
382            $ret{switch_port}        = get_human_readable_port($sw->{model}, $swport); # $swport;
383
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
401   for my $sw (@SWITCH) {
402      $SWITCH_PORT_COUNT{$sw->{hostname}} = {} if not exists $SWITCH_PORT_COUNT{$sw->{hostname}};
403      }
404
405   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($arp);
406   LOOP_ON_ALL_SWITCH:
407   for my $sw (@SWITCH) {
408      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
409      print "$error \n" if $error;
410
411      my $result = $session->get_request(
412         -varbindlist => [$research]
413         );
414
415      if(defined $result and $result->{$research} ne 'noSuchInstance'){
416         my $swport = $result->{$research};
417
418         $ret->{$sw->{hostname}} = {};
419         $ret->{$sw->{hostname}}{hostname}    = $sw->{hostname};
420         $ret->{$sw->{hostname}}{description} = $sw->{description};
421         $ret->{$sw->{hostname}}{port}        = get_human_readable_port($sw->{model}, $swport);
422
423         $SWITCH_PORT_COUNT{$sw->{hostname}}->{$swport}++;
424         }
425
426      $session->close;
427      }
428   return $ret;
429   }
430
431sub get_list_network {
432
433   return keys %{$KLASK_CFG->{network}};
434   }
435
436sub get_current_interface {
437   my $network = shift;
438
439   return $KLASK_CFG->{network}{$network}{interface};
440   }
441
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) {
450      my @line  = @{$KLASK_CFG->{network}{$net}{'ip-subnet'}};
451      for my $cmd (@line) {
452         for my $method (keys %{$cmd}){
453            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
454            }
455         }
456      }
457
458   my @res = ();
459
460   for my $cidr ($cidrlist->list()) {
461      my $net = new NetAddr::IP $cidr;
462      for my $ip (@{$net}) {
463         $ip =~ s{ /32 }{}xms;
464         push @res,  $ip;
465         }
466      }
467
468   return @res;
469   }
470
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) {
478      push @res, $KLASK_CFG->{network}{$net}{'main-router'};
479      }
480
481   return @res;
482   }
483
484sub get_human_readable_port {
485   my $sw_model = shift;
486   my $sw_port  = shift;
487
488   if ($sw_model eq 'HP8000M') {
489
490      my $reste = (($sw_port - 1) % 8) + 1;
491      my $major = int (($sw_port - 1) / 8);
492      return "$INTERNAL_PORT_MAP{$major}$reste";
493      }
494
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;
512   }
513
514sub get_numerical_port {
515   my $sw_model = shift;
516   my $sw_port  = shift;
517
518   if ($sw_model eq 'HP8000M') {
519
520      my $letter = substr $sw_port, 0, 1;
521      my $reste =  substr $sw_port, 1;
522
523      return $INTERNAL_PORT_MAP_REV{$letter} * 8 + $reste;
524      }
525
526   if ($sw_model eq 'HP2424M') {
527      if ($sw_port =~ m/^A/xms ) {
528
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;
545   }
546
547################
548# Les commandes
549################
550
551sub cmd_help {
552
553print <<'END';
554klask - ports manager and finder for switch
555
556 klask updatedb
557 klask exportdb
558
559 klask updatesw
560 klask exportsw
561
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
569   return;
570   }
571
572sub cmd_version {
573
574print <<'END';
575Klask - ports manager and finder for switch
576Copyright (C) 2005-2008 Gabriel Moreau
577
578END
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";
582   return;
583   }
584
585sub cmd_search {
586   my @computer = @_;
587
588   init_switch_names();    #nomme les switchs
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
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"
594         unless $where{switch_description} eq 'unknow' and $resol_arp{hostname_fq} eq 'unknow' and $resol_arp{mac_address} eq 'unknow';
595      }
596   return;
597   }
598
599sub cmd_searchdb {
600   my @computer = @_;
601
602   fastping(@computer);
603   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
604
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};
609
610      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
611
612      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
613      $year += 1900;
614      $mon++;
615      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
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      }
625   return;
626   }
627
628sub cmd_updatedb {
629   my @network = @_;
630      @network = get_list_network() if not @network;
631
632   test_switchdb_environnement();
633
634   my $computerdb = {};
635      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
636   my $timestamp = time;
637
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;
642   my $size_of_database   = keys %{$computerdb};
643      $size_of_database   = 1 if $size_of_database == 0;
644   my $i = 0;
645   my $detected_computer = 0;
646
647   init_switch_names('yes');    #nomme les switchs
648
649   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
650   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
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 = ();
654   for my $swport (keys %db_switch_connected_on_port) {
655      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
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}};
662         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{hostname}};
663         }
664#      print "$sw->{hostname} ++ @{$sw->{portignore}}\n";
665      }
666   }
667
668   my %router_mac_ip = ();
669   DETECT_ALL_ROUTER:
670#   for my $one_router ('194.254.66.254') {
671   for my $one_router ( get_list_main_router(@network) ) {
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++;
687
688         my $total_percent = int (($i*100)/$number_of_computer);
689
690         my $localtime = time - $timestamp;
691         my ($sec,$min) = localtime $localtime;
692
693         my $time_elapse = 0;
694            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
695         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
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);
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;
703
704         my %resol_arp = resolve_ip_arp_host($one_computer,$current_interface);
705
706         # do not search on router connection (why ?)
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
712         # do not search on switch inter-connection
713         if (exists $switch_level{$resol_arp{hostname_fq}}) {
714            $computer_not_detected{$one_computer} = $current_interface;
715            next LOOP_ON_COMPUTER;
716            }
717
718         my $switch_proposal = q{};
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
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
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
739#         if ($resol_arp{mac_address} eq 'unknow') {
740#            $computer_not_detected{$one_computer} = $current_interface;
741#            next LOOP_ON_COMPUTER;
742#            }
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,
754               network            => $net,
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},
769               network            => $net,
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};
775
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;
782
783         }
784      }
785
786   # final end of line at the end of the loop
787   printf "\n";
788
789   my $dirdb = $KLASK_DB_FILE;
790      $dirdb =~ s{ / [^/]* $}{}xms;
791   mkdir "$dirdb", 0755 unless -d "$dirdb";
792   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
793
794   for my $one_computer (keys %computer_not_detected) {
795      my $interface = $computer_not_detected{$one_computer};
796      system "arping -c 1 -w 1 -rR -i $interface $one_computer &>/dev/null";
797#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
798      }
799   return;
800   }
801
802sub cmd_removedb {
803   my @computer = @_;
804
805   test_maindb_environnement();
806
807   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
808
809   LOOP_ON_COMPUTER:
810   for my $one_computer (@computer) {
811
812      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
813            and exists $computerdb->{$one_computer} ) {
814         delete $computerdb->{$one_computer};
815         next;
816         }
817
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
823   my $dirdb = $KLASK_DB_FILE;
824      $dirdb =~ s{ / [^/]* $}{}xms;
825   mkdir "$dirdb", 0755 unless -d "$dirdb";
826   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
827   return;
828   }
829
830sub cmd_exportdb {
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};
845
846   $possible_format{$format}->(@ARGV);
847   return;
848   }
849
850sub cmd_exportdb_txt {
851   test_maindb_environnement();
852
853   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
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:
859   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
860
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';
868
869      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
870      $year += 1900;
871      $mon++;
872      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
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      }
882   return;
883   }
884
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
893   print <<'END_HTML';
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>
903   <th scope="col" class="sorttable_alpha">MAC-Address</th>
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>
919END_HTML
920
921   my %mac_count = ();
922   LOOP_ON_IP_ADDRESS:
923   foreach my $ip (keys %{$computerdb}) {
924
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 !
927
928      $mac_count{$computerdb->{$ip}{mac_address}}++;
929      }
930
931   my $typerow = 'even';
932
933   LOOP_ON_IP_ADDRESS:
934   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
935
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
939      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
940      $year += 1900;
941      $mon++;
942      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
943
944#      $odd_or_even++;
945#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
946      $typerow = $typerow eq 'even' ? 'odd' : 'even';
947
948      my $switch_hostname = $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description} || 'unkown';
949      chomp $switch_hostname;
950      my $switch_hostname_sort = sprintf '%s %3s' ,$switch_hostname, $computerdb->{$ip}{switch_port};
951
952      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
953
954      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{mac_address}}, $computerdb->{$ip}{mac_address};
955
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};
958
959      print <<"END_HTML";
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>
969END_HTML
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
981      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
982
983      $typerow = $typerow eq 'even' ? 'odd' : 'even';
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++;
994      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
995
996      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
997
998      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
999
1000      my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{switch}, 1), $db_switch_parent{$sw}->{port};
1001
1002      print <<"END_HTML";
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>
1012END_HTML
1013         }
1014      else {
1015         print <<"END_HTML";
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>
1025END_HTML
1026         }
1027      }
1028
1029   for my $swport (sort keys %db_switch_connected_on_port) {
1030      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1031      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1032
1033         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
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++;
1042      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1043
1044      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1045
1046      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1047
1048      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1049
1050         if (exists $db_switch_output_port{$sw}) {
1051
1052            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1053
1054            print <<"END_HTML";
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>
1064END_HTML
1065            }
1066         else {
1067            print <<"END_HTML";
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>
1077END_HTML
1078            }
1079         }
1080      }
1081
1082   print <<'END_HTML';
1083 </tbody>
1084</table>
1085END_HTML
1086   return;
1087   }
1088
1089sub cmd_iplocation {
1090   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1091
1092   LOOP_ON_IP_ADDRESS:
1093   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
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
1097      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1098      next if $sw_hostname eq 'unknow';
1099
1100      my $sw_location = q{};
1101      for my $sw (@SWITCH) {
1102         next if $sw_hostname ne $sw->{hostname};
1103         $sw_location = $sw->{location};
1104         last;
1105         }
1106
1107      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1108      }
1109   return;
1110   }
1111
1112sub cmd_enable {
1113   my $switch = shift;
1114   my $port   = shift;
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)
1118   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
1119   return;
1120   }
1121
1122sub cmd_disable {
1123   my $switch = shift;
1124   my $port   = shift;
1125
1126   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
1127   return;
1128   }
1129
1130sub cmd_status {
1131   my $switch = shift;
1132   my $port   = shift;
1133
1134   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
1135   return;
1136   }
1137
1138sub cmd_search_mac_on_switch {
1139   my $switch_name = shift || q{};
1140   my $mac_address = shift || q{};
1141
1142   if ($switch_name eq q{} or $mac_address eq q{}) {
1143      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1144      }
1145
1146   if (not defined $SWITCH_DB{$switch_name}) {
1147      die "Switch $switch_name must be defined in klask configuration file\n";
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;
1154   if (exists $sw->{version} and $sw->{version} eq '3') {
1155      $session{-username} = $sw->{username} || 'snmpadmin';
1156      }
1157   else {
1158      $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1159      }
1160
1161   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($mac_address);
1162   print "Klask search OID $research on switch $switch_name\n";
1163
1164   my ($session, $error) = Net::SNMP->session( %session );
1165   print "$error \n" if $error;
1166
1167   my $result = $session->get_request(
1168      -varbindlist => [$research]
1169      );
1170
1171   if (not defined $result or $result->{$research} eq 'noSuchInstance') {
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";
1180   return;
1181   }
1182
1183sub cmd_updatesw {
1184
1185   init_switch_names('yes');    #nomme les switchs
1186   print "\n";
1187
1188   my %where = ();
1189   my %db_switch_output_port = ();
1190   my %db_switch_ip_hostname = ();
1191
1192   DETECT_ALL_ROUTER:
1193#   for my $one_computer ('194.254.66.254') {
1194   for my $one_router ( get_list_main_router(get_list_network()) ) {
1195      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1196      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1197      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1198      }
1199
1200   ALL_ROUTER_IP_ADDRESS:
1201   for my $ip (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1202
1203      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip}; # /a priori/ idiot car ne sers à rien...
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';
1211
1212         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1213         }
1214      }
1215
1216   my %db_switch_link_with = ();
1217
1218   my @list_switch_ip = ();
1219   my @list_switch_ipv4 = ();
1220   for my $sw (@SWITCH){
1221      push @list_switch_ip, $sw->{hostname};
1222      }
1223
1224   my $timestamp = time;
1225
1226   ALL_SWITCH:
1227   for my $one_computer (@list_switch_ip) {
1228      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1229      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1230
1231      push @list_switch_ipv4,$resol_arp{ipv4_address};
1232
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};
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;
1240      }
1241
1242   ALL_SWITCH_IP_ADDRESS:
1243   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1244
1245      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1246
1247      DETECTED_SWITCH:
1248      for my $switch_detected ( keys %{$where{$ip}} ) {
1249
1250         next DETECTED_SWITCH if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostname{$ip}};
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      }
1263
1264   my %db_switch_connected_on_port = ();
1265   my $maybe_more_than_one_switch_connected = 'yes';
1266
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}}) {
1270
1271            my $port = $db_switch_link_with{$sw}->{$connect};
1272
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) {
1282
1283         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1284
1285         $maybe_more_than_one_switch_connected = 'yes';
1286
1287         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
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) {
1292
1293            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1294
1295            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1296
1297            for my $other_sw (@sw_on_same_port) {
1298               next if $other_sw eq $sw_connected;
1299
1300               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1301               }
1302
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}}) {
1313
1314         my $port = $db_switch_link_with{$sw}->{$connect};
1315
1316         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1317         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1318
1319         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1320         }
1321      }
1322
1323   print "Switch output port and parent port connection\n";
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";
1337   for my $swport (sort keys %db_switch_connected_on_port) {
1338      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
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,
1354      switch_db         => \%SWITCH_DB,
1355      };
1356
1357   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1358   return;
1359   }
1360
1361sub cmd_exportsw {
1362   my @ARGV   = @_;
1363
1364   test_switchdb_environnement();
1365
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};
1378
1379   $possible_format{$format}->(@ARGV);
1380   return;
1381   }
1382
1383sub cmd_exportsw_txt {
1384
1385   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1386
1387   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1388   my %db_switch_parent            = %{$switch_connection->{parent}};
1389   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1390
1391   print "Switch output port and parent port connection\n";
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";
1405   for my $swport (sort keys %db_switch_connected_on_port) {
1406      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
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      }
1416   return;
1417   }
1418
1419sub cmd_exportsw_dot {
1420
1421   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1422
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}};
1427   my %db_switch_global            = %{$switch_connection->{switch_db}};
1428
1429   my %db_building= ();
1430   for my $sw (@SWITCH) {
1431      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
1432      $db_building{$building} ||= {};
1433      $db_building{$building}->{$location} ||= {};
1434      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1435      }
1436
1437
1438   print "digraph G {\n";
1439
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";
1442
1443   my $b=0;
1444   for my $building (keys %db_building) {
1445      $b++;
1446
1447      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1448      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
1449
1450      my $l = 0;
1451      for my $loc (keys %{$db_building{$building}}) {
1452         $l++;
1453
1454         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
1455#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
1456         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
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
1462            my $swname  = $sw;
1463               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
1464            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
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";
1467
1468
1469            for my $swport (keys %db_switch_connected_on_port) {
1470               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
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";
1474               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1475              }
1476            }
1477         }
1478      }
1479
1480#   print "Switch output port and parent port connection\n";
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";
1494   for my $swport (sort keys %db_switch_connected_on_port) {
1495      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
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";
1507   return;
1508   }
1509
1510
1511__END__
1512
1513=head1 NAME
1514
1515klask - ports manager and finder for switch
1516
1517
1518=head1 USAGE
1519
1520 klask updatedb
1521 klask exportdb --format [txt|html]
1522
1523 klask updatesw
1524 klask exportsw --format [txt|dot]
1525
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
1536klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1537
1538Klask has now a web site dedicated for it !
1539
1540 http://servforge.legi.inpg.fr/projects/klask
1541
1542
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
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
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:
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
1607
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
1615 switch:
1616   - hostname: sw1.klask.local
1617     portignore:
1618       - 1
1619       - 2
1620
1621   - hostname: sw2.klask.local
1622     location: BatK / 2 / K203
1623     type: HP2424
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
1635 /var/cache/klask/switchdb
1636
1637=head1 SEE ALSO
1638
1639Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1640
1641
1642=head1 VERSION
1643
1644$Id: klask 67 2010-11-02 10:21:34Z g7moreau $
1645
1646
1647=head1 AUTHOR
1648
1649Written by Gabriel Moreau, Grenoble - France
1650
1651
1652=head1 LICENSE AND COPYRIGHT
1653
1654GPL version 2 or later and Perl equivalent
1655
1656Copyright (C) 2005-2009 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.