source: trunk/klask @ 13

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