source: trunk/klask @ 122

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