source: trunk/klask @ 184

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