source: trunk/klask @ 117

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