source: trunk/klask @ 119

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