source: trunk/klask @ 138

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