source: trunk/klask @ 116

Last change on this file since 116 was 116, checked in by g7moreau, 12 years ago
  • A very litle more on Trk port...
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 64.6 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2012 Gabriel Moreau.
4#
5# $Id: klask 116 2012-10-03 12:25:02Z 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: 116 $'."\n";
592   print ' $Date: 2012-10-03 12:25:02 +0000 (Wed, 03 Oct 2012) $'."\n";
593   print ' $Id: klask 116 2012-10-03 12:25:02Z 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_timestamp > $swithportdb{$swpt}->{timestamp})
1234         and (
1235            $ip_hostname_fq !~ m/^float/
1236            or $ip_timestamp > ( $swithportdb{$swpt}->{timestamp} + (15 * 24 * 3600) )
1237            )
1238         ) {
1239         $swithportdb{$swpt} = {
1240            ip          => $ip,
1241            timestamp   => $ip_timestamp,
1242            vlan        => $computerdb->{$ip}{network},
1243            hostname_fq => $ip_hostname_fq,
1244            mac_address => $ip_mac,
1245            };
1246         }
1247      }
1248
1249   foreach my $swpt (keys %swithportdb) {
1250      next if $swpt =~ m/^\s*0$/;
1251      next if $swithportdb{$swpt}->{hostname_fq} !~ m/^float/;
1252     
1253      my $src_ip = $swithportdb{$swpt}->{ip};
1254      my $src_timestamp = 0;
1255      foreach my $ip (keys %{$computerdb}) {
1256         next if $computerdb->{$ip}{mac_address} ne  $swithportdb{$swpt}->{mac_address};
1257         next if $computerdb->{$ip}{hostname_fq} =~ m/^float/;
1258         next if $computerdb->{$ip}{timestamp} < $src_timestamp;
1259         
1260         $src_ip = $ip;
1261         $src_timestamp = $computerdb->{$ip}{timestamp};
1262         }
1263     
1264      next if $src_timestamp == 0;
1265     
1266      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $swithportdb{$swpt}->{timestamp};
1267      $year += 1900;
1268      $mon++;
1269      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1270
1271      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{timestamp};
1272      $year += 1900;
1273      $mon++;
1274      my $src_date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1275
1276      printf "%s / %-10s +-> %-10s  %s / %s # %s\n",
1277         $swpt, $swithportdb{$swpt}->{vlan}, $computerdb->{$src_ip}{network},
1278         $date,
1279         $src_date,
1280         $computerdb->{$src_ip}{hostname_fq};
1281      }
1282   }
1283
1284sub cmd_ip_location {
1285   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1286
1287   LOOP_ON_IP_ADDRESS:
1288   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1289
1290      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1291
1292      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1293      next if $sw_hostname eq 'unknow';
1294
1295      my $sw_location = q{};
1296      for my $sw (@SWITCH) {
1297         next if $sw_hostname ne $sw->{hostname};
1298         $sw_location = $sw->{location};
1299         last;
1300         }
1301
1302      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1303      }
1304   return;
1305   }
1306
1307sub cmd_ip_free {
1308   @ARGV = @_; # VLAN name with option
1309
1310   my $days_to_dead = 365 * 2;
1311   my $format = 'txt';
1312   my $verbose;
1313
1314   my $ret = GetOptions(
1315      'day|d=i'      => \$days_to_dead,
1316      'format|f=s'   => \$format,
1317      'verbose|v'    => \$verbose,
1318      );
1319
1320   my %possible_format = (
1321      txt  => \&cmd_ip_free_txt,
1322      html => \&cmd_ip_free_html,
1323      none => sub {},
1324      );
1325   $format = 'txt' if not defined $possible_format{$format};
1326
1327   my @vlan_name = @ARGV;
1328   @vlan_name = get_list_network() if not @vlan_name;
1329
1330   my $computerdb = {};
1331      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
1332   my $timestamp = time;
1333
1334   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_dead );
1335
1336   my %result_ip = ();
1337
1338   ALL_NETWORK:
1339   for my $vlan (@vlan_name) {
1340
1341      my @ip_list = get_list_ip($vlan);
1342
1343      LOOP_ON_IP_ADDRESS:
1344      for my $ip (@ip_list) {
1345
1346         next LOOP_ON_IP_ADDRESS if
1347            exists $computerdb->{$ip}
1348            && $computerdb->{$ip}{timestamp} > $timestamp_barrier;
1349
1350         my $ip_date_last_detection = '';
1351         if (exists $computerdb->{$ip}) {
1352            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1353            $year += 1900;
1354            $mon++;
1355            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1356            }
1357
1358         $result_ip{$ip} ||= {};
1359         $result_ip{$ip}->{date_last_detection} = $ip_date_last_detection;
1360
1361         my $packed_ip = scalar gethostbyname($ip);
1362         my $hostname_fq = 'unknown';
1363            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip;
1364         $result_ip{$ip}->{hostname_fq} = $hostname_fq;
1365
1366         $result_ip{$ip}->{vlan} = $vlan;
1367
1368         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
1369         }
1370      }
1371
1372   $possible_format{$format}->(%result_ip);
1373   }
1374
1375sub cmd_ip_free_txt {
1376   my %result_ip = @_;
1377   
1378   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
1379   print "-------------------------------------------------------------------------------\n";
1380   LOOP_ON_IP_ADDRESS:
1381   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1382         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{hostname_fq}, $result_ip{$ip}->{date_last_detection}, $result_ip{$ip}->{vlan};
1383      }
1384   }
1385
1386sub cmd_ip_free_html {
1387   my %result_ip = @_;
1388
1389   print <<'END_HTML';
1390<table class="sortable" summary="Klask Free IP Database">
1391 <caption>Klask Free IP Database</caption>
1392 <thead>
1393  <tr>
1394   <th scope="col" class="klask-header-left">IPv4-Address</th>
1395   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1396   <th scope="col" class="sorttable_alpha">VLAN</th>
1397   <th scope="col" class="klask-header-right">Date</th>
1398  </tr>
1399 </thead>
1400 <tfoot>
1401  <tr>
1402   <th scope="col" class="klask-footer-left">IPv4-Address</th>
1403   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1404   <th scope="col" class="fklask-vlan">VLAN</th>
1405   <th scope="col" class="klask-footer-right">Date</th>
1406  </tr>
1407 </tfoot>
1408 <tbody>
1409END_HTML
1410
1411   my $typerow = 'even';
1412
1413   LOOP_ON_IP_ADDRESS:
1414   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1415
1416      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1417
1418      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1419      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{hostname_fq};
1420
1421      print <<"END_HTML";
1422  <tr class="$typerow">
1423   <td sorttable_customkey="$ip_sort">$ip</td>
1424   <td sorttable_customkey="$host_short">$result_ip{$ip}->{hostname_fq}</td>
1425   <td>$result_ip{$ip}->{vlan}</td>
1426   <td>$result_ip{$ip}->{date_last_detection}</td>
1427  </tr>
1428END_HTML
1429      }
1430   print <<'END_HTML';
1431 </tbody>
1432</table>
1433END_HTML
1434   }
1435
1436sub cmd_enable {
1437   my $switch = shift;
1438   my $port   = shift;
1439
1440   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
1441   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
1442   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
1443   return;
1444   }
1445
1446sub cmd_disable {
1447   my $switch = shift;
1448   my $port   = shift;
1449
1450   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
1451   return;
1452   }
1453
1454sub cmd_status {
1455   my $switch = shift;
1456   my $port   = shift;
1457
1458   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
1459   return;
1460   }
1461
1462sub cmd_search_mac_on_switch {
1463   my $switch_name = shift || q{};
1464   my $mac_address = shift || q{};
1465
1466   if ($switch_name eq q{} or $mac_address eq q{}) {
1467      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1468      }
1469
1470   $switch_name = join(',', map {$_->{hostname}} @SWITCH ) if $switch_name eq q{*};
1471
1472   for my $sw_name (split /,/, $switch_name) {
1473      if (not defined $SWITCH_DB{$sw_name}) {
1474         die "Switch $sw_name must be defined in klask configuration file\n";
1475         }
1476
1477      my $sw = $SWITCH_DB{$sw_name};
1478      my %session = ( -hostname => $sw->{hostname} );
1479         $session{-version} = $sw->{version}   || 1;
1480         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1481      if (exists $sw->{version} and $sw->{version} eq '3') {
1482         $session{-username} = $sw->{username} || 'snmpadmin';
1483         }
1484      else {
1485         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1486         }
1487
1488      my $research = $OID_NUMBER{searchPort} . mac_address_hex_to_dec($mac_address);
1489      print "Klask search OID $research on switch $sw_name\n";
1490
1491      my ($session, $error) = Net::SNMP->session( %session );
1492      print "$error \n" if $error;
1493
1494      my $result = $session->get_request(
1495         -varbindlist => [$research]
1496         );
1497
1498      if (not defined $result or $result->{$research} eq 'noSuchInstance') {
1499         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1500         $session->close;
1501         }
1502
1503      my $swport = $result->{$research};
1504      $session->close;
1505
1506      print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1507      }
1508   return;
1509   }
1510
1511sub cmd_updatesw {
1512   @ARGV = @_;
1513
1514   my $verbose;
1515
1516   my $ret = GetOptions(
1517      'verbose|v' => \$verbose,
1518      );
1519
1520   init_switch_names('yes');    #nomme les switchs
1521   print "\n";
1522
1523   my %where = ();
1524   my %db_switch_output_port = ();
1525   my %db_switch_ip_hostnamefq = ();
1526
1527   DETECT_ALL_ROUTER:
1528#   for my $one_computer ('194.254.66.254') {
1529   for my $one_router ( get_list_main_router(get_list_network()) ) {
1530      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1531
1532      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1533      print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
1534
1535      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1536      }
1537
1538   ALL_ROUTER_IP_ADDRESS:
1539   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1540
1541      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
1542
1543      ALL_SWITCH_CONNECTED:
1544      for my $switch_detected ( keys %{$where{$ip_router}} ) {
1545
1546         my $switch = $where{$ip_router}->{$switch_detected};
1547
1548         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
1549
1550         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1551         print "VERBOSE_2: output port $switch->{hostname} : $switch->{port}\n" if $verbose;
1552         }
1553      }
1554
1555   my %db_switch_link_with = ();
1556
1557   my @list_all_switch = ();
1558   my @list_switch_ipv4 = ();
1559   for my $sw (@SWITCH){
1560      push @list_all_switch, $sw->{hostname};
1561      }
1562
1563   my $timestamp = time;
1564
1565   ALL_SWITCH:
1566   for my $one_computer (@list_all_switch) {
1567      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1568      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1569
1570      push @list_switch_ipv4, $resol_arp{ipv4_address};
1571
1572      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
1573
1574      if ($verbose) {
1575         print "VERBOSE_3: $one_computer $resol_arp{ipv4_address} $resol_arp{mac_address}\n";
1576         print "VERBOSE_3: $one_computer --- ",
1577            join(' + ', keys %{$where{$resol_arp{ipv4_address}}}),
1578            "\n";
1579         }
1580
1581      $db_switch_ip_hostnamefq{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
1582      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{ipv4_address} -> $resol_arp{hostname_fq}\n" if $verbose;
1583
1584      $SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
1585      $SWITCH_DB{$one_computer}->{mac_address}  = $resol_arp{mac_address};
1586      $SWITCH_DB{$one_computer}->{timestamp}    = $timestamp;
1587      }
1588
1589   ALL_SWITCH_IP_ADDRESS:
1590   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1591
1592      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
1593
1594      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1595#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
1596
1597      DETECTED_SWITCH:
1598      for my $switch_detected ( keys %{$where{$ip}} ) {
1599
1600         my $switch = $where{$ip}->{$switch_detected};
1601         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{hostname} : $switch->{port}\n" if $verbose;
1602
1603         next if $switch->{port}     eq '0';
1604         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
1605         next if $switch->{hostname} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{hostname};
1606
1607         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
1608         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{hostname} } = $switch->{port};
1609         print "VERBOSE_7: +++++\n" if $verbose;
1610         }
1611
1612      }
1613
1614   my %db_switch_connected_on_port = ();
1615   my $maybe_more_than_one_switch_connected = 'yes';
1616
1617   while ($maybe_more_than_one_switch_connected eq 'yes') {
1618      for my $sw (keys %db_switch_link_with) {
1619         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1620
1621            my $port = $db_switch_link_with{$sw}->{$connect};
1622
1623            $db_switch_connected_on_port{"$connect:$port"} ||= {};
1624            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
1625            }
1626         }
1627
1628      $maybe_more_than_one_switch_connected  = 'no';
1629
1630      SWITCH_AND_PORT:
1631      for my $swport (keys %db_switch_connected_on_port) {
1632
1633         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1634
1635         $maybe_more_than_one_switch_connected = 'yes';
1636
1637         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1638         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1639
1640         CONNECTED:
1641         for my $sw_connected (@sw_on_same_port) {
1642
1643            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1644
1645            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1646
1647            for my $other_sw (@sw_on_same_port) {
1648               next if $other_sw eq $sw_connected;
1649
1650               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1651               }
1652
1653            # We can not do better for this switch for this loop
1654            next SWITCH_AND_PORT;
1655            }
1656         }
1657      }
1658
1659   my %db_switch_parent =();
1660
1661   for my $sw (keys %db_switch_link_with) {
1662      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1663
1664         my $port = $db_switch_link_with{$sw}->{$connect};
1665
1666         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1667         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1668
1669         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1670         }
1671      }
1672
1673   print "Switch output port and parent port connection\n";
1674   print "---------------------------------------------\n";
1675   for my $sw (sort keys %db_switch_output_port) {
1676      if (exists $db_switch_parent{$sw}) {
1677         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1678         }
1679      else {
1680         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1681         }
1682      }
1683   print "\n";
1684
1685   print "Switch parent and children port inter-connection\n";
1686   print "------------------------------------------------\n";
1687   for my $swport (sort keys %db_switch_connected_on_port) {
1688      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1689      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1690         if (exists $db_switch_output_port{$sw}) {
1691            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1692            }
1693         else {
1694            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1695            }
1696         }
1697      }
1698
1699   my $switch_connection = {
1700      output_port       => \%db_switch_output_port,
1701      parent            => \%db_switch_parent,
1702      connected_on_port => \%db_switch_connected_on_port,
1703      link_with         => \%db_switch_link_with,
1704      switch_db         => \%SWITCH_DB,
1705      };
1706
1707   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1708   return;
1709   }
1710
1711sub cmd_exportsw {
1712   @ARGV = @_;
1713
1714   test_switchdb_environnement();
1715
1716   my $format = 'txt';
1717
1718   my $ret = GetOptions(
1719      'format|f=s'  => \$format,
1720      );
1721
1722   my %possible_format = (
1723      txt => \&cmd_exportsw_txt,
1724      dot => \&cmd_exportsw_dot,
1725      );
1726
1727   $format = 'txt' if not defined $possible_format{$format};
1728
1729   $possible_format{$format}->(@ARGV);
1730   return;
1731   }
1732
1733sub cmd_exportsw_txt {
1734
1735   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1736
1737   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1738   my %db_switch_parent            = %{$switch_connection->{parent}};
1739   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1740
1741   print "Switch output port and parent port connection\n";
1742   print "---------------------------------------------\n";
1743   for my $sw (sort keys %db_switch_output_port) {
1744      if (exists $db_switch_parent{$sw}) {
1745         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1746         }
1747      else {
1748         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1749         }
1750      }
1751   print "\n";
1752
1753   print "Switch parent and children port inter-connection\n";
1754   print "------------------------------------------------\n";
1755   for my $swport (sort keys %db_switch_connected_on_port) {
1756      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1757      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1758         if (exists $db_switch_output_port{$sw}) {
1759            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1760            }
1761         else {
1762            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1763            }
1764         }
1765      }
1766   return;
1767   }
1768
1769sub cmd_exportsw_dot {
1770
1771   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1772
1773   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1774   my %db_switch_parent            = %{$switch_connection->{parent}};
1775   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1776   my %db_switch_link_with         = %{$switch_connection->{link_with}};
1777   my %db_switch_global            = %{$switch_connection->{switch_db}};
1778
1779   my %db_building= ();
1780   for my $sw (@SWITCH) {
1781      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
1782      $db_building{$building} ||= {};
1783      $db_building{$building}->{$location} ||= {};
1784      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1785      }
1786
1787
1788   print "digraph G {\n";
1789
1790   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
1791   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
1792
1793   my $b=0;
1794   for my $building (keys %db_building) {
1795      $b++;
1796
1797      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1798      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
1799
1800      my $l = 0;
1801      for my $loc (keys %{$db_building{$building}}) {
1802         $l++;
1803
1804         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
1805#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
1806         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
1807
1808         for my $sw (keys %{$db_building{$building}->{$loc}}) {
1809
1810            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
1811
1812            my $swname  = $sw;
1813               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
1814            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
1815            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
1816            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
1817
1818
1819            for my $swport (keys %db_switch_connected_on_port) {
1820               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1821               next if not $sw_connect eq $sw;
1822               next if $port_connect eq $db_switch_output_port{$sw};
1823               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
1824               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1825              }
1826            }
1827         }
1828      }
1829
1830#   print "Switch output port and parent port connection\n";
1831#   print "---------------------------------------------\n";
1832   for my $sw (sort keys %db_switch_output_port) {
1833      if (exists $db_switch_parent{$sw}) {
1834#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
1835         }
1836      else {
1837         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
1838         }
1839      }
1840   print "\n";
1841
1842#   print "Switch parent and children port inter-connection\n";
1843#   print "------------------------------------------------\n";
1844   for my $swport (sort keys %db_switch_connected_on_port) {
1845      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1846      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1847         if (exists $db_switch_output_port{$sw}) {
1848            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
1849            }
1850         else {
1851            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
1852            }
1853         }
1854      }
1855
1856print "}\n";
1857   return;
1858   }
1859
1860
1861__END__
1862
1863=head1 NAME
1864
1865klask - ports manager and finder for switch
1866
1867
1868=head1 USAGE
1869
1870 klask updatedb
1871 klask exportdb --format [txt|html]
1872 klask removedb computer*
1873 klask cleandb  --day number_of_day --verbose
1874
1875 klask updatesw
1876 klask exportsw --format [txt|dot]
1877
1878 klask searchdb computer
1879 klask search   computer
1880 klask search-mac-on-switch switch mac_addr
1881
1882 klask ip-free --day number_of_day --format [txt|html] [vlan_name]
1883
1884 klask enable  switch port
1885 klask disable swith port
1886 klask status  swith port
1887
1888
1889=head1 DESCRIPTION
1890
1891klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1892
1893Klask has now a web site dedicated for it !
1894
1895 http://servforge.legi.grenoble-inp.fr/projects/klask
1896
1897
1898=head1 COMMANDS
1899
1900
1901=head2 search
1902
1903This 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.
1904
1905
1906=head2 enable
1907
1908This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1909
1910
1911=head2 disable
1912
1913This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1914
1915
1916=head2 status
1917
1918This 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.
1919
1920
1921=head2 updatedb
1922
1923This 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.
1924
1925
1926=head2 exportdb
1927
1928This 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...
1929
1930
1931=head2 updatesw
1932
1933This 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.
1934
1935
1936=head2 exportsw --format [txt|dot]
1937
1938This 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.
1939
1940 klask exportsw --format dot > /tmp/map.dot
1941 dot -Tpng /tmp/map.dot > /tmp/map.png
1942
1943
1944
1945=head1 CONFIGURATION
1946
1947Because 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 !
1948
1949Here an example, be aware with indent, it's important in YAML, do not use tabulation !
1950
1951 default:
1952   community: public
1953   snmpport: 161
1954
1955 network:
1956   labnet:
1957     ip-subnet:
1958       - add: 192.168.1.0/24
1959       - add: 192.168.2.0/24
1960     interface: eth0
1961     main-router: gw1.labnet.local
1962
1963   schoolnet:
1964     ip-subnet:
1965       - add: 192.168.6.0/24
1966       - add: 192.168.7.0/24
1967     interface: eth0.38
1968     main-router: gw2.schoolnet.local
1969
1970 switch:
1971   - hostname: sw1.klask.local
1972     portignore:
1973       - 1
1974       - 2
1975
1976   - hostname: sw2.klask.local
1977     location: BatK / 2 / K203
1978     type: HP2424
1979     portignore:
1980       - 1
1981       - 2
1982
1983I 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.
1984
1985
1986=head1 FILES
1987
1988 /etc/klask/klask.conf
1989 /var/lib/klask/klaskdb
1990 /var/lib/klask/switchdb
1991
1992=head1 SEE ALSO
1993
1994Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
1995
1996
1997=head1 VERSION
1998
1999$Id: klask 116 2012-10-03 12:25:02Z g7moreau $
2000
2001
2002=head1 AUTHOR
2003
2004Written by Gabriel Moreau, Grenoble - France
2005
2006
2007=head1 LICENSE AND COPYRIGHT
2008
2009GPL version 2 or later and Perl equivalent
2010
2011Copyright (C) 2005-2012 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.