source: trunk/klask @ 11

Last change on this file since 11 was 11, checked in by g7moreau, 16 years ago
  • Change many global variable to uppercase letters
  • Property svn:executable set to *
File size: 39.7 KB
Line 
1#!/usr/bin/perl -w
2
3use strict;
4use warnings;
5
6use Net::SNMP;
7use YAML qw(:all);
8use Net::Netmask;
9use Net::CIDR::Lite;
10use NetAddr::IP;
11
12# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
13# libcrypt-des-perl libcrypt-hcesha-perl   libdigest-hmac-perl
14
15my $KLASK_DB  = '/var/cache/klask/klaskdb';
16my $KLASK_SW  = '/var/cache/klask/switchdb';
17my $KLASK_CFG = '/etc/klask.conf';
18
19my $SWITCH_DB = YAML::LoadFile("$KLASK_CFG");
20
21my %DEFAULT = %{$SWITCH_DB->{default}};
22my @SWITCH  = @{$SWITCH_DB->{switch}};
23
24my %switch_level = ();
25LEVEL_OF_EACH_SWITCH:
26for my $sw (@SWITCH){
27   $switch_level{$sw->{hostname}} = $sw->{level} || $DEFAULT{switch_level}  || 2;
28   }
29@SWITCH = sort { $switch_level{$b->{hostname}} <=> $switch_level{$a->{hostname}} } @{$SWITCH_DB->{switch}}; 
30
31my %switch_port_count = ();
32
33my %CMD_DB = (
34   help       => \&cmd_help,
35   exportdb   => \&cmd_exportdb,
36   updatedb   => \&cmd_updatedb,
37   searchdb   => \&cmd_searchdb,
38   removedb   => \&cmd_removedb,
39   search     => \&cmd_search,
40   enable     => \&cmd_enable,
41   disable    => \&cmd_disable,
42   status     => \&cmd_status,
43   updatesw   => \&cmd_updatesw,
44   exportsw   => \&cmd_exportsw,
45   dotsw      => \&cmd_exportsw_dot,
46   iplocation => \&cmd_iplocation,
47   );
48
49my %INTERNAL_PORT_MAP = (
50   0 => 'A',
51   1 => 'B',
52   2 => 'C',
53   3 => 'D',
54   4 => 'E',
55   5 => 'F',
56   6 => 'G',
57   7 => 'H',
58   );
59my %INTERNAL_PORT_MAP_REV = reverse %INTERNAL_PORT_MAP;
60
61
62################
63# principal
64################
65
66my $cmd = shift @ARGV || 'help';
67if (defined $CMD_DB{$cmd}) {
68   $CMD_DB{$cmd}->(@ARGV);
69   }
70else {
71   print STDERR "klask: command $cmd not found\n\n";
72   $CMD_DB{help}->();
73   exit 1;
74   }
75
76exit;
77
78###
79# fast ping dont l'objectif est de remplir la table arp de la machine
80sub fastping {
81   system "fping -c 1 @_ >/dev/null 2>&1";
82   }
83
84###
85# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
86sub resolve_ip_arp_host {
87   my $param_ip_or_host = shift;
88   my $interface = shift || '*';
89   my $type      = shift || 'fast';
90
91   my %ret = (
92      hostname_fq  => 'unknow',
93      ipv4_address => '0.0.0.0',
94      mac_address  => 'unknow',
95      );
96
97#   my $cmdarping  = `arping -c 1 -w 1 -rR $param 2>/dev/null`;
98
99   # controler que arpwatch tourne !
100   # resultat de la commande arpwatch
101   # /var/lib/arpwatch/arp.dat
102   # 0:13:d3:e1:92:d0        192.168.24.109  1163681980      theo8sv109
103   #my $cmd = "grep  -e '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/arp.dat | sort +2rn | head -1";
104#   my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/*.dat | sort +2rn | head -1";
105   my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/$interface.dat | sort +2rn | head -1";
106   my $cmd_arpwatch = `$cmd`;
107   chomp $cmd_arpwatch;
108   my ($arp, $ip, $timestamp, $host) = split /\s+/, $cmd_arpwatch;
109#print "OOO $cmd\n";
110#print "TTT arp $arp -> $ip pour host $host\n";
111   $ret{ipv4_address} = $ip        if $ip;
112   $ret{mac_address}  = $arp       if $arp;
113   $ret{timestamp}    = $timestamp if $timestamp;
114
115   my $nowtimestamp = time();
116
117   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 3 * 3600 ) ) ) {
118      $ret{mac_address} = 'unknow';
119      return %ret;
120      }
121
122  # resultat de la commande arp
123   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
124   my $cmd_arp  = `arp -a $param_ip_or_host 2>/dev/null`;
125   chomp $cmd_arp;
126   $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})/;
127   $ret{hostname_fq}  = $1 if(defined($1));
128   $ret{ipv4_address} = $2 if(defined($2));
129   $ret{mac_address}  = $3 if(defined($3));
130
131#   if ($ret{ipv4_address} eq '0.0.0.0' and $ret{mac_address} eq 'unknow'and $ret{hostname_fq} eq 'unknow') {
132      # resultat de la commande host si le parametre est ip
133      # 250.66.254.194.in-addr.arpa domain name pointer legihp2100.hmg.inpg.fr.
134      my $cmd_host = `host $param_ip_or_host 2>/dev/null`;
135      chomp $cmd_host;
136      $cmd_host =~ m/domain\sname\spointer\s(\S+)\.$/;
137      $ret{hostname_fq} = $1 if defined $1;
138
139      # resultat de la commande host si parametre est hostname
140      # tech7meylan.hmg.inpg.fr has address 194.254.66.240
141      $cmd_host =~ m/\shas\saddress\s([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/;
142      $ret{ipv4_address} = $1 if defined $1;
143
144      $cmd_host =~ m/\b([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.in-addr\.arpa\s/;
145      $ret{ipv4_address} = "$4.$3.$2.$1"     if defined $1 and  defined $2 and  defined $3 and  defined $4;
146      $ret{hostname_fq}  = $param_ip_or_host if not defined $1 and $ret{hostname_fq} eq 'unknow';
147#      }
148
149   unless ($ret{mac_address} eq 'unknow') {
150      my @paquets = ();
151      foreach ( split(/:/, $ret{mac_address}) ) {
152         my @chars = split //, uc("00$_");
153         push @paquets, "$chars[-2]$chars[-1]";
154         }
155      $ret{mac_address} = join ':', @paquets;
156      }
157
158   return %ret;
159   }
160
161###
162# va rechercher le nom des switchs pour savoir qui est qui
163sub init_switch_names {
164   my $verbose = shift;
165   
166   print "Switch description\n" if $verbose;
167   print "------------------\n" if $verbose;
168
169   INIT_EACH_SWITCH:
170   for my $sw (@SWITCH) {
171#print "$sw->{hostname} \n";
172      my %session = ( -hostname   => $sw->{hostname} );
173         $session{-version} = $sw->{version}   || 1;
174         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
175         if (exists $sw->{version} and $sw->{version} eq 3) {
176            $session{-username} = $sw->{username} || 'snmpadmin';
177#print "$sw->{hostname}  -- $session{-username}  \n";
178#for (keys %session) {
179#print "++ $_ -> $session{$_}\n";
180#}
181            }
182         else {
183            $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
184            }
185
186      $sw->{local_session} = \%session;
187
188      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
189#         -hostname   => $sw->{hostname},
190#         -version    => $sw->{version}   || 1,
191#         -community  => $sw->{community} || $DEFAULT{community} || 'public',
192#         -username   => $sw->{username}  || 'snmpadmin',
193#         -port       => $sw->{snmpport}  || $DEFAULT{snmpport}  || 161,
194#         );
195#         if (exists $sw->{version} and $sw->{version} eq 3) {
196print "$error \n" if $error;
197#}
198      my $result = $session->get_request(
199         -varbindlist => ['1.3.6.1.2.1.1.5.0']
200         );
201      $sw->{description} = $result->{"1.3.6.1.2.1.1.5.0"} || $sw->{hostname};
202      #$sw->{location} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{hostname};
203      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{hostname};
204      $session->close;
205 
206      my ($desc, $type) = split ':', $sw->{description}, 2;
207      printf "%-25s 0--------->>>> %-25s %s\n", $sw->{hostname}, $desc, uc($type) if $verbose;
208      }
209
210   print "\n" if $verbose;
211   }
212
213###
214# convertit l'hexa (uniquement 2 chiffres) en decimal
215sub hex_to_dec {
216   #00:0F:1F:43:E4:2B
217   my $car = '00' . uc(shift);
218
219   return '00' if $car eq '00UNKNOW';
220   my %table = (
221      "0"=>"0",  "1"=>"1",  "2"=>"2",  "3"=>"3",  "4"=>"4",  "5"=>"5", "6"=>"6", "7"=>"7", "8"=>"8", "9"=>"9",
222      "A"=>"10", "B"=>"11", "C"=>"12", "D"=>"13", "E"=>"14", "F"=>"15"
223      );
224   my @chars = split(//, $car);
225   return $table{$chars[-2]}*16 + $table{$chars[-1]};
226   }
227
228###
229# convertit l'@ arp en decimal
230sub arp_hex_to_dec {
231   #00:0F:1F:43:E4:2B
232   my $arp = shift;
233
234   my @paquets = split /:/, $arp;
235   my $return = '';
236   foreach(@paquets) {
237      $return .= ".".hex_to_dec($_);
238      }
239   return $return;
240   }
241
242###
243# va rechercher le port et le switch sur lequel est la machine
244sub find_switch_port {
245   my $arp = shift;
246   my $switch_proposal = shift || '';
247   
248   my %ret;
249   $ret{switch_description} = "unknow";
250   $ret{switch_port} = "0";
251
252   return %ret if $arp eq 'unknow';;
253
254   my @SWITCH_search = @SWITCH;
255   if ($switch_proposal ne '') {
256      for my $sw (@SWITCH) {
257         next if $sw->{hostname} ne $switch_proposal;
258         unshift @SWITCH_search, $sw;
259         last;
260         }
261      }
262
263   my $research = "1.3.6.1.2.1.17.4.3.1.2".arp_hex_to_dec($arp);
264   
265   LOOP_ON_SWITCH:
266   for my $sw (@SWITCH_search) {
267      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
268print "$error \n" if $error;
269#         -hostname   => $sw->{hostname},
270#         -community  => $sw->{community} || $DEFAULT{community} || 'public',
271#         -port       => $sw->{snmpport}  || $DEFAULT{snmpport}  || 161
272#         );
273#print "$sw->{hostname} --  $research \n";
274      my $result = $session->get_request(
275         -varbindlist => [$research]
276         );
277#      if(defined($result)) {
278      if (not defined($result) or $result->{$research} eq 'noSuchInstance') {
279#print "$sw->{hostname} --  $research --".$session->error()."\n";
280         $session->close;
281         next LOOP_ON_SWITCH;
282         }
283
284         my $swport = $result->{$research};
285         $session->close;
286
287         # IMPORTANT !!
288         # ceci empeche la detection sur certains port ...
289         # en effet les switch sont relies entre eux par un cable reseau et du coup
290         # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
291         # cette partie est a ameliore, voir a configurer dans l'entete
292         # 21->24 45->48
293#         my $flag = 0;
294         SWITCH_PORT_IGNORE:
295         foreach my $p (@{$sw->{portignore}}) {
296            next SWITCH_PORT_IGNORE if $swport ne get_numerical_port($sw->{hostname},$p);
297#            $flag = 1;
298            next LOOP_ON_SWITCH;
299            }
300#         if ($flag == 0) {
301            $ret{switch_hostname}    = $sw->{hostname};
302            $ret{switch_description} = $sw->{description};
303            $ret{switch_port}        = get_human_readable_port($sw->{hostname}, $swport); # $swport;
304           
305            last LOOP_ON_SWITCH;
306#            }
307#         }
308#      $session->close;
309      }
310   return %ret;
311   }
312
313###
314# va rechercher les port et les switch sur lequel est la machine
315sub find_all_switch_port {
316   my $arp = shift;
317
318   my $ret = {};
319#   $ret->{unknow} = {};
320#   $ret->{unknow}{description} = "unknow";
321#   $ret->{unknow}{port} = "0";
322
323   return $ret if $arp eq 'unknow';
324
325   for my $sw (@SWITCH) {
326      $switch_port_count{$sw->{hostname}} = {} if not exists $switch_port_count{$sw->{hostname}};
327#      $sw->{_port} = {} if not exists $sw->{_port};
328      }
329
330   my $research = "1.3.6.1.2.1.17.4.3.1.2".arp_hex_to_dec($arp);
331#print " $arp -> $research\n";
332   LOOP_ON_ALL_SWITCH:
333   for my $sw (@SWITCH) {
334      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
335print "$error \n" if $error;
336#         -hostname   => $sw->{hostname},
337#         -community  => $sw->{community} || $DEFAULT{community} || 'public',
338#         -port       => $sw->{snmpport}  || $DEFAULT{snmpport}  || 161
339#         );
340#print "$sw->{hostname} --  $research \n";
341      my $result = $session->get_request(
342         -varbindlist => [$research]
343         );
344      if(defined($result) and $result->{$research} ne 'noSuchInstance'){
345#print "+++ $sw->{hostname} -> $result->{$research}\n" ;
346         my $swport = $result->{$research};
347
348         $ret->{$sw->{hostname}} = {};
349         $ret->{$sw->{hostname}}{hostname}    = $sw->{hostname};
350         $ret->{$sw->{hostname}}{description} = $sw->{description};
351         $ret->{$sw->{hostname}}{port}        = get_human_readable_port($sw->{hostname}, $swport);
352
353         $switch_port_count{$sw->{hostname}}->{$swport}++;
354#         $sw->{_port}{$swport}++;
355         }
356      else {
357#print "$sw->{hostname} --  $research --".$session->error()."\n";
358         }
359
360      $session->close;
361      }
362#print "end \n";
363   return $ret;
364   }
365
366sub get_list_network {
367
368   return keys %{$SWITCH_DB->{network}};
369   }
370
371sub get_current_interface {
372   my $network = shift;
373
374   return $SWITCH_DB->{network}{$network}{interface};
375   }
376 
377###
378# liste l'ensemble des adresses ip d'un réseau
379sub get_list_ip {
380   my @network = @_;
381
382   my $cidrlist = Net::CIDR::Lite->new;
383
384   for my $net (@network) {
385      my @line  = @{$SWITCH_DB->{network}{$net}{'ip-subnet'}};
386      for my $cmd (@line) {
387         for my $method (keys %$cmd){
388            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
389            }
390         }
391      }
392
393   my @res = ();
394
395   for my $cidr ($cidrlist->list()) {
396      my $net = new NetAddr::IP $cidr;
397      for my $ip (@$net) {
398         $ip =~ s#/32##;
399         push @res,  $ip;
400         }
401      }
402
403   return @res;
404   }
405
406# liste l'ensemble des routeurs du réseau
407sub get_list_main_router {
408   my @network = @_;
409
410   my @res = ();
411
412   for my $net (@network) {
413      push @res, $SWITCH_DB->{network}{$net}{'main-router'};
414      }
415
416   return @res;
417   }
418
419sub get_human_readable_port {
420   my $sw = shift;
421   my $port = shift;
422   
423   return $port if not $sw eq 'sw8000-batA.hmg.priv';
424   
425   my $reste = (($port - 1) % 8) + 1;
426   my $major = int( ($port - 1) / 8 );
427
428   return "$INTERNAL_PORT_MAP{$major}$reste";
429   }
430
431sub get_numerical_port {
432   my $sw   = shift;
433   my $port = shift;
434   
435   return $port if not $sw eq 'sw8000-batA.hmg.priv';
436
437   my $letter = substr($port, 0, 1);
438   
439#   return $port if $letter =~ m/\d/;
440   
441   my $reste =  substr($port, 1);
442   
443   return $INTERNAL_PORT_MAP_REV{$letter} * 8 + $reste;
444   }
445
446################
447# Les commandes
448################
449
450sub cmd_help {
451
452print <<END;
453klask - ports manager and finder for switch
454
455 klask updatedb
456 klask exportdf
457
458 klask searchdb computer
459 klask search   computer
460
461 klask enable  switch port
462 klask disable switch port
463 klask status  switch port
464END
465   }
466
467sub cmd_search {
468   my @computer = @_;
469   
470   init_switch_names();    #nomme les switchs
471   fastping(@computer);
472   for my $clientname (@computer) {
473      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
474      my %where     = find_switch_port($resol_arp{mac_address}); #retrouve l'emplacement
475      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"
476         unless $where{switch_description} eq 'unknow' and $resol_arp{hostname_fq} eq 'unknow' and $resol_arp{mac_address} eq 'unknow';
477      }
478   }
479
480sub cmd_searchdb {
481   my @computer = @_;
482
483   fastping(@computer);
484   my $computerdb = YAML::LoadFile("$KLASK_DB");
485   
486   LOOP_ON_COMPUTER:
487   for my $clientname (@computer) {
488      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
489      my $ip = $resol_arp{ipv4_address};
490     
491      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
492     
493      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($computerdb->{$ip}{timestamp});
494      $year += 1900;
495      $mon++;
496      my $date = sprintf "%04i-%02i-%02i %02i:%02i", $year,$mon,$mday,$hour,$min;
497
498      printf "%-22s %2s %-30s %-15s %-18s %s\n",
499         $computerdb->{$ip}{switch_name},
500         $computerdb->{$ip}{switch_port},
501         $computerdb->{$ip}{hostname_fq},
502         $ip,
503         $computerdb->{$ip}{mac_address},
504         $date;
505      }
506   }
507
508sub cmd_updatedb {
509   my @network = @_;
510      @network = get_list_network() if not @network;
511
512   my $computerdb = YAML::LoadFile("$KLASK_DB");
513   my $timestamp = time;
514   
515   my %computer_not_detected = ();
516   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
517
518   my $number_of_computer = get_list_ip(@network); # + 1;
519   my $size_of_database   = keys %$computerdb;
520   my $i = 0;
521   my $detected_computer = 0;
522   
523   init_switch_names('yes');    #nomme les switchs
524
525   my %router_mac_ip = ();
526   DETECT_ALL_ROUTER:
527#   for my $one_router ('194.254.66.254') {
528   for my $one_router ( get_list_main_router(@network) ) {
529      my %resol_arp = resolve_ip_arp_host($one_router);
530      $router_mac_ip{ $resol_arp{mac_address} } = $resol_arp{ipv4_address};
531      }
532
533   ALL_NETWORK:
534   for my $net (@network) {
535
536      my @computer = get_list_ip($net);
537      my $current_interface = get_current_interface($net);
538
539      fastping(@computer);
540
541      LOOP_ON_COMPUTER:
542      for my $one_computer (@computer) {
543         $i++;
544         
545         my $total_percent = int(($i*100)/$number_of_computer);
546
547         my $localtime = time - $timestamp;
548         my ($sec,$min) = localtime($localtime);
549
550         my $time_elapse = 0;
551            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
552         my ($sec_elapse,$min_elapse) = localtime($time_elapse);
553
554         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
555#         printf ", Computer detected: %4i/%i (%2i%%)", $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
556         printf ", detected: %4i/%i (%2i%%)", $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
557         printf " [Time: %02i:%02i / %02i:%02i]", int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
558#         printf "  [%02i:%02i/%02i:%02i]", int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
559         printf " %-14s", $one_computer;
560
561         my %resol_arp = resolve_ip_arp_host($one_computer,$current_interface);
562         
563         # do not search on router connection (why ?)
564         if ( exists $router_mac_ip{$resol_arp{mac_address}}) {
565            $computer_not_detected{$one_computer} = $current_interface;
566            next LOOP_ON_COMPUTER;
567            }
568
569         # do not search on switch inter-connection
570         if (exists $switch_level{$resol_arp{hostname_fq}}) {
571            $computer_not_detected{$one_computer} = $current_interface;
572            next LOOP_ON_COMPUTER;
573            }
574
575         my $switch_proposal = '';
576         if (exists $computerdb->{$resol_arp{ipv4_address}} and exists $computerdb->{$resol_arp{ipv4_address}}{switch_hostname}) {
577            $switch_proposal = $computerdb->{$resol_arp{ipv4_address}}{switch_hostname};
578            }
579
580         # do not have a mac address
581         if ($resol_arp{mac_address} eq 'unknow' or (exists $resol_arp{timestamps} and $resol_arp{timestamps} < ($timestamp - 3 * 3600))) {
582            $computer_not_detected{$one_computer} = $current_interface;
583            next LOOP_ON_COMPUTER;
584            }
585
586         my %where = find_switch_port($resol_arp{mac_address},$switch_proposal);
587
588         #192.168.24.156:
589         #  arp: 00:0B:DB:D5:F6:65
590         #  hostname: pcroyon.hmg.priv
591         #  port: 5
592         #  switch: sw-batH-legi:hp2524
593         #  timestamp: 1164355525
594
595         # do not have a mac address
596#         if ($resol_arp{mac_address} eq 'unknow') {
597#            $computer_not_detected{$one_computer} = $current_interface;
598#            next LOOP_ON_COMPUTER;
599#            }
600
601         # detected on a switch
602         if ($where{switch_description} ne 'unknow') {
603            $detected_computer++;
604            $computerdb->{$resol_arp{ipv4_address}} = {
605               hostname_fq        => $resol_arp{hostname_fq},
606               mac_address        => $resol_arp{mac_address},
607               switch_hostname    => $where{switch_hostname},
608               switch_description => $where{switch_description},
609               switch_port        => $where{switch_port},
610               timestamp          => $timestamp,
611               };
612            next LOOP_ON_COMPUTER;
613            }
614
615         # new in the database but where it is ?
616         if (not exists $computerdb->{$resol_arp{ipv4_address}}) {
617            $detected_computer++;
618            $computerdb->{$resol_arp{ipv4_address}} = {
619               hostname_fq        => $resol_arp{hostname_fq},
620               mac_address        => $resol_arp{mac_address},
621               switch_hostname    => $where{switch_hostname},
622               switch_description => $where{switch_description},
623               switch_port        => $where{switch_port},
624               timestamp          => $resol_arp{timestamp},
625               };
626            }
627
628         # mise a jour du nom de la machine si modification dans le dns
629         $computerdb->{$resol_arp{ipv4_address}}{hostname_fq} = $resol_arp{hostname_fq};
630       
631         # mise à jour de la date de détection si détection plus récente par arpwatch
632         $computerdb->{$resol_arp{ipv4_address}}{timestamp}   = $resol_arp{timestamp} if exists $resol_arp{timestamp} and $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $resol_arp{timestamp};
633
634         # provisoire car changement de nom des attributs
635#         $computerdb->{$resol_arp{ipv4_address}}{mac_address}        = $computerdb->{$resol_arp{ipv4_address}}{arp};
636#         $computerdb->{$resol_arp{ipv4_address}}{switch_description} = $computerdb->{$resol_arp{ipv4_address}}{switch};
637#         $computerdb->{$resol_arp{ipv4_address}}{switch_port}        = $computerdb->{$resol_arp{ipv4_address}}{port};
638       
639         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
640#         push @computer_not_detected, $resol_arp{ipv4_address} if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
641         $computer_not_detected{$resol_arp{ipv4_address}} = $current_interface if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
642       
643         }
644      }
645
646   # final end of line at the end of the loop
647   printf "\n";
648
649   my $dirdb = $KLASK_DB;
650      $dirdb =~ s#/[^/]*$##;
651   mkdir "$dirdb", 0755 unless -d "$dirdb";
652   YAML::DumpFile("$KLASK_DB", $computerdb);
653
654   for my $one_computer (keys %computer_not_detected) {
655      my $interface = $computer_not_detected{$one_computer};
656      system "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null";
657#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
658      }
659   }
660
661sub cmd_removedb {
662   my @computer = @_;
663   
664   my $computerdb = YAML::LoadFile("$KLASK_DB");
665
666   LOOP_ON_COMPUTER:
667   for my $one_computer (@computer) {
668
669      my %resol_arp = resolve_ip_arp_host($one_computer);
670
671      delete $computerdb->{$resol_arp{ipv4_address}} if exists $computerdb->{$resol_arp{ipv4_address}};
672      }
673
674   my $dirdb = $KLASK_DB;
675      $dirdb =~ s#/[^/]*$##;
676   mkdir "$dirdb", 0755 unless -d "$dirdb";
677   YAML::DumpFile("$KLASK_DB", $computerdb);
678   }
679
680sub cmd_exportdb {
681   my $computerdb = YAML::LoadFile("$KLASK_DB");
682
683   printf "%-24s %-4s            %-30s %-15s %-18s %-s\n", qw(Switch Port Hostname IPv4-Address MAC-Address Date);
684   print "---------------------------------------------------------------------------------------------------------------------------\n";
685
686   LOOP_ON_IP_ADDRESS:
687   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %$computerdb)) {
688   
689#      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq 'unknow';
690
691      # to be improve in the future
692      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
693
694# dans le futur
695#      next if $computerdb->{$ip}{hostname_fq} eq 'unknow';
696     
697      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($computerdb->{$ip}{timestamp});
698      $year += 1900;
699      $mon++;
700      my $date = sprintf "%04i-%02i-%02i %02i:%02i", $year,$mon,$mday,$hour,$min;
701
702      printf "%-25s  %2s  <-------  %-30s %-15s %-18s %s\n",
703         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
704         $computerdb->{$ip}{switch_port},
705         $computerdb->{$ip}{hostname_fq},
706         $ip,
707         $computerdb->{$ip}{mac_address},
708         $date;
709      }
710   }
711
712sub cmd_iplocation {
713   my $computerdb = YAML::LoadFile("$KLASK_DB");
714
715   LOOP_ON_IP_ADDRESS:
716   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %$computerdb)) {
717
718      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
719
720      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || '';
721      next if $sw_hostname eq 'unknow';
722 
723      my $sw_location = '';
724      for my $sw (@SWITCH) {
725         next if $sw_hostname ne $sw->{hostname};
726         $sw_location = $sw->{location};
727         last;
728         }
729
730      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq '';
731      }
732   }
733
734sub cmd_enable {
735   my $switch = shift;
736   my $port   = shift;
737   
738   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
739   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
740   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
741   }
742
743sub cmd_disable {
744   my $switch = shift;
745   my $port   = shift;
746   
747   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
748   }
749
750sub cmd_status {
751   my $switch = shift;
752   my $port   = shift;
753   
754   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
755   }
756
757
758sub cmd_updatesw {
759
760   init_switch_names('yes');    #nomme les switchs
761   print "\n";
762
763   my %where = ();
764   my %db_switch_output_port = ();
765   my %db_switch_ip_hostname = ();
766
767   DETECT_ALL_ROUTER:
768#   for my $one_computer ('194.254.66.254') {
769   for my $one_router ( get_list_main_router(get_list_network()) ) {
770      my %resol_arp = resolve_ip_arp_host($one_router,'*','low');            #resolution arp
771      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
772     
773      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); #retrouve les emplacements
774      }
775
776   ALL_ROUTER_IP_ADDRESS:
777   for my $ip (Net::Netmask::sort_by_ip_address('194.254.66.254')) {
778   
779      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip};
780
781      ALL_SWITCH_CONNECTED:
782      for my $switch_detected ( keys %{$where{$ip}} ) {
783
784         my $switch = $where{$ip}->{$switch_detected};
785
786         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
787         
788         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
789         }
790      }   
791
792#   print "Switch output port\n"; 
793#   print "------------------\n";
794#   for my $sw (sort keys %db_switch_output_port) {
795#      printf "%-25s %2s\n", $sw, $db_switch_output_port{$sw};
796#      }
797#   print "\n";
798
799
800   my %db_switch_link_with = ();
801
802   my @list_switch_ip = ();
803   my @list_switch_ipv4 = ();
804   for my $sw (@SWITCH){
805      push @list_switch_ip, $sw->{hostname};
806      }
807
808   ALL_SWITCH:
809   for my $one_computer (@list_switch_ip) {
810      my %resol_arp = resolve_ip_arp_host($one_computer,'*','low'); # arp resolution
811      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
812     
813      push @list_switch_ipv4,$resol_arp{ipv4_address};
814     
815      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
816
817      $db_switch_ip_hostname{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
818      }
819     
820   ALL_SWITCH_IP_ADDRESS:
821   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
822   
823      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
824
825      DETECTED_SWITCH:
826      for my $switch_detected ( keys %{$where{$ip}} ) {
827
828         next DETECTED_SWITCH if not exists $switch_port_count{ $db_switch_ip_hostname{$ip}};
829
830         my $switch = $where{$ip}->{$switch_detected};
831
832         next if $switch->{port}     eq '0';
833         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
834         next if $switch->{hostname} eq $db_switch_ip_hostname{$ip}; # $computerdb->{$ip}{hostname};
835
836         $db_switch_link_with{ $db_switch_ip_hostname{$ip} } ||= {};
837         $db_switch_link_with{ $db_switch_ip_hostname{$ip} }->{ $switch->{hostname} } = $switch->{port};
838         }
839
840      }
841   
842   my %db_switch_connected_on_port = ();
843   my $maybe_more_than_one_switch_connected = 'yes';
844   
845   while ($maybe_more_than_one_switch_connected eq 'yes') {
846      for my $sw (keys %db_switch_link_with) {
847         for my $connect (keys %{$db_switch_link_with{$sw}}) {
848         
849            my $port = $db_switch_link_with{$sw}->{$connect};
850         
851            $db_switch_connected_on_port{"$connect:$port"} ||= {};
852            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
853            }
854         }
855
856      $maybe_more_than_one_switch_connected  = 'no';
857
858      SWITCH_AND_PORT:
859      for my $swport (keys %db_switch_connected_on_port) {
860         
861         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
862         
863         $maybe_more_than_one_switch_connected = 'yes';
864
865         my ($sw_connect,$port_connect) = split ':', $swport;
866         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
867
868         CONNECTED:
869         for my $sw_connected (@sw_on_same_port) {
870           
871            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
872           
873            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
874           
875            for my $other_sw (@sw_on_same_port) {
876               next if $other_sw eq $sw_connected;
877               
878               delete $db_switch_link_with{$other_sw}->{$sw_connect};
879               }
880           
881            # We can not do better for this switch for this loop
882            next SWITCH_AND_PORT;
883            }
884         }
885      }
886
887   my %db_switch_parent =();
888
889   for my $sw (keys %db_switch_link_with) {
890      for my $connect (keys %{$db_switch_link_with{$sw}}) {
891     
892         my $port = $db_switch_link_with{$sw}->{$connect};
893     
894         $db_switch_connected_on_port{"$connect:$port"} ||= {};
895         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
896       
897         $db_switch_parent{$sw} = {switch => $connect, port => $port};
898         }
899      }
900
901   print "Switch output port and parent port connection\n"; 
902   print "---------------------------------------------\n";
903   for my $sw (sort keys %db_switch_output_port) {
904      if (exists $db_switch_parent{$sw}) {
905         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
906         }
907      else {
908         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
909         }
910      }
911   print "\n";
912
913   print "Switch parent and children port inter-connection\n";
914   print "------------------------------------------------\n";
915   for my $swport (sort keys %db_switch_connected_on_port) {       
916      my ($sw_connect,$port_connect) = split ':', $swport;
917      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
918         if (exists $db_switch_output_port{$sw}) {
919            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
920            }
921         else {
922            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
923            }
924         }
925      }
926
927   my $switch_connection = {
928      output_port       => \%db_switch_output_port,
929      parent            => \%db_switch_parent,
930      connected_on_port => \%db_switch_connected_on_port,
931      link_with         => \%db_switch_link_with,
932      };
933     
934   YAML::DumpFile("$KLASK_SW", $switch_connection);
935   }
936
937sub cmd_exportsw {
938   my @ARGV   = @_;
939
940   my $format = 'txt';
941   use Getopt::Long;
942
943   my $ret = GetOptions(
944      'format|f=s'  => \$format,
945      );
946
947   my %possible_format = (
948      txt => \&cmd_exportsw_txt,
949      dot => \&cmd_exportsw_dot,
950      );
951
952   $format = 'txt' if not defined $possible_format{$format};
953   
954   $possible_format{$format}->(@ARGV);
955   }
956
957sub cmd_exportsw_txt {
958
959   my $switch_connection = YAML::LoadFile("$KLASK_SW");
960
961   my %db_switch_output_port       = %{$switch_connection->{output_port}};
962   my %db_switch_parent            = %{$switch_connection->{parent}};
963   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
964
965   print "Switch output port and parent port connection\n"; 
966   print "---------------------------------------------\n";
967   for my $sw (sort keys %db_switch_output_port) {
968      if (exists $db_switch_parent{$sw}) {
969         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
970         }
971      else {
972         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
973         }
974      }
975   print "\n";
976
977   print "Switch parent and children port inter-connection\n";
978   print "------------------------------------------------\n";
979   for my $swport (sort keys %db_switch_connected_on_port) {       
980      my ($sw_connect,$port_connect) = split ':', $swport;
981      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
982         if (exists $db_switch_output_port{$sw}) {
983            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
984            }
985         else {
986            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
987            }
988         }
989      }
990   }
991
992sub cmd_exportsw_dot {
993
994   my $switch_connection = YAML::LoadFile("$KLASK_SW");
995   
996   my %db_switch_output_port       = %{$switch_connection->{output_port}};
997   my %db_switch_parent            = %{$switch_connection->{parent}};
998   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
999   my %db_switch_link_with         = %{$switch_connection->{link_with}};
1000     
1001   my %db_building= ();
1002   for my $sw (@SWITCH) {
1003      my ($building, $location) = split /\//, $sw->{location}, 2;
1004      $db_building{$building} ||= {};
1005      $db_building{$building}->{$location} ||= {};
1006      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1007      }
1008 
1009 
1010   print "digraph G {\n";
1011
1012   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
1013   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
1014
1015   my $b=0;
1016   for my $building (keys %db_building) {
1017      $b++;
1018     
1019      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1020      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
1021
1022      my $l = 0;
1023      for my $loc (keys %{$db_building{$building}}) {
1024         $l++;
1025 
1026         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
1027         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
1028
1029         for my $sw (keys %{$db_building{$building}->{$loc}}) {
1030
1031            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
1032
1033            print "\"$sw\" [label = \"$sw\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
1034            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
1035            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
1036
1037
1038            for my $swport (keys %db_switch_connected_on_port) {
1039               my ($sw_connect,$port_connect) = split ':', $swport;
1040               next if not $sw_connect eq $sw;
1041               next if $port_connect eq $db_switch_output_port{$sw};
1042               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
1043               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1044              }
1045            }
1046         }
1047      }
1048
1049#   print "Switch output port and parent port connection\n"; 
1050#   print "---------------------------------------------\n";
1051   for my $sw (sort keys %db_switch_output_port) {
1052      if (exists $db_switch_parent{$sw}) {
1053#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
1054         }
1055      else {
1056         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
1057         }
1058      }
1059   print "\n";
1060
1061#   print "Switch parent and children port inter-connection\n";
1062#   print "------------------------------------------------\n";
1063   for my $swport (sort keys %db_switch_connected_on_port) {       
1064      my ($sw_connect,$port_connect) = split ':', $swport;
1065      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1066         if (exists $db_switch_output_port{$sw}) {
1067            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
1068            }
1069         else {
1070            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
1071            }
1072         }
1073      }
1074
1075print "}\n";
1076   }
1077
1078
1079__END__
1080
1081
1082=head1 NAME
1083
1084klask - ports manager and finder for switch
1085
1086
1087=head1 SYNOPSIS
1088
1089 klask updatedb
1090 klask exportdb
1091
1092 klask updatesw
1093 klask exportsw --format [txt|dot]
1094
1095 klask searchdb computer
1096 klask search   computer
1097
1098 klask enable  switch port
1099 klask disable swith port
1100 klask status  swith port
1101
1102
1103=head1 DESCRIPTION
1104
1105klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1106
1107Klask has now a web site dedicated for it !
1108
1109 http://servforge.legi.inpg.fr/projects/klask
1110
1111
1112=head1 COMMANDS
1113
1114
1115=head2 search
1116
1117This 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.
1118
1119
1120=head2 enable
1121
1122This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1123
1124
1125=head2 disable
1126
1127This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1128
1129
1130=head2 status
1131
1132This 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.
1133
1134
1135=head2 updatedb
1136
1137This 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.
1138
1139
1140=head2 exportdb
1141
1142This 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...
1143
1144
1145=head2 updatesw
1146
1147This 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.
1148
1149
1150=head2 exportsw --format [txt|dot]
1151
1152This 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.
1153
1154 klask exportsw --format dot > /tmp/map.dot
1155 dot -Tpng /tmp/map.dot > /tmp/map.png
1156
1157
1158
1159=head1 CONFIGURATION
1160
1161Because 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 !
1162
1163Here an example, be aware with indent, it's important in YAML, do not use tabulation !
1164
1165 default:
1166   community: public
1167   snmpport: 161
1168
1169 network:
1170   labnet:
1171     ip-subnet:
1172       - add: 192.168.1.0/24
1173       - add: 192.168.2.0/24
1174     interface: eth0
1175     main-router: gw1.labnet.local
1176
1177   schoolnet:
1178     ip-subnet:
1179       - add: 192.168.6.0/24
1180       - add: 192.168.7.0/24
1181     interface: eth0.38
1182     main-router: gw2.schoolnet.local
1183
1184 switch:
1185   - hostname: sw1.klask.local
1186     portignore:
1187       - 1
1188       - 2
1189
1190   - hostname: sw2.klask.local
1191     location: BatK / 2 / K203
1192     type: HP2424
1193     portignore:
1194       - 1
1195       - 2
1196
1197I 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.
1198
1199
1200=head1 FILES
1201
1202 /etc/klask.conf
1203 /var/cache/klask/klaskdb
1204 /var/cache/klask/switchdb
1205
1206=head1 SEE ALSO
1207
1208Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1209
1210
1211=head1 VERSION
1212
12130.4
1214
1215
1216=head1 AUTHOR
1217
1218Written by Gabriel Moreau, Grenoble - France
1219
1220
1221=head1 COPYRIGHT
1222       
1223Copyright (C) 2005-2008 Gabriel Moreau.
1224
1225
1226=head1 LICENCE
1227
1228GPL version 2 or later and Perl equivalent
Note: See TracBrowser for help on using the repository browser.