source: trunk/klask @ 123

Last change on this file since 123 was 123, checked in by g7moreau, 11 years ago
  • Error in test and close two time SNMP session...
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 66.4 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2012 Gabriel Moreau.
4#
5# $Id: klask 123 2013-04-21 08:31:57Z 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: 123 $'."\n";
619   print ' $Date: 2013-04-21 08:31:57 +0000 (Sun, 21 Apr 2013) $'."\n";
620   print ' $Id: klask 123 2013-04-21 08:31:57Z 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      print "Klask search OID $research2 on switch $sw_name\n";
1521
1522      my ($session, $error) = Net::SNMP->session( %session );
1523      print "$error \n" if $error;
1524
1525      my $result = $session->get_request(
1526         -varbindlist => [$research1, $research2]
1527         );
1528
1529      if (defined $result and ($result->{$research1} ne 'noSuchInstance' or $result->{$research2} ne 'noSuchInstance')) {
1530         my $swport = $result->{$research1};
1531         $swport = $result->{$research2} if $result->{$research1} eq 'noSuchInstance';
1532         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1533         }
1534      else {
1535         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1536         }
1537
1538      $session->close;
1539      }
1540   return;
1541   }
1542
1543sub cmd_updatesw {
1544   @ARGV = @_;
1545
1546   my $verbose;
1547
1548   my $ret = GetOptions(
1549      'verbose|v' => \$verbose,
1550      );
1551
1552   init_switch_names('yes');    #nomme les switchs
1553   print "\n";
1554
1555   my %where = ();
1556   my %db_switch_output_port = ();
1557   my %db_switch_ip_hostnamefq = ();
1558
1559   DETECT_ALL_ROUTER:
1560#   for my $one_computer ('194.254.66.254') {
1561   for my $one_router ( get_list_main_router(get_list_network()) ) {
1562      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1563
1564      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1565      print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
1566
1567      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1568      }
1569
1570   ALL_ROUTER_IP_ADDRESS:
1571   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1572
1573      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
1574
1575      ALL_SWITCH_CONNECTED:
1576      for my $switch_detected ( keys %{$where{$ip_router}} ) {
1577
1578         my $switch = $where{$ip_router}->{$switch_detected};
1579
1580         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
1581
1582         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1583         print "VERBOSE_2: output port $switch->{hostname} : $switch->{port}\n" if $verbose;
1584         }
1585      }
1586
1587   my %db_switch_link_with = ();
1588
1589   my @list_all_switch = ();
1590   my @list_switch_ipv4 = ();
1591   for my $sw (@SWITCH){
1592      push @list_all_switch, $sw->{hostname};
1593      }
1594
1595   my $timestamp = time;
1596
1597   ALL_SWITCH:
1598   for my $one_computer (@list_all_switch) {
1599      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1600      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1601
1602      push @list_switch_ipv4, $resol_arp{ipv4_address};
1603
1604      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
1605
1606      if ($verbose) {
1607         print "VERBOSE_3: $one_computer $resol_arp{ipv4_address} $resol_arp{mac_address}\n";
1608         print "VERBOSE_3: $one_computer --- ",
1609            join(' + ', keys %{$where{$resol_arp{ipv4_address}}}),
1610            "\n";
1611         }
1612
1613      $db_switch_ip_hostnamefq{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
1614      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{ipv4_address} -> $resol_arp{hostname_fq}\n" if $verbose;
1615
1616      $SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
1617      $SWITCH_DB{$one_computer}->{mac_address}  = $resol_arp{mac_address};
1618      $SWITCH_DB{$one_computer}->{timestamp}    = $timestamp;
1619      }
1620
1621   ALL_SWITCH_IP_ADDRESS:
1622   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1623
1624      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
1625
1626      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1627#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
1628
1629      DETECTED_SWITCH:
1630      for my $switch_detected ( keys %{$where{$ip}} ) {
1631
1632         my $switch = $where{$ip}->{$switch_detected};
1633         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{hostname} : $switch->{port}\n" if $verbose;
1634
1635         next if $switch->{port}     eq '0';
1636         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
1637         next if $switch->{hostname} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{hostname};
1638
1639         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
1640         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{hostname} } = $switch->{port};
1641         print "VERBOSE_7: +++++\n" if $verbose;
1642         }
1643
1644      }
1645
1646   my %db_switch_connected_on_port = ();
1647   my $maybe_more_than_one_switch_connected = 'yes';
1648
1649   while ($maybe_more_than_one_switch_connected eq 'yes') {
1650      for my $sw (keys %db_switch_link_with) {
1651         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1652
1653            my $port = $db_switch_link_with{$sw}->{$connect};
1654
1655            $db_switch_connected_on_port{"$connect:$port"} ||= {};
1656            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
1657            }
1658         }
1659
1660      $maybe_more_than_one_switch_connected  = 'no';
1661
1662      SWITCH_AND_PORT:
1663      for my $swport (keys %db_switch_connected_on_port) {
1664
1665         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1666
1667         $maybe_more_than_one_switch_connected = 'yes';
1668
1669         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1670         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1671
1672         CONNECTED:
1673         for my $sw_connected (@sw_on_same_port) {
1674
1675            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1676
1677            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1678
1679            for my $other_sw (@sw_on_same_port) {
1680               next if $other_sw eq $sw_connected;
1681
1682               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1683               }
1684
1685            # We can not do better for this switch for this loop
1686            next SWITCH_AND_PORT;
1687            }
1688         }
1689      }
1690
1691   my %db_switch_parent =();
1692
1693   for my $sw (keys %db_switch_link_with) {
1694      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1695
1696         my $port = $db_switch_link_with{$sw}->{$connect};
1697
1698         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1699         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1700
1701         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1702         }
1703      }
1704
1705   print "Switch output port and parent port connection\n";
1706   print "---------------------------------------------\n";
1707   for my $sw (sort keys %db_switch_output_port) {
1708      if (exists $db_switch_parent{$sw}) {
1709         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1710         }
1711      else {
1712         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1713         }
1714      }
1715   print "\n";
1716
1717   print "Switch parent and children port inter-connection\n";
1718   print "------------------------------------------------\n";
1719   for my $swport (sort keys %db_switch_connected_on_port) {
1720      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1721      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1722         if (exists $db_switch_output_port{$sw}) {
1723            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1724            }
1725         else {
1726            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1727            }
1728         }
1729      }
1730
1731   my $switch_connection = {
1732      output_port       => \%db_switch_output_port,
1733      parent            => \%db_switch_parent,
1734      connected_on_port => \%db_switch_connected_on_port,
1735      link_with         => \%db_switch_link_with,
1736      switch_db         => \%SWITCH_DB,
1737      };
1738
1739   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1740   return;
1741   }
1742
1743sub cmd_exportsw {
1744   @ARGV = @_;
1745
1746   test_switchdb_environnement();
1747
1748   my $format = 'txt';
1749
1750   my $ret = GetOptions(
1751      'format|f=s'  => \$format,
1752      );
1753
1754   my %possible_format = (
1755      txt => \&cmd_exportsw_txt,
1756      dot => \&cmd_exportsw_dot,
1757      );
1758
1759   $format = 'txt' if not defined $possible_format{$format};
1760
1761   $possible_format{$format}->(@ARGV);
1762   return;
1763   }
1764
1765sub cmd_exportsw_txt {
1766
1767   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1768
1769   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1770   my %db_switch_parent            = %{$switch_connection->{parent}};
1771   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1772
1773   print "Switch output port and parent port connection\n";
1774   print "---------------------------------------------\n";
1775   for my $sw (sort keys %db_switch_output_port) {
1776      if (exists $db_switch_parent{$sw}) {
1777         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1778         }
1779      else {
1780         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1781         }
1782      }
1783   print "\n";
1784
1785   print "Switch parent and children port inter-connection\n";
1786   print "------------------------------------------------\n";
1787   for my $swport (sort keys %db_switch_connected_on_port) {
1788      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1789      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1790         if (exists $db_switch_output_port{$sw}) {
1791            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1792            }
1793         else {
1794            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1795            }
1796         }
1797      }
1798   return;
1799   }
1800
1801sub cmd_exportsw_dot {
1802
1803   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1804
1805   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1806   my %db_switch_parent            = %{$switch_connection->{parent}};
1807   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1808   my %db_switch_link_with         = %{$switch_connection->{link_with}};
1809   my %db_switch_global            = %{$switch_connection->{switch_db}};
1810
1811   my %db_building= ();
1812   for my $sw (@SWITCH) {
1813      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
1814      $db_building{$building} ||= {};
1815      $db_building{$building}->{$location} ||= {};
1816      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1817      }
1818
1819
1820   print "digraph G {\n";
1821
1822   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
1823   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
1824
1825   my $b=0;
1826   for my $building (keys %db_building) {
1827      $b++;
1828
1829      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
1830      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
1831
1832      my $l = 0;
1833      for my $loc (keys %{$db_building{$building}}) {
1834         $l++;
1835
1836         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
1837#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
1838         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
1839
1840         for my $sw (keys %{$db_building{$building}->{$loc}}) {
1841
1842            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
1843
1844            my $swname  = $sw;
1845               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
1846            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
1847            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
1848            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
1849
1850
1851            for my $swport (keys %db_switch_connected_on_port) {
1852               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1853               next if not $sw_connect eq $sw;
1854               next if $port_connect eq $db_switch_output_port{$sw};
1855               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
1856               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
1857              }
1858            }
1859         }
1860      }
1861
1862#   print "Switch output port and parent port connection\n";
1863#   print "---------------------------------------------\n";
1864   for my $sw (sort keys %db_switch_output_port) {
1865      if (exists $db_switch_parent{$sw}) {
1866#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
1867         }
1868      else {
1869         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
1870         }
1871      }
1872   print "\n";
1873
1874#   print "Switch parent and children port inter-connection\n";
1875#   print "------------------------------------------------\n";
1876   for my $swport (sort keys %db_switch_connected_on_port) {
1877      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1878      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1879         if (exists $db_switch_output_port{$sw}) {
1880            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
1881            }
1882         else {
1883            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
1884            }
1885         }
1886      }
1887
1888print "}\n";
1889   return;
1890   }
1891
1892
1893__END__
1894
1895=head1 NAME
1896
1897klask - ports manager and finder for switch
1898
1899
1900=head1 USAGE
1901
1902 klask updatedb
1903 klask exportdb --format [txt|html]
1904 klask removedb computer*
1905 klask cleandb  --day number_of_day --verbose
1906
1907 klask updatesw
1908 klask exportsw --format [txt|dot]
1909
1910 klask searchdb computer
1911 klask search   computer
1912 klask search-mac-on-switch switch mac_addr
1913
1914 klask ip-free --day number_of_day --format [txt|html] [vlan_name]
1915
1916 klask enable  switch port
1917 klask disable swith port
1918 klask status  swith port
1919
1920
1921=head1 DESCRIPTION
1922
1923klask is a small tool to find where is a host in a big network. klask mean search in brittany.
1924
1925Klask has now a web site dedicated for it !
1926
1927 http://servforge.legi.grenoble-inp.fr/projects/klask
1928
1929
1930=head1 COMMANDS
1931
1932
1933=head2 search
1934
1935This 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.
1936
1937
1938=head2 enable
1939
1940This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1941
1942
1943=head2 disable
1944
1945This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
1946
1947
1948=head2 status
1949
1950This 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.
1951
1952
1953=head2 updatedb
1954
1955This 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.
1956
1957
1958=head2 exportdb
1959
1960This 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...
1961
1962
1963=head2 updatesw
1964
1965This 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.
1966
1967
1968=head2 exportsw --format [txt|dot]
1969
1970This 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.
1971
1972 klask exportsw --format dot > /tmp/map.dot
1973 dot -Tpng /tmp/map.dot > /tmp/map.png
1974
1975
1976
1977=head1 CONFIGURATION
1978
1979Because 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 !
1980
1981Here an example, be aware with indent, it's important in YAML, do not use tabulation !
1982
1983 default:
1984   community: public
1985   snmpport: 161
1986
1987 network:
1988   labnet:
1989     ip-subnet:
1990       - add: 192.168.1.0/24
1991       - add: 192.168.2.0/24
1992     interface: eth0
1993     main-router: gw1.labnet.local
1994
1995   schoolnet:
1996     ip-subnet:
1997       - add: 192.168.6.0/24
1998       - add: 192.168.7.0/24
1999     interface: eth0.38
2000     main-router: gw2.schoolnet.local
2001
2002 switch:
2003   - hostname: sw1.klask.local
2004     portignore:
2005       - 1
2006       - 2
2007
2008   - hostname: sw2.klask.local
2009     location: BatK / 2 / K203
2010     type: HP2424
2011     portignore:
2012       - 1
2013       - 2
2014
2015I 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.
2016
2017
2018=head1 FILES
2019
2020 /etc/klask/klask.conf
2021 /var/lib/klask/klaskdb
2022 /var/lib/klask/switchdb
2023
2024=head1 SEE ALSO
2025
2026Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
2027
2028
2029=head1 VERSION
2030
2031$Id: klask 123 2013-04-21 08:31:57Z g7moreau $
2032
2033
2034=head1 AUTHOR
2035
2036Written by Gabriel Moreau, Grenoble - France
2037
2038
2039=head1 LICENSE AND COPYRIGHT
2040
2041GPL version 2 or later and Perl equivalent
2042
2043Copyright (C) 2005-2012 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.