source: trunk/klask @ 186

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