source: trunk/klask @ 62

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