source: trunk/klask @ 16

Last change on this file since 16 was 16, checked in by g7moreau, 16 years ago
  • Redirect both standart output and error output to /dev/null
  • 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;
11
12# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
13# libcrypt-des-perl libcrypt-hcesha-perl   libdigest-hmac-perl
14
15my $KLASK_DB_FILE  = '/var/cache/klask/klaskdb';
16my $KLASK_SW_FILE  = '/var/cache/klask/switchdb';
17my $KLASK_CFG_FILE = '/etc/klask.conf';
18
19my $KLASK_CFG = YAML::LoadFile("$KLASK_CFG_FILE");
20
21my %DEFAULT = %{$KLASK_CFG->{default}};
22my @SWITCH  = @{$KLASK_CFG->{switch}};
23
24my %switch_level = ();
25LEVEL_OF_EACH_SWITCH:
26for my $sw (@SWITCH){
27   $switch_level{$sw->{hostname}} = $sw->{level} || $DEFAULT{switch_level}  || 2;
28   }
29@SWITCH = sort { $switch_level{$b->{hostname}} <=> $switch_level{$a->{hostname}} } @{$KLASK_CFG->{switch}}; 
30
31my %SWITCH_PORT_COUNT = ();
32
33my %CMD_DB = (
34   help       => \&cmd_help,
35   exportdb   => \&cmd_exportdb,
36   updatedb   => \&cmd_updatedb,
37   searchdb   => \&cmd_searchdb,
38   removedb   => \&cmd_removedb,
39   search     => \&cmd_search,
40   enable     => \&cmd_enable,
41   disable    => \&cmd_disable,
42   status     => \&cmd_status,
43   updatesw   => \&cmd_updatesw,
44   exportsw   => \&cmd_exportsw,
45   dotsw      => \&cmd_exportsw_dot,
46   iplocation => \&cmd_iplocation,
47   );
48
49my %INTERNAL_PORT_MAP = (
50   0 => 'A',
51   1 => 'B',
52   2 => 'C',
53   3 => 'D',
54   4 => 'E',
55   5 => 'F',
56   6 => 'G',
57   7 => 'H',
58   );
59my %INTERNAL_PORT_MAP_REV = reverse %INTERNAL_PORT_MAP;
60
61
62################
63# principal
64################
65
66my $cmd = shift @ARGV || 'help';
67if (defined $CMD_DB{$cmd}) {
68   $CMD_DB{$cmd}->(@ARGV);
69   }
70else {
71   print STDERR "klask: command $cmd not found\n\n";
72   $CMD_DB{help}->();
73   exit 1;
74   }
75
76exit;
77
78###
79# fast ping dont l'objectif est de remplir la table arp de la machine
80sub fastping {
81   system "fping -c 1 @_ >/dev/null 2>&1";
82   }
83
84###
85# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
86sub resolve_ip_arp_host {
87   my $param_ip_or_host = shift;
88   my $interface = shift || '*';
89   my $type      = shift || 'fast';
90
91   my %ret = (
92      hostname_fq  => 'unknow',
93      ipv4_address => '0.0.0.0',
94      mac_address  => 'unknow',
95      );
96
97#   my $cmdarping  = `arping -c 1 -w 1 -rR $param 2>/dev/null`;
98
99   # controler que arpwatch tourne !
100   # resultat de la commande arpwatch
101   # /var/lib/arpwatch/arp.dat
102   # 0:13:d3:e1:92:d0        192.168.24.109  1163681980      theo8sv109
103   #my $cmd = "grep  -e '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/arp.dat | sort +2rn | head -1";
104#   my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/*.dat | sort +2rn | head -1";
105   my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/$interface.dat | sort +2rn | head -1";
106   my $cmd_arpwatch = `$cmd`;
107   chomp $cmd_arpwatch;
108   my ($arp, $ip, $timestamp, $host) = split /\s+/, $cmd_arpwatch;
109#print "OOO $cmd\n";
110#print "TTT arp $arp -> $ip pour host $host\n";
111   $ret{ipv4_address} = $ip        if $ip;
112   $ret{mac_address}  = $arp       if $arp;
113   $ret{timestamp}    = $timestamp if $timestamp;
114
115   my $nowtimestamp = time();
116
117   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 3 * 3600 ) ) ) {
118      $ret{mac_address} = 'unknow';
119      return %ret;
120      }
121
122  # resultat de la commande arp
123   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
124   my $cmd_arp  = `arp -a $param_ip_or_host 2>/dev/null`;
125   chomp $cmd_arp;
126   $cmd_arp =~ /(\S*)\s\(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\)\sat\s([0-9,A-Z]{2}:[0-9,A-Z]{2}:[0-9,A-Z]{2}:[0-9,A-Z]{2}:[0-9,A-Z]{2}:[0-9,A-Z]{2})/;
127   $ret{hostname_fq}  = $1 if(defined($1));
128   $ret{ipv4_address} = $2 if(defined($2));
129   $ret{mac_address}  = $3 if(defined($3));
130
131#   if ($ret{ipv4_address} eq '0.0.0.0' and $ret{mac_address} eq 'unknow'and $ret{hostname_fq} eq 'unknow') {
132      # resultat de la commande host si le parametre est ip
133      # 250.66.254.194.in-addr.arpa domain name pointer legihp2100.hmg.inpg.fr.
134      my $cmd_host = `host $param_ip_or_host 2>/dev/null`;
135      chomp $cmd_host;
136      $cmd_host =~ m/domain\sname\spointer\s(\S+)\.$/;
137      $ret{hostname_fq} = $1 if defined $1;
138
139      # resultat de la commande host si parametre est hostname
140      # tech7meylan.hmg.inpg.fr has address 194.254.66.240
141      $cmd_host =~ m/\shas\saddress\s([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/;
142      $ret{ipv4_address} = $1 if defined $1;
143
144      $cmd_host =~ m/\b([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.in-addr\.arpa\s/;
145      $ret{ipv4_address} = "$4.$3.$2.$1"     if defined $1 and  defined $2 and  defined $3 and  defined $4;
146      $ret{hostname_fq}  = $param_ip_or_host if not defined $1 and $ret{hostname_fq} eq 'unknow';
147#      }
148
149   unless ($ret{mac_address} eq 'unknow') {
150      my @paquets = ();
151      foreach ( split(/:/, $ret{mac_address}) ) {
152         my @chars = split //, uc("00$_");
153         push @paquets, "$chars[-2]$chars[-1]";
154         }
155      $ret{mac_address} = join ':', @paquets;
156      }
157
158   return %ret;
159   }
160
161###
162# va rechercher le nom des switchs pour savoir qui est qui
163sub init_switch_names {
164   my $verbose = shift;
165   
166   printf "%-25s                %-25s %s\n",'Switch','Description','Type';
167#   print "Switch description\n" if $verbose;
168   print "-------------------------------------------------------------------------\n" if $verbose;
169
170   INIT_EACH_SWITCH:
171   for my $sw (@SWITCH) {
172      my %session = ( -hostname   => $sw->{hostname} );
173         $session{-version} = $sw->{version}   || 1;
174         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
175         if (exists $sw->{version} and $sw->{version} eq 3) {
176            $session{-username} = $sw->{username} || 'snmpadmin';
177            }
178         else {
179            $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
180            }
181
182      $sw->{local_session} = \%session;
183
184      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
185      print "$error \n" if $error;
186
187      my $result = $session->get_request(
188         -varbindlist => ['1.3.6.1.2.1.1.5.0']
189         );
190      $sw->{description} = $result->{"1.3.6.1.2.1.1.5.0"} || $sw->{hostname};
191      #$sw->{location} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{hostname};
192      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{hostname};
193      $session->close;
194 
195      my ($desc, $type) = split ':', $sw->{description}, 2;
196      printf "%-25s 0--------->>>> %-25s %s\n", $sw->{hostname}, $desc, uc($type) if $verbose;
197      }
198
199   print "\n" if $verbose;
200   }
201
202###
203# convertit l'hexa (uniquement 2 chiffres) en decimal
204sub hex_to_dec {
205   #00:0F:1F:43:E4:2B
206   my $car = '00' . uc(shift);
207
208   return '00' if $car eq '00UNKNOW';
209   my %table = (
210      "0"=>"0",  "1"=>"1",  "2"=>"2",  "3"=>"3",  "4"=>"4",  "5"=>"5", "6"=>"6", "7"=>"7", "8"=>"8", "9"=>"9",
211      "A"=>"10", "B"=>"11", "C"=>"12", "D"=>"13", "E"=>"14", "F"=>"15"
212      );
213   my @chars = split(//, $car);
214   return $table{$chars[-2]}*16 + $table{$chars[-1]};
215   }
216
217###
218# convertit l'@ arp en decimal
219sub arp_hex_to_dec {
220   #00:0F:1F:43:E4:2B
221   my $arp = shift;
222
223   my @paquets = split /:/, $arp;
224   my $return = '';
225   foreach(@paquets) {
226      $return .= ".".hex_to_dec($_);
227      }
228   return $return;
229   }
230
231###
232# va rechercher le port et le switch sur lequel est la machine
233sub find_switch_port {
234   my $arp = shift;
235   my $switch_proposal = shift || '';
236   
237   my %ret;
238   $ret{switch_description} = "unknow";
239   $ret{switch_port} = "0";
240
241   return %ret if $arp eq 'unknow';;
242
243   my @SWITCH_search = @SWITCH;
244   if ($switch_proposal ne '') {
245      for my $sw (@SWITCH) {
246         next if $sw->{hostname} ne $switch_proposal;
247         unshift @SWITCH_search, $sw;
248         last;
249         }
250      }
251
252   my $research = "1.3.6.1.2.1.17.4.3.1.2".arp_hex_to_dec($arp);
253   
254   LOOP_ON_SWITCH:
255   for my $sw (@SWITCH_search) {
256      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
257print "$error \n" if $error;
258#         -hostname   => $sw->{hostname},
259#         -community  => $sw->{community} || $DEFAULT{community} || 'public',
260#         -port       => $sw->{snmpport}  || $DEFAULT{snmpport}  || 161
261#         );
262#print "$sw->{hostname} --  $research \n";
263      my $result = $session->get_request(
264         -varbindlist => [$research]
265         );
266#      if(defined($result)) {
267      if (not defined($result) or $result->{$research} eq 'noSuchInstance') {
268#print "$sw->{hostname} --  $research --".$session->error()."\n";
269         $session->close;
270         next LOOP_ON_SWITCH;
271         }
272
273         my $swport = $result->{$research};
274         $session->close;
275
276         # IMPORTANT !!
277         # ceci empeche la detection sur certains port ...
278         # en effet les switch sont relies entre eux par un cable reseau et du coup
279         # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
280         # cette partie est a ameliore, voir a configurer dans l'entete
281         # 21->24 45->48
282#         my $flag = 0;
283         SWITCH_PORT_IGNORE:
284         foreach my $p (@{$sw->{portignore}}) {
285            next SWITCH_PORT_IGNORE if $swport ne get_numerical_port($sw->{hostname},$p);
286#            $flag = 1;
287            next LOOP_ON_SWITCH;
288            }
289#         if ($flag == 0) {
290            $ret{switch_hostname}    = $sw->{hostname};
291            $ret{switch_description} = $sw->{description};
292            $ret{switch_port}        = get_human_readable_port($sw->{hostname}, $swport); # $swport;
293           
294            last LOOP_ON_SWITCH;
295#            }
296#         }
297#      $session->close;
298      }
299   return %ret;
300   }
301
302###
303# va rechercher les port et les switch sur lequel est la machine
304sub find_all_switch_port {
305   my $arp = shift;
306
307   my $ret = {};
308
309   return $ret if $arp eq 'unknow';
310
311   for my $sw (@SWITCH) {
312      $SWITCH_PORT_COUNT{$sw->{hostname}} = {} if not exists $SWITCH_PORT_COUNT{$sw->{hostname}};
313      }
314
315   my $research = "1.3.6.1.2.1.17.4.3.1.2".arp_hex_to_dec($arp);
316   LOOP_ON_ALL_SWITCH:
317   for my $sw (@SWITCH) {
318      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
319      print "$error \n" if $error;
320
321      my $result = $session->get_request(
322         -varbindlist => [$research]
323         );
324
325      if(defined($result) and $result->{$research} ne 'noSuchInstance'){
326         my $swport = $result->{$research};
327
328         $ret->{$sw->{hostname}} = {};
329         $ret->{$sw->{hostname}}{hostname}    = $sw->{hostname};
330         $ret->{$sw->{hostname}}{description} = $sw->{description};
331         $ret->{$sw->{hostname}}{port}        = get_human_readable_port($sw->{hostname}, $swport);
332
333         $SWITCH_PORT_COUNT{$sw->{hostname}}->{$swport}++;
334         }
335
336      $session->close;
337      }
338   return $ret;
339   }
340
341sub get_list_network {
342
343   return keys %{$KLASK_CFG->{network}};
344   }
345
346sub get_current_interface {
347   my $network = shift;
348
349   return $KLASK_CFG->{network}{$network}{interface};
350   }
351 
352###
353# liste l'ensemble des adresses ip d'un réseau
354sub get_list_ip {
355   my @network = @_;
356
357   my $cidrlist = Net::CIDR::Lite->new;
358
359   for my $net (@network) {
360      my @line  = @{$KLASK_CFG->{network}{$net}{'ip-subnet'}};
361      for my $cmd (@line) {
362         for my $method (keys %$cmd){
363            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
364            }
365         }
366      }
367
368   my @res = ();
369
370   for my $cidr ($cidrlist->list()) {
371      my $net = new NetAddr::IP $cidr;
372      for my $ip (@$net) {
373         $ip =~ s#/32##;
374         push @res,  $ip;
375         }
376      }
377
378   return @res;
379   }
380
381# liste l'ensemble des routeurs du réseau
382sub get_list_main_router {
383   my @network = @_;
384
385   my @res = ();
386
387   for my $net (@network) {
388      push @res, $KLASK_CFG->{network}{$net}{'main-router'};
389      }
390
391   return @res;
392   }
393
394sub get_human_readable_port {
395   my $sw = shift;
396   my $port = shift;
397   
398   return $port if not $sw eq 'sw8000-batA.hmg.priv';
399   
400   my $reste = (($port - 1) % 8) + 1;
401   my $major = int( ($port - 1) / 8 );
402
403   return "$INTERNAL_PORT_MAP{$major}$reste";
404   }
405
406sub get_numerical_port {
407   my $sw   = shift;
408   my $port = shift;
409   
410   return $port if not $sw eq 'sw8000-batA.hmg.priv';
411
412   my $letter = substr($port, 0, 1);
413   
414#   return $port if $letter =~ m/\d/;
415   
416   my $reste =  substr($port, 1);
417   
418   return $INTERNAL_PORT_MAP_REV{$letter} * 8 + $reste;
419   }
420
421################
422# Les commandes
423################
424
425sub cmd_help {
426
427print <<END;
428klask - ports manager and finder for switch
429
430 klask updatedb
431 klask exportdf
432
433 klask searchdb computer
434 klask search   computer
435
436 klask enable  switch port
437 klask disable switch port
438 klask status  switch port
439END
440   }
441
442sub cmd_search {
443   my @computer = @_;
444   
445   init_switch_names();    #nomme les switchs
446   fastping(@computer);
447   for my $clientname (@computer) {
448      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
449      my %where     = find_switch_port($resol_arp{mac_address}); #retrouve l'emplacement
450      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"
451         unless $where{switch_description} eq 'unknow' and $resol_arp{hostname_fq} eq 'unknow' and $resol_arp{mac_address} eq 'unknow';
452      }
453   }
454
455sub cmd_searchdb {
456   my @computer = @_;
457
458   fastping(@computer);
459   my $computerdb = YAML::LoadFile("$KLASK_DB_FILE");
460   
461   LOOP_ON_COMPUTER:
462   for my $clientname (@computer) {
463      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
464      my $ip = $resol_arp{ipv4_address};
465     
466      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
467     
468      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($computerdb->{$ip}{timestamp});
469      $year += 1900;
470      $mon++;
471      my $date = sprintf "%04i-%02i-%02i %02i:%02i", $year,$mon,$mday,$hour,$min;
472
473      printf "%-22s %2s %-30s %-15s %-18s %s\n",
474         $computerdb->{$ip}{switch_name},
475         $computerdb->{$ip}{switch_port},
476         $computerdb->{$ip}{hostname_fq},
477         $ip,
478         $computerdb->{$ip}{mac_address},
479         $date;
480      }
481   }
482
483sub cmd_updatedb {
484   my @network = @_;
485      @network = get_list_network() if not @network;
486
487   my $computerdb = YAML::LoadFile("$KLASK_DB_FILE");
488   my $timestamp = time;
489   
490   my %computer_not_detected = ();
491   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
492
493   my $number_of_computer = get_list_ip(@network); # + 1;
494   my $size_of_database   = keys %$computerdb;
495   my $i = 0;
496   my $detected_computer = 0;
497   
498   init_switch_names('yes');    #nomme les switchs
499
500   my %router_mac_ip = ();
501   DETECT_ALL_ROUTER:
502#   for my $one_router ('194.254.66.254') {
503   for my $one_router ( get_list_main_router(@network) ) {
504      my %resol_arp = resolve_ip_arp_host($one_router);
505      $router_mac_ip{ $resol_arp{mac_address} } = $resol_arp{ipv4_address};
506      }
507
508   ALL_NETWORK:
509   for my $net (@network) {
510
511      my @computer = get_list_ip($net);
512      my $current_interface = get_current_interface($net);
513
514      fastping(@computer);
515
516      LOOP_ON_COMPUTER:
517      for my $one_computer (@computer) {
518         $i++;
519         
520         my $total_percent = int(($i*100)/$number_of_computer);
521
522         my $localtime = time - $timestamp;
523         my ($sec,$min) = localtime($localtime);
524
525         my $time_elapse = 0;
526            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
527         my ($sec_elapse,$min_elapse) = localtime($time_elapse);
528
529         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
530#         printf ", Computer detected: %4i/%i (%2i%%)", $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
531         printf ", detected: %4i/%i (%2i%%)", $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
532         printf " [Time: %02i:%02i / %02i:%02i]", int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
533#         printf "  [%02i:%02i/%02i:%02i]", int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
534         printf " %-14s", $one_computer;
535
536         my %resol_arp = resolve_ip_arp_host($one_computer,$current_interface);
537         
538         # do not search on router connection (why ?)
539         if ( exists $router_mac_ip{$resol_arp{mac_address}}) {
540            $computer_not_detected{$one_computer} = $current_interface;
541            next LOOP_ON_COMPUTER;
542            }
543
544         # do not search on switch inter-connection
545         if (exists $switch_level{$resol_arp{hostname_fq}}) {
546            $computer_not_detected{$one_computer} = $current_interface;
547            next LOOP_ON_COMPUTER;
548            }
549
550         my $switch_proposal = '';
551         if (exists $computerdb->{$resol_arp{ipv4_address}} and exists $computerdb->{$resol_arp{ipv4_address}}{switch_hostname}) {
552            $switch_proposal = $computerdb->{$resol_arp{ipv4_address}}{switch_hostname};
553            }
554
555         # do not have a mac address
556         if ($resol_arp{mac_address} eq 'unknow' or (exists $resol_arp{timestamps} and $resol_arp{timestamps} < ($timestamp - 3 * 3600))) {
557            $computer_not_detected{$one_computer} = $current_interface;
558            next LOOP_ON_COMPUTER;
559            }
560
561         my %where = find_switch_port($resol_arp{mac_address},$switch_proposal);
562
563         #192.168.24.156:
564         #  arp: 00:0B:DB:D5:F6:65
565         #  hostname: pcroyon.hmg.priv
566         #  port: 5
567         #  switch: sw-batH-legi:hp2524
568         #  timestamp: 1164355525
569
570         # do not have a mac address
571#         if ($resol_arp{mac_address} eq 'unknow') {
572#            $computer_not_detected{$one_computer} = $current_interface;
573#            next LOOP_ON_COMPUTER;
574#            }
575
576         # detected on a switch
577         if ($where{switch_description} ne 'unknow') {
578            $detected_computer++;
579            $computerdb->{$resol_arp{ipv4_address}} = {
580               hostname_fq        => $resol_arp{hostname_fq},
581               mac_address        => $resol_arp{mac_address},
582               switch_hostname    => $where{switch_hostname},
583               switch_description => $where{switch_description},
584               switch_port        => $where{switch_port},
585               timestamp          => $timestamp,
586               };
587            next LOOP_ON_COMPUTER;
588            }
589
590         # new in the database but where it is ?
591         if (not exists $computerdb->{$resol_arp{ipv4_address}}) {
592            $detected_computer++;
593            $computerdb->{$resol_arp{ipv4_address}} = {
594               hostname_fq        => $resol_arp{hostname_fq},
595               mac_address        => $resol_arp{mac_address},
596               switch_hostname    => $where{switch_hostname},
597               switch_description => $where{switch_description},
598               switch_port        => $where{switch_port},
599               timestamp          => $resol_arp{timestamp},
600               };
601            }
602
603         # mise a jour du nom de la machine si modification dans le dns
604         $computerdb->{$resol_arp{ipv4_address}}{hostname_fq} = $resol_arp{hostname_fq};
605       
606         # mise à jour de la date de détection si détection plus récente par arpwatch
607         $computerdb->{$resol_arp{ipv4_address}}{timestamp}   = $resol_arp{timestamp} if exists $resol_arp{timestamp} and $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $resol_arp{timestamp};
608
609         # provisoire car changement de nom des attributs
610#         $computerdb->{$resol_arp{ipv4_address}}{mac_address}        = $computerdb->{$resol_arp{ipv4_address}}{arp};
611#         $computerdb->{$resol_arp{ipv4_address}}{switch_description} = $computerdb->{$resol_arp{ipv4_address}}{switch};
612#         $computerdb->{$resol_arp{ipv4_address}}{switch_port}        = $computerdb->{$resol_arp{ipv4_address}}{port};
613       
614         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
615#         push @computer_not_detected, $resol_arp{ipv4_address} if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
616         $computer_not_detected{$resol_arp{ipv4_address}} = $current_interface if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
617       
618         }
619      }
620
621   # final end of line at the end of the loop
622   printf "\n";
623
624   my $dirdb = $KLASK_DB_FILE;
625      $dirdb =~ s#/[^/]*$##;
626   mkdir "$dirdb", 0755 unless -d "$dirdb";
627   YAML::DumpFile("$KLASK_DB_FILE", $computerdb);
628
629   for my $one_computer (keys %computer_not_detected) {
630      my $interface = $computer_not_detected{$one_computer};
631      system "arping -c 1 -w 1 -rR -i $interface $one_computer &>/dev/null";
632#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
633      }
634   }
635
636sub cmd_removedb {
637   my @computer = @_;
638   
639   my $computerdb = YAML::LoadFile("$KLASK_DB_FILE");
640
641   LOOP_ON_COMPUTER:
642   for my $one_computer (@computer) {
643
644      my %resol_arp = resolve_ip_arp_host($one_computer);
645
646      delete $computerdb->{$resol_arp{ipv4_address}} if exists $computerdb->{$resol_arp{ipv4_address}};
647      }
648
649   my $dirdb = $KLASK_DB_FILE;
650      $dirdb =~ s#/[^/]*$##;
651   mkdir "$dirdb", 0755 unless -d "$dirdb";
652   YAML::DumpFile("$KLASK_DB_FILE", $computerdb);
653   }
654
655sub cmd_exportdb {
656   my $computerdb = YAML::LoadFile("$KLASK_DB_FILE");
657
658   printf "%-24s %-4s            %-30s %-15s %-18s %-s\n", qw(Switch Port Hostname IPv4-Address MAC-Address Date);
659   print "---------------------------------------------------------------------------------------------------------------------------\n";
660
661   LOOP_ON_IP_ADDRESS:
662   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %$computerdb)) {
663   
664#      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq 'unknow';
665
666      # to be improve in the future
667      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
668
669# dans le futur
670#      next if $computerdb->{$ip}{hostname_fq} eq 'unknow';
671     
672      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($computerdb->{$ip}{timestamp});
673      $year += 1900;
674      $mon++;
675      my $date = sprintf "%04i-%02i-%02i %02i:%02i", $year,$mon,$mday,$hour,$min;
676
677      printf "%-25s  %2s  <-------  %-30s %-15s %-18s %s\n",
678         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
679         $computerdb->{$ip}{switch_port},
680         $computerdb->{$ip}{hostname_fq},
681         $ip,
682         $computerdb->{$ip}{mac_address},
683         $date;
684      }
685   }
686
687sub cmd_iplocation {
688   my $computerdb = YAML::LoadFile("$KLASK_DB_FILE");
689
690   LOOP_ON_IP_ADDRESS:
691   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %$computerdb)) {
692
693      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
694
695      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || '';
696      next if $sw_hostname eq 'unknow';
697 
698      my $sw_location = '';
699      for my $sw (@SWITCH) {
700         next if $sw_hostname ne $sw->{hostname};
701         $sw_location = $sw->{location};
702         last;
703         }
704
705      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq '';
706      }
707   }
708
709sub cmd_enable {
710   my $switch = shift;
711   my $port   = shift;
712   
713   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
714   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
715   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
716   }
717
718sub cmd_disable {
719   my $switch = shift;
720   my $port   = shift;
721   
722   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
723   }
724
725sub cmd_status {
726   my $switch = shift;
727   my $port   = shift;
728   
729   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
730   }
731
732
733sub cmd_updatesw {
734
735   init_switch_names('yes');    #nomme les switchs
736   print "\n";
737
738   my %where = ();
739   my %db_switch_output_port = ();
740   my %db_switch_ip_hostname = ();
741
742   DETECT_ALL_ROUTER:
743#   for my $one_computer ('194.254.66.254') {
744   for my $one_router ( get_list_main_router(get_list_network()) ) {
745      my %resol_arp = resolve_ip_arp_host($one_router,'*','low');            # resolution arp
746      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
747     
748      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
749      }
750
751   ALL_ROUTER_IP_ADDRESS:
752   for my $ip (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
753   
754      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip}; # /a priori/ idiot car ne sers à rien...
755
756      ALL_SWITCH_CONNECTED:
757      for my $switch_detected ( keys %{$where{$ip}} ) {
758
759         my $switch = $where{$ip}->{$switch_detected};
760
761         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
762         
763         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
764         }
765      }   
766
767#   print "Switch output port\n"; 
768#   print "------------------\n";
769#   for my $sw (sort keys %db_switch_output_port) {
770#      printf "%-25s %2s\n", $sw, $db_switch_output_port{$sw};
771#      }
772#   print "\n";
773
774
775   my %db_switch_link_with = ();
776
777   my @list_switch_ip = ();
778   my @list_switch_ipv4 = ();
779   for my $sw (@SWITCH){
780      push @list_switch_ip, $sw->{hostname};
781      }
782
783   ALL_SWITCH:
784   for my $one_computer (@list_switch_ip) {
785      my %resol_arp = resolve_ip_arp_host($one_computer,'*','low'); # arp resolution
786      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
787     
788      push @list_switch_ipv4,$resol_arp{ipv4_address};
789     
790      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
791
792      $db_switch_ip_hostname{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
793      }
794     
795   ALL_SWITCH_IP_ADDRESS:
796   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
797   
798      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
799
800      DETECTED_SWITCH:
801      for my $switch_detected ( keys %{$where{$ip}} ) {
802
803         next DETECTED_SWITCH if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostname{$ip}};
804
805         my $switch = $where{$ip}->{$switch_detected};
806
807         next if $switch->{port}     eq '0';
808         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
809         next if $switch->{hostname} eq $db_switch_ip_hostname{$ip}; # $computerdb->{$ip}{hostname};
810
811         $db_switch_link_with{ $db_switch_ip_hostname{$ip} } ||= {};
812         $db_switch_link_with{ $db_switch_ip_hostname{$ip} }->{ $switch->{hostname} } = $switch->{port};
813         }
814
815      }
816   
817   my %db_switch_connected_on_port = ();
818   my $maybe_more_than_one_switch_connected = 'yes';
819   
820   while ($maybe_more_than_one_switch_connected eq 'yes') {
821      for my $sw (keys %db_switch_link_with) {
822         for my $connect (keys %{$db_switch_link_with{$sw}}) {
823         
824            my $port = $db_switch_link_with{$sw}->{$connect};
825         
826            $db_switch_connected_on_port{"$connect:$port"} ||= {};
827            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
828            }
829         }
830
831      $maybe_more_than_one_switch_connected  = 'no';
832
833      SWITCH_AND_PORT:
834      for my $swport (keys %db_switch_connected_on_port) {
835         
836         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
837         
838         $maybe_more_than_one_switch_connected = 'yes';
839
840         my ($sw_connect,$port_connect) = split ':', $swport;
841         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
842
843         CONNECTED:
844         for my $sw_connected (@sw_on_same_port) {
845           
846            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
847           
848            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
849           
850            for my $other_sw (@sw_on_same_port) {
851               next if $other_sw eq $sw_connected;
852               
853               delete $db_switch_link_with{$other_sw}->{$sw_connect};
854               }
855           
856            # We can not do better for this switch for this loop
857            next SWITCH_AND_PORT;
858            }
859         }
860      }
861
862   my %db_switch_parent =();
863
864   for my $sw (keys %db_switch_link_with) {
865      for my $connect (keys %{$db_switch_link_with{$sw}}) {
866     
867         my $port = $db_switch_link_with{$sw}->{$connect};
868     
869         $db_switch_connected_on_port{"$connect:$port"} ||= {};
870         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
871       
872         $db_switch_parent{$sw} = {switch => $connect, port => $port};
873         }
874      }
875
876   print "Switch output port and parent port connection\n"; 
877   print "---------------------------------------------\n";
878   for my $sw (sort keys %db_switch_output_port) {
879      if (exists $db_switch_parent{$sw}) {
880         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
881         }
882      else {
883         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
884         }
885      }
886   print "\n";
887
888   print "Switch parent and children port inter-connection\n";
889   print "------------------------------------------------\n";
890   for my $swport (sort keys %db_switch_connected_on_port) {       
891      my ($sw_connect,$port_connect) = split ':', $swport;
892      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
893         if (exists $db_switch_output_port{$sw}) {
894            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
895            }
896         else {
897            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
898            }
899         }
900      }
901
902   my $switch_connection = {
903      output_port       => \%db_switch_output_port,
904      parent            => \%db_switch_parent,
905      connected_on_port => \%db_switch_connected_on_port,
906      link_with         => \%db_switch_link_with,
907      };
908     
909   YAML::DumpFile("$KLASK_SW_FILE", $switch_connection);
910   }
911
912sub cmd_exportsw {
913   my @ARGV   = @_;
914
915   my $format = 'txt';
916   use Getopt::Long;
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.