source: trunk/klask @ 128

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