source: trunk/klask @ 135

Last change on this file since 135 was 135, checked in by g7moreau, 11 years ago
  • More severe criteria for ip-free
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 74.4 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2013 Gabriel Moreau.
4#
5# $Id: klask 135 2013-11-05 10:07:36Z 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: 135 $'."\n";
668   print ' $Date: 2013-11-05 10:07:36 +0000 (Tue, 05 Nov 2013) $'."\n";
669   print ' $Id: klask 135 2013-11-05 10:07:36Z 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         if (exists $computerdb->{$ip}) {
1552            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{timestamp} > $timestamp_barrier;
1553           
1554            my $mac_address = $computerdb->{$ip}{mac_address};
1555            LOOP_ON_DATABASE:
1556            foreach my $ip_db (keys %{$computerdb}) {
1557               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{mac_address} ne $mac_address;
1558               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{timestamp} > $timestamp_barrier;
1559               }
1560            }
1561
1562         my $ip_date_last_detection = '';
1563         if (exists $computerdb->{$ip}) {
1564            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1565            $year += 1900;
1566            $mon++;
1567            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1568            }
1569
1570         my $packed_ip = scalar gethostbyname($ip);
1571         my $hostname_fq = 'unknown';
1572            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip;
1573
1574         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1575
1576         $result_ip{$ip} ||= {};
1577         $result_ip{$ip}->{date_last_detection} = $ip_date_last_detection;
1578         $result_ip{$ip}->{hostname_fq} = $hostname_fq;
1579         $result_ip{$ip}->{vlan} = $vlan;
1580
1581         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
1582         }
1583      }
1584
1585   $possible_format{$format}->(%result_ip);
1586   }
1587
1588sub cmd_ip_free_txt {
1589   my %result_ip = @_;
1590   
1591   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
1592   print "-------------------------------------------------------------------------------\n";
1593   LOOP_ON_IP_ADDRESS:
1594   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1595         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{hostname_fq}, $result_ip{$ip}->{date_last_detection}, $result_ip{$ip}->{vlan};
1596      }
1597   }
1598
1599sub cmd_ip_free_html {
1600   my %result_ip = @_;
1601
1602   print <<'END_HTML';
1603<table class="sortable" summary="Klask Free IP Database">
1604 <caption>Klask Free IP Database</caption>
1605 <thead>
1606  <tr>
1607   <th scope="col" class="klask-header-left">IPv4-Address</th>
1608   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1609   <th scope="col" class="sorttable_alpha">VLAN</th>
1610   <th scope="col" class="klask-header-right">Date</th>
1611  </tr>
1612 </thead>
1613 <tfoot>
1614  <tr>
1615   <th scope="col" class="klask-footer-left">IPv4-Address</th>
1616   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1617   <th scope="col" class="fklask-vlan">VLAN</th>
1618   <th scope="col" class="klask-footer-right">Date</th>
1619  </tr>
1620 </tfoot>
1621 <tbody>
1622END_HTML
1623
1624   my $typerow = 'even';
1625
1626   LOOP_ON_IP_ADDRESS:
1627   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1628
1629      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1630
1631      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1632      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{hostname_fq};
1633
1634      print <<"END_HTML";
1635  <tr class="$typerow">
1636   <td sorttable_customkey="$ip_sort">$ip</td>
1637   <td sorttable_customkey="$host_short">$result_ip{$ip}->{hostname_fq}</td>
1638   <td>$result_ip{$ip}->{vlan}</td>
1639   <td>$result_ip{$ip}->{date_last_detection}</td>
1640  </tr>
1641END_HTML
1642      }
1643   print <<'END_HTML';
1644 </tbody>
1645</table>
1646END_HTML
1647   }
1648
1649sub cmd_enable {
1650   my $switch = shift;
1651   my $port   = shift;
1652
1653   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
1654   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
1655   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
1656   return;
1657   }
1658
1659sub cmd_disable {
1660   my $switch = shift;
1661   my $port   = shift;
1662
1663   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
1664   return;
1665   }
1666
1667sub cmd_status {
1668   my $switch = shift;
1669   my $port   = shift;
1670
1671   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
1672   return;
1673   }
1674
1675sub cmd_search_mac_on_switch {
1676   my $switch_name = shift || q{};
1677   my $mac_address = shift || q{};
1678
1679   if ($switch_name eq q{} or $mac_address eq q{}) {
1680      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1681      }
1682
1683   $switch_name = join(',', map {$_->{hostname}} @SWITCH ) if $switch_name eq q{*};
1684
1685   for my $sw_name (split /,/, $switch_name) {
1686      if (not defined $SWITCH_DB{$sw_name}) {
1687         die "Switch $sw_name must be defined in klask configuration file\n";
1688         }
1689
1690      my $sw = $SWITCH_DB{$sw_name};
1691      my %session = ( -hostname => $sw->{hostname} );
1692         $session{-version} = $sw->{version}   || 1;
1693         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1694      if (exists $sw->{version} and $sw->{version} eq '3') {
1695         $session{-username} = $sw->{username} || 'snmpadmin';
1696         }
1697      else {
1698         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1699         }
1700
1701      my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
1702      my $research2 = $OID_NUMBER{searchPort2} . mac_address_hex_to_dec($mac_address);
1703      print "Klask search OID $research1 on switch $sw_name\n";
1704      print "Klask search OID $research2 on switch $sw_name\n";
1705
1706      my ($session, $error) = Net::SNMP->session( %session );
1707      print "$error \n" if $error;
1708
1709      my $result = $session->get_request(
1710         -varbindlist => [$research1]
1711         );
1712      if (not defined $result) {
1713         $result = $session->get_request(
1714            -varbindlist => [$research2]
1715            );
1716         $result->{$research1} = $result->{$research2} if defined $result;
1717         }
1718
1719      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1720         my $swport = $result->{$research1};
1721         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1722         }
1723      else {
1724         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1725         }
1726
1727      $session->close;
1728      }
1729   return;
1730   }
1731
1732sub cmd_updatesw {
1733   @ARGV = @_;
1734
1735   my $verbose;
1736
1737   my $ret = GetOptions(
1738      'verbose|v' => \$verbose,
1739      );
1740
1741   init_switch_names('yes');    #nomme les switchs
1742   print "\n";
1743
1744   my %where = ();
1745   my %db_switch_output_port = ();
1746   my %db_switch_ip_hostnamefq = ();
1747
1748   DETECT_ALL_ROUTER:
1749#   for my $one_computer ('194.254.66.254') {
1750   for my $one_router ( get_list_main_router(get_list_network()) ) {
1751      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1752
1753      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1754      print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
1755
1756      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1757      }
1758
1759   ALL_ROUTER_IP_ADDRESS:
1760   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1761
1762      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
1763
1764      ALL_SWITCH_CONNECTED:
1765      for my $switch_detected ( keys %{$where{$ip_router}} ) {
1766
1767         my $switch = $where{$ip_router}->{$switch_detected};
1768
1769         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
1770
1771         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1772         print "VERBOSE_2: output port $switch->{hostname} : $switch->{port}\n" if $verbose;
1773         }
1774      }
1775
1776   my %db_switch_link_with = ();
1777
1778   my @list_all_switch = ();
1779   my @list_switch_ipv4 = ();
1780   for my $sw (@SWITCH){
1781      push @list_all_switch, $sw->{hostname};
1782      }
1783
1784   my $timestamp = time;
1785
1786   ALL_SWITCH:
1787   for my $one_computer (@list_all_switch) {
1788      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1789      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1790
1791      push @list_switch_ipv4, $resol_arp{ipv4_address};
1792
1793      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
1794
1795      if ($verbose) {
1796         print "VERBOSE_3: $one_computer $resol_arp{ipv4_address} $resol_arp{mac_address}\n";
1797         print "VERBOSE_3: $one_computer --- ",
1798            join(' + ', keys %{$where{$resol_arp{ipv4_address}}}),
1799            "\n";
1800         }
1801
1802      $db_switch_ip_hostnamefq{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
1803      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{ipv4_address} -> $resol_arp{hostname_fq}\n" if $verbose;
1804
1805      $SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
1806      $SWITCH_DB{$one_computer}->{mac_address}  = $resol_arp{mac_address};
1807      $SWITCH_DB{$one_computer}->{timestamp}    = $timestamp;
1808      }
1809
1810   ALL_SWITCH_IP_ADDRESS:
1811   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1812
1813      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
1814
1815      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1816#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
1817
1818      DETECTED_SWITCH:
1819      for my $switch_detected ( keys %{$where{$ip}} ) {
1820
1821         my $switch = $where{$ip}->{$switch_detected};
1822         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{hostname} : $switch->{port}\n" if $verbose;
1823
1824         next if $switch->{port}     eq '0';
1825         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
1826         next if $switch->{hostname} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{hostname};
1827
1828         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
1829         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{hostname} } = $switch->{port};
1830         print "VERBOSE_7: +++++\n" if $verbose;
1831         }
1832
1833      }
1834
1835   my %db_switch_connected_on_port = ();
1836   my $maybe_more_than_one_switch_connected = 'yes';
1837
1838   while ($maybe_more_than_one_switch_connected eq 'yes') {
1839      for my $sw (keys %db_switch_link_with) {
1840         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1841
1842            my $port = $db_switch_link_with{$sw}->{$connect};
1843
1844            $db_switch_connected_on_port{"$connect:$port"} ||= {};
1845            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
1846            }
1847         }
1848
1849      $maybe_more_than_one_switch_connected  = 'no';
1850
1851      SWITCH_AND_PORT:
1852      for my $swport (keys %db_switch_connected_on_port) {
1853
1854         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1855
1856         $maybe_more_than_one_switch_connected = 'yes';
1857
1858         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1859         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1860
1861         CONNECTED:
1862         for my $sw_connected (@sw_on_same_port) {
1863
1864            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1865
1866            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1867
1868            for my $other_sw (@sw_on_same_port) {
1869               next if $other_sw eq $sw_connected;
1870
1871               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1872               }
1873
1874            # We can not do better for this switch for this loop
1875            next SWITCH_AND_PORT;
1876            }
1877         }
1878      }
1879
1880   my %db_switch_parent =();
1881
1882   for my $sw (keys %db_switch_link_with) {
1883      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1884
1885         my $port = $db_switch_link_with{$sw}->{$connect};
1886
1887         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1888         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1889
1890         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1891         }
1892      }
1893
1894   print "Switch output port and parent port connection\n";
1895   print "---------------------------------------------\n";
1896   for my $sw (sort keys %db_switch_output_port) {
1897      if (exists $db_switch_parent{$sw}) {
1898         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1899         }
1900      else {
1901         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1902         }
1903      }
1904   print "\n";
1905
1906   print "Switch parent and children port inter-connection\n";
1907   print "------------------------------------------------\n";
1908   for my $swport (sort keys %db_switch_connected_on_port) {
1909      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1910      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1911         if (exists $db_switch_output_port{$sw}) {
1912            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1913            }
1914         else {
1915            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1916            }
1917         }
1918      }
1919
1920   my $switch_connection = {
1921      output_port       => \%db_switch_output_port,
1922      parent            => \%db_switch_parent,
1923      connected_on_port => \%db_switch_connected_on_port,
1924      link_with         => \%db_switch_link_with,
1925      switch_db         => \%SWITCH_DB,
1926      };
1927
1928   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1929   return;
1930   }
1931
1932sub cmd_exportsw {
1933   @ARGV = @_;
1934
1935   test_switchdb_environnement();
1936
1937   my $format = 'txt';
1938
1939   my $ret = GetOptions(
1940      'format|f=s'  => \$format,
1941      );
1942
1943   my %possible_format = (
1944      txt => \&cmd_exportsw_txt,
1945      dot => \&cmd_exportsw_dot,
1946      );
1947
1948   $format = 'txt' if not defined $possible_format{$format};
1949
1950   $possible_format{$format}->(@ARGV);
1951   return;
1952   }
1953
1954sub cmd_exportsw_txt {
1955
1956   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1957
1958   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1959   my %db_switch_parent            = %{$switch_connection->{parent}};
1960   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1961
1962   print "Switch output port and parent port connection\n";
1963   print "---------------------------------------------\n";
1964   for my $sw (sort keys %db_switch_output_port) {
1965      if (exists $db_switch_parent{$sw}) {
1966         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1967         }
1968      else {
1969         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1970         }
1971      }
1972   print "\n";
1973
1974   print "Switch parent and children port inter-connection\n";
1975   print "------------------------------------------------\n";
1976   for my $swport (sort keys %db_switch_connected_on_port) {
1977      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1978      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1979         if (exists $db_switch_output_port{$sw}) {
1980            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1981            }
1982         else {
1983            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1984            }
1985         }
1986      }
1987   return;
1988   }
1989
1990sub cmd_exportsw_dot {
1991
1992   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1993
1994   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1995   my %db_switch_parent            = %{$switch_connection->{parent}};
1996   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1997   my %db_switch_link_with         = %{$switch_connection->{link_with}};
1998   my %db_switch_global            = %{$switch_connection->{switch_db}};
1999
2000   my %db_building= ();
2001   for my $sw (@SWITCH) {
2002      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
2003      $db_building{$building} ||= {};
2004      $db_building{$building}->{$location} ||= {};
2005      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
2006      }
2007
2008
2009   print "digraph G {\n";
2010   print "rankdir = LR;\n";
2011
2012   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2013   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
2014
2015   my $b=0;
2016   for my $building (keys %db_building) {
2017      $b++;
2018
2019      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2020      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
2021
2022      my $l = 0;
2023      for my $loc (keys %{$db_building{$building}}) {
2024         $l++;
2025
2026         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
2027#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
2028         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
2029
2030         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2031
2032            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
2033
2034            my $swname  = $sw;
2035               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2036            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
2037            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2038            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
2039
2040
2041            for my $swport (keys %db_switch_connected_on_port) {
2042               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2043               next if not $sw_connect eq $sw;
2044               next if $port_connect eq $db_switch_output_port{$sw};
2045               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
2046               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
2047              }
2048            }
2049         }
2050      }
2051
2052#   print "Switch output port and parent port connection\n";
2053#   print "---------------------------------------------\n";
2054   for my $sw (sort keys %db_switch_output_port) {
2055      if (exists $db_switch_parent{$sw}) {
2056#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
2057         }
2058      else {
2059         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
2060         }
2061      }
2062   print "\n";
2063
2064#   print "Switch parent and children port inter-connection\n";
2065#   print "------------------------------------------------\n";
2066   for my $swport (sort keys %db_switch_connected_on_port) {
2067      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2068      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2069         if (exists $db_switch_output_port{$sw}) {
2070            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2071            }
2072         else {
2073            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
2074            }
2075         }
2076      }
2077
2078print "}\n";
2079   return;
2080   }
2081
2082
2083__END__
2084
2085=head1 NAME
2086
2087klask - ports manager and finder for switch
2088
2089
2090=head1 USAGE
2091
2092 klask updatedb
2093 klask exportdb --format [txt|html]
2094 klask removedb computer*
2095 klask cleandb  --day number_of_day --verbose
2096
2097 klask updatesw
2098 klask exportsw --format [txt|dot]
2099
2100 klask searchdb computer
2101 klask search   computer
2102 klask search-mac-on-switch switch mac_addr
2103
2104 klask ip-free --day number_of_day --format [txt|html] [vlan_name]
2105
2106 klask enable  switch port
2107 klask disable swith port
2108 klask status  swith port
2109
2110
2111=head1 DESCRIPTION
2112
2113klask is a small tool to find where is a host in a big network. klask mean search in brittany.
2114
2115Klask has now a web site dedicated for it !
2116
2117 http://servforge.legi.grenoble-inp.fr/projects/klask
2118
2119
2120=head1 COMMANDS
2121
2122
2123=head2 search
2124
2125This 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.
2126
2127
2128=head2 enable
2129
2130This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
2131
2132
2133=head2 disable
2134
2135This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
2136
2137
2138=head2 status
2139
2140This 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.
2141
2142
2143=head2 updatedb
2144
2145This 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.
2146
2147
2148=head2 exportdb
2149
2150This 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...
2151
2152
2153=head2 updatesw
2154
2155This 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.
2156
2157
2158=head2 exportsw --format [txt|dot]
2159
2160This 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.
2161
2162 klask exportsw --format dot > /tmp/map.dot
2163 dot -Tpng /tmp/map.dot > /tmp/map.png
2164
2165
2166
2167=head1 CONFIGURATION
2168
2169Because 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 !
2170
2171Here an example, be aware with indent, it's important in YAML, do not use tabulation !
2172
2173 default:
2174   community: public
2175   snmpport: 161
2176
2177 network:
2178   labnet:
2179     ip-subnet:
2180       - add: 192.168.1.0/24
2181       - add: 192.168.2.0/24
2182     interface: eth0
2183     main-router: gw1.labnet.local
2184
2185   schoolnet:
2186     ip-subnet:
2187       - add: 192.168.6.0/24
2188       - add: 192.168.7.0/24
2189     interface: eth0.38
2190     main-router: gw2.schoolnet.local
2191
2192 switch:
2193   - hostname: sw1.klask.local
2194     portignore:
2195       - 1
2196       - 2
2197
2198   - hostname: sw2.klask.local
2199     location: BatK / 2 / K203
2200     type: HP2424
2201     portignore:
2202       - 1
2203       - 2
2204
2205I 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.
2206
2207
2208=head1 FILES
2209
2210 /etc/klask/klask.conf
2211 /var/lib/klask/klaskdb
2212 /var/lib/klask/switchdb
2213
2214=head1 SEE ALSO
2215
2216Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
2217
2218
2219=head1 VERSION
2220
2221$Id: klask 135 2013-11-05 10:07:36Z g7moreau $
2222
2223
2224=head1 AUTHOR
2225
2226Written by Gabriel Moreau, Grenoble - France
2227
2228
2229=head1 LICENSE AND COPYRIGHT
2230
2231GPL version 2 or later and Perl equivalent
2232
2233Copyright (C) 2005-2013 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.