source: trunk/klask @ 3

Last change on this file since 3 was 3, checked in by g7moreau, 16 years ago
  • Last mod from my active server
  • Property svn:executable set to *
File size: 37.4 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   search     => \&cmd_search,
38   enable     => \&cmd_enable,
39   disable    => \&cmd_disable,
40   status     => \&cmd_status,
41   switchmap  => \&cmd_switchmap,
42   dotsw      => \&cmd_swdot,
43   removedb   => \&cmd_removedb,
44   iplocation => \&cmd_iplocation,
45   );
46
47our %INTERNALPORTMAP = (
48   0 => 'A',
49   1 => 'B',
50   2 => 'C',
51   3 => 'D',
52   4 => 'E',
53   5 => 'F',
54   6 => 'G',
55   7 => 'H',
56   );
57our %INTERNALPORTMAPREV = reverse %INTERNALPORTMAP;
58
59
60################
61# principal
62################
63
64my $cmd = shift;
65if (defined $cmddb{$cmd}) {
66   $cmddb{$cmd}->(@ARGV);
67   }
68else {
69   print STDERR "klask: command $cmd not found\n\n";
70   $cmddb{help}->();
71   exit 1;
72   }
73
74exit;
75
76###
77# fast ping dont l'objectif est de remplir la table arp de la machine
78sub fastping {
79   system "fping -c 1 @_ >/dev/null 2>&1";
80   }
81
82###
83# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
84sub resolve_ip_arp_host {
85   my $param_ip_or_host = shift;
86   my $interface = shift || '*';
87   my $type      = shift || 'fast';
88
89   my %ret = (
90      hostname_fq  => 'unknow',
91      ipv4_address => '0.0.0.0',
92      mac_address  => 'unknow',
93      );
94
95#   my $cmdarping  = `arping -c 1 -w 1 -rR $param 2>/dev/null`;
96
97   # controler que arpwatch tourne !
98   # resultat de la commande arpwatch
99   # /var/lib/arpwatch/arp.dat
100   # 0:13:d3:e1:92:d0        192.168.24.109  1163681980      theo8sv109
101   #my $cmd = "grep  -e '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/arp.dat | sort +2rn | head -1";
102#   my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/*.dat | sort +2rn | head -1";
103   my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/$interface.dat | sort +2rn | head -1";
104   my $cmd_arpwatch = `$cmd`;
105   chomp $cmd_arpwatch;
106   my ($arp, $ip, $timestamp, $host) = split /\s+/, $cmd_arpwatch;
107#print "OOO $cmd\n";
108#print "TTT arp $arp -> $ip pour host $host\n";
109   $ret{ipv4_address} = $ip        if $ip;
110   $ret{mac_address}  = $arp       if $arp;
111   $ret{timestamp}    = $timestamp if $timestamp;
112
113   my $nowtimestamp = time();
114
115   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 3 * 3600 ) ) ) {
116      $ret{mac_address} = 'unknow';
117      return %ret;
118      }
119
120  # resultat de la commande arp
121   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
122   my $cmd_arp  = `arp -a $param_ip_or_host 2>/dev/null`;
123   chomp $cmd_arp;
124   $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})/;
125   $ret{hostname_fq}  = $1 if(defined($1));
126   $ret{ipv4_address} = $2 if(defined($2));
127   $ret{mac_address}  = $3 if(defined($3));
128
129#   if ($ret{ipv4_address} eq '0.0.0.0' and $ret{mac_address} eq 'unknow'and $ret{hostname_fq} eq 'unknow') {
130      # resultat de la commande host si le parametre est ip
131      # 250.66.254.194.in-addr.arpa domain name pointer legihp2100.hmg.inpg.fr.
132      my $cmd_host = `host $param_ip_or_host 2>/dev/null`;
133      chomp $cmd_host;
134      $cmd_host =~ m/domain\sname\spointer\s(\S+)\.$/;
135      $ret{hostname_fq} = $1 if defined $1;
136
137      # resultat de la commande host si parametre est hostname
138      # tech7meylan.hmg.inpg.fr has address 194.254.66.240
139      $cmd_host =~ m/\shas\saddress\s([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/;
140      $ret{ipv4_address} = $1 if defined $1;
141
142      $cmd_host =~ m/\b([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.in-addr\.arpa\s/;
143      $ret{ipv4_address} = "$4.$3.$2.$1"     if defined $1 and  defined $2 and  defined $3 and  defined $4;
144      $ret{hostname_fq}  = $param_ip_or_host if not defined $1 and $ret{hostname_fq} eq 'unknow';
145#      }
146
147   unless ($ret{mac_address} eq 'unknow') {
148      my @paquets = ();
149      foreach ( split(/:/, $ret{mac_address}) ) {
150         my @chars = split //, uc("00$_");
151         push @paquets, "$chars[-2]$chars[-1]";
152         }
153      $ret{mac_address} = join ':', @paquets;
154      }
155
156   return %ret;
157   }
158
159###
160# va rechercher le nom des switchs pour savoir qui est qui
161sub initswitchnames {
162   my $verbose = shift;
163   
164   print "Switch description\n" if $verbose;
165   print "------------------\n" if $verbose;
166
167   INIT_EACH_SWITCH:
168   for my $sw (@switch) {
169#print "$sw->{hostname} \n";
170      my %session = ( -hostname   => $sw->{hostname} );
171         $session{-version} = $sw->{version}   || 1;
172         $session{-port}    = $sw->{snmpport}  || $default{snmpport}  || 161;
173         if (exists $sw->{version} and $sw->{version} eq 3) {
174            $session{-username} = $sw->{username} || 'snmpadmin';
175#print "$sw->{hostname}  -- $session{-username}  \n";
176#for (keys %session) {
177#print "++ $_ -> $session{$_}\n";
178#}
179            }
180         else {
181            $session{-community} = $sw->{community} || $default{community} || 'public';
182            }
183
184      $sw->{local_session} = \%session;
185
186      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
187#         -hostname   => $sw->{hostname},
188#         -version    => $sw->{version}   || 1,
189#         -community  => $sw->{community} || $default{community} || 'public',
190#         -username   => $sw->{username}  || 'snmpadmin',
191#         -port       => $sw->{snmpport}  || $default{snmpport}  || 161,
192#         );
193#         if (exists $sw->{version} and $sw->{version} eq 3) {
194print "$error \n" if $error;
195#}
196      my $result = $session->get_request(
197         -varbindlist => ['1.3.6.1.2.1.1.5.0']
198         );
199      $sw->{description} = $result->{"1.3.6.1.2.1.1.5.0"} || $sw->{hostname};
200      #$sw->{location} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{hostname};
201      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{hostname};
202      $session->close;
203 
204      my ($desc, $type) = split ':', $sw->{description}, 2;
205      printf "%-25s 0--------->>>> %-25s %s\n", $sw->{hostname}, $desc, uc($type) if $verbose;
206      }
207
208   print "\n" if $verbose;
209   }
210
211###
212# convertit l'hexa (uniquement 2 chiffres) en decimal
213sub hextodec {
214   #00:0F:1F:43:E4:2B
215   my $car = '00' . shift;
216
217   return '00' if $car eq '00unknow';
218   my %table = (
219      "0"=>"0",  "1"=>"1",  "2"=>"2",  "3"=>"3",  "4"=>"4",  "5"=>"5", "6"=>"6", "7"=>"7", "8"=>"8", "9"=>"9",
220      "a"=>"10", "b"=>"11", "c"=>"12", "d"=>"13", "e"=>"14", "f"=>"15",
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 arphextodec {
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 .= ".".hextodec($_);
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".arphextodec($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".arphextodec($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   initswitchnames();    #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   initswitchnames('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_switchmap {
744
745   initswitchnames('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
760print "etape 1\n";
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      }   
776print "etape 2\n";
777
778#   print "Switch output port\n"; 
779#   print "------------------\n";
780#   for my $sw (sort keys %db_switch_output_port) {
781#      printf "%-25s %2s\n", $sw, $db_switch_output_port{$sw};
782#      }
783#   print "\n";
784
785
786   my %db_switch_link_with = ();
787
788   my @list_switch_ip = ();
789   my @list_switch_ipv4 = ();
790   for my $sw (@switch){
791      push @list_switch_ip, $sw->{hostname};
792      }
793
794print "etape 3\n";
795   ALL_SWITCH:
796   for my $one_computer (@list_switch_ip) {
797      my %resol_arp = resolve_ip_arp_host($one_computer,'*','low'); # arp resolution
798      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
799     
800      push @list_switch_ipv4,$resol_arp{ipv4_address};
801     
802      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
803
804      $db_switch_ip_hostname{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
805      }
806     
807print "etape 4\n";
808   ALL_SWITCH_IP_ADDRESS:
809   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
810   
811      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
812
813      DETECTED_SWITCH:
814      for my $switch_detected ( keys %{$where{$ip}} ) {
815
816         next DETECTED_SWITCH if not exists $switch_port_count{ $db_switch_ip_hostname{$ip}};
817
818         my $switch = $where{$ip}->{$switch_detected};
819
820         next if $switch->{port}     eq '0';
821         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
822         next if $switch->{hostname} eq $db_switch_ip_hostname{$ip}; # $computerdb->{$ip}{hostname};
823
824         $db_switch_link_with{ $db_switch_ip_hostname{$ip} } ||= {};
825         $db_switch_link_with{ $db_switch_ip_hostname{$ip} }->{ $switch->{hostname} } = $switch->{port};
826         }
827
828      }
829   
830print "etape 5\n";
831   my %db_switch_connected_on_port = ();
832   my $maybe_more_than_one_switch_connected = 'yes';
833   
834   while ($maybe_more_than_one_switch_connected eq 'yes') {
835      for my $sw (keys %db_switch_link_with) {
836         for my $connect (keys %{$db_switch_link_with{$sw}}) {
837         
838            my $port = $db_switch_link_with{$sw}->{$connect};
839         
840            $db_switch_connected_on_port{"$connect:$port"} ||= {};
841            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
842            }
843         }
844
845      $maybe_more_than_one_switch_connected  = 'no';
846
847      SWITCH_AND_PORT:
848      for my $swport (keys %db_switch_connected_on_port) {
849         
850         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
851         
852         $maybe_more_than_one_switch_connected = 'yes';
853
854         my ($sw_connect,$port_connect) = split ':', $swport;
855         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
856
857         CONNECTED:
858         for my $sw_connected (@sw_on_same_port) {
859           
860            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
861           
862            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
863           
864            for my $other_sw (@sw_on_same_port) {
865               next if $other_sw eq $sw_connected;
866               
867               delete $db_switch_link_with{$other_sw}->{$sw_connect};
868               }
869           
870            # We can not do better for this switch for this loop
871            next SWITCH_AND_PORT;
872            }
873         }
874      }
875
876print "etape 6\n";
877   my %db_switch_parent =();
878
879   for my $sw (keys %db_switch_link_with) {
880      for my $connect (keys %{$db_switch_link_with{$sw}}) {
881     
882         my $port = $db_switch_link_with{$sw}->{$connect};
883     
884         $db_switch_connected_on_port{"$connect:$port"} ||= {};
885         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
886       
887         $db_switch_parent{$sw} = {switch => $connect, port => $port};
888         }
889      }
890
891   print "Switch output port and parent port connection\n"; 
892   print "---------------------------------------------\n";
893   for my $sw (sort keys %db_switch_output_port) {
894      if (exists $db_switch_parent{$sw}) {
895         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
896         }
897      else {
898         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
899         }
900      }
901   print "\n";
902
903   print "Switch parent and children port inter-connection\n";
904   print "------------------------------------------------\n";
905   for my $swport (sort keys %db_switch_connected_on_port) {       
906      my ($sw_connect,$port_connect) = split ':', $swport;
907      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
908         if (exists $db_switch_output_port{$sw}) {
909            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
910            }
911         else {
912            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
913            }
914         }
915      }
916
917   my $switch_connection = {
918      output_port       => \%db_switch_output_port,
919      parent            => \%db_switch_parent,
920      connected_on_port => \%db_switch_connected_on_port,
921      link_with         => \%db_switch_link_with,
922      };
923     
924   YAML::DumpFile("/var/cache/klask/switchdb", $switch_connection);
925   }
926
927sub cmd_swdot {
928
929   my $switch_connection = YAML::LoadFile("/var/cache/klask/switchdb");
930   
931   my %db_switch_output_port       = %{$switch_connection->{output_port}};
932   my %db_switch_parent            = %{$switch_connection->{parent}};
933   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
934   my %db_switch_link_with         = %{$switch_connection->{link_with}};
935     
936   my %db_building= ();
937   for my $sw (@switch) {
938      my ($building, $location) = split /\//, $sw->{location}, 2;
939      $db_building{$building} ||= {};
940      $db_building{$building}->{$location} ||= {};
941      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
942      }
943 
944 
945   print "digraph G {\n";
946
947#print "size = \"11.4,8.2!\"; dpi = 600; orientation = landscape;\n";
948
949print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
950print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
951
952   my $b=0;
953   for my $building (keys %db_building) {
954      $b++;
955     
956#      print <<ENDB;
957#   subgraph "cluster_$b" {
958#      label = "$building";
959#      style = filled;
960#      color = yellow;
961#ENDB
962
963print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
964print "site -> \"building$b\" [len = 2, color = firebrick];\n";
965
966      my $l = 0;
967      for my $loc (keys %{$db_building{$building}}) {
968         $l++;
969 
970#         print <<ENDL;
971#      subgraph "cluster_$b-$l" {
972#         label = "$building / $loc";
973#         style = filled;
974#         color = purple;
975#ENDL
976
977print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
978print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
979
980         for my $sw (keys %{$db_building{$building}->{$loc}}) {
981
982#            print <<ENDS;
983#         subgraph "cluster_$sw" {
984#            label = "$sw";
985#            style = filled;
986#            color = hot_pink;
987#ENDS
988# "sw-batB-legi:24" [label = 24, color = white,  peripheries = 3, style = filled];
989            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
990
991print "\"$sw\" [label = \"$sw\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
992print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
993print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
994
995
996            for my $swport (keys %db_switch_connected_on_port) {
997               my ($sw_connect,$port_connect) = split ':', $swport;
998               next if not $sw_connect eq $sw;
999               next if $port_connect eq $db_switch_output_port{$sw};
1000               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
1001print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1002              }
1003
1004#            print <<ENDS;
1005#            }
1006#ENDS
1007
1008            }
1009
1010#         print <<ENDL;
1011#         }
1012#ENDL
1013       
1014         }
1015
1016#      print <<ENDB;
1017#      }
1018#ENDB
1019
1020      }
1021
1022#   print "Switch output port and parent port connection\n"; 
1023#   print "---------------------------------------------\n";
1024   for my $sw (sort keys %db_switch_output_port) {
1025      if (exists $db_switch_parent{$sw}) {
1026#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
1027         }
1028      else {
1029         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
1030         }
1031      }
1032   print "\n";
1033
1034#   print "Switch parent and children port inter-connection\n";
1035#   print "------------------------------------------------\n";
1036   for my $swport (sort keys %db_switch_connected_on_port) {       
1037      my ($sw_connect,$port_connect) = split ':', $swport;
1038      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1039         if (exists $db_switch_output_port{$sw}) {
1040            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
1041            }
1042         else {
1043            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
1044            }
1045         }
1046      }
1047
1048print "}\n";
1049   }
1050
1051
1052__END__
1053
1054
1055=head1 NAME
1056
1057klask - ports manager and finder for switch
1058
1059
1060=head1 SYNOPSIS
1061
1062 klask updatedb
1063 klask exportdf
1064
1065 klask searchdb computer
1066 klask search   computer
1067
1068 klask enable  switch port
1069 klask disable swith port
1070 klask status  swith port
1071
1072
1073=head1 DESCRIPTION
1074
1075klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1076
1077
1078=head1 COMMANDS
1079
1080
1081=head2 search
1082
1083This 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.
1084
1085
1086=head2 enable
1087
1088This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1089
1090
1091=head2 disable
1092
1093This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1094
1095
1096=head2 status
1097
1098This 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.
1099
1100
1101=head2 updatedb
1102
1103This 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.
1104
1105
1106=head2 exportdb
1107
1108This 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...
1109
1110
1111=head1 CONFIGURATION
1112
1113Because 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 !
1114
1115Here an example, be aware with indent, it's important in YAML, do not use tabulation !
1116
1117 default:
1118   community: public
1119   snmpport: 161
1120
1121 network:
1122   - add: 192.168.1.0/24
1123   - add: 192.168.2.0/24
1124
1125 switch:
1126   - hostname: sw1.klask.local
1127     portignore:
1128       - 1
1129       - 2
1130
1131   - hostname: sw2.klask.local
1132     portignore:
1133       - 1
1134       - 2
1135
1136I 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.
1137
1138
1139=head1 FILES
1140
1141 /etc/klask.conf
1142 /var/cache/klask/klaskdb
1143
1144
1145=head1 SEE ALSO
1146
1147Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1148
1149
1150=head1 VERSION
1151
11520.2
1153
1154
1155=head1 AUTHOR
1156
1157Written by Gabriel Moreau
1158
1159
1160=head1 COPYRIGHT
1161       
1162Copyright (C) 2005 Gabriel Moreau.
1163
1164
1165=head1 LICENCE
1166
1167GPL version 2 or later and Perl equivalent
Note: See TracBrowser for help on using the repository browser.