source: trunk/klask @ 131

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