source: trunk/klask @ 124

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