source: trunk/klask @ 6

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