source: trunk/klask @ 2

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