source: trunk/klask @ 134

Last change on this file since 134 was 134, checked in by g7moreau, 11 years ago
  • Update method search but some work need to be done
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 73.9 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2013 Gabriel Moreau.
4#
5# $Id: klask 134 2013-11-01 19:19:32Z 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-id'          => \&cmd_bad_vlan_id,
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 -q -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' if $verbose;
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-id
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: 134 $'."\n";
668   print ' $Date: 2013-11-01 19:19:32 +0000 (Fri, 01 Nov 2013) $'."\n";
669   print ' $Id: klask 134 2013-11-01 19:19:32Z 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
679   LOOP_ON_COMPUTER:
680   for my $clientname (@computer) {
681      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
682      my %where     = find_switch_port($resol_arp{mac_address}); #retrouve l'emplacement
683
684      next LOOP_ON_COMPUTER if $where{switch_description} eq 'unknow' or $resol_arp{hostname_fq} eq 'unknow' or $resol_arp{mac_address} eq 'unknow';
685
686      printf '%-22s %2i %-30s %-15s %18s',
687         $where{switch_hostname},
688         $where{switch_port},
689         $resol_arp{hostname_fq},
690         $resol_arp{ipv4_address},
691         $resol_arp{mac_address}."\n";
692      }
693   return;
694   }
695
696sub cmd_searchdb {
697   my @computer = @_;
698
699   fast_ping(@computer);
700   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
701
702   LOOP_ON_COMPUTER:
703   for my $clientname (@computer) {
704      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
705      my $ip = $resol_arp{ipv4_address};
706
707      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
708
709      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
710      $year += 1900;
711      $mon++;
712      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
713
714      printf "%-22s %2s %-30s %-15s %-18s %s\n",
715         $computerdb->{$ip}{switch_hostname},
716         $computerdb->{$ip}{switch_port},
717         $computerdb->{$ip}{hostname_fq},
718         $ip,
719         $computerdb->{$ip}{mac_address},
720         $date;
721      }
722   return;
723   }
724
725sub cmd_updatedb {
726   my @network = @_;
727      @network = get_list_network() if not @network;
728
729   test_switchdb_environnement();
730
731   my $computerdb = {};
732      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
733   my $timestamp = time;
734
735   my %computer_not_detected = ();
736   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
737
738   my $number_of_computer = get_list_ip(@network); # + 1;
739   my $size_of_database   = keys %{$computerdb};
740      $size_of_database   = 1 if $size_of_database == 0;
741   my $i = 0;
742   my $detected_computer = 0;
743
744   init_switch_names('yes');    #nomme les switchs
745
746   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
747   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
748   my %db_switch_output_port       = %{$switch_connection->{output_port}};
749   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
750   my %db_switch_chained_port = ();
751   for my $swport (keys %db_switch_connected_on_port) {
752      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
753      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
754      }
755   for my $sw (@SWITCH){
756      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{hostname}}  if exists $db_switch_output_port{$sw->{hostname}};
757      if ( exists $db_switch_chained_port{$sw->{hostname}} ) {
758         chop $db_switch_chained_port{$sw->{hostname}};
759         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{hostname}};
760         }
761#      print "$sw->{hostname} ++ @{$sw->{portignore}}\n";
762      }
763   }
764
765   my %router_mac_ip = ();
766   DETECT_ALL_ROUTER:
767#   for my $one_router ('194.254.66.254') {
768   for my $one_router ( get_list_main_router(@network) ) {
769      my %resol_arp = resolve_ip_arp_host($one_router);
770      $router_mac_ip{ $resol_arp{mac_address} } = $resol_arp{ipv4_address};
771      }
772
773   ALL_NETWORK:
774   for my $net (@network) {
775
776      my @computer = get_list_ip($net);
777      my $current_interface = get_current_interface($net);
778
779      fast_ping(@computer);
780
781      LOOP_ON_COMPUTER:
782      for my $one_computer (@computer) {
783         $i++;
784
785         my $total_percent = int (($i*100)/$number_of_computer);
786
787         my $localtime = time - $timestamp;
788         my ($sec,$min) = localtime $localtime;
789
790         my $time_elapse = 0;
791            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
792         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
793
794         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
795         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
796         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
797         printf ' %-8s %-14s', $current_interface, $one_computer;
798
799         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface);
800
801         # do not search on router connection (why ?)
802         if ( exists $router_mac_ip{$resol_arp{mac_address}}) {
803            $computer_not_detected{$one_computer} = $current_interface;
804            next LOOP_ON_COMPUTER;
805            }
806
807         # do not search on switch inter-connection
808         if (exists $switch_level{$resol_arp{hostname_fq}}) {
809            $computer_not_detected{$one_computer} = $current_interface;
810            next LOOP_ON_COMPUTER;
811            }
812
813         my $switch_proposal = q{};
814         if (exists $computerdb->{$resol_arp{ipv4_address}} and exists $computerdb->{$resol_arp{ipv4_address}}{switch_hostname}) {
815            $switch_proposal = $computerdb->{$resol_arp{ipv4_address}}{switch_hostname};
816            }
817
818         # do not have a mac address
819         if ($resol_arp{mac_address} eq 'unknow' or (exists $resol_arp{timestamps} and $resol_arp{timestamps} < ($timestamp - 3 * 3600))) {
820            $computer_not_detected{$one_computer} = $current_interface;
821            next LOOP_ON_COMPUTER;
822            }
823
824         my %where = find_switch_port($resol_arp{mac_address},$switch_proposal);
825
826         #192.168.24.156:
827         #  arp: 00:0B:DB:D5:F6:65
828         #  hostname: pcroyon.hmg.priv
829         #  port: 5
830         #  switch: sw-batH-legi:hp2524
831         #  timestamp: 1164355525
832
833         # do not have a mac address
834#         if ($resol_arp{mac_address} eq 'unknow') {
835#            $computer_not_detected{$one_computer} = $current_interface;
836#            next LOOP_ON_COMPUTER;
837#            }
838
839         # detected on a switch
840         if ($where{switch_description} ne 'unknow') {
841            $detected_computer++;
842            $computerdb->{$resol_arp{ipv4_address}} = {
843               hostname_fq        => $resol_arp{hostname_fq},
844               mac_address        => $resol_arp{mac_address},
845               switch_hostname    => $where{switch_hostname},
846               switch_description => $where{switch_description},
847               switch_port        => $where{switch_port},
848               timestamp          => $timestamp,
849               network            => $net,
850               };
851            next LOOP_ON_COMPUTER;
852            }
853
854         # new in the database but where it is ?
855         if (not exists $computerdb->{$resol_arp{ipv4_address}}) {
856            $detected_computer++;
857            $computerdb->{$resol_arp{ipv4_address}} = {
858               hostname_fq        => $resol_arp{hostname_fq},
859               mac_address        => $resol_arp{mac_address},
860               switch_hostname    => $where{switch_hostname},
861               switch_description => $where{switch_description},
862               switch_port        => $where{switch_port},
863               timestamp          => $resol_arp{timestamp},
864               network            => $net,
865               };
866            }
867
868         # mise a jour du nom de la machine si modification dans le dns
869         $computerdb->{$resol_arp{ipv4_address}}{hostname_fq} = $resol_arp{hostname_fq};
870
871         # mise à jour de la date de détection si détection plus récente par arpwatch
872         $computerdb->{$resol_arp{ipv4_address}}{timestamp}   = $resol_arp{timestamp} if exists $resol_arp{timestamp} and $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $resol_arp{timestamp};
873
874         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
875#         push @computer_not_detected, $resol_arp{ipv4_address} if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
876         $computer_not_detected{$resol_arp{ipv4_address}} = $current_interface if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
877
878         }
879      }
880
881   # final end of line at the end of the loop
882   printf "\n";
883
884   my $dirdb = $KLASK_DB_FILE;
885      $dirdb =~ s{ / [^/]* $}{}xms;
886   mkdir "$dirdb", 0755 unless -d "$dirdb";
887   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
888
889   for my $one_computer (keys %computer_not_detected) {
890      my $interface = $computer_not_detected{$one_computer};
891      system "arping -c 1 -w 1 -rR -i $interface $one_computer &>/dev/null";
892#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
893      }
894   return;
895   }
896
897sub cmd_removedb {
898   my @computer = @_;
899
900   test_maindb_environnement();
901
902   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
903
904   LOOP_ON_COMPUTER:
905   for my $one_computer (@computer) {
906
907      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
908            and exists $computerdb->{$one_computer} ) {
909         delete $computerdb->{$one_computer};
910         next;
911         }
912
913      my %resol_arp = resolve_ip_arp_host($one_computer);
914
915      delete $computerdb->{$resol_arp{ipv4_address}} if exists $computerdb->{$resol_arp{ipv4_address}};
916      }
917
918   my $dirdb = $KLASK_DB_FILE;
919      $dirdb =~ s{ / [^/]* $}{}xms;
920   mkdir "$dirdb", 0755 unless -d "$dirdb";
921   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
922   return;
923   }
924
925sub cmd_cleandb {
926   my @ARGV  = @_;
927
928   my $days_to_clean = 15;
929   my $verbose;
930   my $database_has_changed;
931
932   my $ret = GetOptions(
933      'day|d=i'   => \$days_to_clean,
934      'verbose|v' => \$verbose,
935      );
936
937   my @vlan_name = get_list_network();
938
939   my $computerdb = {};
940      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
941   my $timestamp = time;
942
943   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
944   my $timestamp_3month  = 3600 * 24 * 90;
945
946   my %mactimedb = ();
947   ALL_VLAN:
948   for my $vlan (shuffle @vlan_name) {
949
950      my @ip_list   = shuffle get_list_ip($vlan);
951     
952      LOOP_ON_IP_ADDRESS:
953      for my $ip (@ip_list) {
954
955         next LOOP_ON_IP_ADDRESS if
956            not exists $computerdb->{$ip};
957           
958            #&& $computerdb->{$ip}{timestamp} > $timestamp_barrier;
959         my $ip_timestamp   = $computerdb->{$ip}{timestamp};
960         my $ip_mac         = $computerdb->{$ip}{mac_address};
961         my $ip_hostname_fq = $computerdb->{$ip}{hostname_fq};
962
963         $mactimedb{$ip_mac} ||= {
964            ip          => $ip,
965            timestamp   => $ip_timestamp,
966            vlan        => $vlan,
967            hostname_fq => $ip_hostname_fq,
968            };
969         
970         if (
971            ( $mactimedb{$ip_mac}->{timestamp} - $ip_timestamp > $timestamp_barrier
972               or (
973                  $mactimedb{$ip_mac}->{timestamp} > $ip_timestamp
974                  and $timestamp - $mactimedb{$ip_mac}->{timestamp} > $timestamp_3month
975                  )
976            )
977            and (
978               not $mactimedb{$ip_mac}->{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/
979               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
980               )) {
981            print "remove ip $ip\n" if $verbose;
982            delete $computerdb->{$ip};
983            $database_has_changed++;
984            }
985
986         elsif (
987            ( $ip_timestamp - $mactimedb{$ip_mac}->{timestamp} > $timestamp_barrier
988               or (
989                  $ip_timestamp > $mactimedb{$ip_mac}->{timestamp}
990                  and $timestamp - $ip_timestamp > $timestamp_3month
991                  )
992            )
993            and (
994               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
995               or $mactimedb{$ip_mac}->{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/
996               )) {
997            print "remove ip ".$mactimedb{$ip_mac}->{ip}."\n" if $verbose;
998            delete $computerdb->{$mactimedb{$ip_mac}->{ip}};
999            $database_has_changed++;
1000            }
1001
1002         if ( $ip_timestamp > $mactimedb{$ip_mac}->{timestamp}) {
1003            $mactimedb{$ip_mac} = {
1004               ip          => $ip,
1005               timestamp   => $ip_timestamp,
1006               vlan        => $vlan,
1007               hostname_fq => $ip_hostname_fq,
1008               };
1009            }
1010         }
1011      }
1012
1013   if ( $database_has_changed ) {
1014      my $dirdb = $KLASK_DB_FILE;
1015         $dirdb =~ s{ / [^/]* $}{}xms;
1016      mkdir "$dirdb", 0755 unless -d "$dirdb";
1017      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1018      }
1019   return;
1020   }
1021
1022sub cmd_exportdb {
1023   @ARGV = @_;
1024
1025   my $format = 'txt';
1026
1027   my $ret = GetOptions(
1028      'format|f=s'  => \$format,
1029      );
1030
1031   my %possible_format = (
1032      txt  => \&cmd_exportdb_txt,
1033      html => \&cmd_exportdb_html,
1034      );
1035
1036   $format = 'txt' if not defined $possible_format{$format};
1037
1038   $possible_format{$format}->(@ARGV);
1039   return;
1040   }
1041
1042sub cmd_exportdb_txt {
1043   test_maindb_environnement();
1044
1045   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1046
1047   printf "%-27s %-4s            %-40s %-15s %-18s %-16s %s\n", qw(Switch Port Hostname-FQ IPv4-Address MAC-Address Date VLAN);
1048   print "--------------------------------------------------------------------------------------------------------------------------------------------\n";
1049
1050   LOOP_ON_IP_ADDRESS:
1051   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1052
1053#      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq 'unknow';
1054
1055      # to be improve in the future
1056      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1057
1058# dans le futur
1059#      next if $computerdb->{$ip}{hostname_fq} eq 'unknow';
1060
1061      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1062      $year += 1900;
1063      $mon++;
1064      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1065
1066      printf "%-28s  %2s  <-------  %-40s %-15s %-18s %-16s %s\n",
1067         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
1068         $computerdb->{$ip}{switch_port},
1069         $computerdb->{$ip}{hostname_fq},
1070         $ip,
1071         $computerdb->{$ip}{mac_address},
1072         $date,
1073         $computerdb->{$ip}{network} || '';
1074      }
1075   return;
1076   }
1077
1078sub cmd_exportdb_html {
1079   test_maindb_environnement();
1080
1081   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1082
1083#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1084#<script src="sorttable-klask.js"></script>
1085
1086   print <<'END_HTML';
1087<table class="sortable" summary="Klask Host Database">
1088 <caption>Klask Host Database</caption>
1089 <thead>
1090  <tr>
1091   <th scope="col" class="klask-header-left">Switch</th>
1092   <th scope="col" class="sorttable_nosort">Port</th>
1093   <th scope="col" class="sorttable_nosort">Link</th>
1094   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1095   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1096   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1097   <th scope="col" class="sorttable_alpha">VLAN</th>
1098   <th scope="col" class="klask-header-right">Date</th>
1099  </tr>
1100 </thead>
1101 <tfoot>
1102  <tr>
1103   <th scope="col" class="klask-footer-left">Switch</th>
1104   <th scope="col" class="fklask-port">Port</th>
1105   <th scope="col" class="fklask-link">Link</th>
1106   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1107   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1108   <th scope="col" class="fklask-mac">MAC-Address</th>
1109   <th scope="col" class="fklask-vlan">VLAN</th>
1110   <th scope="col" class="klask-footer-right">Date</th>
1111  </tr>
1112 </tfoot>
1113 <tbody>
1114END_HTML
1115
1116   my %mac_count = ();
1117   LOOP_ON_IP_ADDRESS:
1118   foreach my $ip (keys %{$computerdb}) {
1119
1120      # to be improve in the future
1121      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1122
1123      $mac_count{$computerdb->{$ip}{mac_address}}++;
1124      }
1125
1126   my $typerow = 'even';
1127
1128   LOOP_ON_IP_ADDRESS:
1129   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1130
1131      # to be improve in the future
1132      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1133
1134      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1135      $year += 1900;
1136      $mon++;
1137      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1138
1139#      $odd_or_even++;
1140#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1141      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1142
1143      my $switch_hostname = $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description} || 'unkown';
1144      chomp $switch_hostname;
1145      my $switch_hostname_sort = sprintf '%s %3s' ,$switch_hostname, $computerdb->{$ip}{switch_port};
1146
1147      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1148
1149      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{mac_address}}, $computerdb->{$ip}{mac_address};
1150
1151      $computerdb->{$ip}{hostname_fq} = 'unknow' if $computerdb->{$ip}{hostname_fq} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1152      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{hostname_fq};
1153
1154      my $vlan = $computerdb->{$ip}{network} || '';
1155
1156      print <<"END_HTML";
1157  <tr class="$typerow">
1158   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1159   <td class="bklask-port">$computerdb->{$ip}{switch_port}</td>
1160   <td><-------</td>
1161   <td sorttable_customkey="$host_short">$computerdb->{$ip}{hostname_fq}</td>
1162   <td sorttable_customkey="$ip_sort">$ip</td>
1163   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{mac_address}</td>
1164   <td>$vlan</td>
1165   <td>$date</td>
1166  </tr>
1167END_HTML
1168      }
1169
1170   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1171
1172   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1173   my %db_switch_parent            = %{$switch_connection->{parent}};
1174   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1175   my %db_switch                   = %{$switch_connection->{switch_db}};
1176
1177   for my $sw (sort keys %db_switch_output_port) {
1178
1179      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1180
1181      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1182
1183      if (exists $db_switch_parent{$sw}) {
1184
1185      my $mac_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{mac_address};
1186      my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{ipv4_address};
1187      my $timestamp = $db_switch{$db_switch_parent{$sw}->{switch}}->{timestamp};
1188
1189      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1190      $year += 1900;
1191      $mon++;
1192      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1193
1194      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1195
1196      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1197
1198      my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{switch}, 1), $db_switch_parent{$sw}->{port};
1199
1200      print <<"END_HTML";
1201  <tr class="$typerow">
1202   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1203   <td class="bklask-port">$db_switch_output_port{$sw}</>
1204   <td>+--> $db_switch_parent{$sw}->{port}</td>
1205   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{switch}</>
1206   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1207   <td sorttable_customkey="$mac_sort">$mac_address</td>
1208   <td></td>
1209   <td>$date</td>
1210  </tr>
1211END_HTML
1212         }
1213      else {
1214         print <<"END_HTML";
1215  <tr class="$typerow">
1216   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1217   <td class="bklask-port">$db_switch_output_port{$sw}</>
1218   <td>+--></td>
1219   <td sorttable_customkey="router">router</>
1220   <td sorttable_customkey="999999999999"></td>
1221   <td sorttable_customkey="99999"></td>
1222   <td></td>
1223   <td></td>
1224  </tr>
1225END_HTML
1226         }
1227      }
1228
1229   for my $swport (sort keys %db_switch_connected_on_port) {
1230      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1231      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1232
1233         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1234
1235      my $mac_address = $db_switch{$sw}->{mac_address};
1236      my $ipv4_address = $db_switch{$sw}->{ipv4_address};
1237      my $timestamp = $db_switch{$sw}->{timestamp};
1238
1239      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1240      $year += 1900;
1241      $mon++;
1242      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1243
1244      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1245
1246      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1247
1248      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1249
1250         if (exists $db_switch_output_port{$sw}) {
1251
1252            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1253
1254            print <<"END_HTML";
1255  <tr class="$typerow">
1256   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1257   <td class="bklask-port">$port_connect</>
1258   <td>&lt;--+ $db_switch_output_port{$sw}</td>
1259   <td sorttable_customkey="$host_short">$sw</>
1260   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1261   <td sorttable_customkey="$mac_sort">$mac_address</td>
1262   <td></td>
1263   <td>$date</td>
1264  </tr>
1265END_HTML
1266            }
1267         else {
1268            print <<"END_HTML";
1269  <tr class="$typerow">
1270   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1271   <td class="bklask-port">$port_connect</>
1272   <td>&lt;--+</td>
1273   <td sorttable_customkey="$sw">$sw</>
1274   <td sorttable_customkey="">$ipv4_address</td>
1275   <td sorttable_customkey="">$mac_address</td>
1276   <td></td>
1277   <td>$date</td>
1278  </tr>
1279END_HTML
1280            }
1281         }
1282      }
1283
1284   print <<'END_HTML';
1285 </tbody>
1286</table>
1287END_HTML
1288   return;
1289   }
1290
1291sub cmd_bad_vlan_id {
1292   test_maindb_environnement();
1293
1294   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1295
1296   # create a database with the most recent computer by switch port
1297   my %swithportdb = ();
1298   LOOP_ON_IP_ADDRESS:
1299   foreach my $ip (keys %{$computerdb}) {
1300      # to be improve in the future
1301      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1302      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}) eq 'unknow';
1303      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{switch_port} eq '0';
1304
1305      my $ip_timestamp   = $computerdb->{$ip}{timestamp};
1306      my $ip_mac         = $computerdb->{$ip}{mac_address};
1307      my $ip_hostname_fq = $computerdb->{$ip}{hostname_fq};
1308
1309      my $swpt = sprintf "%-28s  %2s",
1310         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
1311         $computerdb->{$ip}{switch_port};
1312      $swithportdb{$swpt} ||= {
1313         ip          => $ip,
1314         timestamp   => $ip_timestamp,
1315         vlan        => $computerdb->{$ip}{network},
1316         hostname_fq => $ip_hostname_fq,
1317         mac_address => $ip_mac,
1318         };
1319
1320      # if float computer, set date 15 day before warning...
1321      my $ip_timestamp_mod = $ip_timestamp;
1322      my $ip_timestamp_ref = $swithportdb{$swpt}->{timestamp};
1323      $ip_timestamp_mod -= 15 * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1324      $ip_timestamp_ref -= 15 * 24 * 3600 if $swithportdb{$swpt}->{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/;
1325     
1326      if ($ip_timestamp_mod > $ip_timestamp_ref) {
1327         $swithportdb{$swpt} = {
1328            ip          => $ip,
1329            timestamp   => $ip_timestamp,
1330            vlan        => $computerdb->{$ip}{network},
1331            hostname_fq => $ip_hostname_fq,
1332            mac_address => $ip_mac,
1333            };
1334         }
1335      }
1336
1337   LOOP_ON_RECENT_COMPUTER:
1338   foreach my $swpt (keys %swithportdb) {
1339      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
1340      next LOOP_ON_RECENT_COMPUTER if $swithportdb{$swpt}->{hostname_fq} !~ m/$RE_FLOAT_HOSTNAME/;
1341
1342      my $src_ip = $swithportdb{$swpt}->{ip};
1343      my $src_timestamp = 0;
1344      LOOP_ON_IP_ADDRESS:
1345      foreach my $ip (keys %{$computerdb}) {
1346         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{mac_address} ne  $swithportdb{$swpt}->{mac_address};
1347         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/;
1348         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{timestamp} < $src_timestamp;
1349         
1350         $src_ip = $ip;
1351         $src_timestamp = $computerdb->{$ip}{timestamp};
1352         }
1353
1354      # keep only if float computer is the most recent
1355      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
1356      next LOOP_ON_RECENT_COMPUTER if $swithportdb{$swpt}->{timestamp} < $src_timestamp;
1357
1358      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $swithportdb{$swpt}->{timestamp};
1359      $year += 1900;
1360      $mon++;
1361      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1362
1363      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{timestamp};
1364      $year += 1900;
1365      $mon++;
1366      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1367
1368      printf "%s / %-10s +-> %-10s  %s %s %s %s\n",
1369         $swpt, $swithportdb{$swpt}->{vlan}, $computerdb->{$src_ip}{network},
1370         $date,
1371         $src_date,
1372         $computerdb->{$src_ip}{mac_address},
1373         $computerdb->{$src_ip}{hostname_fq};
1374      }
1375   }
1376
1377sub cmd_set_vlan_port {
1378   my $switch_name = shift || q{};
1379   my $mac_address = shift || q{};
1380
1381   if ($switch_name eq q{} or $mac_address eq q{}) {
1382      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1383      }
1384
1385   $switch_name = join(',', map {$_->{hostname}} @SWITCH ) if $switch_name eq q{*};
1386
1387   for my $sw_name (split /,/, $switch_name) {
1388      if (not defined $SWITCH_DB{$sw_name}) {
1389         die "Switch $sw_name must be defined in klask configuration file\n";
1390         }
1391
1392      my $sw = $SWITCH_DB{$sw_name};
1393      my %session = ( -hostname => $sw->{hostname} );
1394         $session{-version} = $sw->{version}   || 1;
1395         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1396      if (exists $sw->{version} and $sw->{version} eq '3') {
1397         $session{-username} = $sw->{username} || 'snmpadmin';
1398         }
1399      else {
1400         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1401         }
1402
1403      my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
1404      my $research2 = $OID_NUMBER{searchPort2} . mac_address_hex_to_dec($mac_address);
1405      print "Klask search OID $research1 on switch $sw_name\n";
1406      print "Klask search OID $research2 on switch $sw_name\n";
1407
1408      my ($session, $error) = Net::SNMP->session( %session );
1409      print "$error \n" if $error;
1410
1411      my $result = $session->get_request(
1412         -varbindlist => [$research1]
1413         );
1414      if (not defined $result) {
1415         $result = $session->get_request(
1416            -varbindlist => [$research2]
1417            );
1418         $result->{$research1} = $result->{$research2} if defined $result;
1419         }
1420
1421      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1422         my $swport = $result->{$research1};
1423         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1424         }
1425      else {
1426         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1427         }
1428
1429      $session->close;
1430      }
1431   return;
1432   }
1433
1434sub cmd_get_vlan_port {
1435   }
1436
1437sub cmd_set_vlan_name {
1438   }
1439
1440sub cmd_get_vlan_name {
1441   my $switch_name = shift || q{};
1442   my $vlan_id     = shift || q{};
1443
1444   if ($switch_name eq q{} or $vlan_id eq q{}) {
1445      die "Usage: klask get-vlan-name SWITCH_NAME VLAN_ID\n";
1446      }
1447
1448   $switch_name = join(',', map {$_->{hostname}} @SWITCH ) if $switch_name eq q{*};
1449
1450   for my $sw_name (split /,/, $switch_name) {
1451      if (not defined $SWITCH_DB{$sw_name}) {
1452         die "Switch $sw_name must be defined in klask configuration file\n";
1453         }
1454
1455      my $sw = $SWITCH_DB{$sw_name};
1456      my %session = ( -hostname => $sw->{hostname} );
1457         $session{-version} = $sw->{version}   || 1;
1458         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1459      if (exists $sw->{version} and $sw->{version} eq '3') {
1460         $session{-username} = $sw->{username} || 'snmpadmin';
1461         }
1462      else {
1463         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1464         }
1465
1466      my $search_vlan_name = $OID_NUMBER{vlanName} . ".$vlan_id";
1467
1468      my ($session, $error) = Net::SNMP->session( %session );
1469      print "$error \n" if $error;
1470
1471      my $result = $session->get_request(
1472         -varbindlist => [$search_vlan_name]
1473         );
1474
1475      if (defined $result and $result->{$search_vlan_name} ne 'noSuchInstance') {
1476         my $vlan_name = $result->{$search_vlan_name} || 'empty';
1477         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
1478         }
1479      else {
1480         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
1481         }
1482
1483      $session->close;
1484      }
1485   return;
1486   }
1487
1488sub cmd_ip_location {
1489   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1490
1491   LOOP_ON_IP_ADDRESS:
1492   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1493
1494      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1495
1496      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1497      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
1498
1499      my $sw_location = q{};
1500      LOOP_ON_ALL_SWITCH:
1501      for my $sw (@SWITCH) {
1502         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{hostname};
1503         $sw_location = $sw->{location};
1504         last;
1505         }
1506
1507      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1508      }
1509   return;
1510   }
1511
1512sub cmd_ip_free {
1513   @ARGV = @_; # VLAN name with option
1514
1515   my $days_to_dead = 365 * 2;
1516   my $format = 'txt';
1517   my $verbose;
1518
1519   my $ret = GetOptions(
1520      'day|d=i'      => \$days_to_dead,
1521      'format|f=s'   => \$format,
1522      'verbose|v'    => \$verbose,
1523      );
1524
1525   my %possible_format = (
1526      txt  => \&cmd_ip_free_txt,
1527      html => \&cmd_ip_free_html,
1528      none => sub {},
1529      );
1530   $format = 'txt' if not defined $possible_format{$format};
1531
1532   my @vlan_name = @ARGV;
1533   @vlan_name = get_list_network() if not @vlan_name;
1534
1535   my $computerdb = {};
1536      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
1537   my $timestamp = time;
1538
1539   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_dead );
1540
1541   my %result_ip = ();
1542
1543   ALL_NETWORK:
1544   for my $vlan (@vlan_name) {
1545
1546      my @ip_list = get_list_ip($vlan);
1547
1548      LOOP_ON_IP_ADDRESS:
1549      for my $ip (@ip_list) {
1550
1551         next LOOP_ON_IP_ADDRESS if
1552            exists $computerdb->{$ip}
1553            && $computerdb->{$ip}{timestamp} > $timestamp_barrier;
1554
1555         my $ip_date_last_detection = '';
1556         if (exists $computerdb->{$ip}) {
1557            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1558            $year += 1900;
1559            $mon++;
1560            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1561            }
1562
1563         $result_ip{$ip} ||= {};
1564         $result_ip{$ip}->{date_last_detection} = $ip_date_last_detection;
1565
1566         my $packed_ip = scalar gethostbyname($ip);
1567         my $hostname_fq = 'unknown';
1568            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip;
1569         $result_ip{$ip}->{hostname_fq} = $hostname_fq;
1570
1571         $result_ip{$ip}->{vlan} = $vlan;
1572
1573         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
1574         }
1575      }
1576
1577   $possible_format{$format}->(%result_ip);
1578   }
1579
1580sub cmd_ip_free_txt {
1581   my %result_ip = @_;
1582   
1583   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
1584   print "-------------------------------------------------------------------------------\n";
1585   LOOP_ON_IP_ADDRESS:
1586   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1587         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{hostname_fq}, $result_ip{$ip}->{date_last_detection}, $result_ip{$ip}->{vlan};
1588      }
1589   }
1590
1591sub cmd_ip_free_html {
1592   my %result_ip = @_;
1593
1594   print <<'END_HTML';
1595<table class="sortable" summary="Klask Free IP Database">
1596 <caption>Klask Free IP Database</caption>
1597 <thead>
1598  <tr>
1599   <th scope="col" class="klask-header-left">IPv4-Address</th>
1600   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1601   <th scope="col" class="sorttable_alpha">VLAN</th>
1602   <th scope="col" class="klask-header-right">Date</th>
1603  </tr>
1604 </thead>
1605 <tfoot>
1606  <tr>
1607   <th scope="col" class="klask-footer-left">IPv4-Address</th>
1608   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1609   <th scope="col" class="fklask-vlan">VLAN</th>
1610   <th scope="col" class="klask-footer-right">Date</th>
1611  </tr>
1612 </tfoot>
1613 <tbody>
1614END_HTML
1615
1616   my $typerow = 'even';
1617
1618   LOOP_ON_IP_ADDRESS:
1619   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1620
1621      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1622
1623      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1624      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{hostname_fq};
1625
1626      print <<"END_HTML";
1627  <tr class="$typerow">
1628   <td sorttable_customkey="$ip_sort">$ip</td>
1629   <td sorttable_customkey="$host_short">$result_ip{$ip}->{hostname_fq}</td>
1630   <td>$result_ip{$ip}->{vlan}</td>
1631   <td>$result_ip{$ip}->{date_last_detection}</td>
1632  </tr>
1633END_HTML
1634      }
1635   print <<'END_HTML';
1636 </tbody>
1637</table>
1638END_HTML
1639   }
1640
1641sub cmd_enable {
1642   my $switch = shift;
1643   my $port   = shift;
1644
1645   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
1646   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
1647   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
1648   return;
1649   }
1650
1651sub cmd_disable {
1652   my $switch = shift;
1653   my $port   = shift;
1654
1655   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
1656   return;
1657   }
1658
1659sub cmd_status {
1660   my $switch = shift;
1661   my $port   = shift;
1662
1663   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
1664   return;
1665   }
1666
1667sub cmd_search_mac_on_switch {
1668   my $switch_name = shift || q{};
1669   my $mac_address = shift || q{};
1670
1671   if ($switch_name eq q{} or $mac_address eq q{}) {
1672      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1673      }
1674
1675   $switch_name = join(',', map {$_->{hostname}} @SWITCH ) if $switch_name eq q{*};
1676
1677   for my $sw_name (split /,/, $switch_name) {
1678      if (not defined $SWITCH_DB{$sw_name}) {
1679         die "Switch $sw_name must be defined in klask configuration file\n";
1680         }
1681
1682      my $sw = $SWITCH_DB{$sw_name};
1683      my %session = ( -hostname => $sw->{hostname} );
1684         $session{-version} = $sw->{version}   || 1;
1685         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1686      if (exists $sw->{version} and $sw->{version} eq '3') {
1687         $session{-username} = $sw->{username} || 'snmpadmin';
1688         }
1689      else {
1690         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1691         }
1692
1693      my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
1694      my $research2 = $OID_NUMBER{searchPort2} . mac_address_hex_to_dec($mac_address);
1695      print "Klask search OID $research1 on switch $sw_name\n";
1696      print "Klask search OID $research2 on switch $sw_name\n";
1697
1698      my ($session, $error) = Net::SNMP->session( %session );
1699      print "$error \n" if $error;
1700
1701      my $result = $session->get_request(
1702         -varbindlist => [$research1]
1703         );
1704      if (not defined $result) {
1705         $result = $session->get_request(
1706            -varbindlist => [$research2]
1707            );
1708         $result->{$research1} = $result->{$research2} if defined $result;
1709         }
1710
1711      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1712         my $swport = $result->{$research1};
1713         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1714         }
1715      else {
1716         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1717         }
1718
1719      $session->close;
1720      }
1721   return;
1722   }
1723
1724sub cmd_updatesw {
1725   @ARGV = @_;
1726
1727   my $verbose;
1728
1729   my $ret = GetOptions(
1730      'verbose|v' => \$verbose,
1731      );
1732
1733   init_switch_names('yes');    #nomme les switchs
1734   print "\n";
1735
1736   my %where = ();
1737   my %db_switch_output_port = ();
1738   my %db_switch_ip_hostnamefq = ();
1739
1740   DETECT_ALL_ROUTER:
1741#   for my $one_computer ('194.254.66.254') {
1742   for my $one_router ( get_list_main_router(get_list_network()) ) {
1743      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1744
1745      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1746      print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
1747
1748      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1749      }
1750
1751   ALL_ROUTER_IP_ADDRESS:
1752   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1753
1754      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
1755
1756      ALL_SWITCH_CONNECTED:
1757      for my $switch_detected ( keys %{$where{$ip_router}} ) {
1758
1759         my $switch = $where{$ip_router}->{$switch_detected};
1760
1761         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
1762
1763         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1764         print "VERBOSE_2: output port $switch->{hostname} : $switch->{port}\n" if $verbose;
1765         }
1766      }
1767
1768   my %db_switch_link_with = ();
1769
1770   my @list_all_switch = ();
1771   my @list_switch_ipv4 = ();
1772   for my $sw (@SWITCH){
1773      push @list_all_switch, $sw->{hostname};
1774      }
1775
1776   my $timestamp = time;
1777
1778   ALL_SWITCH:
1779   for my $one_computer (@list_all_switch) {
1780      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1781      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1782
1783      push @list_switch_ipv4, $resol_arp{ipv4_address};
1784
1785      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
1786
1787      if ($verbose) {
1788         print "VERBOSE_3: $one_computer $resol_arp{ipv4_address} $resol_arp{mac_address}\n";
1789         print "VERBOSE_3: $one_computer --- ",
1790            join(' + ', keys %{$where{$resol_arp{ipv4_address}}}),
1791            "\n";
1792         }
1793
1794      $db_switch_ip_hostnamefq{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
1795      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{ipv4_address} -> $resol_arp{hostname_fq}\n" if $verbose;
1796
1797      $SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
1798      $SWITCH_DB{$one_computer}->{mac_address}  = $resol_arp{mac_address};
1799      $SWITCH_DB{$one_computer}->{timestamp}    = $timestamp;
1800      }
1801
1802   ALL_SWITCH_IP_ADDRESS:
1803   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1804
1805      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
1806
1807      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1808#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
1809
1810      DETECTED_SWITCH:
1811      for my $switch_detected ( keys %{$where{$ip}} ) {
1812
1813         my $switch = $where{$ip}->{$switch_detected};
1814         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{hostname} : $switch->{port}\n" if $verbose;
1815
1816         next if $switch->{port}     eq '0';
1817         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
1818         next if $switch->{hostname} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{hostname};
1819
1820         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
1821         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{hostname} } = $switch->{port};
1822         print "VERBOSE_7: +++++\n" if $verbose;
1823         }
1824
1825      }
1826
1827   my %db_switch_connected_on_port = ();
1828   my $maybe_more_than_one_switch_connected = 'yes';
1829
1830   while ($maybe_more_than_one_switch_connected eq 'yes') {
1831      for my $sw (keys %db_switch_link_with) {
1832         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1833
1834            my $port = $db_switch_link_with{$sw}->{$connect};
1835
1836            $db_switch_connected_on_port{"$connect:$port"} ||= {};
1837            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
1838            }
1839         }
1840
1841      $maybe_more_than_one_switch_connected  = 'no';
1842
1843      SWITCH_AND_PORT:
1844      for my $swport (keys %db_switch_connected_on_port) {
1845
1846         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1847
1848         $maybe_more_than_one_switch_connected = 'yes';
1849
1850         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1851         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1852
1853         CONNECTED:
1854         for my $sw_connected (@sw_on_same_port) {
1855
1856            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1857
1858            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1859
1860            for my $other_sw (@sw_on_same_port) {
1861               next if $other_sw eq $sw_connected;
1862
1863               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1864               }
1865
1866            # We can not do better for this switch for this loop
1867            next SWITCH_AND_PORT;
1868            }
1869         }
1870      }
1871
1872   my %db_switch_parent =();
1873
1874   for my $sw (keys %db_switch_link_with) {
1875      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1876
1877         my $port = $db_switch_link_with{$sw}->{$connect};
1878
1879         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1880         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1881
1882         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1883         }
1884      }
1885
1886   print "Switch output port and parent port connection\n";
1887   print "---------------------------------------------\n";
1888   for my $sw (sort keys %db_switch_output_port) {
1889      if (exists $db_switch_parent{$sw}) {
1890         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1891         }
1892      else {
1893         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1894         }
1895      }
1896   print "\n";
1897
1898   print "Switch parent and children port inter-connection\n";
1899   print "------------------------------------------------\n";
1900   for my $swport (sort keys %db_switch_connected_on_port) {
1901      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1902      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1903         if (exists $db_switch_output_port{$sw}) {
1904            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1905            }
1906         else {
1907            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1908            }
1909         }
1910      }
1911
1912   my $switch_connection = {
1913      output_port       => \%db_switch_output_port,
1914      parent            => \%db_switch_parent,
1915      connected_on_port => \%db_switch_connected_on_port,
1916      link_with         => \%db_switch_link_with,
1917      switch_db         => \%SWITCH_DB,
1918      };
1919
1920   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1921   return;
1922   }
1923
1924sub cmd_exportsw {
1925   @ARGV = @_;
1926
1927   test_switchdb_environnement();
1928
1929   my $format = 'txt';
1930
1931   my $ret = GetOptions(
1932      'format|f=s'  => \$format,
1933      );
1934
1935   my %possible_format = (
1936      txt => \&cmd_exportsw_txt,
1937      dot => \&cmd_exportsw_dot,
1938      );
1939
1940   $format = 'txt' if not defined $possible_format{$format};
1941
1942   $possible_format{$format}->(@ARGV);
1943   return;
1944   }
1945
1946sub cmd_exportsw_txt {
1947
1948   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1949
1950   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1951   my %db_switch_parent            = %{$switch_connection->{parent}};
1952   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1953
1954   print "Switch output port and parent port connection\n";
1955   print "---------------------------------------------\n";
1956   for my $sw (sort keys %db_switch_output_port) {
1957      if (exists $db_switch_parent{$sw}) {
1958         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1959         }
1960      else {
1961         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1962         }
1963      }
1964   print "\n";
1965
1966   print "Switch parent and children port inter-connection\n";
1967   print "------------------------------------------------\n";
1968   for my $swport (sort keys %db_switch_connected_on_port) {
1969      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1970      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1971         if (exists $db_switch_output_port{$sw}) {
1972            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1973            }
1974         else {
1975            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1976            }
1977         }
1978      }
1979   return;
1980   }
1981
1982sub cmd_exportsw_dot {
1983
1984   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1985
1986   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1987   my %db_switch_parent            = %{$switch_connection->{parent}};
1988   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1989   my %db_switch_link_with         = %{$switch_connection->{link_with}};
1990   my %db_switch_global            = %{$switch_connection->{switch_db}};
1991
1992   my %db_building= ();
1993   for my $sw (@SWITCH) {
1994      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
1995      $db_building{$building} ||= {};
1996      $db_building{$building}->{$location} ||= {};
1997      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
1998      }
1999
2000
2001   print "digraph G {\n";
2002   print "rankdir = LR;\n";
2003
2004   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2005   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
2006
2007   my $b=0;
2008   for my $building (keys %db_building) {
2009      $b++;
2010
2011      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2012      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
2013
2014      my $l = 0;
2015      for my $loc (keys %{$db_building{$building}}) {
2016         $l++;
2017
2018         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
2019#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
2020         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
2021
2022         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2023
2024            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
2025
2026            my $swname  = $sw;
2027               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2028            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
2029            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2030            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
2031
2032
2033            for my $swport (keys %db_switch_connected_on_port) {
2034               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2035               next if not $sw_connect eq $sw;
2036               next if $port_connect eq $db_switch_output_port{$sw};
2037               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
2038               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
2039              }
2040            }
2041         }
2042      }
2043
2044#   print "Switch output port and parent port connection\n";
2045#   print "---------------------------------------------\n";
2046   for my $sw (sort keys %db_switch_output_port) {
2047      if (exists $db_switch_parent{$sw}) {
2048#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
2049         }
2050      else {
2051         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
2052         }
2053      }
2054   print "\n";
2055
2056#   print "Switch parent and children port inter-connection\n";
2057#   print "------------------------------------------------\n";
2058   for my $swport (sort keys %db_switch_connected_on_port) {
2059      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2060      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2061         if (exists $db_switch_output_port{$sw}) {
2062            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2063            }
2064         else {
2065            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
2066            }
2067         }
2068      }
2069
2070print "}\n";
2071   return;
2072   }
2073
2074
2075__END__
2076
2077=head1 NAME
2078
2079klask - ports manager and finder for switch
2080
2081
2082=head1 USAGE
2083
2084 klask updatedb
2085 klask exportdb --format [txt|html]
2086 klask removedb computer*
2087 klask cleandb  --day number_of_day --verbose
2088
2089 klask updatesw
2090 klask exportsw --format [txt|dot]
2091
2092 klask searchdb computer
2093 klask search   computer
2094 klask search-mac-on-switch switch mac_addr
2095
2096 klask ip-free --day number_of_day --format [txt|html] [vlan_name]
2097
2098 klask enable  switch port
2099 klask disable swith port
2100 klask status  swith port
2101
2102
2103=head1 DESCRIPTION
2104
2105klask is a small tool to find where is a host in a big network. klask mean search in brittany.
2106
2107Klask has now a web site dedicated for it !
2108
2109 http://servforge.legi.grenoble-inp.fr/projects/klask
2110
2111
2112=head1 COMMANDS
2113
2114
2115=head2 search
2116
2117This 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.
2118
2119
2120=head2 enable
2121
2122This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
2123
2124
2125=head2 disable
2126
2127This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
2128
2129
2130=head2 status
2131
2132This 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.
2133
2134
2135=head2 updatedb
2136
2137This 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.
2138
2139
2140=head2 exportdb
2141
2142This 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...
2143
2144
2145=head2 updatesw
2146
2147This 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.
2148
2149
2150=head2 exportsw --format [txt|dot]
2151
2152This 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.
2153
2154 klask exportsw --format dot > /tmp/map.dot
2155 dot -Tpng /tmp/map.dot > /tmp/map.png
2156
2157
2158
2159=head1 CONFIGURATION
2160
2161Because 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 !
2162
2163Here an example, be aware with indent, it's important in YAML, do not use tabulation !
2164
2165 default:
2166   community: public
2167   snmpport: 161
2168
2169 network:
2170   labnet:
2171     ip-subnet:
2172       - add: 192.168.1.0/24
2173       - add: 192.168.2.0/24
2174     interface: eth0
2175     main-router: gw1.labnet.local
2176
2177   schoolnet:
2178     ip-subnet:
2179       - add: 192.168.6.0/24
2180       - add: 192.168.7.0/24
2181     interface: eth0.38
2182     main-router: gw2.schoolnet.local
2183
2184 switch:
2185   - hostname: sw1.klask.local
2186     portignore:
2187       - 1
2188       - 2
2189
2190   - hostname: sw2.klask.local
2191     location: BatK / 2 / K203
2192     type: HP2424
2193     portignore:
2194       - 1
2195       - 2
2196
2197I 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.
2198
2199
2200=head1 FILES
2201
2202 /etc/klask/klask.conf
2203 /var/lib/klask/klaskdb
2204 /var/lib/klask/switchdb
2205
2206=head1 SEE ALSO
2207
2208Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
2209
2210
2211=head1 VERSION
2212
2213$Id: klask 134 2013-11-01 19:19:32Z g7moreau $
2214
2215
2216=head1 AUTHOR
2217
2218Written by Gabriel Moreau, Grenoble - France
2219
2220
2221=head1 LICENSE AND COPYRIGHT
2222
2223GPL version 2 or later and Perl equivalent
2224
2225Copyright (C) 2005-2013 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.