source: trunk/klask @ 69

Last change on this file since 69 was 69, checked in by g7moreau, 13 years ago
  • Add command cmd_ip_free

klask ip-free

return ip not use in one or more VLAN.

  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 57.8 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2008 Gabriel Moreau.
4#
5# $Id: klask 69 2010-11-02 15:43:26Z g7moreau $
6
7use strict;
8use warnings;
9use version; our $VERSION = qv('0.5.5');
10
11use Readonly;
12use FileHandle;
13use Net::SNMP;
14#use YAML;
15use YAML::Syck;
16use Net::Netmask;
17use Net::CIDR::Lite;
18use NetAddr::IP;
19use Getopt::Long qw(GetOptions GetOptionsFromArray);;
20use Socket;
21
22# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
23# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
24# arping fping bind9-host arpwatch
25
26my $KLASK_VAR      = '/var/cache/klask';
27my $KLASK_CFG_FILE = '/etc/klask.conf';
28my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
29my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
30
31test_running_environnement();
32
33my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
34
35my %DEFAULT = %{ $KLASK_CFG->{default} };
36my @SWITCH  = @{ $KLASK_CFG->{switch}  };
37
38my %switch_level = ();
39my %SWITCH_DB    = ();
40LEVEL_OF_EACH_SWITCH:
41for my $sw (@SWITCH){
42   $switch_level{$sw->{hostname}} = $sw->{level} || $DEFAULT{switch_level}  || 2;
43   $SWITCH_DB{$sw->{hostname}} = $sw;
44   }
45@SWITCH = reverse sort { $switch_level{$a->{hostname}} <=> $switch_level{$b->{hostname}} } @{$KLASK_CFG->{switch}};
46
47my %SWITCH_PORT_COUNT = ();
48
49my %CMD_DB = (
50   help       => \&cmd_help,
51   version    => \&cmd_version,
52   exportdb   => \&cmd_exportdb,
53   updatedb   => \&cmd_updatedb,
54   searchdb   => \&cmd_searchdb,
55   removedb   => \&cmd_removedb,
56   search     => \&cmd_search,
57   enable     => \&cmd_enable,
58   disable    => \&cmd_disable,
59   status     => \&cmd_status,
60   updatesw   => \&cmd_updatesw,
61   exportsw   => \&cmd_exportsw,
62   iplocation => \&cmd_iplocation,
63   'ip-free'  => \&cmd_ip_free,
64   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
65   );
66
67Readonly my %INTERNAL_PORT_MAP => (
68   0 => 'A',
69   1 => 'B',
70   2 => 'C',
71   3 => 'D',
72   4 => 'E',
73   5 => 'F',
74   6 => 'G',
75   7 => 'H',
76   );
77Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
78
79Readonly my %SWITCH_KIND => (
80   J3299A => { model => 'HP224M',     match => 'HP J3299A ProCurve Switch 224M'  },
81   J4120A => { model => 'HP1600M',    match => 'HP J4120A ProCurve Switch 1600M' },
82   J9029A => { model => 'HP1800-8G',  match => 'PROCURVE J9029A'                 },
83   J9449A => { model => 'HP1810-8G',  match => 'HP ProCurve 1810G - 8 GE'        },
84   J4093A => { model => 'HP2424M',    match => 'HP J4093A ProCurve Switch 2424M' },
85   J4813A => { model => 'HP2524',     match => 'HP J4813A ProCurve Switch 2524'  },
86   J4900A => { model => 'HP2626A',    match => 'HP J4900A ProCurve Switch 2626'  },
87   J4900B => { model => 'HP2626B',    match => 'J4900B.+?Switch 2626'            },# ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
88   J4899B => { model => 'HP2650',     match => 'ProCurve J4899B Switch 2650'     },
89   J9021A => { model => 'HP2810-24G', match => 'ProCurve J9021A Switch 2810-24G' },
90   J9022A => { model => 'HP2810-48G', match => 'ProCurve J9022A Switch 2810-48G' },
91   J4903A => { model => 'HP2824',     match => 'J4903A.+?Switch 2824,'           },
92   J4110A => { model => 'HP8000M',    match => 'HP J4110A ProCurve Switch 8000M' },
93   BS350T => { model => 'BS350T',     match => 'BayStack 350T HW'                },
94   );
95
96Readonly my %OID_NUMBER => (
97   sysDescription  => '1.3.6.1.2.1.1.1.0',
98   sysName         => '1.3.6.1.2.1.1.5.0',
99   sysContact      => '1.3.6.1.2.1.1.4.0',
100   sysLocation     => '1.3.6.1.2.1.1.6.0',
101   );
102
103Readonly my $RE_MAC_ADDRESS  => qr{ [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} }xms;
104Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
105
106
107################
108# principal
109################
110
111my $cmd = shift @ARGV || 'help';
112if (defined $CMD_DB{$cmd}) {
113   $CMD_DB{$cmd}->(@ARGV);
114   }
115else {
116   print {*STDERR} "klask: command $cmd not found\n\n";
117   $CMD_DB{help}->();
118   exit 1;
119   }
120
121exit;
122
123sub test_running_environnement {
124   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
125   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
126   return;
127   }
128
129sub test_switchdb_environnement {
130   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
131   return;
132   }
133
134sub test_maindb_environnement {
135   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
136   return;
137   }
138
139###
140# fast ping dont l'objectif est de remplir la table arp de la machine
141sub fastping {
142   # Launch this command without waiting...
143   system "fping -c 1 @_ >/dev/null 2>&1 &";
144   return;
145   }
146
147sub shell_command {
148   my $cmd = shift;
149
150   my $fh     = new FileHandle;
151   my $result = '';
152   open $fh, q{-|}, "$cmd" or die "Can't exec $cmd\n";
153   $result .= <$fh>;
154   close $fh;
155   chomp $result;
156   return $result;
157   }
158
159###
160# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
161sub resolve_ip_arp_host {
162   my $param_ip_or_host = shift;
163   my $interface = shift || q{*};
164   my $type      = shift || q{fast};
165
166   my %ret = (
167      hostname_fq  => 'unknow',
168      ipv4_address => '0.0.0.0',
169      mac_address  => 'unknow',
170      );
171
172#   my $cmdarping  = `arping -c 1 -w 1 -rR $param 2>/dev/null`;
173#   if ( not $param_ip_or_host =~ m/^\d+ \. \d+ \. \d+ \. \d+$/xms ) {
174#      $param_ip_or_host =~ s/ \. .* //xms;
175#      }
176
177   #my $dns_resolver =  Net::DNS::Resolver->new;
178   #my $dns_answer = $dns_resolver->query($param_ip_or_host);
179   #if ($dns_answer) {
180   #   $ret{ipv4_address} = $dns_answer->address;
181   #   }
182
183   # perl -MSocket -E 'say inet_ntoa(scalar gethostbyname("tech7meylan.hmg.inpg.fr"))'
184   my $packed_ip = scalar gethostbyname($param_ip_or_host);
185   return %ret if not defined $packed_ip;
186   $ret{ipv4_address} = inet_ntoa($packed_ip);
187
188   # perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("194.254.66.240"), AF_INET)'
189   my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
190#   return %ret if not defined $hostname_fq;
191   $ret{hostname_fq} = $hostname_fq if defined $hostname_fq;
192
193#print "DEBUG : $param_ip_or_host --  $ret{ipv4_address} --   $hostname_fq \n";
194
195   # controler que arpwatch tourne !
196   # resultat de la commande arpwatch
197   # /var/lib/arpwatch/arp.dat
198   # 0:13:d3:e1:92:d0        192.168.24.109  1163681980      theo8sv109
199   # my $cmd = "grep  -e '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/arp.dat | sort +2rn | head -1";
200   # my $cmd = "grep  -he '".'\b'."$param_ip_or_host".'\b'."' /var/lib/arpwatch/*.dat | sort +2rn | head -1";
201
202### BOGUE ICI
203### FAIRE LE GREP SUR L'IP !
204
205   # my $cmd = q{grep  -he '\b} . $param_ip_or_host . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
206   my $cmd = q{grep  -he '\b} . $ret{ipv4_address} . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
207   my $cmd_arpwatch = shell_command $cmd;
208   my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
209
210#   $ret{ipv4_address} = $ip        if $ip;
211   $ret{mac_address}  = $arp       if $arp;
212   $ret{timestamp}    = $timestamp if $timestamp;
213
214   my $nowtimestamp = time;
215
216   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 3 * 3600 ) ) ) {
217      $ret{mac_address} = 'unknow';
218      return %ret;
219      }
220
221   # resultat de la commande arp
222   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
223   # sw2-batF0-legi.hmg.priv (192.168.22.112) at 00:30:c1:76:9c:01 [ether] on eth0.37
224   my $cmd_arp  = shell_command "arp -a $param_ip_or_host";
225   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
226      ( $ret{hostname_fq}, $ret{ipv4_address}, $ret{mac_address} )  = ($1, $2, $3);
227      }
228
229   # resultat de la commande host si le parametre est ip
230   # 250.66.254.194.in-addr.arpa domain name pointer legihp2100.hmg.inpg.fr.
231#   my $cmd_host = shell_command "host $param_ip_or_host";
232#   if ( $cmd_host =~ m/domain \s name \s pointer \s (\S+) \.$/xms ) {
233#      $ret{hostname_fq} = $1;
234#      }
235
236   # resultat de la commande host si parametre est hostname
237   # tech7meylan.hmg.inpg.fr has address 194.254.66.240
238#   if ( $cmd_host =~ m/(\S*) \s has \s address \s ( $RE_IPv4_ADDRESS )$/xms ) {
239#      ( $ret{hostname_fq}, $ret{ipv4_address} ) = ($1, $2);
240#      }
241
242   # Connerie !
243   # Inverse les IP !!
244   # if ( $cmd_host =~ m/ \b ( $RE_IPv4_ADDRESS ) \. in-addr \. arpa \s/xms ) {
245   #    $ret{ipv4_address} = $1;
246   #    }
247   #$ret{hostname_fq}  = $param_ip_or_host if not defined $1 and $ret{hostname_fq} eq 'unknow';
248
249   if ($ret{mac_address} ne 'unknow') {
250      my @paquets = ();
251      foreach ( split m/ : /xms, $ret{mac_address} ) {
252         my @chars = split m//xms, uc "00$_";
253         push @paquets, "$chars[-2]$chars[-1]";
254         }
255      $ret{mac_address} = join q{:}, @paquets;
256      }
257
258   return %ret;
259   }
260
261# Find Surname of a switch
262sub get_switch_model {
263   my $sw_snmp_description = shift || 'unknow';
264
265   for my $sw_kind (keys %SWITCH_KIND) {
266      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/ms; # option xms break search, why ?
267
268      return $SWITCH_KIND{$sw_kind}->{model};
269      }
270
271   return $sw_snmp_description;
272   }
273
274###
275# va rechercher le nom des switchs pour savoir qui est qui
276sub init_switch_names {
277   my $verbose = shift;
278
279   printf "%-25s                %-25s %s\n",'Switch','Description','Type';
280   print "-------------------------------------------------------------------------\n" if $verbose;
281
282   INIT_EACH_SWITCH:
283   for my $sw (@SWITCH) {
284      my %session = ( -hostname   => $sw->{hostname} );
285         $session{-version} = $sw->{version}   || 1;
286         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
287         if (exists $sw->{version} and $sw->{version} eq '3') {
288            $session{-username} = $sw->{username} || 'snmpadmin';
289            }
290         else {
291            $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
292            }
293
294      $sw->{local_session} = \%session;
295
296      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
297      print "$error \n" if $error;
298
299      my $result = $session->get_request(
300         -varbindlist => [
301            $OID_NUMBER{sysDescription},
302            $OID_NUMBER{sysName},
303            $OID_NUMBER{sysContact},
304            $OID_NUMBER{sysLocation},
305            ]
306         );
307      $sw->{description} = $result->{$OID_NUMBER{sysName}} || $sw->{hostname};
308      $sw->{model} = get_switch_model( $result->{$OID_NUMBER{sysDescription}});
309      #$sw->{location} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{hostname};
310      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{hostname};
311      $session->close;
312
313      # Ligne à virer car on récupère maintenant le modèle du switch
314      my ($desc, $type) = split m/ : /xms, $sw->{description}, 2;
315      printf "%-25s 0--------->>>> %-25s %s\n", $sw->{hostname}, $desc, $sw->{model} if $verbose;
316      }
317
318   print "\n" if $verbose;
319   return;
320   }
321
322###
323# convertit l'hexa (uniquement 2 chiffres) en decimal
324sub hex_to_dec {
325   #00:0F:1F:43:E4:2B
326   my $car = '00' . uc shift;
327
328   return '00' if $car eq '00UNKNOW';
329   my %table = (
330      '0'=>'0',  '1'=>'1',  '2'=>'2',  '3'=>'3',  '4'=>'4',
331      '5'=>'5',  '6'=>'6',  '7'=>'7',  '8'=>'8',  '9'=>'9',
332      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15',
333      );
334   my @chars = split m//xms, $car;
335   return $table{$chars[-2]}*16 + $table{$chars[-1]};
336   }
337
338###
339# convertit l'@ arp en decimal
340sub arp_hex_to_dec {
341   #00:0F:1F:43:E4:2B
342   my $arp = shift;
343
344   my @paquets = split m/ : /xms, $arp;
345   my $return = q{};
346   foreach(@paquets) {
347      $return .= q{.} . hex_to_dec($_);
348      }
349   return $return;
350   }
351
352###
353# va rechercher le port et le switch sur lequel est la machine
354sub find_switch_port {
355   my $arp             = shift;
356   my $switch_proposal = shift || q{};
357
358   my %ret;
359   $ret{switch_description} = 'unknow';
360   $ret{switch_port} = '0';
361
362   return %ret if $arp eq 'unknow';;
363
364   my @switch_search = @SWITCH;
365   if ($switch_proposal ne q{}) {
366      for my $sw (@SWITCH) {
367         next if $sw->{hostname} ne $switch_proposal;
368         unshift @switch_search, $sw;
369         last;
370         }
371      }
372
373   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($arp);
374
375   LOOP_ON_SWITCH:
376   for my $sw (@switch_search) {
377      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
378      print "$error \n" if $error;
379
380      my $result = $session->get_request(
381         -varbindlist => [$research]
382         );
383      if (not defined $result or $result->{$research} eq 'noSuchInstance') {
384         $session->close;
385         next LOOP_ON_SWITCH;
386         }
387
388         my $swport = $result->{$research};
389         $session->close;
390
391         # IMPORTANT !!
392         # ceci empeche la detection sur certains port ...
393         # en effet les switch sont relies entre eux par un cable reseau et du coup
394         # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
395         # cette partie est a ameliore, voir a configurer dans l'entete
396         # 21->24 45->48
397#         my $flag = 0;
398         SWITCH_PORT_IGNORE:
399         foreach my $p (@{$sw->{portignore}}) {
400            next SWITCH_PORT_IGNORE if $swport ne get_numerical_port($sw->{model},$p);
401#            $flag = 1;
402            next LOOP_ON_SWITCH;
403            }
404#         if ($flag == 0) {
405            $ret{switch_hostname}    = $sw->{hostname};
406            $ret{switch_description} = $sw->{description};
407            $ret{switch_port}        = get_human_readable_port($sw->{model}, $swport); # $swport;
408
409            last LOOP_ON_SWITCH;
410#            }
411#         }
412#      $session->close;
413      }
414   return %ret;
415   }
416
417###
418# va rechercher les port et les switch sur lequel est la machine
419sub find_all_switch_port {
420   my $arp = shift;
421
422   my $ret = {};
423
424   return $ret if $arp eq 'unknow';
425
426   for my $sw (@SWITCH) {
427      $SWITCH_PORT_COUNT{$sw->{hostname}} = {} if not exists $SWITCH_PORT_COUNT{$sw->{hostname}};
428      }
429
430   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($arp);
431   LOOP_ON_ALL_SWITCH:
432   for my $sw (@SWITCH) {
433      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
434      print "$error \n" if $error;
435
436      my $result = $session->get_request(
437         -varbindlist => [$research]
438         );
439
440      if(defined $result and $result->{$research} ne 'noSuchInstance'){
441         my $swport = $result->{$research};
442
443#print "DEBUG $arp  $swport --  $sw->{hostname} \n";
444         if ( $sw->{hostname} eq 'sw10-batE1-3s.hmg.priv' and $swport == 19 ) { $swport = 20; print "DEBUG $swport --  $sw->{hostname} \n";}
445         if ( $sw->{hostname} eq 'sw8-batE1-3s.hmg.priv'  and $swport == 23 ) { $swport = 24; print "DEBUG $swport --  $sw->{hostname} \n";}
446#         if ( $sw->{hostname} eq 'sw10-batE1-3s.hmg.priv' ) {  print "DEBUG $swport --  $sw->{hostname} \n";}
447#         if ( $sw->{hostname} eq 'sw8-batE1-3s.hmg.priv'  ) {  print "DEBUG $swport --  $sw->{hostname} \n";}
448
449         $ret->{$sw->{hostname}} = {};
450         $ret->{$sw->{hostname}}{hostname}    = $sw->{hostname};
451         $ret->{$sw->{hostname}}{description} = $sw->{description};
452         $ret->{$sw->{hostname}}{port}        = get_human_readable_port($sw->{model}, $swport);
453
454         $SWITCH_PORT_COUNT{$sw->{hostname}}->{$swport}++;
455         }
456
457      $session->close;
458      }
459   return $ret;
460   }
461
462sub get_list_network {
463
464   return keys %{$KLASK_CFG->{network}};
465   }
466
467sub get_current_interface {
468   my $network = shift;
469
470   return $KLASK_CFG->{network}{$network}{interface};
471   }
472
473###
474# liste l'ensemble des adresses ip d'un réseau
475sub get_list_ip {
476   my @network = @_;
477
478   my $cidrlist = Net::CIDR::Lite->new;
479
480   for my $net (@network) {
481      my @line  = @{$KLASK_CFG->{network}{$net}{'ip-subnet'}};
482      for my $cmd (@line) {
483         for my $method (keys %{$cmd}){
484            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
485            }
486         }
487      }
488
489   my @res = ();
490
491   for my $cidr ($cidrlist->list()) {
492      my $net = new NetAddr::IP $cidr;
493      for my $ip (@{$net}) {
494         $ip =~ s{ /32 }{}xms;
495         push @res,  $ip;
496         }
497      }
498
499   return @res;
500   }
501
502# liste l'ensemble des routeurs du réseau
503sub get_list_main_router {
504   my @network = @_;
505
506   my @res = ();
507
508   for my $net (@network) {
509      push @res, $KLASK_CFG->{network}{$net}{'main-router'};
510      }
511
512   return @res;
513   }
514
515sub get_human_readable_port {
516   my $sw_model = shift;
517   my $sw_port  = shift;
518
519   if ($sw_model eq 'HP8000M') {
520
521      my $reste = (($sw_port - 1) % 8) + 1;
522      my $major = int (($sw_port - 1) / 8);
523      return "$INTERNAL_PORT_MAP{$major}$reste";
524      }
525
526   if ($sw_model eq 'HP2424M') {
527      if ($sw_port > 24) {
528         
529         my $reste = $sw_port - 24;
530         return "A$reste";
531         }
532      }
533
534   if ($sw_model eq 'HP1600M') {
535      if ($sw_port > 16) {
536         
537         my $reste = $sw_port - 16;
538         return "A$reste";
539         }
540      }
541
542   return $sw_port;
543   }
544
545sub get_numerical_port {
546   my $sw_model = shift;
547   my $sw_port  = shift;
548
549   if ($sw_model eq 'HP8000M') {
550
551      my $letter = substr $sw_port, 0, 1;
552      my $reste =  substr $sw_port, 1;
553
554      return $INTERNAL_PORT_MAP_REV{$letter} * 8 + $reste;
555      }
556
557   if ($sw_model eq 'HP2424M') {
558      if ($sw_port =~ m/^A/xms ) {
559
560         my $reste =  substr $sw_port, 1;
561
562         return 24 + $reste;
563         }
564      }
565
566   if ($sw_model eq 'HP1600M') {
567      if ($sw_port =~ m/^A/xms ) {
568
569         my $reste =  substr $sw_port, 1;
570
571         return 16 + $reste;
572         }
573      }
574
575   return $sw_port;
576   }
577
578################
579# Les commandes
580################
581
582sub cmd_help {
583
584print <<'END';
585klask - ports manager and finder for switch
586
587 klask updatedb
588 klask exportdb --format [txt|html]
589
590 klask updatesw
591 klask exportsw --format [txt|dot]
592
593 klask searchdb computer
594 klask search   computer
595 klask search-mac-on-switch switch mac_addr
596
597 klask ip-free --day number_of_day [vlan_name]
598
599 klask enable  switch port
600 klask disable switch port
601 klask status  switch port
602END
603   return;
604   }
605
606sub cmd_version {
607
608print <<'END';
609Klask - ports manager and finder for switch
610Copyright (C) 2005-2008 Gabriel Moreau
611
612END
613   print ' $Rev: 69 $'."\n";
614   print ' $Date: 2010-11-02 15:43:26 +0000 (Tue, 02 Nov 2010) $'."\n";
615   print ' $Id: klask 69 2010-11-02 15:43:26Z g7moreau $'."\n";
616   return;
617   }
618
619sub cmd_search {
620   my @computer = @_;
621
622   init_switch_names();    #nomme les switchs
623   fastping(@computer);
624   for my $clientname (@computer) {
625      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
626      my %where     = find_switch_port($resol_arp{mac_address}); #retrouve l'emplacement
627      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"
628         unless $where{switch_description} eq 'unknow' and $resol_arp{hostname_fq} eq 'unknow' and $resol_arp{mac_address} eq 'unknow';
629      }
630   return;
631   }
632
633sub cmd_searchdb {
634   my @computer = @_;
635
636   fastping(@computer);
637   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
638
639   LOOP_ON_COMPUTER:
640   for my $clientname (@computer) {
641      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
642      my $ip = $resol_arp{ipv4_address};
643
644      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
645
646      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
647      $year += 1900;
648      $mon++;
649      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
650
651      printf "%-22s %2s %-30s %-15s %-18s %s\n",
652         $computerdb->{$ip}{switch_name},
653         $computerdb->{$ip}{switch_port},
654         $computerdb->{$ip}{hostname_fq},
655         $ip,
656         $computerdb->{$ip}{mac_address},
657         $date;
658      }
659   return;
660   }
661
662sub cmd_updatedb {
663   my @network = @_;
664      @network = get_list_network() if not @network;
665
666   test_switchdb_environnement();
667
668   my $computerdb = {};
669      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
670   my $timestamp = time;
671
672   my %computer_not_detected = ();
673   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
674
675   my $number_of_computer = get_list_ip(@network); # + 1;
676   my $size_of_database   = keys %{$computerdb};
677      $size_of_database   = 1 if $size_of_database == 0;
678   my $i = 0;
679   my $detected_computer = 0;
680
681   init_switch_names('yes');    #nomme les switchs
682
683   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
684   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
685   my %db_switch_output_port       = %{$switch_connection->{output_port}};
686   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
687   my %db_switch_chained_port = ();
688   for my $swport (keys %db_switch_connected_on_port) {
689      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
690      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
691      }
692   for my $sw (@SWITCH){
693      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{hostname}}  if exists $db_switch_output_port{$sw->{hostname}};
694      if ( exists $db_switch_chained_port{$sw->{hostname}} ) {
695         chop $db_switch_chained_port{$sw->{hostname}};
696         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{hostname}};
697         }
698#      print "$sw->{hostname} ++ @{$sw->{portignore}}\n";
699      }
700   }
701
702   my %router_mac_ip = ();
703   DETECT_ALL_ROUTER:
704#   for my $one_router ('194.254.66.254') {
705   for my $one_router ( get_list_main_router(@network) ) {
706      my %resol_arp = resolve_ip_arp_host($one_router);
707      $router_mac_ip{ $resol_arp{mac_address} } = $resol_arp{ipv4_address};
708      }
709
710   ALL_NETWORK:
711   for my $net (@network) {
712
713      my @computer = get_list_ip($net);
714      my $current_interface = get_current_interface($net);
715
716      fastping(@computer);
717
718      LOOP_ON_COMPUTER:
719      for my $one_computer (@computer) {
720         $i++;
721
722         my $total_percent = int (($i*100)/$number_of_computer);
723
724         my $localtime = time - $timestamp;
725         my ($sec,$min) = localtime $localtime;
726
727         my $time_elapse = 0;
728            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
729         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
730
731         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
732#         printf ", Computer detected: %4i/%i (%2i%%)", $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
733         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
734         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
735#         printf '  [%02i:%02i/%02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
736         printf ' %-14s', $one_computer;
737
738         my %resol_arp = resolve_ip_arp_host($one_computer,$current_interface);
739
740         # do not search on router connection (why ?)
741         if ( exists $router_mac_ip{$resol_arp{mac_address}}) {
742            $computer_not_detected{$one_computer} = $current_interface;
743            next LOOP_ON_COMPUTER;
744            }
745
746         # do not search on switch inter-connection
747         if (exists $switch_level{$resol_arp{hostname_fq}}) {
748            $computer_not_detected{$one_computer} = $current_interface;
749            next LOOP_ON_COMPUTER;
750            }
751
752         my $switch_proposal = q{};
753         if (exists $computerdb->{$resol_arp{ipv4_address}} and exists $computerdb->{$resol_arp{ipv4_address}}{switch_hostname}) {
754            $switch_proposal = $computerdb->{$resol_arp{ipv4_address}}{switch_hostname};
755            }
756
757         # do not have a mac address
758         if ($resol_arp{mac_address} eq 'unknow' or (exists $resol_arp{timestamps} and $resol_arp{timestamps} < ($timestamp - 3 * 3600))) {
759            $computer_not_detected{$one_computer} = $current_interface;
760            next LOOP_ON_COMPUTER;
761            }
762
763         my %where = find_switch_port($resol_arp{mac_address},$switch_proposal);
764
765         #192.168.24.156:
766         #  arp: 00:0B:DB:D5:F6:65
767         #  hostname: pcroyon.hmg.priv
768         #  port: 5
769         #  switch: sw-batH-legi:hp2524
770         #  timestamp: 1164355525
771
772         # do not have a mac address
773#         if ($resol_arp{mac_address} eq 'unknow') {
774#            $computer_not_detected{$one_computer} = $current_interface;
775#            next LOOP_ON_COMPUTER;
776#            }
777
778         # detected on a switch
779         if ($where{switch_description} ne 'unknow') {
780            $detected_computer++;
781            $computerdb->{$resol_arp{ipv4_address}} = {
782               hostname_fq        => $resol_arp{hostname_fq},
783               mac_address        => $resol_arp{mac_address},
784               switch_hostname    => $where{switch_hostname},
785               switch_description => $where{switch_description},
786               switch_port        => $where{switch_port},
787               timestamp          => $timestamp,
788               network            => $net,
789               };
790            next LOOP_ON_COMPUTER;
791            }
792
793         # new in the database but where it is ?
794         if (not exists $computerdb->{$resol_arp{ipv4_address}}) {
795            $detected_computer++;
796            $computerdb->{$resol_arp{ipv4_address}} = {
797               hostname_fq        => $resol_arp{hostname_fq},
798               mac_address        => $resol_arp{mac_address},
799               switch_hostname    => $where{switch_hostname},
800               switch_description => $where{switch_description},
801               switch_port        => $where{switch_port},
802               timestamp          => $resol_arp{timestamp},
803               network            => $net,
804               };
805            }
806
807         # mise a jour du nom de la machine si modification dans le dns
808         $computerdb->{$resol_arp{ipv4_address}}{hostname_fq} = $resol_arp{hostname_fq};
809
810         # mise à jour de la date de détection si détection plus récente par arpwatch
811         $computerdb->{$resol_arp{ipv4_address}}{timestamp}   = $resol_arp{timestamp} if exists $resol_arp{timestamp} and $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $resol_arp{timestamp};
812
813         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
814#         push @computer_not_detected, $resol_arp{ipv4_address} if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
815         $computer_not_detected{$resol_arp{ipv4_address}} = $current_interface if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
816
817         }
818      }
819
820   # final end of line at the end of the loop
821   printf "\n";
822
823   my $dirdb = $KLASK_DB_FILE;
824      $dirdb =~ s{ / [^/]* $}{}xms;
825   mkdir "$dirdb", 0755 unless -d "$dirdb";
826   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
827
828   for my $one_computer (keys %computer_not_detected) {
829      my $interface = $computer_not_detected{$one_computer};
830      system "arping -c 1 -w 1 -rR -i $interface $one_computer &>/dev/null";
831#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
832      }
833   return;
834   }
835
836sub cmd_removedb {
837   my @computer = @_;
838
839   test_maindb_environnement();
840
841   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
842
843   LOOP_ON_COMPUTER:
844   for my $one_computer (@computer) {
845
846      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
847            and exists $computerdb->{$one_computer} ) {
848         delete $computerdb->{$one_computer};
849         next;
850         }
851
852      my %resol_arp = resolve_ip_arp_host($one_computer);
853
854      delete $computerdb->{$resol_arp{ipv4_address}} if exists $computerdb->{$resol_arp{ipv4_address}};
855      }
856
857   my $dirdb = $KLASK_DB_FILE;
858      $dirdb =~ s{ / [^/]* $}{}xms;
859   mkdir "$dirdb", 0755 unless -d "$dirdb";
860   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
861   return;
862   }
863
864sub cmd_exportdb {
865   my @ARGV   = @_;
866
867   my $format = 'txt';
868
869   my $ret = GetOptions(
870      'format|f=s'  => \$format,
871      );
872
873   my %possible_format = (
874      txt  => \&cmd_exportdb_txt,
875      html => \&cmd_exportdb_html,
876      );
877
878   $format = 'txt' if not defined $possible_format{$format};
879
880   $possible_format{$format}->(@ARGV);
881   return;
882   }
883
884sub cmd_exportdb_txt {
885   test_maindb_environnement();
886
887   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
888
889   printf "%-24s %-4s            %-30s %-15s %-18s %-s\n", qw(Switch Port Hostname IPv4-Address MAC-Address Date);
890   print "---------------------------------------------------------------------------------------------------------------------------\n";
891
892   LOOP_ON_IP_ADDRESS:
893   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
894
895#      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq 'unknow';
896
897      # to be improve in the future
898      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
899
900# dans le futur
901#      next if $computerdb->{$ip}{hostname_fq} eq 'unknow';
902
903      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
904      $year += 1900;
905      $mon++;
906      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
907
908      printf "%-25s  %2s  <-------  %-30s %-15s %-18s %s\n",
909         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
910         $computerdb->{$ip}{switch_port},
911         $computerdb->{$ip}{hostname_fq},
912         $ip,
913         $computerdb->{$ip}{mac_address},
914         $date;
915      }
916   return;
917   }
918
919sub cmd_exportdb_html {
920   test_maindb_environnement();
921
922   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
923
924#<link rel="stylesheet" type="text/css" href="style-klask.css" />
925#<script src="sorttable-klask.js"></script>
926
927   print <<'END_HTML';
928<table class="sortable" summary="Klask export database">
929 <caption>Klask database</caption>
930 <thead>
931  <tr>
932   <th scope="col" class="hklask-switch">Switch</th>
933   <th scope="col" class="sorttable_nosort">Port</th>
934   <th scope="col" class="sorttable_nosort">Link</th>
935   <th scope="col" class="sorttable_alpha">Hostname</th>
936   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
937   <th scope="col" class="sorttable_alpha">MAC-Address</th>
938   <th scope="col" class="hklask-date">Date</th>
939  </tr>
940 </thead>
941 <tfoot>
942  <tr>
943   <th scope="col" class="fklask-switch">Switch</th>
944   <th scope="col" class="fklask-port">Port</th>
945   <th scope="col" class="fklask-link">Link</th>
946   <th scope="col" class="fklask-hostname">Hostname</th>
947   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
948   <th scope="col" class="fklask-mac">MAC-Address</th>
949   <th scope="col" class="fklask-date">Date</th>
950  </tr>
951 </tfoot>
952 <tbody>
953END_HTML
954
955   my %mac_count = ();
956   LOOP_ON_IP_ADDRESS:
957   foreach my $ip (keys %{$computerdb}) {
958
959      # to be improve in the future
960      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
961
962      $mac_count{$computerdb->{$ip}{mac_address}}++;
963      }
964
965   my $typerow = 'even';
966
967   LOOP_ON_IP_ADDRESS:
968   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
969
970      # to be improve in the future
971      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
972
973      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
974      $year += 1900;
975      $mon++;
976      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
977
978#      $odd_or_even++;
979#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
980      $typerow = $typerow eq 'even' ? 'odd' : 'even';
981
982      my $switch_hostname = $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description} || 'unkown';
983      chomp $switch_hostname;
984      my $switch_hostname_sort = sprintf '%s %3s' ,$switch_hostname, $computerdb->{$ip}{switch_port};
985
986      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
987
988      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{mac_address}}, $computerdb->{$ip}{mac_address};
989
990      $computerdb->{$ip}{hostname_fq} = 'unknow' if $computerdb->{$ip}{hostname_fq} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
991      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{hostname_fq};
992
993      print <<"END_HTML";
994  <tr class="$typerow">
995   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
996   <td class="bklask-port">$computerdb->{$ip}{switch_port}</td>
997   <td><-------</td>
998   <td sorttable_customkey="$host_short">$computerdb->{$ip}{hostname_fq}</td>
999   <td sorttable_customkey="$ip_sort">$ip</td>
1000   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{mac_address}</td>
1001   <td>$date</td>
1002  </tr>
1003END_HTML
1004      }
1005
1006   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1007
1008   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1009   my %db_switch_parent            = %{$switch_connection->{parent}};
1010   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1011   my %db_switch                   = %{$switch_connection->{switch_db}};
1012
1013   for my $sw (sort keys %db_switch_output_port) {
1014
1015      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1016
1017      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1018
1019      if (exists $db_switch_parent{$sw}) {
1020
1021      my $mac_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{mac_address};
1022      my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{ipv4_address};
1023      my $timestamp = $db_switch{$db_switch_parent{$sw}->{switch}}->{timestamp};
1024
1025      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1026      $year += 1900;
1027      $mon++;
1028      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1029
1030      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1031
1032      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1033
1034      my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{switch}, 1), $db_switch_parent{$sw}->{port};
1035
1036      print <<"END_HTML";
1037  <tr class="$typerow">
1038   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1039   <td class="bklask-port">$db_switch_output_port{$sw}</>
1040   <td>+--> $db_switch_parent{$sw}->{port}</td>
1041   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{switch}</>
1042   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1043   <td sorttable_customkey="$mac_sort">$mac_address</td>
1044   <td>$date</td>
1045  </tr>
1046END_HTML
1047         }
1048      else {
1049         print <<"END_HTML";
1050  <tr class="$typerow">
1051   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1052   <td class="bklask-port">$db_switch_output_port{$sw}</>
1053   <td>+--></td>
1054   <td sorttable_customkey="router">router</>
1055   <td sorttable_customkey="999999999999"></td>
1056   <td sorttable_customkey="99999"></td>
1057   <td></td>
1058  </tr>
1059END_HTML
1060         }
1061      }
1062
1063   for my $swport (sort keys %db_switch_connected_on_port) {
1064      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1065      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1066
1067         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1068
1069      my $mac_address = $db_switch{$sw}->{mac_address};
1070      my $ipv4_address = $db_switch{$sw}->{ipv4_address};
1071      my $timestamp = $db_switch{$sw}->{timestamp};
1072
1073      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1074      $year += 1900;
1075      $mon++;
1076      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1077
1078      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1079
1080      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1081
1082      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1083
1084         if (exists $db_switch_output_port{$sw}) {
1085
1086            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1087
1088            print <<"END_HTML";
1089  <tr class="$typerow">
1090   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1091   <td class="bklask-port">$port_connect</>
1092   <td>&lt;--+ $db_switch_output_port{$sw}</td>
1093   <td sorttable_customkey="$host_short">$sw</>
1094   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1095   <td sorttable_customkey="$mac_sort">$mac_address</td>
1096   <td>$date</td>
1097  </tr>
1098END_HTML
1099            }
1100         else {
1101            print <<"END_HTML";
1102  <tr class="$typerow">
1103   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1104   <td class="bklask-port">$port_connect</>
1105   <td>&lt;--+</td>
1106   <td sorttable_customkey="$sw">$sw</>
1107   <td sorttable_customkey="">$ipv4_address</td>
1108   <td sorttable_customkey="">$mac_address</td>
1109   <td>$date</td>
1110  </tr>
1111END_HTML
1112            }
1113         }
1114      }
1115
1116   print <<'END_HTML';
1117 </tbody>
1118</table>
1119END_HTML
1120   return;
1121   }
1122
1123sub cmd_iplocation {
1124   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1125
1126   LOOP_ON_IP_ADDRESS:
1127   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1128
1129      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1130
1131      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1132      next if $sw_hostname eq 'unknow';
1133
1134      my $sw_location = q{};
1135      for my $sw (@SWITCH) {
1136         next if $sw_hostname ne $sw->{hostname};
1137         $sw_location = $sw->{location};
1138         last;
1139         }
1140
1141      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1142      }
1143   return;
1144   }
1145
1146sub cmd_ip_free {
1147   my @vlan_name = @_;
1148
1149   my $days_to_dead = 365 * 2;
1150   my $format = 'txt';
1151
1152   my $ret = GetOptionsFromArray(\@vlan_name,
1153      'day|d=i'      => \$days_to_dead,
1154      'format|f=s'   => \$format,
1155      );
1156
1157   @vlan_name = get_list_network() if not @vlan_name;
1158
1159#   my %possible_format = (
1160#      txt  => \&cmd_exportdb_txt,
1161#      html => \&cmd_exportdb_html,
1162#      );
1163#
1164#   $format = 'txt' if not defined $possible_format{$format};
1165#
1166#   $possible_format{$format}->(@ARGV);
1167
1168   my $computerdb = {};
1169      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
1170   my $timestamp = time;
1171
1172   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_dead );
1173
1174   my %result_ip= ();
1175
1176   ALL_NETWORK:
1177   for my $vlan (@vlan_name) {
1178
1179      my @ip_list = get_list_ip($vlan);
1180#      my $current_interface = get_current_interface($vlan);
1181     
1182      LOOP_ON_IP_ADDRESS:
1183      for my $ip (@ip_list) {
1184
1185         next LOOP_ON_IP_ADDRESS if
1186            exists $computerdb->{$ip}
1187            && $computerdb->{$ip}{timestamp} > $timestamp_barrier;
1188
1189         my $ip_date_last_detection = '';
1190         if (exists $computerdb->{$ip}) {
1191            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1192            $year += 1900;
1193            $mon++;
1194            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1195            }
1196
1197         $result_ip{$ip} ||= {};
1198         $result_ip{$ip}->{date_last_detection} = $ip_date_last_detection;
1199
1200         my $packed_ip = scalar gethostbyname($ip);
1201         my $hostname_fq = 'unknown';
1202            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip;
1203         $result_ip{$ip}->{hostname_fq} = $hostname_fq;
1204         
1205         $result_ip{$ip}->{vlan} = $vlan;
1206         }
1207      }
1208
1209   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
1210   print "-------------------------------------------------------------------------------\n";
1211   LOOP_ON_RESULT:
1212   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1213         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{hostname_fq}, $result_ip{$ip}->{date_last_detection}, $result_ip{$ip}->{vlan};
1214      }
1215   }
1216
1217sub cmd_enable {
1218   my $switch = shift;
1219   my $port   = shift;
1220
1221   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
1222   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
1223   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
1224   return;
1225   }
1226
1227sub cmd_disable {
1228   my $switch = shift;
1229   my $port   = shift;
1230
1231   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
1232   return;
1233   }
1234
1235sub cmd_status {
1236   my $switch = shift;
1237   my $port   = shift;
1238
1239   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
1240   return;
1241   }
1242
1243sub cmd_search_mac_on_switch {
1244   my $switch_name = shift || q{};
1245   my $mac_address = shift || q{};
1246
1247   if ($switch_name eq q{} or $mac_address eq q{}) {
1248      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1249      }
1250
1251   if (not defined $SWITCH_DB{$switch_name}) {
1252      die "Switch $switch_name must be defined in klask configuration file\n";
1253      }
1254
1255   my $sw = $SWITCH_DB{$switch_name};
1256   my %session = ( -hostname => $sw->{hostname} );
1257      $session{-version} = $sw->{version}   || 1;
1258      $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1259   if (exists $sw->{version} and $sw->{version} eq '3') {
1260      $session{-username} = $sw->{username} || 'snmpadmin';
1261      }
1262   else {
1263      $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1264      }
1265
1266   my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($mac_address);
1267   print "Klask search OID $research on switch $switch_name\n";
1268
1269   my ($session, $error) = Net::SNMP->session( %session );
1270   print "$error \n" if $error;
1271
1272   my $result = $session->get_request(
1273      -varbindlist => [$research]
1274      );
1275
1276   if (not defined $result or $result->{$research} eq 'noSuchInstance') {
1277      print "Klask do not find MAC $mac_address on switch $switch_name\n";
1278      $session->close;
1279      }
1280
1281   my $swport = $result->{$research};
1282   $session->close;
1283
1284   print "Klask find MAC $mac_address on switch $switch_name port $swport\n";
1285   return;
1286   }
1287
1288sub cmd_updatesw {
1289
1290   init_switch_names('yes');    #nomme les switchs
1291   print "\n";
1292
1293   my %where = ();
1294   my %db_switch_output_port = ();
1295   my %db_switch_ip_hostname = ();
1296
1297   DETECT_ALL_ROUTER:
1298#   for my $one_computer ('194.254.66.254') {
1299   for my $one_router ( get_list_main_router(get_list_network()) ) {
1300      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1301      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1302      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1303      }
1304
1305   ALL_ROUTER_IP_ADDRESS:
1306   for my $ip (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1307
1308      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip}; # /a priori/ idiot car ne sers à rien...
1309
1310      ALL_SWITCH_CONNECTED:
1311      for my $switch_detected ( keys %{$where{$ip}} ) {
1312
1313         my $switch = $where{$ip}->{$switch_detected};
1314
1315         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
1316
1317         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1318         }
1319      }
1320
1321   my %db_switch_link_with = ();
1322
1323   my @list_switch_ip = ();
1324   my @list_switch_ipv4 = ();
1325   for my $sw (@SWITCH){
1326      push @list_switch_ip, $sw->{hostname};
1327      }
1328
1329   my $timestamp = time;
1330
1331   ALL_SWITCH:
1332   for my $one_computer (@list_switch_ip) {
1333      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1334      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1335
1336      push @list_switch_ipv4,$resol_arp{ipv4_address};
1337
1338      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
1339#my @l = ();
1340#for my $c  ( keys %{$where{$resol_arp{ipv4_address}}} ) {
1341#push @l, "$c -+ "; #$where{$resol_arp{ipv4_address}}->{$c}{hostname};
1342#}
1343#print "DEBUG  $one_computer $resol_arp{ipv4_address} $resol_arp{mac_address} --- @l\n";
1344
1345      $db_switch_ip_hostname{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
1346
1347      $SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
1348      $SWITCH_DB{$one_computer}->{mac_address}  = $resol_arp{mac_address};
1349      $SWITCH_DB{$one_computer}->{timestamp}    = $timestamp;
1350      }
1351
1352   ALL_SWITCH_IP_ADDRESS:
1353   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1354
1355      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1356
1357      DETECTED_SWITCH:
1358      for my $switch_detected ( keys %{$where{$ip}} ) {
1359
1360         next DETECTED_SWITCH if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostname{$ip}};
1361
1362         my $switch = $where{$ip}->{$switch_detected};
1363#print "DEBUG1 :  $db_switch_ip_hostname{$ip} / $switch->{hostname} : $switch->{port}\n" if  $switch->{hostname} =~ m/sw3-batA0-3s/;
1364
1365         next if $switch->{port}     eq '0';
1366         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
1367         next if $switch->{hostname} eq $db_switch_ip_hostname{$ip}; # $computerdb->{$ip}{hostname};
1368
1369         $db_switch_link_with{ $db_switch_ip_hostname{$ip} } ||= {};
1370         $db_switch_link_with{ $db_switch_ip_hostname{$ip} }->{ $switch->{hostname} } = $switch->{port};
1371#print "DEBUG :  $db_switch_ip_hostname{$ip} / $switch->{hostname} : $switch->{port}\n" if $switch->{hostname} !~ m/sw3-batA0-3s/;
1372         }
1373
1374      }
1375
1376   my %db_switch_connected_on_port = ();
1377   my $maybe_more_than_one_switch_connected = 'yes';
1378
1379   while ($maybe_more_than_one_switch_connected eq 'yes') {
1380      for my $sw (keys %db_switch_link_with) {
1381         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1382
1383            my $port = $db_switch_link_with{$sw}->{$connect};
1384
1385            $db_switch_connected_on_port{"$connect:$port"} ||= {};
1386            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
1387            }
1388         }
1389
1390      $maybe_more_than_one_switch_connected  = 'no';
1391
1392      SWITCH_AND_PORT:
1393      for my $swport (keys %db_switch_connected_on_port) {
1394
1395         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1396
1397         $maybe_more_than_one_switch_connected = 'yes';
1398
1399         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1400         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1401
1402         CONNECTED:
1403         for my $sw_connected (@sw_on_same_port) {
1404
1405            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1406
1407            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1408
1409            for my $other_sw (@sw_on_same_port) {
1410               next if $other_sw eq $sw_connected;
1411
1412               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1413               }
1414
1415            # We can not do better for this switch for this loop
1416            next SWITCH_AND_PORT;
1417            }
1418         }
1419      }
1420
1421   my %db_switch_parent =();
1422
1423   for my $sw (keys %db_switch_link_with) {
1424      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1425
1426         my $port = $db_switch_link_with{$sw}->{$connect};
1427
1428         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1429         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1430
1431         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1432         }
1433      }
1434
1435   print "Switch output port and parent port connection\n";
1436   print "---------------------------------------------\n";
1437   for my $sw (sort keys %db_switch_output_port) {
1438      if (exists $db_switch_parent{$sw}) {
1439         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1440         }
1441      else {
1442         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1443         }
1444      }
1445   print "\n";
1446
1447   print "Switch parent and children port inter-connection\n";
1448   print "------------------------------------------------\n";
1449   for my $swport (sort keys %db_switch_connected_on_port) {
1450      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1451      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1452         if (exists $db_switch_output_port{$sw}) {
1453            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1454            }
1455         else {
1456            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1457            }
1458         }
1459      }
1460
1461   my $switch_connection = {
1462      output_port       => \%db_switch_output_port,
1463      parent            => \%db_switch_parent,
1464      connected_on_port => \%db_switch_connected_on_port,
1465      link_with         => \%db_switch_link_with,
1466      switch_db         => \%SWITCH_DB,
1467      };
1468
1469   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1470   return;
1471   }
1472
1473sub cmd_exportsw {
1474   my @ARGV   = @_;
1475
1476   test_switchdb_environnement();
1477
1478   my $format = 'txt';
1479
1480   my $ret = GetOptions(
1481      'format|f=s'  => \$format,
1482      );
1483
1484   my %possible_format = (
1485      txt => \&cmd_exportsw_txt,
1486      dot => \&cmd_exportsw_dot,
1487      );
1488
1489   $format = 'txt' if not defined $possible_format{$format};
1490
1491   $possible_format{$format}->(@ARGV);
1492   return;
1493   }
1494
1495sub cmd_exportsw_txt {
1496
1497   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1498
1499   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1500   my %db_switch_parent            = %{$switch_connection->{parent}};
1501   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1502
1503   print "Switch output port and parent port connection\n";
1504   print "---------------------------------------------\n";
1505   for my $sw (sort keys %db_switch_output_port) {
1506      if (exists $db_switch_parent{$sw}) {
1507         printf "%-25s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1508         }
1509      else {
1510         printf "%-25s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1511         }
1512      }
1513   print "\n";
1514
1515   print "Switch parent and children port inter-connection\n";
1516   print "------------------------------------------------\n";
1517   for my $swport (sort keys %db_switch_connected_on_port) {
1518      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1519      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1520         if (exists $db_switch_output_port{$sw}) {
1521            printf "%-25s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1522            }
1523         else {
1524            printf "%-25s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1525            }
1526         }
1527      }
1528   return;
1529   }
1530
1531sub cmd_exportsw_dot {
1532
1533   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1534
1535   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1536   my %db_switch_parent            = %{$switch_connection->{parent}};
1537   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1538   my %db_switch_link_with         = %{$switch_connection->{link_with}};
1539   my %db_switch_global            = %{$switch_connection->{switch_db}};
1540
1541   my %db_building= ();
1542   for my $sw (@SWITCH) {
1543      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
1544      $db_building{$building} ||= {};
1545      $db_building{$building}->{$location} ||= {};
1546      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1547      }
1548
1549
1550   print "digraph G {\n";
1551
1552   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
1553   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
1554
1555   my $b=0;
1556   for my $building (keys %db_building) {
1557      $b++;
1558
1559      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1560      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
1561
1562      my $l = 0;
1563      for my $loc (keys %{$db_building{$building}}) {
1564         $l++;
1565
1566         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
1567#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
1568         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
1569
1570         for my $sw (keys %{$db_building{$building}->{$loc}}) {
1571
1572            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
1573
1574            my $swname  = $sw;
1575               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
1576            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
1577            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
1578            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
1579
1580
1581            for my $swport (keys %db_switch_connected_on_port) {
1582               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1583               next if not $sw_connect eq $sw;
1584               next if $port_connect eq $db_switch_output_port{$sw};
1585               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
1586               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1587              }
1588            }
1589         }
1590      }
1591
1592#   print "Switch output port and parent port connection\n";
1593#   print "---------------------------------------------\n";
1594   for my $sw (sort keys %db_switch_output_port) {
1595      if (exists $db_switch_parent{$sw}) {
1596#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
1597         }
1598      else {
1599         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
1600         }
1601      }
1602   print "\n";
1603
1604#   print "Switch parent and children port inter-connection\n";
1605#   print "------------------------------------------------\n";
1606   for my $swport (sort keys %db_switch_connected_on_port) {
1607      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1608      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1609         if (exists $db_switch_output_port{$sw}) {
1610            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
1611            }
1612         else {
1613            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
1614            }
1615         }
1616      }
1617
1618print "}\n";
1619   return;
1620   }
1621
1622
1623__END__
1624
1625=head1 NAME
1626
1627klask - ports manager and finder for switch
1628
1629
1630=head1 USAGE
1631
1632 klask updatedb
1633 klask exportdb --format [txt|html]
1634
1635 klask updatesw
1636 klask exportsw --format [txt|dot]
1637
1638 klask searchdb computer
1639 klask search   computer
1640 klask search-mac-on-switch switch mac_addr
1641
1642 klask ip-free --day number_of_day [vlan_name]
1643
1644 klask enable  switch port
1645 klask disable swith port
1646 klask status  swith port
1647
1648
1649=head1 DESCRIPTION
1650
1651klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1652
1653Klask has now a web site dedicated for it !
1654
1655 http://servforge.legi.inpg.fr/projects/klask
1656
1657
1658=head1 COMMANDS
1659
1660
1661=head2 search
1662
1663This 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.
1664
1665
1666=head2 enable
1667
1668This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1669
1670
1671=head2 disable
1672
1673This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1674
1675
1676=head2 status
1677
1678This 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.
1679
1680
1681=head2 updatedb
1682
1683This 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.
1684
1685
1686=head2 exportdb
1687
1688This 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...
1689
1690
1691=head2 updatesw
1692
1693This 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.
1694
1695
1696=head2 exportsw --format [txt|dot]
1697
1698This 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.
1699
1700 klask exportsw --format dot > /tmp/map.dot
1701 dot -Tpng /tmp/map.dot > /tmp/map.png
1702
1703
1704
1705=head1 CONFIGURATION
1706
1707Because 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 !
1708
1709Here an example, be aware with indent, it's important in YAML, do not use tabulation !
1710
1711 default:
1712   community: public
1713   snmpport: 161
1714
1715 network:
1716   labnet:
1717     ip-subnet:
1718       - add: 192.168.1.0/24
1719       - add: 192.168.2.0/24
1720     interface: eth0
1721     main-router: gw1.labnet.local
1722
1723   schoolnet:
1724     ip-subnet:
1725       - add: 192.168.6.0/24
1726       - add: 192.168.7.0/24
1727     interface: eth0.38
1728     main-router: gw2.schoolnet.local
1729
1730 switch:
1731   - hostname: sw1.klask.local
1732     portignore:
1733       - 1
1734       - 2
1735
1736   - hostname: sw2.klask.local
1737     location: BatK / 2 / K203
1738     type: HP2424
1739     portignore:
1740       - 1
1741       - 2
1742
1743I 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.
1744
1745
1746=head1 FILES
1747
1748 /etc/klask.conf
1749 /var/cache/klask/klaskdb
1750 /var/cache/klask/switchdb
1751
1752=head1 SEE ALSO
1753
1754Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1755
1756
1757=head1 VERSION
1758
1759$Id: klask 69 2010-11-02 15:43:26Z g7moreau $
1760
1761
1762=head1 AUTHOR
1763
1764Written by Gabriel Moreau, Grenoble - France
1765
1766
1767=head1 LICENSE AND COPYRIGHT
1768
1769GPL version 2 or later and Perl equivalent
1770
1771Copyright (C) 2005-2009 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.