source: trunk/klask @ 194

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