source: trunk/klask @ 8

Last change on this file since 8 was 8, checked in by g7moreau, 16 years ago
  • Change aptitude command to apt-get
  • Property svn:executable set to *
File size: 39.3 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
15our $KLASK_DB  = '/var/cache/klask/klaskdb';
16our $KLASK_SW  = '/var/cache/klask/switchdb';
17our $KLASK_CFG = '/etc/klask.conf';
18
19my $switchdb = YAML::LoadFile("$KLASK_CFG");
20
21our %default = %{$switchdb->{default}};
22our @switch  = @{$switchdb->{switch}};
23
24our %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}} } @{$switchdb->{switch}}; 
30
31our %switch_port_count = ();
32
33our %cmddb = (
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
49our %INTERNALPORTMAP = (
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   );
59our %INTERNALPORTMAPREV = reverse %INTERNALPORTMAP;
60
61
62################
63# principal
64################
65
66my $cmd = shift;
67if (defined $cmddb{$cmd}) {
68   $cmddb{$cmd}->(@ARGV);
69   }
70else {
71   print STDERR "klask: command $cmd not found\n\n";
72   $cmddb{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 %{$switchdb->{network}};
369   }
370
371sub get_current_interface {
372   my $network = shift;
373
374   return $switchdb->{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  = @{$switchdb->{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
406sub get_human_readable_port {
407   my $sw = shift;
408   my $port = shift;
409   
410   return $port if not $sw eq 'sw8000-batA.hmg.priv';
411   
412   my $reste = (($port - 1) % 8) + 1;
413   my $major = int( ($port - 1) / 8 );
414
415   return "$INTERNALPORTMAP{$major}$reste";
416   }
417
418sub get_numerical_port {
419   my $sw   = shift;
420   my $port = shift;
421   
422   return $port if not $sw eq 'sw8000-batA.hmg.priv';
423
424   my $letter = substr($port, 0, 1);
425   
426#   return $port if $letter =~ m/\d/;
427   
428   my $reste =  substr($port, 1);
429   
430   return $INTERNALPORTMAPREV{$letter} * 8 + $reste;
431   }
432
433################
434# Les commandes
435################
436
437sub cmd_help {
438
439print <<END;
440klask - ports manager and finder for switch
441
442 klask updatedb
443 klask exportdf
444
445 klask searchdb computer
446 klask search   computer
447
448 klask enable  switch port
449 klask disable switch port
450 klask status  switch port
451END
452   }
453
454sub cmd_search {
455   my @computer = @_;
456   
457   init_switch_names();    #nomme les switchs
458   fastping(@computer);
459   for my $clientname (@computer) {
460      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
461      my %where     = find_switch_port($resol_arp{mac_address}); #retrouve l'emplacement
462      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"
463         unless $where{switch_description} eq 'unknow' and $resol_arp{hostname_fq} eq 'unknow' and $resol_arp{mac_address} eq 'unknow';
464      }
465   }
466
467sub cmd_searchdb {
468   my @computer = @_;
469
470   fastping(@computer);
471   my $computerdb = YAML::LoadFile("$KLASK_DB");
472   
473   LOOP_ON_COMPUTER:
474   for my $clientname (@computer) {
475      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
476      my $ip = $resol_arp{ipv4_address};
477     
478      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
479     
480      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($computerdb->{$ip}{timestamp});
481      $year += 1900;
482      $mon++;
483      my $date = sprintf "%04i-%02i-%02i %02i:%02i", $year,$mon,$mday,$hour,$min;
484
485      printf "%-22s %2s %-30s %-15s %-18s %s\n",
486         $computerdb->{$ip}{switch_name},
487         $computerdb->{$ip}{switch_port},
488         $computerdb->{$ip}{hostname_fq},
489         $ip,
490         $computerdb->{$ip}{mac_address},
491         $date;
492      }
493   }
494
495sub cmd_updatedb {
496   my @network = @_;
497      @network = get_list_network() if not @network;
498
499   my $computerdb = YAML::LoadFile("$KLASK_DB");
500   my $timestamp = time;
501   
502   my %computer_not_detected = ();
503   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
504
505   my $number_of_computer = get_list_ip(@network); # + 1;
506   my $size_of_database   = keys %$computerdb;
507   my $i = 0;
508   my $detected_computer = 0;
509   
510   init_switch_names('yes');    #nomme les switchs
511
512   my %router_mac_ip = ();
513   DETECT_ALL_ROUTER:
514   for my $one_router ('194.254.66.254') {
515      my %resol_arp = resolve_ip_arp_host($one_router);
516      $router_mac_ip{ $resol_arp{mac_address} } = $resol_arp{ipv4_address};
517      }
518
519   ALL_NETWORK:
520   for my $net (@network) {
521
522      my @computer = get_list_ip($net);
523      my $current_interface = get_current_interface($net);
524
525      fastping(@computer);
526
527      LOOP_ON_COMPUTER:
528      for my $one_computer (@computer) {
529         $i++;
530         
531         my $total_percent = int(($i*100)/$number_of_computer);
532
533         my $localtime = time - $timestamp;
534         my ($sec,$min) = localtime($localtime);
535
536         my $time_elapse = 0;
537            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
538         my ($sec_elapse,$min_elapse) = localtime($time_elapse);
539
540         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
541#         printf ", Computer detected: %4i/%i (%2i%%)", $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
542         printf ", detected: %4i/%i (%2i%%)", $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
543         printf " [Time: %02i:%02i / %02i:%02i]", int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
544#         printf "  [%02i:%02i/%02i:%02i]", int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
545         printf " %-14s", $one_computer;
546
547         my %resol_arp = resolve_ip_arp_host($one_computer,$current_interface);
548         
549         # do not search router connection
550         if ( exists $router_mac_ip{$resol_arp{mac_address}}) {
551            $computer_not_detected{$one_computer} = $current_interface;
552            next LOOP_ON_COMPUTER;
553            }
554
555         # do not search switch connection
556         if (exists $switch_level{$resol_arp{hostname_fq}}) {
557            $computer_not_detected{$one_computer} = $current_interface;
558            next LOOP_ON_COMPUTER;
559            }
560
561         my $switch_proposal = '';
562         if (exists $computerdb->{$resol_arp{ipv4_address}} and exists $computerdb->{$resol_arp{ipv4_address}}{switch_hostname}) {
563            $switch_proposal = $computerdb->{$resol_arp{ipv4_address}}{switch_hostname};
564            }
565
566         # do not have a mac address
567         if ($resol_arp{mac_address} eq 'unknow' or (exists $resol_arp{timestamps} and $resol_arp{timestamps} < ($timestamp - 3 * 3600))) {
568            $computer_not_detected{$one_computer} = $current_interface;
569            next LOOP_ON_COMPUTER;
570            }
571
572         my %where = find_switch_port($resol_arp{mac_address},$switch_proposal);
573
574         #192.168.24.156:
575         #  arp: 00:0B:DB:D5:F6:65
576         #  hostname: pcroyon.hmg.priv
577         #  port: 5
578         #  switch: sw-batH-legi:hp2524
579         #  timestamp: 1164355525
580
581         # do not have a mac address
582#         if ($resol_arp{mac_address} eq 'unknow') {
583#            $computer_not_detected{$one_computer} = $current_interface;
584#            next LOOP_ON_COMPUTER;
585#            }
586
587         # detected on a switch
588         if ($where{switch_description} ne 'unknow') {
589            $detected_computer++;
590            $computerdb->{$resol_arp{ipv4_address}} = {
591               hostname_fq        => $resol_arp{hostname_fq},
592               mac_address        => $resol_arp{mac_address},
593               switch_hostname    => $where{switch_hostname},
594               switch_description => $where{switch_description},
595               switch_port        => $where{switch_port},
596               timestamp          => $timestamp,
597               };
598            next LOOP_ON_COMPUTER;
599            }
600
601         # new in the database but where it is ?
602         if (not exists $computerdb->{$resol_arp{ipv4_address}}) {
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          => $resol_arp{timestamp},
611               };
612            }
613
614         # mise a jour du nom de la machine si modification dans le dns
615         $computerdb->{$resol_arp{ipv4_address}}{hostname_fq} = $resol_arp{hostname_fq};
616       
617         # mise à jour de la date de détection si détection plus récente par arpwatch
618         $computerdb->{$resol_arp{ipv4_address}}{timestamp}   = $resol_arp{timestamp} if exists $resol_arp{timestamp} and $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $resol_arp{timestamp};
619
620         # provisoire car changement de nom des attributs
621#         $computerdb->{$resol_arp{ipv4_address}}{mac_address}        = $computerdb->{$resol_arp{ipv4_address}}{arp};
622#         $computerdb->{$resol_arp{ipv4_address}}{switch_description} = $computerdb->{$resol_arp{ipv4_address}}{switch};
623#         $computerdb->{$resol_arp{ipv4_address}}{switch_port}        = $computerdb->{$resol_arp{ipv4_address}}{port};
624       
625         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
626#         push @computer_not_detected, $resol_arp{ipv4_address} if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
627         $computer_not_detected{$resol_arp{ipv4_address}} = $current_interface if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
628       
629         }
630      }
631
632   # final end of line at the end of the loop
633   printf "\n";
634
635   my $dirdb = $KLASK_DB;
636      $dirdb =~ s#/[^/]*$##;
637   mkdir "$dirdb", 0755 unless -d "$dirdb";
638   YAML::DumpFile("$KLASK_DB", $computerdb);
639
640   for my $one_computer (keys %computer_not_detected) {
641      my $interface = $computer_not_detected{$one_computer};
642      system "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null";
643#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
644      }
645   }
646
647sub cmd_removedb {
648   my @computer = @_;
649   
650   my $computerdb = YAML::LoadFile("$KLASK_DB");
651
652   LOOP_ON_COMPUTER:
653   for my $one_computer (@computer) {
654
655      my %resol_arp = resolve_ip_arp_host($one_computer);
656
657      delete $computerdb->{$resol_arp{ipv4_address}} if exists $computerdb->{$resol_arp{ipv4_address}};
658      }
659
660   my $dirdb = $KLASK_DB;
661      $dirdb =~ s#/[^/]*$##;
662   mkdir "$dirdb", 0755 unless -d "$dirdb";
663   YAML::DumpFile("$KLASK_DB", $computerdb);
664   }
665
666sub cmd_exportdb {
667   my $computerdb = YAML::LoadFile("$KLASK_DB");
668
669   printf "%-24s %-4s            %-30s %-15s %-18s %-s\n", qw(Switch Port Hostname IPv4-Address MAC-Address Date);
670   print "---------------------------------------------------------------------------------------------------------------------------\n";
671
672   LOOP_ON_IP_ADDRESS:
673   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %$computerdb)) {
674   
675#      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq 'unknow';
676
677      # to be improve in the future
678      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
679
680# dans le futur
681#      next if $computerdb->{$ip}{hostname_fq} eq 'unknow';
682     
683      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($computerdb->{$ip}{timestamp});
684      $year += 1900;
685      $mon++;
686      my $date = sprintf "%04i-%02i-%02i %02i:%02i", $year,$mon,$mday,$hour,$min;
687
688      printf "%-25s  %2s  <-------  %-30s %-15s %-18s %s\n",
689         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
690         $computerdb->{$ip}{switch_port},
691         $computerdb->{$ip}{hostname_fq},
692         $ip,
693         $computerdb->{$ip}{mac_address},
694         $date;
695      }
696   }
697
698sub cmd_iplocation {
699   my $computerdb = YAML::LoadFile("$KLASK_DB");
700
701   LOOP_ON_IP_ADDRESS:
702   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %$computerdb)) {
703
704      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
705
706      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || '';
707      next if $sw_hostname eq 'unknow';
708 
709      my $sw_location = '';
710      for my $sw (@switch) {
711         next if $sw_hostname ne $sw->{hostname};
712         $sw_location = $sw->{location};
713         last;
714         }
715
716      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq '';
717      }
718   }
719
720sub cmd_enable {
721   my $switch = shift;
722   my $port   = shift;
723   
724   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
725   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
726   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
727   }
728
729sub cmd_disable {
730   my $switch = shift;
731   my $port   = shift;
732   
733   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
734   }
735
736sub cmd_status {
737   my $switch = shift;
738   my $port   = shift;
739   
740   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
741   }
742
743
744sub cmd_updatesw {
745
746   init_switch_names('yes');    #nomme les switchs
747   print "\n";
748
749   my %where = ();
750   my %db_switch_output_port = ();
751   my %db_switch_ip_hostname = ();
752
753   DETECT_ALL_ROUTER:
754   for my $one_computer ('194.254.66.254') {
755      my %resol_arp = resolve_ip_arp_host($one_computer,'*','low');            #resolution arp
756      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
757     
758      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); #retrouve les emplacements
759      }
760
761   ALL_ROUTER_IP_ADDRESS:
762   for my $ip (Net::Netmask::sort_by_ip_address('194.254.66.254')) {
763   
764      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip};
765
766      ALL_SWITCH_CONNECTED:
767      for my $switch_detected ( keys %{$where{$ip}} ) {
768
769         my $switch = $where{$ip}->{$switch_detected};
770
771         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
772         
773         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
774         }
775      }   
776
777#   print "Switch output port\n"; 
778#   print "------------------\n";
779#   for my $sw (sort keys %db_switch_output_port) {
780#      printf "%-25s %2s\n", $sw, $db_switch_output_port{$sw};
781#      }
782#   print "\n";
783
784
785   my %db_switch_link_with = ();
786
787   my @list_switch_ip = ();
788   my @list_switch_ipv4 = ();
789   for my $sw (@switch){
790      push @list_switch_ip, $sw->{hostname};
791      }
792
793   ALL_SWITCH:
794   for my $one_computer (@list_switch_ip) {
795      my %resol_arp = resolve_ip_arp_host($one_computer,'*','low'); # arp resolution
796      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
797     
798      push @list_switch_ipv4,$resol_arp{ipv4_address};
799     
800      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
801
802      $db_switch_ip_hostname{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
803      }
804     
805   ALL_SWITCH_IP_ADDRESS:
806   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
807   
808      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
809
810      DETECTED_SWITCH:
811      for my $switch_detected ( keys %{$where{$ip}} ) {
812
813         next DETECTED_SWITCH if not exists $switch_port_count{ $db_switch_ip_hostname{$ip}};
814
815         my $switch = $where{$ip}->{$switch_detected};
816
817         next if $switch->{port}     eq '0';
818         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
819         next if $switch->{hostname} eq $db_switch_ip_hostname{$ip}; # $computerdb->{$ip}{hostname};
820
821         $db_switch_link_with{ $db_switch_ip_hostname{$ip} } ||= {};
822         $db_switch_link_with{ $db_switch_ip_hostname{$ip} }->{ $switch->{hostname} } = $switch->{port};
823         }
824
825      }
826   
827   my %db_switch_connected_on_port = ();
828   my $maybe_more_than_one_switch_connected = 'yes';
829   
830   while ($maybe_more_than_one_switch_connected eq 'yes') {
831      for my $sw (keys %db_switch_link_with) {
832         for my $connect (keys %{$db_switch_link_with{$sw}}) {
833         
834            my $port = $db_switch_link_with{$sw}->{$connect};
835         
836            $db_switch_connected_on_port{"$connect:$port"} ||= {};
837            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
838            }
839         }
840
841      $maybe_more_than_one_switch_connected  = 'no';
842
843      SWITCH_AND_PORT:
844      for my $swport (keys %db_switch_connected_on_port) {
845         
846         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
847         
848         $maybe_more_than_one_switch_connected = 'yes';
849
850         my ($sw_connect,$port_connect) = split ':', $swport;
851         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
852
853         CONNECTED:
854         for my $sw_connected (@sw_on_same_port) {
855           
856            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
857           
858            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
859           
860            for my $other_sw (@sw_on_same_port) {
861               next if $other_sw eq $sw_connected;
862               
863               delete $db_switch_link_with{$other_sw}->{$sw_connect};
864               }
865           
866            # We can not do better for this switch for this loop
867            next SWITCH_AND_PORT;
868            }
869         }
870      }
871
872   my %db_switch_parent =();
873
874   for my $sw (keys %db_switch_link_with) {
875      for my $connect (keys %{$db_switch_link_with{$sw}}) {
876     
877         my $port = $db_switch_link_with{$sw}->{$connect};
878     
879         $db_switch_connected_on_port{"$connect:$port"} ||= {};
880         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
881       
882         $db_switch_parent{$sw} = {switch => $connect, port => $port};
883         }
884      }
885
886   print "Switch output port and parent port connection\n"; 
887   print "---------------------------------------------\n";
888   for my $sw (sort keys %db_switch_output_port) {
889      if (exists $db_switch_parent{$sw}) {
890         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
891         }
892      else {
893         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
894         }
895      }
896   print "\n";
897
898   print "Switch parent and children port inter-connection\n";
899   print "------------------------------------------------\n";
900   for my $swport (sort keys %db_switch_connected_on_port) {       
901      my ($sw_connect,$port_connect) = split ':', $swport;
902      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
903         if (exists $db_switch_output_port{$sw}) {
904            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
905            }
906         else {
907            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
908            }
909         }
910      }
911
912   my $switch_connection = {
913      output_port       => \%db_switch_output_port,
914      parent            => \%db_switch_parent,
915      connected_on_port => \%db_switch_connected_on_port,
916      link_with         => \%db_switch_link_with,
917      };
918     
919   YAML::DumpFile("$KLASK_SW", $switch_connection);
920   }
921
922sub cmd_exportsw {
923   my @ARGV   = @_;
924
925   my $format = 'txt';
926   use Getopt::Long;
927
928   my $ret = GetOptions(
929      'format|f=s'  => \$format,
930      );
931
932   my %possible_format = (
933      txt => \&cmd_exportsw_txt,
934      dot => \&cmd_exportsw_dot,
935      );
936
937   $format = 'txt' if not defined $possible_format{$format};
938   
939   $possible_format{$format}->(@ARGV);
940   }
941
942sub cmd_exportsw_txt {
943
944   my $switch_connection = YAML::LoadFile("$KLASK_SW");
945
946   my %db_switch_output_port       = %{$switch_connection->{output_port}};
947   my %db_switch_parent            = %{$switch_connection->{parent}};
948   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
949
950   print "Switch output port and parent port connection\n"; 
951   print "---------------------------------------------\n";
952   for my $sw (sort keys %db_switch_output_port) {
953      if (exists $db_switch_parent{$sw}) {
954         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
955         }
956      else {
957         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
958         }
959      }
960   print "\n";
961
962   print "Switch parent and children port inter-connection\n";
963   print "------------------------------------------------\n";
964   for my $swport (sort keys %db_switch_connected_on_port) {       
965      my ($sw_connect,$port_connect) = split ':', $swport;
966      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
967         if (exists $db_switch_output_port{$sw}) {
968            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
969            }
970         else {
971            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
972            }
973         }
974      }
975   }
976
977sub cmd_exportsw_dot {
978
979   my $switch_connection = YAML::LoadFile("$KLASK_SW");
980   
981   my %db_switch_output_port       = %{$switch_connection->{output_port}};
982   my %db_switch_parent            = %{$switch_connection->{parent}};
983   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
984   my %db_switch_link_with         = %{$switch_connection->{link_with}};
985     
986   my %db_building= ();
987   for my $sw (@switch) {
988      my ($building, $location) = split /\//, $sw->{location}, 2;
989      $db_building{$building} ||= {};
990      $db_building{$building}->{$location} ||= {};
991      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
992      }
993 
994 
995   print "digraph G {\n";
996
997   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
998   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
999
1000   my $b=0;
1001   for my $building (keys %db_building) {
1002      $b++;
1003     
1004      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1005      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
1006
1007      my $l = 0;
1008      for my $loc (keys %{$db_building{$building}}) {
1009         $l++;
1010 
1011         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
1012         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
1013
1014         for my $sw (keys %{$db_building{$building}->{$loc}}) {
1015
1016            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
1017
1018            print "\"$sw\" [label = \"$sw\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
1019            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
1020            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
1021
1022
1023            for my $swport (keys %db_switch_connected_on_port) {
1024               my ($sw_connect,$port_connect) = split ':', $swport;
1025               next if not $sw_connect eq $sw;
1026               next if $port_connect eq $db_switch_output_port{$sw};
1027               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
1028               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1029              }
1030            }
1031         }
1032      }
1033
1034#   print "Switch output port and parent port connection\n"; 
1035#   print "---------------------------------------------\n";
1036   for my $sw (sort keys %db_switch_output_port) {
1037      if (exists $db_switch_parent{$sw}) {
1038#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
1039         }
1040      else {
1041         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
1042         }
1043      }
1044   print "\n";
1045
1046#   print "Switch parent and children port inter-connection\n";
1047#   print "------------------------------------------------\n";
1048   for my $swport (sort keys %db_switch_connected_on_port) {       
1049      my ($sw_connect,$port_connect) = split ':', $swport;
1050      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1051         if (exists $db_switch_output_port{$sw}) {
1052            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
1053            }
1054         else {
1055            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
1056            }
1057         }
1058      }
1059
1060print "}\n";
1061   }
1062
1063
1064__END__
1065
1066
1067=head1 NAME
1068
1069klask - ports manager and finder for switch
1070
1071
1072=head1 SYNOPSIS
1073
1074 klask updatedb
1075 klask exportdb
1076
1077 klask updatesw
1078 klask exportsw --format [txt|dot]
1079
1080 klask searchdb computer
1081 klask search   computer
1082
1083 klask enable  switch port
1084 klask disable swith port
1085 klask status  swith port
1086
1087
1088=head1 DESCRIPTION
1089
1090klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1091
1092Klask has now a web site dedicated for it !
1093
1094 http://servforge.legi.inpg.fr/projects/klask
1095
1096
1097=head1 COMMANDS
1098
1099
1100=head2 search
1101
1102This 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.
1103
1104
1105=head2 enable
1106
1107This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1108
1109
1110=head2 disable
1111
1112This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1113
1114
1115=head2 status
1116
1117This 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.
1118
1119
1120=head2 updatedb
1121
1122This 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.
1123
1124
1125=head2 exportdb
1126
1127This 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...
1128
1129
1130=head2 updatesw
1131
1132This 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.
1133
1134
1135=head2 exportsw --format [txt|dot]
1136
1137This 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.
1138
1139 klask exportsw --format dot > /tmp/map.dot
1140 dot -Tpng /tmp/map.dot > /tmp/map.png
1141
1142
1143
1144=head1 CONFIGURATION
1145
1146Because 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 !
1147
1148Here an example, be aware with indent, it's important in YAML, do not use tabulation !
1149
1150 default:
1151   community: public
1152   snmpport: 161
1153
1154 network:
1155   labnet:
1156     ip-subnet:
1157       - add: 192.168.1.0/24
1158       - add: 192.168.2.0/24
1159     interface: eth0
1160     main-router: gw1.labnet.local
1161
1162   schoolnet:
1163     ip-subnet:
1164       - add: 192.168.6.0/24
1165       - add: 192.168.7.0/24
1166     interface: eth0.38
1167     main-router: gw2.schoolnet.local
1168
1169 switch:
1170   - hostname: sw1.klask.local
1171     portignore:
1172       - 1
1173       - 2
1174
1175   - hostname: sw2.klask.local
1176     location: BatK / 2 / K203
1177     type: HP2424
1178     portignore:
1179       - 1
1180       - 2
1181
1182I 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.
1183
1184
1185=head1 FILES
1186
1187 /etc/klask.conf
1188 /var/cache/klask/klaskdb
1189 /var/cache/klask/switchdb
1190
1191=head1 SEE ALSO
1192
1193Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1194
1195
1196=head1 VERSION
1197
11980.4
1199
1200
1201=head1 AUTHOR
1202
1203Written by Gabriel Moreau, Grenoble - France
1204
1205
1206=head1 COPYRIGHT
1207       
1208Copyright (C) 2005-2008 Gabriel Moreau.
1209
1210
1211=head1 LICENCE
1212
1213GPL version 2 or later and Perl equivalent
Note: See TracBrowser for help on using the repository browser.