source: trunk/klask @ 17

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