source: trunk/klask @ 136

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