source: trunk/klask @ 50

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