source: trunk/klask @ 53

Last change on this file since 53 was 53, checked in by g7moreau, 15 years ago
  • Suppress perlcritic warning level 2 String delimiter used with "split" at line XXX, column XX. Express it as a regex instead.
  • 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 53 2009-07-23 23:01:13Z g7moreau $
6
7use strict;
8use warnings;
9
10use Readonly;
11use Filehandle;
12use Net::SNMP;
13#use YAML;
14use YAML::Syck;
15use Net::Netmask;
16use Net::CIDR::Lite;
17use NetAddr::IP;
18use Getopt::Long;
19
20# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
21# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
22# arping fping bind9-host arpwatch
23
24my $KLASK_VAR      = '/var/cache/klask';
25my $KLASK_CFG_FILE = '/etc/klask.conf';
26my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
27my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
28
29test_running_environnement();
30
31my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
32
33my %DEFAULT = %{ $KLASK_CFG->{default} };
34my @SWITCH  = @{ $KLASK_CFG->{switch}  };
35
36my %switch_level = ();
37my %SWITCH_DB    = ();
38LEVEL_OF_EACH_SWITCH:
39for my $sw (@SWITCH){
40   $switch_level{$sw->{hostname}} = $sw->{level} || $DEFAULT{switch_level}  || 2;
41   $SWITCH_DB{$sw->{hostname}} = $sw;
42   }
43@SWITCH = sort { $switch_level{$b->{hostname}} <=> $switch_level{$a->{hostname}} } @{$KLASK_CFG->{switch}};
44
45my %SWITCH_PORT_COUNT = ();
46
47my %CMD_DB = (
48   help       => \&cmd_help,
49   version    => \&cmd_version,
50   exportdb   => \&cmd_exportdb,
51   updatedb   => \&cmd_updatedb,
52   searchdb   => \&cmd_searchdb,
53   removedb   => \&cmd_removedb,
54   search     => \&cmd_search,
55   enable     => \&cmd_enable,
56   disable    => \&cmd_disable,
57   status     => \&cmd_status,
58   updatesw   => \&cmd_updatesw,
59   exportsw   => \&cmd_exportsw,
60   iplocation => \&cmd_iplocation,
61   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
62   );
63
64Readonly my %INTERNAL_PORT_MAP => (
65   0 => 'A',
66   1 => 'B',
67   2 => 'C',
68   3 => 'D',
69   4 => 'E',
70   5 => 'F',
71   6 => 'G',
72   7 => 'H',
73   );
74Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
75
76Readonly my %SWITCH_KIND => (
77   J3299A => { model => 'HP224M',     match => 'HP J3299A ProCurve Switch 224M'  },
78   J4120A => { model => 'HP1600M',    match => 'HP J4120A ProCurve Switch 1600M' },
79   J9029A => { model => 'HP1800-8G',  match => 'PROCURVE J9029A'                 },
80   J4093A => { model => 'HP2424M',    match => 'HP J4093A ProCurve Switch 2424M' },
81   J4813A => { model => 'HP2524',     match => 'HP J4813A ProCurve Switch 2524'  },
82   J4900A => { model => 'HP2626A',    match => 'HP J4900A ProCurve Switch 2626'  },
83   J4900B => { model => 'HP2626B',    match => 'J4900B.+?Switch 2626'            },# ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
84   J4899B => { model => 'HP2650',     match => 'ProCurve J4899B Switch 2650'     },
85   J9021A => { model => 'HP2810-24G', match => 'ProCurve J9021A Switch 2810-24G' },
86   J9022A => { model => 'HP2810-48G', match => 'ProCurve J9022A Switch 2810-48G' },
87   J4903A => { model => 'HP2824',     match => 'J4903A.+?Switch 2824,'           },
88   J4110A => { model => 'HP8000M',    match => 'HP J4110A ProCurve Switch 8000M' },
89   BS350T => { model => 'BS350T',     match => 'BayStack 350T HW'                },
90   );
91
92Readonly my %OID_NUMBER => (
93   sysDescription  => '1.3.6.1.2.1.1.1.0',
94   sysName         => '1.3.6.1.2.1.1.5.0',
95   sysContact      => '1.3.6.1.2.1.1.4.0',
96   sysLocation     => '1.3.6.1.2.1.1.6.0',
97   );
98
99Readonly 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;
100Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
101
102
103################
104# principal
105################
106
107my $cmd = shift @ARGV || 'help';
108if (defined $CMD_DB{$cmd}) {
109   $CMD_DB{$cmd}->(@ARGV);
110   }
111else {
112   print STDERR "klask: command $cmd not found\n\n";
113   $CMD_DB{help}->();
114   exit 1;
115   }
116
117exit;
118
119sub test_running_environnement {
120   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
121   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
122   return;
123   }
124
125sub test_switchdb_environnement {
126   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
127   return;
128   }
129
130sub test_maindb_environnement {
131   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
132   return;
133   }
134
135###
136# fast ping dont l'objectif est de remplir la table arp de la machine
137sub fastping {
138   system "fping -c 1 @_ >/dev/null 2>&1";
139   return;
140   }
141
142sub shell_command {
143   my $cmd = shift;
144
145   my $fh = new Filehandle;
146   open $fh, '-|', "$cmd" or die "Can't exec $cmd\n";
147   my $result = <$fh>;
148   close $fh;
149   chomp $result;
150   return $result;
151   }
152
153###
154# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
155sub resolve_ip_arp_host {
156   my $param_ip_or_host = shift;
157   my $interface = shift || '*';
158   my $type      = shift || 'fast';
159
160   my %ret = (
161      hostname_fq  => 'unknow',
162      ipv4_address => '0.0.0.0',
163      mac_address  => 'unknow',
164      );
165
166#   my $cmdarping  = `arping -c 1 -w 1 -rR $param 2>/dev/null`;
167   if ( not $param_ip_or_host =~ m/^\d+ \. \d+ \. \d+ \. \d+$/x ) {
168      $param_ip_or_host =~ s/ \. .* //x;
169      }
170
171   # controler que arpwatch tourne !
172   # resultat de la commande arpwatch
173   # /var/lib/arpwatch/arp.dat
174   # 0:13:d3:e1:92:d0        192.168.24.109  1163681980      theo8sv109
175   # my $cmd = "grep  -e '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/arp.dat | sort +2rn | head -1";
176   # my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/*.dat | sort +2rn | head -1";
177   my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
178   my $cmd_arpwatch = shell_command $cmd;
179   my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
180
181   $ret{ipv4_address} = $ip        if $ip;
182   $ret{mac_address}  = $arp       if $arp;
183   $ret{timestamp}    = $timestamp if $timestamp;
184
185   my $nowtimestamp = time();
186
187   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 3 * 3600 ) ) ) {
188      $ret{mac_address} = 'unknow';
189      return %ret;
190      }
191
192   # resultat de la commande arp
193   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
194   # sw2-batF0-legi.hmg.priv (192.168.22.112) at 00:30:c1:76:9c:01 [ether] on eth0.37
195   my $cmd_arp  = shell_command "arp -a $param_ip_or_host";
196   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
197      ( $ret{hostname_fq}, $ret{ipv4_address}, $ret{mac_address} )  = ($1, $2, $3);
198      }
199
200   # resultat de la commande host si le parametre est ip
201   # 250.66.254.194.in-addr.arpa domain name pointer legihp2100.hmg.inpg.fr.
202   my $cmd_host = shell_command "host $param_ip_or_host";
203   if ( $cmd_host =~ m/domain \s name \s pointer \s (\S+) \.$/xms ) {
204      $ret{hostname_fq} = $1;
205      }
206
207   # resultat de la commande host si parametre est hostname
208   # tech7meylan.hmg.inpg.fr has address 194.254.66.240
209   if ( $cmd_host =~ m/(\S*) \s has \s address \s ( $RE_IPv4_ADDRESS )$/xms ) {
210      ( $ret{hostname_fq}, $ret{ipv4_address} ) = ($1, $2);
211      }
212
213   if ( $cmd_host =~ m/ \b ( $RE_IPv4_ADDRESS ) \. in-addr \. arpa \s/xms ) {
214      $ret{ipv4_address} = $1;
215      }
216   #$ret{hostname_fq}  = $param_ip_or_host if not defined $1 and $ret{hostname_fq} eq 'unknow';
217
218   unless ($ret{mac_address} eq 'unknow') {
219      my @paquets = ();
220      foreach ( split m/ : /x, $ret{mac_address} ) {
221         my @chars = split m//xms, uc("00$_");
222         push @paquets, "$chars[-2]$chars[-1]";
223         }
224      $ret{mac_address} = join ':', @paquets;
225      }
226
227   return %ret;
228   }
229
230# Find Surname of a switch
231sub get_switch_model {
232   my $sw_snmp_description = shift || 'unknow';
233
234   for my $sw_kind (keys %SWITCH_KIND) {
235      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/x;
236
237      return $SWITCH_KIND{$sw_kind}->{model};
238      }
239
240   return $sw_snmp_description;
241   }
242
243###
244# va rechercher le nom des switchs pour savoir qui est qui
245sub init_switch_names {
246   my $verbose = shift;
247
248   printf "%-25s                %-25s %s\n",'Switch','Description','Type';
249#   print "Switch description\n" if $verbose;
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 = '';
316   foreach(@paquets) {
317      $return .= ".".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 || '';
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 '') {
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 }{}x;
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: 53 $'."\n";
539   print ' $Date: 2009-07-23 23:01:13 +0000 (Thu, 23 Jul 2009) $'."\n";
540   print ' $Id: klask 53 2009-07-23 23:01:13Z 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 = '';
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{ / [^/]* $}{}x;
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{ / [^/]* $}{}x;
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+ $/x;
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} || '';
1051      next if $sw_hostname eq 'unknow';
1052
1053      my $sw_location = '';
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 '';
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 || '';
1093   my $mac_address = shift || '';
1094
1095   if ($switch_name eq '' or $mac_address eq '') {
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,'*','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,'*','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".'/'.join('\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 SYNOPSIS
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 53 2009-07-23 23:01:13Z g7moreau $
1598
1599
1600=head1 AUTHOR
1601
1602Written by Gabriel Moreau, Grenoble - France
1603
1604
1605=head1 COPYRIGHT
1606
1607Copyright (C) 2005-2009 Gabriel Moreau.
1608
1609
1610=head1 LICENCE
1611
1612GPL version 2 or later and Perl equivalent
1613
Note: See TracBrowser for help on using the repository browser.