source: trunk/klask @ 302

Last change on this file since 302 was 302, checked in by g7moreau, 7 years ago
  • Forget a }
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 125.8 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
4#
5# $Id: klask 302 2017-10-04 18:48:28Z g7moreau $
6
7use strict;
8use warnings;
9use version; our $VERSION = qv('0.7.0');
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';
22use Digest::SHA qw(sha512_base64);
23
24# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
25# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
26# arping net-tools fping bind9-host arpwatch
27
28################################################################
29# general initialization
30################################################################
31
32my $KLASK_VAR      = '/var/lib/klask';
33my $KLASK_CFG_FILE = '/etc/klask/klask.conf';
34my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
35my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
36
37test_running_environnement();
38
39my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
40
41my %DEFAULT     = %{$KLASK_CFG->{'default'}};
42my @SWITCH_LIST = @{$KLASK_CFG->{'switch'}};
43
44my %SWITCH_LEVEL = ();
45my %SWITCH_DB    = ();
46LEVEL_OF_EACH_SWITCH:
47for my $sw (@SWITCH_LIST) {
48   $SWITCH_LEVEL{$sw->{'hostname'}} = $sw->{'level'} || $DEFAULT{'switch_level'}  || 2;
49   $SWITCH_DB{$sw->{'hostname'}} = $sw;
50
51   # SNMP parameter initialisation
52   my %session = ( -hostname   => $sw->{'hostname'} );
53   $session{-version} = $sw->{'version'}  || 1;
54   $session{-port}    = $sw->{'snmpport'} || $DEFAULT{'snmpport'}  || 161;
55   if (exists $sw->{'version'} and $sw->{'version'} eq '3') {
56      $session{-username} = $sw->{'username'} || 'snmpadmin';
57      }
58   else {
59      $session{-community} = $sw->{'community'} || $DEFAULT{'community'} || 'public';
60      }
61   $sw->{'snmp_param_session'} = \%session;
62   }
63@SWITCH_LIST = reverse sort { $SWITCH_LEVEL{$a->{'hostname'}} <=> $SWITCH_LEVEL{$b->{'hostname'}} } @{$KLASK_CFG->{'switch'}};
64
65#my %SWITCH_PORT_COUNT = ();
66
67my %CMD_DB = (
68   'help'                 => \&cmd_help,
69   'version'              => \&cmd_version,
70   'exportdb'             => \&cmd_exportdb,
71   'updatedb'             => \&cmd_updatedb,
72   'searchdb'             => \&cmd_searchdb,
73   'removedb'             => \&cmd_removedb,
74   'cleandb'              => \&cmd_cleandb,
75   'search'               => \&cmd_search,
76   'enable'               => \&cmd_enable,
77   'disable'              => \&cmd_disable,
78   'status'               => \&cmd_status,
79   'updatesw'             => \&cmd_updatesw,
80   'exportsw'             => \&cmd_exportsw,
81   'iplocation'           => \&cmd_ip_location,
82   'ip-free'              => \&cmd_ip_free,
83   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
84   'bad-vlan-id'          => \&cmd_bad_vlan_id,
85   'poe-enable'           => \&cmd_poe_enable,
86   'poe-disable'          => \&cmd_poe_disable,
87   'poe-status'           => \&cmd_poe_status,
88   'port-setvlan'         => \&cmd_port_setvlan,
89   'port-getvlan'         => \&cmd_port_getvlan,
90   'vlan-setname'         => \&cmd_vlan_setname,
91   'vlan-getname'         => \&cmd_vlan_getname,
92   'vlan-list'            => \&cmd_vlan_list,
93   'rebootsw'             => \&cmd_rebootsw,
94   );
95
96#Readonly my %INTERNAL_PORT_MAP => (
97#   0 => 'A',
98#   1 => 'B',
99#   2 => 'C',
100#   3 => 'D',
101#   4 => 'E',
102#   5 => 'F',
103#   6 => 'G',
104#   7 => 'H',
105#   );
106#Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
107
108Readonly my %SWITCH_KIND => (
109   # HP
110   J3299A           => { type => 1, model => 'HP224M',         match => 'HP J3299A ProCurve Switch 224M'       },
111   J4120A           => { type => 1, model => 'HP1600M',        match => 'HP J4120A ProCurve Switch 1600M'      },
112   J9029A           => { type => 1, model => 'HP1800-8G',      match => 'PROCURVE J9029A'                      },
113   J9449A           => { type => 1, model => 'HP1810-8G',      match => 'HP ProCurve 1810G - 8 GE'             },
114   J4093A           => { type => 1, model => 'HP2424M',        match => 'HP J4093A ProCurve Switch 2424M'      },
115   J9279A           => { type => 1, model => 'HP2510G-24',     match => 'ProCurve J9279A Switch 2510G-24'      },
116   J9280A           => { type => 1, model => 'HP2510G-48',     match => 'ProCurve J9280A Switch 2510G-48'      },
117   J4813A           => { type => 1, model => 'HP2524',         match => 'HP J4813A ProCurve Switch 2524'       },
118   J4900A           => { type => 1, model => 'HP2626A',        match => 'HP J4900A ProCurve Switch 2626'       },
119   J4900B           => { type => 1, model => 'HP2626B',        match => 'J4900B.+?Switch 2626'                 }, # ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
120   J4899B           => { type => 1, model => 'HP2650',         match => 'ProCurve J4899B Switch 2650'          },
121   J9021A           => { type => 1, model => 'HP2810-24G',     match => 'ProCurve J9021A Switch 2810-24G'      },
122   J9022A           => { type => 1, model => 'HP2810-48G',     match => 'ProCurve J9022A Switch 2810-48G'      },
123   J8692A           => { type => 1, model => 'HP3500-24G',     match => 'J8692A Switch 3500yl-24G'             },
124   J4903A           => { type => 1, model => 'HP2824',         match => 'J4903A.+?Switch 2824,'                },
125   J4110A           => { type => 1, model => 'HP8000M',        match => 'HP J4110A ProCurve Switch 8000M'      },
126   JE074A           => { type => 2, model => 'HP5120-24G',     match => 'HP Comware.+?A5120-24G EI Switch'     },
127   JE069A           => { type => 2, model => 'HP5120-48G',     match => 'HP Comware.+?A5120-48G EI Switch'     },
128   JD377A           => { type => 2, model => 'HP5500-24G',     match => 'HP Comware.+?A5500-24G EI Switch'     },
129   JD374A           => { type => 2, model => 'HP5500-24F',     match => 'HP Comware.+?A5500-24G-SFP EI Switch' },
130   # BayStack
131   BS350T           => { type => 1, model => 'BS350T',         match => 'BayStack 350T HW'                     },
132   # Nexans
133   N3483G           => { type => 2, model => 'NA3483-6G',      match => 'GigaSwitch V3 TP SFP-I 48.+ ES3'      },
134   N3483P           => { type => 2, model => 'NA3483-6P',      match => 'GigaSwitch V3 TP.PSE.+ 48/54V ES3'    }, # GigaSwitch V3 TP(PSE+) SFP-I 48/54V ES3 (HW3/ENHANCED/SECURITY/V4.10C)
135   # DELL
136   PC7024           => { type => 2, model => 'DPC7024',        match => 'PowerConnect 7024,.+?VxWorks'         },
137   N2048            => { type => 2, model => 'DN2048',         match => 'Dell Networking N2048,'               },
138   N4032F           => { type => 2, model => 'DN4032F',        match => 'Dell Networking N4032F,'              },
139   N4064F           => { type => 2, model => 'DN4064F',        match => 'Dell Networking N4064F,'              },
140   # 3COM
141   'H3C5500'        => { type => 1, model => 'H3C5500',        match => 'H3C S5500-SI Series'                  },
142   '3C17203'        => { type => 1, model => '3C17203',        match => '3Com SuperStack 3 24-Port'            },
143   '3C17204'        => { type => 1, model => '3C17204',        match => '3Com SuperStack 3 48-Port'            },
144   '3CR17562-91'    => { type => 1, model => '3CR17562-91',    match => '3Com Switch 4500 50-Port'             },
145   '3CR17255-91'    => { type => 1, model => '3CR17255-91',    match => '3Com Switch 5500G-EI 48-Port'         },
146   '3CR17251-91'    => { type => 1, model => '3CR17251-91',    match => '3Com Switch 5500G-EI 48-Port'         },
147   '3CR17571-91'    => { type => 1, model => '3CR17571-91',    match => '3Com Switch 4500 PWR 26-Port'         },
148   '3CRWX220095A'   => { type => 1, model => '3CRWX220095A',   match => '3Com Wireless LAN Controller'         },
149   '3CR17254-91'    => { type => 1, model => '3CR17254-91',    match => '3Com Switch 5500G-EI 24-Port'         },
150   '3CRS48G-24S-91' => { type => 1, model => '3CRS48G-24S-91', match => '3Com Switch 4800G 24-Port'            },
151   '3CRS48G-48S-91' => { type => 1, model => '3CRS48G-48S-91', match => '3Com Switch 4800G 48-Port'            },
152   '3C17708'        => { type => 1, model => '3C17708',        match => '3Com Switch 4050'                     },
153   '3C17709'        => { type => 1, model => '3C17709',        match => '3Com Switch 4060'                     },
154   '3C17707'        => { type => 1, model => '3C17707',        match => '3Com Switch 4070'                     },
155   '3CR17258-91'    => { type => 1, model => '3CR17258-91',    match => '3Com Switch 5500G-EI 24-Port SFP'     },
156   '3CR17181-91'    => { type => 1, model => '3CR17181-91',    match => '3Com Switch 5500-EI 28-Port FX'       },
157   '3CR17252-91'    => { type => 1, model => '3CR17252-91',    match => '3Com Switch 5500G-EI PWR 24-Port'     },
158   '3CR17253-91'    => { type => 1, model => '3CR17253-91',    match => '3Com Switch 5500G-EI PWR 48-Port'     },
159   '3CR17250-91'    => { type => 1, model => '3CR17250-91',    match => '3Com Switch 5500G-EI 24-Port'         },
160   '3CR17561-91'    => { type => 1, model => '3CR17561-91',    match => '3Com Switch 4500 26-Port'             },
161   '3CR17572-91'    => { type => 1, model => '3CR17572-91',    match => '3Com Switch 4500 PWR 50-Port'         },
162   '3C17702-US'     => { type => 1, model => '3C17702-US',     match => '3Com Switch 4900 SX'                  },
163   '3C17700'        => { type => 1, model => '3C17700',        match => '3Com Switch 4900'                     },
164   );
165
166Readonly my %OID_NUMBER => (
167   sysDescription  => '1.3.6.1.2.1.1.1.0',
168   sysName         => '1.3.6.1.2.1.1.5.0',
169   sysContact      => '1.3.6.1.2.1.1.4.0',
170   sysLocation     => '1.3.6.1.2.1.1.6.0',
171   searchPort1     => '1.3.6.1.2.1.17.4.3.1.2',          # BRIDGE-MIB (802.1D).
172   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
173   vlanPortDefault => '1.3.6.1.2.1.17.7.1.4.5.1.1',      # dot1qPvid
174   vlanStatus      => '1.3.6.1.2.1.17.7.1.4.3.1.5',      # integer 4 Create, 6 Destroy
175   vlanName        => '1.3.6.1.2.1.17.7.1.4.3.1.1',      # string
176   HPicfReset      => '1.3.6.1.4.1.11.2.14.11.1.4.1',    # HP reboot switch
177   ifIndex         => '1.3.6.1.2.1.17.1.4.1.2',          # dot1dBasePortIfIndex - Interface index redirection
178   ifName          => '1.3.6.1.2.1.31.1.1.1.1',          # Interface name (give port number)
179   portUpDown      => '1.3.6.1.2.1.2.2.1.7',             # 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)  = 2 (down)
180   poeState        => '1.3.6.1.2.1.105.1.1.1.3.1',       # 1.3.6.1.2.1.105.1.1.1.3.1.NoPort = 1 (poe up)  = 2 (poe down) - Cisco and Zyxel
181   NApoeState      => '1.3.6.1.4.1.266.20.3.1.1.21',     # .NoPort = 2 (poe off)  = 8 (poe atHighPower) - Nexans
182   ifAggregator    => '1.2.840.10006.300.43.1.2.1.1.12', # dot3adAggPortSelectedAggID - 0 not part of an  Aggregator - Ciso Dell HP Comware -  See https://stackoverflow.com/questions/14960157/how-to-map-portchannel-to-interfaces-via-snmp https://gist.github.com/bldewolf/6314435
183   );
184
185Readonly my %PORT_UPDOWN => (
186   1 => 'enable',
187   2 => 'disable',
188   );
189
190Readonly 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;
191Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
192
193Readonly my $RE_FLOAT_HOSTNAME => $DEFAULT{'float-regex'} || qr{ ^float }xms;
194
195Readonly my $SEP_AGGREGATOR_PORT => ',';  # : is already use to join switch and port
196Readonly my $SEP_SWITCH_PORT     => ':';
197
198
199################################################################
200# main program
201################################################################
202
203my $cmd = shift @ARGV || 'help';
204if (defined $CMD_DB{$cmd}) {
205   $CMD_DB{$cmd}->(@ARGV);
206   }
207else {
208   print {*STDERR} "klask: command $cmd not found\n\n";
209   $CMD_DB{help}->();
210   exit 1;
211   }
212
213exit;
214
215################################################################
216# subroutine
217################################################################
218
219#---------------------------------------------------------------
220sub test_running_environnement {
221   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
222   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
223   return;
224   }
225
226#---------------------------------------------------------------
227sub test_switchdb_environnement {
228   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
229   return;
230   }
231
232#---------------------------------------------------------------
233sub test_maindb_environnement {
234   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
235   return;
236   }
237
238#---------------------------------------------------------------
239# fast ping dont l'objectif est de remplir la table arp de la machine
240sub fast_ping {
241   # Launch this command without waiting...
242   system "fping -q -c 1 @_ > /dev/null 2>&1 &";
243   return;
244   }
245
246#---------------------------------------------------------------
247sub shell_command {
248   my $cmd = shift;
249
250   my $fh     = new FileHandle;
251   my $result = '';
252   open $fh, q{-|}, "LANG=C $cmd" or die "Can't exec $cmd\n";
253   $result .= <$fh>;
254   close $fh;
255   chomp $result;
256   return $result;
257   }
258
259#---------------------------------------------------------------
260# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
261sub resolve_ip_arp_host {
262   my $param_ip_or_host = shift;
263   my $interface = shift || q{*};
264   my $type      = shift || q{fast};
265   my $already   = shift || q{yes};
266
267   my %ret = (
268      hostname_fq  => 'unknow',
269      ipv4_address => '0.0.0.0',
270      mac_address  => 'unknow',
271      );
272
273   # perl -MSocket -E 'say inet_ntoa(scalar gethostbyname("tech7meylan.hmg.inpg.fr"))'
274   my $packed_ip = scalar gethostbyname($param_ip_or_host);
275   return %ret if not defined $packed_ip;
276   $ret{'ipv4_address'} = inet_ntoa($packed_ip);
277   #if ($ret{'ipv4_address'} !~ m/$RE_IPv4_ADDRESS/) {
278   #   print "Error: for computer $param_ip_or_host on interface $interface, IP $ret{'ipv4_address'} is not valide\n";
279   #   return %ret;
280   #   }
281
282   # perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("194.254.66.240"), AF_INET)'
283   my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) if $already eq 'yes';
284   $ret{'hostname_fq'} = $hostname_fq if defined $hostname_fq;
285
286   # my $cmd = q{grep  -he '\b} . $param_ip_or_host . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
287   #my $cmd = q{grep  -he '\b} . $ret{'ipv4_address'} . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
288   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';
289   #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
290
291   my $cmd_arpwatch = shell_command $cmd;
292   #my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
293   my ($interface2, $arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
294
295   $ret{'interface'}    = $interface2 || $interface;
296   $ret{'mac_address'}  = $arp       if $arp;
297   $ret{'timestamp'}    = $timestamp if $timestamp;
298
299   my $nowtimestamp = time;
300
301   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 45 * 60 ) ) ) { # 45 min
302      $ret{'mac_address'} = 'unknow';
303      return %ret;
304      }
305
306   # ARP result
307   #
308   # LANG=C arp -a 194.254.66.62 -i eth331
309   # gw66-62.legi.grenoble-inp.fr (194.254.66.62) at 00:08:7c:bb:0f:c0 [ether] on eth331
310   #
311   # LANG=C ip neigh show to 194.254.66.62 dev eth331
312   # 194.254.66.62 lladdr 00:08:7c:bb:0f:c0 REACHABLE
313   # LANG=C ip neigh show to 194.254.66.62
314   # 194.254.66.62 dev eth331 lladdr 00:08:7c:bb:0f:c0 REACHABLE
315#   my $cmd_arp  = shell_command "arp -a $param_ip_or_host -i $ret{'interface'}";
316#   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
317#      ( $ret{'hostname_fq'}, $ret{'ipv4_address'}, $ret{'mac_address'} )  = ($1, $2, $3);
318#      }
319   if ($ret{'mac_address'} eq 'unknow') {
320      # Last chance to have the mac_address
321      if ($ret{'interface'} eq '*') {
322         my $cmd_arp  = shell_command "ip neigh show to $ret{'ipv4_address'}";
323         if ( $cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s dev \s ([\w\d\.\:]+) \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms ) {
324            ($ret{'interface'}, $ret{'mac_address'}) = ($1, $2);
325            }
326         }
327      else {
328         my $cmd_arp  = shell_command "ip neigh show to $ret{'ipv4_address'} dev $ret{'interface'}";
329         if ( $cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms ) {
330            $ret{'mac_address'} = $1;
331            }
332         }
333      }
334
335   # Normalize MAC Address
336   if ($ret{'mac_address'} ne 'unknow') {
337      my @paquets = ();
338      for ( split m/ : /xms, $ret{'mac_address'} ) {
339         my @chars = split m//xms, uc "00$_";
340         push @paquets, "$chars[-2]$chars[-1]";
341         }
342      $ret{'mac_address'} = join q{:}, @paquets;
343      }
344
345   return %ret;
346   }
347
348#---------------------------------------------------------------
349# Find Surname of a switch
350sub get_switch_model {
351   my $sw_snmp_description = shift || 'unknow';
352   $sw_snmp_description =~ s/[\n\r]/ /g;
353
354   for my $sw_kind (keys %SWITCH_KIND) {
355      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/ms; # option xms break search, why ?
356
357      return $SWITCH_KIND{$sw_kind}->{model};
358      }
359
360   return $sw_snmp_description;
361   }
362
363#---------------------------------------------------------------
364# va rechercher le nom des switchs pour savoir qui est qui
365sub init_switch_names {
366   my ($verbose, $verb_description, $check_hostname, $check_location) = @_;
367
368   printf "%-26s                %-25s %s\n",'Switch','Description','Type' if $verbose;
369   print "------------------------------------------------------------------------------\n" if $verbose;
370
371   INIT_EACH_SWITCH:
372   for my $sw (my @CLONE = @SWITCH_LIST) { # Make a local clone because some element can be deleted
373      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
374      print "$error \n" if $error;
375
376      my $result = $session->get_request(
377         -varbindlist => [
378            $OID_NUMBER{'sysDescription'},
379            $OID_NUMBER{'sysName'},
380            $OID_NUMBER{'sysContact'},
381            $OID_NUMBER{'sysLocation'},
382            ]
383         );
384      if (!defined $result) {
385         printf {*STDERR} "ERROR: %s.\n", $session->error();
386         $session->close();
387         # Remove bad switch
388         @SWITCH_LIST = grep { $_->{'hostname'} ne $sw->{'hostname'} } @SWITCH_LIST;
389         delete $SWITCH_LEVEL{$sw->{'hostname'}} if exists $SWITCH_LEVEL{$sw->{'hostname'}};
390         delete $SWITCH_DB{$sw->{'hostname'}}    if exists $SWITCH_DB{$sw->{'hostname'}};
391         next INIT_EACH_SWITCH;
392         }
393
394      $sw->{'description'} = $result->{$OID_NUMBER{'sysName'}} || $sw->{'hostname'};
395      $sw->{model} = get_switch_model($result->{$OID_NUMBER{'sysDescription'}});
396      if ($verb_description) {
397         my $desc = $result->{$OID_NUMBER{'sysDescription'}};
398         $desc =~ s/[\n\r]/ /g;
399         print "   description: $desc\n"
400         }
401      if ($check_hostname) {
402         my ($hostname) = split /\./, $sw->{'hostname'}, 2;
403         print " $hostname - error internal hostname: $sw->{'hostname'}\n" if $result->{$OID_NUMBER{'sysName'}} ne $hostname;
404         }
405      if ($check_location) {
406         my $location = $result->{$OID_NUMBER{'sysLocation'}};
407         $location =~ s/^"(.+)"$/$1/;
408         print " $sw->{'hostname'} - error location: '$location' -> '$sw->{'location'}'\n" if $location ne $sw->{'location'};
409         }
410      #$sw->{'location'} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{'hostname'};
411      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{'hostname'};
412      $session->close;
413
414      # Ligne à virer car on récupère maintenant le modèle du switch
415      #my ($desc, $type) = split m/ : /xms, $sw->{'description'}, 2;
416      printf "%-26s 0--------->>>> %-25s %s\n", $sw->{'hostname'}, $sw->{'description'}, $sw->{model} if $verbose;
417      }
418
419   print "\n" if $verbose;
420   return;
421   }
422
423#---------------------------------------------------------------
424# convert hexa (only 2 digits) to decimal
425sub digit_hex2dec {
426   #00:0F:1F:43:E4:2B
427   my $car = '00' . uc shift;
428
429   return '00' if $car eq '00UNKNOW';
430   my %table = (
431      '0'=>'0',  '1'=>'1',  '2'=>'2',  '3'=>'3',  '4'=>'4',
432      '5'=>'5',  '6'=>'6',  '7'=>'7',  '8'=>'8',  '9'=>'9',
433      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15',
434      );
435   my @chars = split m//xms, $car;
436   return $table{$chars[-2]}*16 + $table{$chars[-1]};
437   }
438
439#---------------------------------------------------------------
440
441sub normalize_mac_address {
442   my $mac_address = shift;
443
444   # D07E-28D1-7AB8 or D07E.28D1.7AB8 or d07e28-d17ab8
445   if ($mac_address =~ m{^ (?: [0-9A-Fa-f]{4} [-\.]){2} [0-9A-Fa-f]{4} $}xms
446      or $mac_address =~ m{^ [0-9A-Fa-f]{6} - [0-9A-Fa-f]{6} $}xms
447      ) {
448      $mac_address =~ s/[-\.]//g;
449      return join q{:}, unpack('(A2)*', uc($mac_address));
450      }
451
452   return join q{:}, map { substr( uc("00$_"), -2) } split m/ [:-] /xms, $mac_address;
453   }
454
455#---------------------------------------------------------------
456# convert MAC hex address to decimal
457sub mac_address_hex2dec {
458   #00:0F:1F:43:E4:2B
459   my $mac_address = shift;
460
461   my @paquets = split m/ : /xms, $mac_address;
462   my $return = q{};
463   for (@paquets) {
464      $return .= q{.} . digit_hex2dec($_);
465      }
466   return $return;
467   }
468
469#---------------------------------------------------------------
470sub format_aggregator4html {
471   my $port_hr = shift;
472   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/: /; # First occurence
473   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ /g; # Other occurence
474   return $port_hr;
475   }
476
477#---------------------------------------------------------------
478sub format_aggregator4dot {
479   my $port_hr = shift;
480   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ - /; # First occurence
481   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ /g; # Other occurence
482   return $port_hr;
483   }
484
485#---------------------------------------------------------------
486# return the port and the switch where the computer is connected
487sub find_switch_port {
488   my $mac_address     = shift;
489   my $switch_proposal = shift || q{};
490   my $vlan_id = shift || 0;
491
492   my %ret;
493   $ret{'switch_description'} = 'unknow';
494   $ret{'switch_port_id'} = '0';
495
496   return %ret if $mac_address eq 'unknow';;
497
498   my @switch_search = @SWITCH_LIST;
499   if ($switch_proposal ne q{}) {
500      for my $sw (@SWITCH_LIST) {
501         next if $sw->{'hostname'} ne $switch_proposal;
502         unshift @switch_search, $sw;
503         last;
504         }
505      }
506
507   my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
508   my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
509
510   LOOP_ON_SWITCH:
511   for my $sw (@switch_search) {
512      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
513      print "$error \n" if $error;
514
515      my $result = $session->get_request(
516         -varbindlist => [$oid_search_port1]
517         );
518      if (not defined $result) {
519         $result = $session->get_request(
520            -varbindlist => [$oid_search_port2]
521            );
522         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
523         }
524
525      if (not (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance')) {
526         $session->close;
527         next LOOP_ON_SWITCH;
528         }
529
530      my $swport_id = $result->{$oid_search_port1};
531      my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
532
533      $session->close;
534
535      # IMPORTANT !!
536      # ceci empeche la detection sur certains port ...
537      # en effet les switch sont relies entre eux par un cable reseau et du coup
538      # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
539      # cette partie est a ameliore, voir a configurer dans l'entete
540      # 21->24 45->48
541      SWITCH_PORT_IGNORE:
542      for my $portignore (@{$sw->{portignore}}) {
543         next LOOP_ON_SWITCH if $swport_hr eq $portignore;
544         my ($swport_hr_limited) = split /$SEP_AGGREGATOR_PORT/, $swport_hr; # Beginning of the swith port (Aggregator)
545         next LOOP_ON_SWITCH if $swport_hr_limited eq $portignore;
546         }
547
548      $ret{'switch_hostname'}    = $sw->{'hostname'};
549      $ret{'switch_description'} = $sw->{'description'};
550      $ret{'switch_port_id'}     = $swport_id;
551      $ret{'switch_port_hr'}     = $swport_hr; # human readable
552
553      last LOOP_ON_SWITCH;
554      }
555   return %ret;
556   }
557
558#---------------------------------------------------------------
559# search all the port on all the switches where the computer is detected
560sub find_all_switch_port {
561   my $mac_address = shift;
562   my $vlan_id     = shift || 0;
563
564   my $ret = {};
565
566   return $ret if $mac_address eq 'unknow';
567
568   my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
569   my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
570   LOOP_ON_ALL_SWITCH:
571   for my $sw (@SWITCH_LIST) {
572      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
573      print "$error \n" if $error;
574
575      my $result = $session->get_request(
576         -varbindlist => [$oid_search_port1]
577         );
578      if (not defined $result) {
579         $result = $session->get_request(
580            -varbindlist => [$oid_search_port2]
581            );
582         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
583         }
584
585      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
586         my $swport_id = $result->{$oid_search_port1};
587         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
588
589         SWITCH_PORT_IGNORE:
590         for my $portignore (@{$sw->{portignore}}) {
591            if ($swport_hr eq $portignore) {
592               $session->close;
593               next LOOP_ON_ALL_SWITCH
594               }
595            }
596
597         $ret->{$sw->{'hostname'}} = {};
598         $ret->{$sw->{'hostname'}}{'hostname'}    = $sw->{'hostname'};
599         $ret->{$sw->{'hostname'}}{'description'} = $sw->{'description'};
600         $ret->{$sw->{'hostname'}}{'port_id'}     = $swport_id;
601         $ret->{$sw->{'hostname'}}{'port_hr'}     = $swport_hr;
602         }
603
604      $session->close;
605      }
606   return $ret;
607   }
608
609#---------------------------------------------------------------
610sub get_list_network {
611
612   return keys %{$KLASK_CFG->{'network'}};
613   }
614
615#---------------------------------------------------------------
616sub get_current_interface {
617   my $vlan_name = shift;
618
619   return $KLASK_CFG->{'network'}{$vlan_name}{'interface'};
620   }
621
622#---------------------------------------------------------------
623sub get_current_vlan_id {
624   my $vlan_name = shift;
625
626   return 0 if not exists $KLASK_CFG->{'network'}{$vlan_name};
627   return $KLASK_CFG->{'network'}{$vlan_name}{'vlan-id'};
628   }
629
630#---------------------------------------------------------------
631sub get_current_scan_mode {
632   my $vlan_name = shift;
633
634   return $KLASK_CFG->{'network'}{$vlan_name}{'scan-mode'} || $DEFAULT{'scan-mode'} || 'active';
635   }
636
637#---------------------------------------------------------------
638sub get_current_vlan_name_for_interface {
639   my $interface = shift;
640
641   for my $vlan_name (keys %{$KLASK_CFG->{'network'}}) {
642      next if $KLASK_CFG->{'network'}{$vlan_name}{'interface'} ne $interface;
643      return $vlan_name;
644      }
645   }
646
647#---------------------------------------------------------------
648# liste l'ensemble des adresses ip d'un réseau
649sub get_list_ip {
650   my @vlan_name = @_;
651
652   my $cidrlist = Net::CIDR::Lite->new;
653
654   for my $net (@vlan_name) {
655      my @line  = @{$KLASK_CFG->{'network'}{$net}{'ip-subnet'}};
656      for my $cmd (@line) {
657         for my $method (keys %{$cmd}) {
658            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
659            }
660         }
661      }
662
663   my @res = ();
664
665   for my $cidr ($cidrlist->list()) {
666      my $net = new NetAddr::IP $cidr;
667      for my $ip (@{$net}) {
668         $ip =~ s{ /32 }{}xms;
669         push @res,  $ip;
670         }
671      }
672
673   return @res;
674   }
675
676#---------------------------------------------------------------
677# liste l'ensemble des routeurs du réseau
678sub get_list_main_router {
679   my @vlan_name = @_;
680
681   my @res = ();
682
683   for my $net (@vlan_name) {
684      push @res, $KLASK_CFG->{'network'}{$net}{'main-router'};
685      }
686
687   return @res;
688   }
689
690#---------------------------------------------------------------
691sub normalize_port_human_readable {
692   my $sw_port_hr  = shift;
693
694   # Manufacturer abbreviation
695   $sw_port_hr =~ s/^Bridge-Aggregation/Br/i;
696   $sw_port_hr =~ s/^Port-Channel/Po/i;
697   $sw_port_hr =~ s/^Forty-?GigabitEthernet/Fo/i;
698   $sw_port_hr =~ s/^Ten-?GigabitEthernet/Te/i;
699   $sw_port_hr =~ s/^GigabitEthernet/Gi/i;
700   $sw_port_hr =~ s/^FastEthernet/Fa/i;
701
702   # Customer abbreviation
703   $sw_port_hr =~ s/^Ten/Te/i;
704   $sw_port_hr =~ s/^Giga/Gi/i;
705
706   return ucfirst $sw_port_hr;
707   }
708
709#---------------------------------------------------------------
710sub snmp_get_rwsession {
711   my ($sw) = @_;
712
713   my %session = %{$sw->{'snmp_param_session'}};
714   $session{-community} = $sw->{'community-rw'} || $DEFAULT{'community-rw'} || 'private';
715   return %session;
716   }
717
718#---------------------------------------------------------------
719sub snmp_get_switchport_id2hr {
720   my ($snmp_session, $swport_id) = @_;
721
722   # On H3C, port id (port_id) and port index (port_ix) are not the same
723   # Double SNMP request to get the name
724   # First get the index, second get the name (port_hr)
725
726   my $oid_search_ix = $OID_NUMBER{'ifIndex'} .'.'. $swport_id;
727   my $result_ix = $snmp_session->get_request(
728      -varbindlist => [$oid_search_ix]
729      );
730
731   my $swport_ix = $swport_id;
732   $swport_ix = $result_ix->{$oid_search_ix} if defined $result_ix;
733
734   return snmp_get_switchport_ix2hr($snmp_session, $swport_ix);
735   }
736
737#---------------------------------------------------------------
738sub snmp_get_switchport_ix2hr {
739   my ($snmp_session, $swport_ix) = @_;
740
741   my $oid_search_hr = $OID_NUMBER{'ifName'} .'.'. $swport_ix;
742   my $result_hr = $snmp_session->get_request(
743      -varbindlist => [$oid_search_hr]
744      );
745   my $swport_hr = $swport_ix;
746   $swport_hr = normalize_port_human_readable($result_hr->{$oid_search_hr}) if defined $result_hr;
747
748   # Aggregator port
749   if ($swport_hr =~ m/^(Trk|Br|Po)/) {
750      my $oid_search_index = $OID_NUMBER{'ifAggregator'}; # base OID
751      my @args = ( -varbindlist =>  [$oid_search_index]);
752      LOOP_ON_OID_PORT:
753      while ( defined $snmp_session->get_next_request(@args) ) {
754         my ($oid_current) = $snmp_session->var_bind_names;
755         last LOOP_ON_OID_PORT if  not Net::SNMP::oid_base_match($oid_search_index, $oid_current);
756
757         # IEEE8023-LAG-MIB::dot3adAggPortSelectedAggID.28 = INTEGER: 337
758         # IEEE8023-LAG-MIB::dot3adAggPortAttachedAggID.28 = INTEGER: 337
759         my $port_aggregator_index = $snmp_session->var_bind_list->{$oid_current};
760         my ($current_port_ix) = reverse split /\./, $oid_current; # last number
761
762         # prepare next loop item
763         @args = (-varbindlist => [$oid_current]);
764
765         next LOOP_ON_OID_PORT if $port_aggregator_index == 0;
766         next LOOP_ON_OID_PORT if not $port_aggregator_index == $swport_ix;
767
768         my $current_port_name = snmp_get_switchport_ix2hr($snmp_session, $current_port_ix);
769         $swport_hr .= "$SEP_AGGREGATOR_PORT$current_port_name";
770         }
771      }
772   return $swport_hr;
773   }
774
775#---------------------------------------------------------------
776# Reverse search port number
777sub snmp_get_switchport_hr2id {
778   my ($snmp_session, $swport_hr, $verbose) = @_;
779
780   # Split for Aggregator port
781   # Keep only the Aggregator part
782   ($swport_hr) = split /$SEP_AGGREGATOR_PORT/, $swport_hr;
783
784   my $swport_id = $swport_hr;
785   # direct return if already numeric (next loop is expensive) / old or simple switch
786   return $swport_id if $swport_id =~ m/^\d+$/;
787
788   my $oid_search_ix = $OID_NUMBER{'ifIndex'}; # base OID
789   my @args = ( -varbindlist =>  [$oid_search_ix]);
790   LOOP_ON_OID_PORT:
791   while ( defined $snmp_session->get_next_request(@args) ) {
792      my ($oid_current) = $snmp_session->var_bind_names;
793      last LOOP_ON_OID_PORT if  not Net::SNMP::oid_base_match($oid_search_ix, $oid_current);
794
795      my $port_ifIndex = $snmp_session->var_bind_list->{$oid_current};
796      my ($port_ix) = reverse split /\./, $oid_current; # last number
797      printf "PORT1: %s => %s\n", $oid_current, $port_ifIndex if $verbose;
798
799      # prepare next loop item
800      @args = (-varbindlist => [$oid_current]);
801
802      my $oid_search_ifName = $OID_NUMBER{'ifName'} .'.'. $port_ifIndex;
803      my $result = $snmp_session->get_request(-varbindlist => [$oid_search_ifName]);
804      next LOOP_ON_OID_PORT if not defined $result;
805
806      my $current_port_hr = normalize_port_human_readable($result->{$oid_search_ifName});
807      printf "PORT2: $oid_search_ifName => $current_port_hr\n" if $verbose;
808      if ($current_port_hr eq $swport_hr) {
809         print "PORT3: $current_port_hr <-> $port_ix\n" if $verbose;
810
811         # return port number ifIndex need by OID portUpDown
812         $swport_id = $port_ifIndex; # other possible value could be $port_ix
813         last LOOP_ON_OID_PORT;
814         }
815      }
816   return $swport_id;
817   }
818
819#---------------------------------------------------------------
820# Get the list of all the VLAN define on a switch
821sub snmp_get_vlan_list {
822   my ($snmp_session, $verbose) = @_;
823
824   my %vlandb = (); # Hash vlan number => vlan name
825
826   my $oid_search_index = $OID_NUMBER{'vlanName'}; # base OID
827   my @args = ( -varbindlist =>  [$oid_search_index]);
828   LOOP_ON_VLAN:
829   while ( defined $snmp_session->get_next_request(@args) ) {
830      my ($oid_current) = $snmp_session->var_bind_names;
831      last LOOP_ON_VLAN if not Net::SNMP::oid_base_match($oid_search_index, $oid_current);
832
833      my $vlan_name = $snmp_session->var_bind_list->{$oid_current};
834      my ($vlan_id) = reverse split /\./, $oid_current; # last number
835      printf "VLAN: %s => %s\n", $oid_current, $vlan_name if $verbose;
836
837      $vlandb{$vlan_id} = $vlan_name;
838
839      # prepare next loop item
840      @args = (-varbindlist => [$oid_current]);
841      }
842   return %vlandb;
843   }
844
845#---------------------------------------------------------------
846# Load computer database
847sub computerdb_load {
848   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
849
850   LOOP_ON_IP_ADDRESS:
851   for my $ip (keys %{$computerdb}) {
852
853      # Rename switch_port -> switch_port_id (2017/09/15)
854      if (not exists $computerdb->{$ip}{'switch_port_id' and exists $computerdb->{$ip}{'switch_port'}}) {
855         $computerdb->{$ip}{'switch_port_id'} = $computerdb->{$ip}{'switch_port'} if defined $computerdb->{$ip}{'switch_port'};
856         $computerdb->{$ip}{'switch_port_id'} = 0 if $computerdb->{$ip}{'switch_port_id'} !~ m/^\d+$/; # force numeric
857         }
858      delete $computerdb->{$ip}{'switch_port'} if exists $computerdb->{$ip}{'switch_port'};
859
860      next LOOP_ON_IP_ADDRESS if exists $computerdb->{$ip}{'switch_port_hr'} and defined $computerdb->{$ip}{'switch_port_hr'};
861
862      $computerdb->{$ip}{'switch_port_hr'} = $computerdb->{$ip}{'switch_port_id'};
863      }
864
865   return $computerdb;
866   }
867
868#---------------------------------------------------------------
869sub get_switchdb_checksum {
870   my %switch_db = @_; # same as global %SWITCH_DB
871
872   my $checksum_data = '';
873   for my $sw_name (sort keys %switch_db) { # sort to always have the same order
874      $checksum_data .= join ':',
875         $switch_db{$sw_name}->{'description'},
876         $switch_db{$sw_name}->{model},
877         $switch_db{$sw_name}->{'hostname'},
878         "\n";
879      }
880
881   return sha512_base64($checksum_data);
882   }
883
884#---------------------------------------------------------------
885sub update_switchdb {
886   my %args = (
887      verbose => 0,
888      @_);
889
890   init_switch_names('yes');    #nomme les switchs
891   print "\n";
892
893   my %where = ();
894   my %db_switch_output_port = ();
895   my %db_switch_ip_hostnamefq = ();
896
897   DETECT_ALL_ROUTER:
898   for my $one_router ( get_list_main_router(get_list_network()) ) {
899      print "Info: router loop $one_router\n" if $args{'verbose'};
900      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
901
902      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
903      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $args{'verbose'};
904
905      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
906      my $vlan_id   = get_current_vlan_id($vlan_name);
907      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
908      }
909
910   ALL_ROUTER_IP_ADDRESS:
911   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
912
913      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
914
915      ALL_SWITCH_CONNECTED:
916      for my $switch_detected ( keys %{$where{$ip_router}} ) {
917
918         my $switch = $where{$ip_router}->{$switch_detected};
919
920         next ALL_SWITCH_CONNECTED if $switch->{'port_id'} eq '0';
921
922         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
923         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $args{'verbose'};
924         }
925      }
926
927   my %db_switch_link_with = ();
928
929   my @list_all_switch = ();
930   my @list_switch_ipv4 = ();
931   for my $sw (@SWITCH_LIST) {
932      push @list_all_switch, $sw->{'hostname'};
933      }
934
935   my $timestamp = time;
936
937   ALL_SWITCH:
938   for my $one_switch (@list_all_switch) {
939      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
940      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
941         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
942         fast_ping($fake_ip);
943         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $args{'verbose'};
944         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
945         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
946            $resol_arp{'mac_address'}   = $resol_arp_alt{'mac_address'};
947            $resol_arp{'interface'}     = $resol_arp_alt{'interface'};
948            $resol_arp{'ipv4_address'} .= '*';
949            # Force a MAC trace on switch
950            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
951            }
952         }
953      print "Info: switch loop $one_switch\n" if $args{'verbose'};
954      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
955
956      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
957
958      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
959      my $vlan_id   = get_current_vlan_id($vlan_name);
960      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
961
962      if ($args{'verbose'}) {
963         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
964         print "VERBOSE_3: $one_switch --- ",
965            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
966            "\n";
967         }
968
969      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
970      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $args{'verbose'};
971
972      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
973      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
974      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
975      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
976      }
977
978   ALL_SWITCH_IP_ADDRESS:
979   for my $ip (@list_switch_ipv4) {
980#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
981
982      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $args{'verbose'};
983
984      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
985#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
986
987      DETECTED_SWITCH:
988      for my $switch_detected ( keys %{$where{$ip}} ) {
989
990         my $switch = $where{$ip}->{$switch_detected};
991         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $args{'verbose'};
992
993         next if $switch->{'port_id'}  eq '0';
994         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
995         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
996
997         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
998         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{'hostname'} } = $switch->{'port_hr'};
999         print "VERBOSE_7: +++++\n" if $args{'verbose'};
1000         }
1001
1002      }
1003
1004   my %db_switch_connected_on_port = ();
1005   my $maybe_more_than_one_switch_connected = 'yes';
1006   my $cloop = 0;
1007
1008   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
1009      $cloop++;
1010      print "VERBOSE_9: cloop reduction step: $cloop\n" if $args{'verbose'};
1011      for my $sw (keys %db_switch_link_with) {
1012         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1013
1014            my $port_hr = $db_switch_link_with{$sw}->{$connect};
1015
1016            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
1017            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
1018            }
1019         }
1020
1021      $maybe_more_than_one_switch_connected  = 'no';
1022
1023      SWITCH_AND_PORT:
1024      for my $swport (keys %db_switch_connected_on_port) {
1025
1026         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1027
1028         $maybe_more_than_one_switch_connected = 'yes';
1029
1030         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1031         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1032         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $args{'verbose'};
1033
1034         CONNECTED:
1035         for my $sw_connected (@sw_on_same_port) {
1036
1037            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1038
1039            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1040
1041            for my $other_sw (@sw_on_same_port) {
1042               next if $other_sw eq $sw_connected;
1043
1044               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1045               }
1046
1047            # We can not do better for this switch for this loop
1048            next SWITCH_AND_PORT;
1049            }
1050         }
1051      }
1052
1053   my %db_switch_parent =();
1054
1055   for my $sw (keys %db_switch_link_with) {
1056      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1057
1058         my $port_hr = $db_switch_link_with{$sw}->{$connect};
1059
1060         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
1061         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
1062
1063         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
1064         }
1065      }
1066
1067   print "Switch output port and parent port connection\n";
1068   print "---------------------------------------------\n";
1069   for my $sw (sort keys %db_switch_output_port) {
1070      if (exists $db_switch_parent{$sw}) {
1071         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
1072         }
1073      else {
1074         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1075         }
1076      }
1077   print "\n";
1078
1079   print "Switch parent and children port inter-connection\n";
1080   print "------------------------------------------------\n";
1081   for my $swport (sort keys %db_switch_connected_on_port) {
1082      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1083      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1084         if (exists $db_switch_output_port{$sw}) {
1085            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1086            }
1087         else {
1088            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1089            }
1090         }
1091      }
1092
1093   my $switch_connection = {
1094      output_port       => \%db_switch_output_port,
1095      parent            => \%db_switch_parent,
1096      connected_on_port => \%db_switch_connected_on_port,
1097      link_with         => \%db_switch_link_with,
1098      switch_db         => \%SWITCH_DB,
1099      timestamp         => $timestamp,
1100      checksum          => get_switchdb_checksum(%SWITCH_DB),
1101      };
1102
1103   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1104   return;
1105   }
1106
1107################################################################
1108# command
1109################################################################
1110
1111#---------------------------------------------------------------
1112sub cmd_help {
1113
1114print <<'END';
1115klask - port and search manager for switches, map management
1116
1117 klask version
1118 klask help
1119
1120 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
1121 klask exportdb [--format|-f txt|html]
1122 klask removedb IP* computer*
1123 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
1124
1125 klask updatesw [--verbose|-v]
1126 klask exportsw [--format|-f txt|dot]
1127
1128 klask searchdb [--kind|-k host|mac] computer [mac-address]
1129 klask search   computer
1130 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
1131
1132 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
1133
1134 klask bad-vlan-id [--day|-d days_before_alert]
1135
1136 klask enable  [--verbose|-v] switch port
1137 klask disable [--verbose|-v] switch port
1138 klask status  [--verbose|-v] switch port
1139
1140 klask poe-enable  [--verbose|-v] switch port
1141 klask poe-disable [--verbose|-v] switch port
1142 klask poe-status  [--verbose|-v] switch port
1143
1144 klask vlan-getname switch vlan-id
1145 klask vlan-list switch
1146END
1147   return;
1148   }
1149
1150#---------------------------------------------------------------
1151sub cmd_version {
1152
1153print <<'END';
1154klask - port and search manager for switches, map management
1155Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
1156
1157END
1158   print ' $Id: klask 302 2017-10-04 18:48:28Z g7moreau $'."\n";
1159   return;
1160   }
1161
1162#---------------------------------------------------------------
1163sub cmd_search {
1164   my @computer = @_;
1165
1166   init_switch_names();    #nomme les switchs
1167   fast_ping(@computer);
1168
1169   LOOP_ON_COMPUTER:
1170   for my $clientname (@computer) {
1171      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
1172      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1173      my $vlan_id   = get_current_vlan_id($vlan_name);
1174      my %where     = find_switch_port($resol_arp{'mac_address'}, '', $vlan_id); #retrouve l'emplacement
1175
1176      next LOOP_ON_COMPUTER if $where{'switch_description'} eq 'unknow' or $resol_arp{'hostname_fq'} eq 'unknow' or $resol_arp{'mac_address'} eq 'unknow';
1177
1178      printf '%-22s %2s %-30s %-15s %18s',
1179         $where{'switch_hostname'},
1180         $where{'switch_port_hr'},
1181         $resol_arp{'hostname_fq'},
1182         $resol_arp{'ipv4_address'},
1183         $resol_arp{'mac_address'}."\n";
1184      }
1185   return;
1186   }
1187
1188#---------------------------------------------------------------
1189sub cmd_searchdb {
1190   my @ARGV  = @_;
1191
1192   my $kind;
1193
1194   GetOptions(
1195      'kind=s'   => \$kind,
1196      );
1197
1198   my %possible_search = (
1199      host  => \&cmd_searchdb_host,
1200      mac   => \&cmd_searchdb_mac,
1201      );
1202
1203   $kind = 'host' if not defined $possible_search{$kind};
1204
1205   $possible_search{$kind}->(@ARGV);
1206   return;
1207   }
1208
1209
1210#---------------------------------------------------------------
1211sub cmd_searchdb_host {
1212   my @computer = @_;
1213
1214   fast_ping(@computer);
1215   my $computerdb = computerdb_load();
1216
1217   LOOP_ON_COMPUTER:
1218   for my $clientname (@computer) {
1219      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
1220      my $ip = $resol_arp{'ipv4_address'};
1221
1222      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
1223
1224      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1225      $year += 1900;
1226      $mon++;
1227      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1228
1229      printf "%-22s %2s %-30s %-15s %-18s %s\n",
1230         $computerdb->{$ip}{'switch_hostname'},
1231         $computerdb->{$ip}{'switch_port_hr'},
1232         $computerdb->{$ip}{'hostname_fq'},
1233         $ip,
1234         $computerdb->{$ip}{'mac_address'},
1235         $date;
1236      }
1237   return;
1238   }
1239
1240#---------------------------------------------------------------
1241sub cmd_searchdb_mac {
1242   my @mac = map { normalize_mac_address($_) } @_;
1243
1244   my $computerdb = computerdb_load();
1245
1246   LOOP_ON_MAC:
1247   for my $mac (@mac) {
1248      LOOP_ON_COMPUTER:
1249      for my $ip (keys %{$computerdb}) {
1250         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{'mac_address'};
1251
1252         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1253         $year += 1900;
1254         $mon++;
1255         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1256
1257         printf "%-22s %2s %-30s %-15s %-18s %s\n",
1258            $computerdb->{$ip}{'switch_hostname'},
1259            $computerdb->{$ip}{'switch_port_hr'},
1260            $computerdb->{$ip}{'hostname_fq'},
1261            $ip,
1262            $computerdb->{$ip}{'mac_address'},
1263            $date;
1264         #next LOOP_ON_MAC;
1265         }
1266
1267      }
1268   return;
1269   }
1270
1271#---------------------------------------------------------------
1272sub cmd_updatedb {
1273   @ARGV = @_;
1274
1275   my ($verbose, $verb_description, $check_hostname, $check_location, $no_rebuildsw);
1276
1277   GetOptions(
1278      'verbose|v'          => \$verbose,
1279      'verb-description|d' => \$verb_description,
1280      'chk-hostname|h'     => \$check_hostname,
1281      'chk-location|l'     => \$check_location,
1282      'no-rebuildsw|-R'    => \$no_rebuildsw,
1283      );
1284
1285   my @network = @ARGV;
1286      @network = get_list_network() if not @network;
1287
1288   test_switchdb_environnement();
1289
1290   my $computerdb = {};
1291      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
1292   my $timestamp = time;
1293
1294   my %computer_not_detected = ();
1295   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
1296
1297   my $number_of_computer = get_list_ip(@network); # + 1;
1298   my $size_of_database   = keys %{$computerdb};
1299      $size_of_database   = 1 if $size_of_database == 0;
1300   my $i = 0;
1301   my $detected_computer = 0;
1302
1303   init_switch_names('yes', $verb_description, $check_hostname, $check_location);    #nomme les switchs
1304
1305   {
1306   my $switch_checksum = get_switchdb_checksum(%SWITCH_DB);
1307   # Remplis le champs portignore des ports d'inter-connection pour chaque switch
1308   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1309   if ($switch_checksum ne $switch_connection->{'checksum'}) { # Verify checksum
1310      if ($no_rebuildsw) {
1311         print "WARNING: switch database is outdate, please rebuild if with updatesw command\n";
1312         }
1313      else {
1314         print "WARNING: switch database is going to be rebuilt\n";
1315         update_switchdb(verbose => $verbose)
1316         }
1317      }
1318
1319   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1320   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1321   my %db_switch_chained_port = ();
1322   for my $swport (keys %db_switch_connected_on_port) {
1323      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1324      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
1325      }
1326   for my $sw (@SWITCH_LIST) {
1327      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{'hostname'}}  if exists $db_switch_output_port{$sw->{'hostname'}};
1328      if ( exists $db_switch_chained_port{$sw->{'hostname'}} ) {
1329         chop $db_switch_chained_port{$sw->{'hostname'}};
1330         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{'hostname'}};
1331         }
1332#      print "$sw->{'hostname'} ++ @{$sw->{portignore}}\n";
1333      }
1334   }
1335
1336   my %router_mac_ip = ();
1337   DETECT_ALL_ROUTER:
1338#   for my $one_router ('194.254.66.254') {
1339   for my $one_router ( get_list_main_router(@network) ) {
1340      my %resol_arp = resolve_ip_arp_host($one_router);
1341      $router_mac_ip{ $resol_arp{'mac_address'} } = $resol_arp{'ipv4_address'};
1342      }
1343
1344   ALL_NETWORK:
1345   for my $current_net (@network) {
1346
1347      my @computer = get_list_ip($current_net);
1348      my $current_interface = get_current_interface($current_net);
1349
1350      fast_ping(@computer) if get_current_scan_mode($current_net) eq 'active';
1351
1352      LOOP_ON_COMPUTER:
1353      for my $one_computer (@computer) {
1354         $i++;
1355
1356         my $total_percent = int (($i*100)/$number_of_computer);
1357
1358         my $localtime = time - $timestamp;
1359         my ($sec,$min) = localtime $localtime;
1360
1361         my $time_elapse = 0;
1362            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
1363         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
1364
1365         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
1366         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
1367         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
1368         printf ' %-8s %-14s', $current_interface, $one_computer;
1369
1370         my $already_exist = exists $computerdb->{$one_computer} ? 'yes' : 'no';
1371         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface, 'fast', $already_exist);
1372
1373         # do not search on router connection (why ?)
1374         if ( exists $router_mac_ip{$resol_arp{'mac_address'}}) {
1375            $computer_not_detected{$one_computer} = $current_net;
1376            next LOOP_ON_COMPUTER;
1377            }
1378
1379         # do not search on switch inter-connection
1380         if (exists $SWITCH_LEVEL{$resol_arp{'hostname_fq'}}) {
1381            $computer_not_detected{$one_computer} = $current_net;
1382            next LOOP_ON_COMPUTER;
1383            }
1384
1385         my $switch_proposal = q{};
1386         if (exists $computerdb->{$resol_arp{'ipv4_address'}} and exists $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'}) {
1387            $switch_proposal = $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'};
1388            }
1389
1390         # do not have a mac address
1391         if ($resol_arp{'mac_address'} eq 'unknow' or (exists $resol_arp{'timestamps'} and $resol_arp{'timestamps'} < ($timestamp - 3 * 3600))) {
1392            $computer_not_detected{$one_computer} = $current_net;
1393            next LOOP_ON_COMPUTER;
1394            }
1395
1396         my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1397         my $vlan_id   = get_current_vlan_id($vlan_name);
1398         my %where = find_switch_port($resol_arp{'mac_address'}, $switch_proposal, $vlan_id);
1399
1400         #192.168.24.156:
1401         #  arp: 00:0B:DB:D5:F6:65
1402         #  hostname: pcroyon.hmg.priv
1403         #  port: 5
1404         #  switch: sw-batH-legi:hp2524
1405         #  timestamp: 1164355525
1406
1407         # do not have a mac address
1408#         if ($resol_arp{'mac_address'} eq 'unknow') {
1409#            $computer_not_detected{$one_computer} = $current_interface;
1410#            next LOOP_ON_COMPUTER;
1411#            }
1412
1413         # detected on a switch
1414         if ($where{'switch_description'} ne 'unknow') {
1415            $detected_computer++;
1416            $computerdb->{$resol_arp{'ipv4_address'}} = {
1417               hostname_fq        => $resol_arp{'hostname_fq'},
1418               mac_address        => $resol_arp{'mac_address'},
1419               switch_hostname    => $where{'switch_hostname'},
1420               switch_description => $where{'switch_description'},
1421               switch_port_id     => $where{'switch_port_id'},
1422               switch_port_hr     => $where{'switch_port_hr'},
1423               timestamp          => $timestamp,
1424               network            => $current_net,
1425               };
1426            next LOOP_ON_COMPUTER;
1427            }
1428
1429         # new in the database but where it is ?
1430         if (not exists $computerdb->{$resol_arp{'ipv4_address'}}) {
1431            $detected_computer++;
1432            $computerdb->{$resol_arp{'ipv4_address'}} = {
1433               hostname_fq        => $resol_arp{'hostname_fq'},
1434               mac_address        => $resol_arp{'mac_address'},
1435               switch_hostname    => $where{'switch_hostname'},
1436               switch_description => $where{'switch_description'},
1437               switch_port_id     => $where{'switch_port_id'},
1438               switch_port_hr     => $where{'switch_port_hr'},
1439               timestamp          => $resol_arp{'timestamp'},
1440               network            => $current_net,
1441               };
1442            }
1443
1444         # mise a jour du nom de la machine si modification dans le dns
1445         $computerdb->{$resol_arp{'ipv4_address'}}{'hostname_fq'} = $resol_arp{'hostname_fq'};
1446
1447         # mise à jour de la date de détection si détection plus récente par arpwatch
1448         $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'}   = $resol_arp{'timestamp'} if exists $resol_arp{'timestamp'} and $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $resol_arp{'timestamp'};
1449
1450         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
1451#         push @computer_not_detected, $resol_arp{'ipv4_address'} if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1452         $computer_not_detected{$resol_arp{'ipv4_address'}} = $current_net if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1453
1454         }
1455      }
1456
1457   # final end of line at the end of the loop
1458   printf "\n";
1459
1460   my $dirdb = $KLASK_DB_FILE;
1461      $dirdb =~ s{ / [^/]* $}{}xms;
1462   mkdir "$dirdb", 0755 unless -d "$dirdb";
1463   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1464
1465   for my $one_computer (keys %computer_not_detected) {
1466      my $current_net = $computer_not_detected{$one_computer};
1467      my $current_interface = get_current_interface($current_net);
1468      system "arping -c 1 -w 1 -rR -i $current_interface $one_computer > /dev/null 2>&1" if get_current_scan_mode($current_net) eq 'active';
1469      }
1470   return;
1471   }
1472
1473#---------------------------------------------------------------
1474sub cmd_removedb {
1475   my @computer = @_;
1476
1477   test_maindb_environnement();
1478
1479   my $computerdb = computerdb_load();
1480
1481   LOOP_ON_COMPUTER:
1482   for my $one_computer (@computer) {
1483
1484      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
1485            and exists $computerdb->{$one_computer} ) {
1486         delete $computerdb->{$one_computer};
1487         next;
1488         }
1489
1490      my %resol_arp = resolve_ip_arp_host($one_computer);
1491
1492      delete $computerdb->{$resol_arp{'ipv4_address'}} if exists $computerdb->{$resol_arp{'ipv4_address'}};
1493      }
1494
1495   my $dirdb = $KLASK_DB_FILE;
1496      $dirdb =~ s{ / [^/]* $}{}xms;
1497   mkdir "$dirdb", 0755 unless -d "$dirdb";
1498   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1499   return;
1500   }
1501
1502#---------------------------------------------------------------
1503sub cmd_cleandb {
1504   my @ARGV  = @_;
1505
1506   my $days_to_clean = 15;
1507   my $repairdns;
1508   my $verbose;
1509   my $database_has_changed;
1510
1511   GetOptions(
1512      'day|d=i'   => \$days_to_clean,
1513      'verbose|v' => \$verbose,
1514      'repair-dns|r' => \$repairdns,
1515      );
1516
1517   my @vlan_name = get_list_network();
1518
1519   my $computerdb = computerdb_load();
1520   my $timestamp = time;
1521
1522   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
1523   my $timestamp_3month  = 3600 * 24 * 90;
1524
1525   my %mactimedb = ();
1526   ALL_VLAN:
1527   for my $vlan (shuffle @vlan_name) {
1528
1529      my @ip_list   = shuffle get_list_ip($vlan);
1530
1531      LOOP_ON_IP_ADDRESS:
1532      for my $ip (@ip_list) {
1533
1534         next LOOP_ON_IP_ADDRESS if
1535            not exists $computerdb->{$ip};
1536
1537            #&& $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
1538         my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1539         my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1540         my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1541
1542         $mactimedb{$ip_mac} ||= {
1543            ip          => $ip,
1544            timestamp   => $ip_timestamp,
1545            vlan        => $vlan,
1546            hostname_fq => $ip_hostname_fq,
1547            };
1548
1549         if (
1550            ( $mactimedb{$ip_mac}->{'timestamp'} - $ip_timestamp > $timestamp_barrier
1551               or (
1552                  $mactimedb{$ip_mac}->{'timestamp'} > $ip_timestamp
1553                  and $timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_3month
1554                  )
1555            )
1556            and (
1557               not $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1558               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1559               )) {
1560            print "remove ip $ip\n" if $verbose;
1561            delete $computerdb->{$ip};
1562            $database_has_changed++;
1563            }
1564
1565         elsif (
1566            ( $ip_timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_barrier
1567               or (
1568                  $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}
1569                  and $timestamp - $ip_timestamp > $timestamp_3month
1570                  )
1571            )
1572            and (
1573               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1574               or $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1575               )) {
1576            print "remove ip ".$mactimedb{$ip_mac}->{ip}."\n" if $verbose;
1577            delete $computerdb->{$mactimedb{$ip_mac}->{ip}};
1578            $database_has_changed++;
1579            }
1580
1581         if ( $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}) {
1582            $mactimedb{$ip_mac} = {
1583               ip          => $ip,
1584               timestamp   => $ip_timestamp,
1585               vlan        => $vlan,
1586               hostname_fq => $ip_hostname_fq,
1587               };
1588            }
1589         }
1590      }
1591
1592   if ($repairdns) { # Search and update unkown computer in reverse DNS
1593      LOOP_ON_IP_ADDRESS:
1594      for my $ip (keys %{$computerdb}) {
1595         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} ne 'unknow';
1596
1597         my $packed_ip = scalar gethostbyname($ip);
1598         next LOOP_ON_IP_ADDRESS if not defined $packed_ip;
1599
1600         my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
1601         next LOOP_ON_IP_ADDRESS if not defined $hostname_fq;
1602
1603         $computerdb->{$ip}{'hostname_fq'} = $hostname_fq;
1604         $database_has_changed++;
1605         }
1606      }
1607
1608   if ( $database_has_changed ) {
1609      my $dirdb = $KLASK_DB_FILE;
1610         $dirdb =~ s{ / [^/]* $}{}xms;
1611      mkdir "$dirdb", 0755 unless -d "$dirdb";
1612      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1613      }
1614   return;
1615   }
1616
1617#---------------------------------------------------------------
1618sub cmd_exportdb {
1619   @ARGV = @_;
1620
1621   my $format = 'txt';
1622
1623   GetOptions(
1624      'format|f=s'  => \$format,
1625      );
1626
1627   my %possible_format = (
1628      txt  => \&cmd_exportdb_txt,
1629      html => \&cmd_exportdb_html,
1630      );
1631
1632   $format = 'txt' if not defined $possible_format{$format};
1633
1634   $possible_format{$format}->(@ARGV);
1635   return;
1636   }
1637
1638#---------------------------------------------------------------
1639sub cmd_exportdb_txt {
1640   test_maindb_environnement();
1641
1642   my $computerdb = computerdb_load();
1643
1644   printf "%-28s %8s              %-40s %-15s %-18s %-16s %s\n", qw(Switch Port Hostname-FQ IPv4-Address MAC-Address Date VLAN);
1645   print "--------------------------------------------------------------------------------------------------------------------------------------------\n";
1646
1647   LOOP_ON_IP_ADDRESS:
1648   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1649
1650      # to be improve in the future
1651      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1652
1653      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1654      $year += 1900;
1655      $mon++;
1656      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1657
1658      my $vlan = '';
1659      $vlan = $computerdb->{$ip}{'network'}.'('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1660
1661      my $arrow ='<-----------';
1662         $arrow ='<===========' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1663
1664      printf "%-28s %8s %12s %-40s %-15s %-18s %-16s %s\n",
1665         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1666         $computerdb->{$ip}{'switch_port_hr'},
1667         $arrow,
1668         $computerdb->{$ip}{'hostname_fq'},
1669         $ip,
1670         $computerdb->{$ip}{'mac_address'},
1671         $date,
1672         $vlan;
1673      }
1674   return;
1675   }
1676
1677#---------------------------------------------------------------
1678sub cmd_exportdb_html {
1679   test_maindb_environnement();
1680
1681   my $computerdb = computerdb_load();
1682
1683#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1684#<script src="sorttable-klask.js"></script>
1685
1686   print <<'END_HTML';
1687<table class="sortable" summary="Klask Host Database">
1688 <caption>Klask Host Database</caption>
1689 <thead>
1690  <tr>
1691   <th scope="col" class="klask-header-left">Switch</th>
1692   <th scope="col" class="sorttable_nosort">Port</th>
1693   <th scope="col" class="sorttable_nosort">Link</th>
1694   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1695   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1696   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1697   <th scope="col" class="sorttable_alpha">VLAN</th>
1698   <th scope="col" class="klask-header-right">Date</th>
1699  </tr>
1700 </thead>
1701 <tfoot>
1702  <tr>
1703   <th scope="col" class="klask-footer-left">Switch</th>
1704   <th scope="col" class="fklask-port">Port</th>
1705   <th scope="col" class="fklask-link">Link</th>
1706   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1707   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1708   <th scope="col" class="fklask-mac">MAC-Address</th>
1709   <th scope="col" class="fklask-vlan">VLAN</th>
1710   <th scope="col" class="klask-footer-right">Date</th>
1711  </tr>
1712 </tfoot>
1713 <tbody>
1714END_HTML
1715
1716   my %mac_count = ();
1717   LOOP_ON_IP_ADDRESS:
1718   for my $ip (keys %{$computerdb}) {
1719
1720      # to be improve in the future
1721      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1722
1723      $mac_count{$computerdb->{$ip}{'mac_address'}}++;
1724      }
1725
1726   my $typerow = 'even';
1727
1728   LOOP_ON_IP_ADDRESS:
1729   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1730
1731      # to be improve in the future
1732      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1733
1734      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1735      $year += 1900;
1736      $mon++;
1737      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1738
1739#      $odd_or_even++;
1740#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1741      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1742
1743      #my $arrow ='&#8592;';
1744      #   $arrow ='&#8656;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1745      my $arrow ='&#10229;';
1746         $arrow ='&#10232;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1747
1748      my $switch_hostname = $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'} || 'unkown';
1749      chomp $switch_hostname;
1750      my $switch_hostname_sort = sprintf '%s %06i' ,$switch_hostname, $computerdb->{$ip}{'switch_port_id'}; # Take switch index
1751
1752      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1753
1754      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{'mac_address'}}, $computerdb->{$ip}{'mac_address'};
1755
1756      $computerdb->{$ip}{'hostname_fq'} = 'unknow' if $computerdb->{$ip}{'hostname_fq'} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1757      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{'hostname_fq'};
1758
1759      my $vlan = '';
1760      $vlan = $computerdb->{$ip}{'network'}.' ('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1761
1762      my $parent_port_hr = format_aggregator4html($computerdb->{$ip}{'switch_port_hr'});
1763
1764      print <<"END_HTML";
1765  <tr class="$typerow">
1766   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1767   <td class="bklask-port">$parent_port_hr</td>
1768   <td>$arrow</td>
1769   <td sorttable_customkey="$host_short">$computerdb->{$ip}{'hostname_fq'}</td>
1770   <td sorttable_customkey="$ip_sort">$ip</td>
1771   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{'mac_address'}</td>
1772   <td>$vlan</td>
1773   <td>$date</td>
1774  </tr>
1775END_HTML
1776#   <td colspan="2">$arrow</td>
1777      }
1778
1779   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1780
1781   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1782   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1783   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1784   my %db_switch                   = %{$switch_connection->{'switch_db'}};
1785
1786   # Output switch connection
1787   LOOP_ON_OUTPUT_SWITCH:
1788   for my $sw (sort keys %db_switch_output_port) {
1789
1790      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1791
1792      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1793
1794      #my $arrow ='&#8702;';
1795      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1796      my $arrow ='&#10236;';
1797         $arrow ='&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1798
1799      if (exists $db_switch_parent{$sw}) {
1800         # Link to uplink switch
1801         next LOOP_ON_OUTPUT_SWITCH;
1802
1803         # Do not print anymore
1804         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1805         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1806         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
1807
1808         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1809         $year += 1900;
1810         $mon++;
1811         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1812
1813         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1814
1815         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1816
1817         my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'};
1818
1819         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1820         $vlan .= ' ('.get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}).')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1821
1822         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1823         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
1824
1825         print <<"END_HTML";
1826  <tr class="$typerow">
1827   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1828   <td class="bklask-port">$parent_port_hr</td>
1829   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
1830   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{'switch'}</td>
1831   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1832   <td sorttable_customkey="$mac_sort">$mac_address</td>
1833   <td>$vlan</td>
1834   <td>$date</td>
1835  </tr>
1836END_HTML
1837         }
1838      else {
1839         # Router
1840         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1841
1842         my $host_short = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1843
1844         my $mac_address = $db_switch{$sw}->{'mac_address'};
1845         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1846         my $timestamp = $db_switch{$sw}->{'timestamp'};
1847
1848         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1849         $year += 1900;
1850         $mon++;
1851         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1852
1853         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1854
1855         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1856
1857         my $vlan = $db_switch{$sw}->{'network'};
1858         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1859
1860         my $arrow ='&#10235;';
1861            $arrow ='&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1862
1863         print <<"END_HTML";
1864  <tr class="$typerow">
1865   <td sorttable_customkey="router">router</td>
1866   <td class="bklask-port"></td>
1867   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$parent_port_hr</td></tr></table></td>
1868   <td sorttable_customkey="$host_short">$sw</td>
1869   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1870   <td sorttable_customkey="$mac_sort">$mac_address</td>
1871   <td>$vlan</td>
1872   <td>$date</td>
1873  </tr>
1874END_HTML
1875         #<td>$arrow</td><td>$parent_port_hr</td>
1876
1877         next LOOP_ON_OUTPUT_SWITCH;
1878
1879         # Old print
1880         print <<"END_HTML";
1881  <tr class="$typerow">
1882   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1883   <td class="bklask-port">$parent_port_hr</td>
1884   <td>$arrow</td><td></td>
1885   <td sorttable_customkey="router">router</td>
1886   <td sorttable_customkey="999999999999"></td>
1887   <td sorttable_customkey="99999"></td>
1888   <td></td>
1889   <td></td>
1890  </tr>
1891END_HTML
1892         }
1893      }
1894
1895   # Child switch connection : parent <- child
1896   for my $swport (sort keys %db_switch_connected_on_port) {
1897      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1898      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1899
1900         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1901
1902         my $mac_address = $db_switch{$sw}->{'mac_address'};
1903         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1904         my $timestamp = $db_switch{$sw}->{'timestamp'};
1905
1906         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1907         $year += 1900;
1908         $mon++;
1909         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1910
1911         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1912
1913         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1914
1915         $typerow = $typerow eq 'even' ? 'odd' : 'even';
1916
1917         #my $arrow ='&#8701;';
1918         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
1919         my $arrow ='&#10235;';
1920            $arrow ='&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
1921
1922         my $vlan = $db_switch{$sw}->{'network'};
1923         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1924
1925         if (exists $db_switch_output_port{$sw}) {
1926
1927            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1928
1929            my $parent_port_hr = format_aggregator4html($port_connect);
1930            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
1931
1932            print <<"END_HTML";
1933  <tr class="$typerow">
1934   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1935   <td class="bklask-port">$parent_port_hr</td>
1936   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
1937   <td sorttable_customkey="$host_short">$sw</td>
1938   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1939   <td sorttable_customkey="$mac_sort">$mac_address</td>
1940   <td>$vlan</td>
1941   <td>$date</td>
1942  </tr>
1943END_HTML
1944            }
1945         else {
1946            my $parent_port_hr = format_aggregator4html($port_connect);
1947
1948            print <<"END_HTML";
1949  <tr class="$typerow">
1950   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1951   <td class="bklask-port">$parent_port_hr</td>
1952   <td>$arrow</td>
1953   <td sorttable_customkey="$sw">$sw</td>
1954   <td sorttable_customkey="">$ipv4_address</td>
1955   <td sorttable_customkey="">$mac_address</td>
1956   <td>$vlan</td>
1957   <td>$date</td>
1958  </tr>
1959END_HTML
1960            }
1961         }
1962      }
1963
1964   print <<'END_HTML';
1965 </tbody>
1966</table>
1967END_HTML
1968   return;
1969   }
1970
1971#---------------------------------------------------------------
1972sub cmd_bad_vlan_id {
1973   @ARGV = @_;
1974
1975   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1976   my $verbose;
1977
1978   GetOptions(
1979      'day|d=i'   => \$days_before_alert,
1980      );
1981
1982   test_maindb_environnement();
1983
1984   my $computerdb = computerdb_load();
1985
1986   # create a database with the most recent computer by switch port
1987   my %switchportdb = ();
1988   LOOP_ON_IP_ADDRESS:
1989   for my $ip (keys %{$computerdb}) {
1990      # to be improve in the future
1991      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1992      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
1993      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port_id'} eq '0';
1994
1995      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1996      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1997      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1998
1999      my $swpt = sprintf "%-28s  %2s",
2000         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
2001         $computerdb->{$ip}{'switch_port_hr'};
2002      $switchportdb{$swpt} ||= {
2003         ip          => $ip,
2004         timestamp   => $ip_timestamp,
2005         vlan        => $computerdb->{$ip}{'network'},
2006         hostname_fq => $ip_hostname_fq,
2007         mac_address => $ip_mac,
2008         };
2009
2010      # if float computer, set date 15 day before warning...
2011      my $ip_timestamp_mod = $ip_timestamp;
2012      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
2013      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2014      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
2015
2016      if ($ip_timestamp_mod > $ip_timestamp_ref) {
2017         $switchportdb{$swpt} = {
2018            ip          => $ip,
2019            timestamp   => $ip_timestamp,
2020            vlan        => $computerdb->{$ip}{'network'},
2021            hostname_fq => $ip_hostname_fq,
2022            mac_address => $ip_mac,
2023            };
2024         }
2025      }
2026
2027   LOOP_ON_RECENT_COMPUTER:
2028   for my $swpt (keys %switchportdb) {
2029      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
2030      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
2031
2032      my $src_ip = $switchportdb{$swpt}->{ip};
2033      my $src_timestamp = 0;
2034      LOOP_ON_IP_ADDRESS:
2035      for my $ip (keys %{$computerdb}) {
2036         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne  $switchportdb{$swpt}->{'mac_address'};
2037         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
2038         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
2039
2040         $src_ip = $ip;
2041         $src_timestamp = $computerdb->{$ip}{'timestamp'};
2042         }
2043
2044      # keep only if float computer is the most recent
2045      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
2046      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
2047
2048      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
2049      $year += 1900;
2050      $mon++;
2051      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
2052
2053      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
2054      $year += 1900;
2055      $mon++;
2056      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
2057
2058      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
2059
2060      printf "%s / %-10s +-> %-10s(%i)  %s %s %s %s\n",
2061         $swpt, $switchportdb{$swpt}->{'vlan'}, $computerdb->{$src_ip}{'network'}, $vlan_id,
2062         $date,
2063         $src_date,
2064         $computerdb->{$src_ip}{'mac_address'},
2065         $computerdb->{$src_ip}{'hostname_fq'};
2066      }
2067   }
2068
2069#---------------------------------------------------------------
2070sub cmd_poe_enable {
2071   @ARGV = @_;
2072
2073   my $verbose;
2074   GetOptions(
2075      'verbose|v' => \$verbose,
2076      );
2077
2078   my $switch_name = shift @ARGV || q{};
2079   my $switch_port = shift @ARGV || q{};
2080
2081   if ($switch_name eq q{} or $switch_port eq q{}) {
2082      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
2083      }
2084
2085   for my $sw_name (split /,/, $switch_name) {
2086      if (not defined $SWITCH_DB{$sw_name}) {
2087         die "Switch $sw_name must be defined in klask configuration file\n";
2088         }
2089
2090      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2091
2092      my $sw = $SWITCH_DB{$sw_name};
2093      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2094      print "$error \n" if $error;
2095
2096      my $result = $session->set_request(
2097         -varbindlist => [$oid_search, INTEGER, 8], # Only NEXANS
2098         );
2099      print $session->error()."\n" if $session->error_status();
2100
2101      $session->close;
2102      }
2103   cmd_poe_status($switch_name, $switch_port);
2104   return;
2105   }
2106
2107#---------------------------------------------------------------
2108sub cmd_poe_disable {
2109   @ARGV = @_;
2110
2111   my $verbose;
2112   GetOptions(
2113      'verbose|v' => \$verbose,
2114      );
2115
2116   my $switch_name = shift @ARGV || q{};
2117   my $switch_port = shift @ARGV || q{};
2118
2119   if ($switch_name eq q{} or $switch_port eq q{}) {
2120      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
2121      }
2122
2123   for my $sw_name (split /,/, $switch_name) {
2124      if (not defined $SWITCH_DB{$sw_name}) {
2125         die "Switch $sw_name must be defined in klask configuration file\n";
2126         }
2127
2128      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2129
2130      my $sw = $SWITCH_DB{$sw_name};
2131      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2132      print "$error \n" if $error;
2133
2134      my $result = $session->set_request(
2135         -varbindlist => [$oid_search, INTEGER, 2], # Only NEXANS
2136         );
2137      print $session->error()."\n" if $session->error_status();
2138
2139      $session->close;
2140      }
2141   cmd_poe_status($switch_name, $switch_port);
2142   return;
2143   }
2144
2145#---------------------------------------------------------------
2146sub cmd_poe_status {
2147   @ARGV = @_;
2148
2149   my $verbose;
2150   GetOptions(
2151      'verbose|v' => \$verbose,
2152      );
2153
2154   my $switch_name = shift @ARGV || q{};
2155   my $switch_port = shift @ARGV || q{};
2156
2157   if ($switch_name eq q{} or $switch_port eq q{}) {
2158      die "Usage: klask poe-status SWITCH_NAME PORT\n";
2159      }
2160
2161   for my $sw_name (split /,/, $switch_name) {
2162      if (not defined $SWITCH_DB{$sw_name}) {
2163         die "Switch $sw_name must be defined in klask configuration file\n";
2164         }
2165
2166      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2167
2168      my $sw = $SWITCH_DB{$sw_name};
2169      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2170      print "$error \n" if $error;
2171
2172      my $result = $session->get_request(
2173         -varbindlist => [$oid_search],
2174         );
2175
2176      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2177         my $poe_status = $result->{$oid_search} || 'empty';
2178         $poe_status =~ s/8/enable/;
2179         $poe_status =~ s/2/disable/;
2180         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe_status;
2181         }
2182      else {
2183         print "Klask do not find PoE status on switch $sw_name on port $switch_port\n";
2184         }
2185
2186      $session->close;
2187      }
2188   return;
2189   }
2190
2191#---------------------------------------------------------------
2192# not finish - do not use
2193sub cmd_port_setvlan {
2194   my $switch_name = shift || q{};
2195   my $mac_address = shift || q{};
2196
2197   if ($switch_name eq q{} or $mac_address eq q{}) {
2198      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2199      }
2200
2201   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2202
2203   for my $sw_name (split /,/, $switch_name) {
2204      if (not defined $SWITCH_DB{$sw_name}) {
2205         die "Switch $sw_name must be defined in klask configuration file\n";
2206         }
2207
2208      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2209      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. 0 . mac_address_hex2dec($mac_address);
2210      print "Klask search OID $oid_search_port1 on switch $sw_name\n";
2211      print "Klask search OID $oid_search_port2 on switch $sw_name\n";
2212
2213      my $sw = $SWITCH_DB{$sw_name};
2214      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2215      print "$error \n" if $error;
2216
2217      my $result = $session->get_request(
2218         -varbindlist => [$oid_search_port1]
2219         );
2220      if (not defined $result) {
2221         $result = $session->get_request(
2222            -varbindlist => [$oid_search_port2]
2223            );
2224         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
2225         }
2226
2227      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
2228         my $swport = $result->{$oid_search_port1};
2229         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
2230         }
2231      else {
2232         print "Klask do not find MAC $mac_address on switch $sw_name\n";
2233         }
2234
2235      $session->close;
2236      }
2237   return;
2238   }
2239
2240#---------------------------------------------------------------
2241sub cmd_port_getvlan {
2242   @ARGV = @_;
2243
2244   my $verbose;
2245   GetOptions(
2246      'verbose|v' => \$verbose,
2247      );
2248
2249   my $switch_name = shift @ARGV || q{};
2250   my $switch_port = shift @ARGV || q{};
2251
2252   if ($switch_name eq q{} or $switch_port eq q{}) {
2253      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
2254      }
2255
2256   for my $sw_name (split /,/, $switch_name) {
2257      if (not defined $SWITCH_DB{$sw_name}) {
2258         die "Switch $sw_name must be defined in klask configuration file\n";
2259         }
2260
2261      my $oid_search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
2262
2263      my $sw = $SWITCH_DB{$sw_name};
2264      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2265      print "$error \n" if $error;
2266
2267      my $result = $session->get_request(
2268         -varbindlist => [$oid_search],
2269         );
2270
2271      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2272         my $vlan_id = $result->{$oid_search} || 'empty';
2273         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2274         }
2275      else {
2276         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
2277         }
2278
2279      $session->close;
2280      }
2281   return;
2282   }
2283
2284#---------------------------------------------------------------
2285sub cmd_vlan_setname {
2286   }
2287
2288#---------------------------------------------------------------
2289# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
2290sub cmd_rebootsw {
2291   @ARGV = @_;
2292
2293   my $verbose;
2294   GetOptions(
2295      'verbose|v' => \$verbose,
2296      );
2297
2298   my $switch_name = shift @ARGV || q{};
2299
2300   if ($switch_name eq q{}) {
2301      die "Usage: klask rebootsw SWITCH_NAME\n";
2302      }
2303
2304   for my $sw_name (split /,/, $switch_name) {
2305      if (not defined $SWITCH_DB{$sw_name}) {
2306         die "Switch $sw_name must be defined in klask configuration file\n";
2307         }
2308
2309      my $sw = $SWITCH_DB{$sw_name};
2310      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2311      print "$error \n" if $error;
2312
2313      my $result = $session->set_request(
2314         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
2315         );
2316
2317      $session->close;
2318      }
2319   return;
2320   }
2321
2322#---------------------------------------------------------------
2323sub cmd_vlan_getname {
2324   my $switch_name = shift || q{};
2325   my $vlan_id     = shift || q{};
2326
2327   if ($switch_name eq q{} or $vlan_id eq q{}) {
2328      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
2329      }
2330
2331   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2332
2333   for my $sw_name (split /,/, $switch_name) {
2334      if (not defined $SWITCH_DB{$sw_name}) {
2335         die "Switch $sw_name must be defined in klask configuration file\n";
2336         }
2337
2338      my $oid_search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
2339
2340      my $sw = $SWITCH_DB{$sw_name};
2341      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2342      print "$error \n" if $error;
2343
2344      my $result = $session->get_request(
2345         -varbindlist => [$oid_search_vlan_name]
2346         );
2347
2348      if (defined $result and $result->{$oid_search_vlan_name} ne 'noSuchInstance') {
2349         my $vlan_name = $result->{$oid_search_vlan_name} || 'empty';
2350         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
2351         }
2352      else {
2353         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2354         }
2355
2356      $session->close;
2357      }
2358   return;
2359   }
2360
2361#---------------------------------------------------------------
2362sub cmd_vlan_list {
2363   my $switch_name = shift || q{};
2364
2365   if ($switch_name eq q{}) {
2366      die "Usage: klask vlan-list SWITCH_NAME\n";
2367      }
2368
2369   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2370
2371   for my $sw_name (split /,/, $switch_name) {
2372      if (not defined $SWITCH_DB{$sw_name}) {
2373         die "Switch $sw_name must be defined in klask configuration file\n";
2374         }
2375
2376      my $sw = $SWITCH_DB{$sw_name};
2377      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2378      print "$error \n" if $error;
2379
2380      my %vlandb = snmp_get_vlan_list($session);
2381      $session->close;
2382
2383      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2384      for my $vlan_id (keys %vlandb) {
2385         printf "%7i - %s\n", $vlan_id, $vlandb{$vlan_id};
2386         }
2387      }
2388   return;
2389   }
2390
2391#---------------------------------------------------------------
2392sub cmd_ip_location {
2393   my $computerdb = computerdb_load();
2394
2395   LOOP_ON_IP_ADDRESS:
2396   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
2397
2398      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2399
2400      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
2401      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
2402
2403      my $sw_location = q{};
2404      LOOP_ON_ALL_SWITCH:
2405      for my $sw (@SWITCH_LIST) {
2406         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2407         $sw_location = $sw->{'location'};
2408         last;
2409         }
2410
2411      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
2412      }
2413   return;
2414   }
2415
2416#---------------------------------------------------------------
2417sub cmd_ip_free {
2418   @ARGV = @_;
2419
2420   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
2421   my $format = 'txt';
2422   my $verbose;
2423
2424   GetOptions(
2425      'day|d=i'      => \$days_to_death,
2426      'format|f=s'   => \$format,
2427      'verbose|v'    => \$verbose,
2428      );
2429
2430   my %possible_format = (
2431      txt  => \&cmd_ip_free_txt,
2432      html => \&cmd_ip_free_html,
2433      none => sub {},
2434      );
2435   $format = 'txt' if not defined $possible_format{$format};
2436
2437   my @vlan_name = @ARGV;
2438   @vlan_name = get_list_network() if not @vlan_name;
2439
2440   my $computerdb = {};
2441      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
2442   my $timestamp = time;
2443
2444   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
2445
2446   my %result_ip = ();
2447
2448   ALL_NETWORK:
2449   for my $vlan (@vlan_name) {
2450
2451      my @ip_list = get_list_ip($vlan);
2452
2453      LOOP_ON_IP_ADDRESS:
2454      for my $ip (@ip_list) {
2455
2456         if (exists $computerdb->{$ip}) {
2457            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
2458
2459            my $mac_address = $computerdb->{$ip}{'mac_address'};
2460            LOOP_ON_DATABASE:
2461            for my $ip_db (keys %{$computerdb}) {
2462               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2463               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
2464               }
2465            }
2466
2467         my $ip_date_last_detection = '';
2468         if (exists $computerdb->{$ip}) {
2469            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
2470            $year += 1900;
2471            $mon++;
2472            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2473            }
2474
2475         my $packed_ip = scalar gethostbyname($ip);
2476         my $hostname_fq = 'unknown';
2477            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
2478
2479         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2480
2481         $result_ip{$ip} ||= {};
2482         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2483         $result_ip{$ip}->{'hostname_fq'} = $hostname_fq;
2484         $result_ip{$ip}->{'vlan'} = $vlan;
2485
2486         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
2487         }
2488      }
2489
2490   $possible_format{$format}->(%result_ip);
2491   }
2492
2493#---------------------------------------------------------------
2494sub cmd_ip_free_txt {
2495   my %result_ip = @_;
2496
2497   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2498   print "-------------------------------------------------------------------------------\n";
2499   LOOP_ON_IP_ADDRESS:
2500   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2501         my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2502         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
2503      }
2504   }
2505
2506#---------------------------------------------------------------
2507sub cmd_ip_free_html {
2508   my %result_ip = @_;
2509
2510   print <<'END_HTML';
2511<table class="sortable" summary="Klask Free IP Database">
2512 <caption>Klask Free IP Database</caption>
2513 <thead>
2514  <tr>
2515   <th scope="col" class="klask-header-left">IPv4-Address</th>
2516   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2517   <th scope="col" class="sorttable_alpha">VLAN</th>
2518   <th scope="col" class="klask-header-right">Date</th>
2519  </tr>
2520 </thead>
2521 <tfoot>
2522  <tr>
2523   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2524   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2525   <th scope="col" class="fklask-vlan">VLAN</th>
2526   <th scope="col" class="klask-footer-right">Date</th>
2527  </tr>
2528 </tfoot>
2529 <tbody>
2530END_HTML
2531
2532   my $typerow = 'even';
2533
2534   LOOP_ON_IP_ADDRESS:
2535   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2536
2537      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2538
2539      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
2540      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
2541
2542      my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2543
2544      print <<"END_HTML";
2545  <tr class="$typerow">
2546   <td sorttable_customkey="$ip_sort">$ip</td>
2547   <td sorttable_customkey="$host_short">$result_ip{$ip}->{'hostname_fq'}</td>
2548   <td>$vlan_nameid</td>
2549   <td>$result_ip{$ip}->{'date_last_detection'}</td>
2550  </tr>
2551END_HTML
2552      }
2553   print <<'END_HTML';
2554 </tbody>
2555</table>
2556END_HTML
2557   }
2558
2559#---------------------------------------------------------------
2560sub cmd_enable {
2561   @ARGV = @_;
2562
2563   my $verbose;
2564
2565   GetOptions(
2566      'verbose|v' => \$verbose,
2567      );
2568
2569   my $switch_name = shift @ARGV || q{};
2570   my $port_hr     = shift @ARGV || q{};
2571
2572   if ($switch_name eq q{} or $port_hr eq q{}) {
2573      die "Usage: klask disable SWITCH_NAME PORT\n";
2574      }
2575
2576   if (not defined $SWITCH_DB{$switch_name}) {
2577      die "Switch $switch_name must be defined in klask configuration file\n";
2578      }
2579
2580   my $sw = $SWITCH_DB{$switch_name};
2581   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2582   print "$error \n" if $error;
2583
2584   # Retrieve numeric port value
2585   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2586   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2587
2588   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2589   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2590
2591   my $result = $session->set_request(
2592      -varbindlist => [$oid_search_portstatus, INTEGER, 1],
2593      );
2594   print $session->error()."\n" if $session->error_status();
2595
2596   $session->close;
2597
2598   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2599   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2600   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2601   return;
2602   }
2603
2604#---------------------------------------------------------------
2605sub cmd_disable {
2606   @ARGV = @_;
2607
2608   my $verbose;
2609
2610   GetOptions(
2611      'verbose|v' => \$verbose,
2612      );
2613
2614   my $switch_name = shift @ARGV || q{};
2615   my $port_hr     = shift @ARGV || q{};
2616
2617   if ($switch_name eq q{} or $port_hr eq q{}) {
2618      die "Usage: klask disable SWITCH_NAME PORT\n";
2619      }
2620
2621   if (not defined $SWITCH_DB{$switch_name}) {
2622      die "Switch $switch_name must be defined in klask configuration file\n";
2623      }
2624
2625   my $sw = $SWITCH_DB{$switch_name};
2626   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2627   print "$error \n" if $error;
2628
2629   # Retrieve numeric port value
2630   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2631   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2632
2633   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2634   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2635
2636   my $result = $session->set_request(
2637      -varbindlist => [$oid_search_portstatus, INTEGER, 2],
2638      );
2639   print $session->error()."\n" if $session->error_status();
2640
2641   $session->close;
2642
2643   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2644   return;
2645   }
2646
2647#---------------------------------------------------------------
2648sub cmd_status {
2649   @ARGV = @_;
2650
2651   my $verbose;
2652
2653   GetOptions(
2654      'verbose|v' => \$verbose,
2655      );
2656
2657   my $switch_name = shift @ARGV || q{};
2658   my $port_hr     = shift @ARGV || q{};
2659
2660   if ($switch_name eq q{} or $port_hr eq q{}) {
2661      die "Usage: klask status SWITCH_NAME PORT\n";
2662      }
2663
2664   if (not defined $SWITCH_DB{$switch_name}) {
2665      die "Switch $switch_name must be defined in klask configuration file\n";
2666      }
2667
2668   my $sw = $SWITCH_DB{$switch_name};
2669   my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2670   print "$error \n" if $error;
2671
2672   # Retrieve numeric port value
2673   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2674   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2675
2676   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2677   print "Info: switch $switch_name port $port_hr ($port_id) SNMP OID $oid_search_portstatus\n" if $verbose;
2678
2679   my $result = $session->get_request(
2680      -varbindlist => [$oid_search_portstatus]
2681      );
2682   print $session->error()."\n" if $session->error_status();
2683   if (defined $result) {
2684      print "$PORT_UPDOWN{$result->{$oid_search_portstatus}}\n";
2685      }
2686
2687   $session->close;
2688
2689   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
2690   return;
2691   }
2692
2693#---------------------------------------------------------------
2694sub cmd_search_mac_on_switch {
2695   @ARGV = @_;
2696
2697   my $verbose;
2698   my $vlan_id = 0;
2699
2700   GetOptions(
2701      'verbose|v' => \$verbose,
2702      'vlan|l=i'  => \$vlan_id,
2703      );
2704
2705   my $switch_name = shift @ARGV || q{};
2706   my $mac_address = shift @ARGV || q{};
2707
2708   if ($switch_name eq q{} or $mac_address eq q{}) {
2709      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2710      }
2711
2712   $mac_address = normalize_mac_address($mac_address);
2713   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*} or $switch_name eq q{all};
2714
2715   for my $sw_name (split /,/, $switch_name) {
2716      if (not defined $SWITCH_DB{$sw_name}) {
2717         die "Switch $sw_name must be defined in klask configuration file\n";
2718         }
2719
2720      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2721      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
2722      print "Klask search OID $oid_search_port1 on switch $sw_name\n" if $verbose;
2723      print "Klask search OID $oid_search_port2 on switch $sw_name\n" if $verbose;
2724
2725      my $sw = $SWITCH_DB{$sw_name};
2726      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2727      print "$error \n" if $error;
2728
2729      my $result = $session->get_request(
2730         -varbindlist => [$oid_search_port1]
2731         );
2732      if (not defined $result) {
2733         $result = $session->get_request(
2734            -varbindlist => [$oid_search_port2]
2735            );
2736         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
2737         }
2738
2739      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
2740         my $swport_id = $result->{$oid_search_port1};
2741         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
2742         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
2743         }
2744      else {
2745         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
2746         }
2747
2748      $session->close;
2749      }
2750   return;
2751   }
2752
2753#---------------------------------------------------------------
2754sub cmd_updatesw {
2755   @ARGV = @_;
2756
2757   my $verbose;
2758
2759   GetOptions(
2760      'verbose|v' => \$verbose,
2761      );
2762   
2763   update_switchdb(verbose => $verbose);
2764   }
2765
2766#---------------------------------------------------------------
2767sub cmd_exportsw {
2768   @ARGV = @_;
2769
2770   test_switchdb_environnement();
2771
2772   my $format = 'txt';
2773   my $graph_modulo = 0;
2774   my $graph_shift  = 1;
2775
2776   GetOptions(
2777      'format|f=s'  => \$format,
2778      'modulo|m=i'  => \$graph_modulo,
2779      'shift|s=i'   => \$graph_shift,
2780      );
2781
2782   my %possible_format = (
2783      txt => \&cmd_exportsw_txt,
2784      dot => \&cmd_exportsw_dot,
2785      );
2786
2787   $format = 'txt' if not defined $possible_format{$format};
2788
2789   $possible_format{$format}->($graph_modulo, $graph_shift, @ARGV);
2790   return;
2791   }
2792
2793#---------------------------------------------------------------
2794sub cmd_exportsw_txt {
2795
2796   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2797
2798   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2799   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2800   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2801
2802   print "Switch output port and parent port connection\n";
2803   print "---------------------------------------------\n";
2804   for my $sw (sort keys %db_switch_output_port) {
2805      my $arrow ='-->';
2806         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2807      if (exists $db_switch_parent{$sw}) {
2808         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
2809         }
2810      else {
2811         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
2812         }
2813      }
2814   print "\n";
2815
2816   print "Switch parent and children port inter-connection\n";
2817   print "------------------------------------------------\n";
2818   for my $swport (sort keys %db_switch_connected_on_port) {
2819      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2820      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2821         my $arrow ='<--';
2822            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
2823         if (exists $db_switch_output_port{$sw}) {
2824            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
2825            }
2826         else {
2827            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
2828            }
2829         }
2830      }
2831   return;
2832   }
2833
2834#---------------------------------------------------------------
2835sub cmd_exportsw_dot {
2836   my $graph_modulo = shift;
2837   my $graph_shift  = shift;
2838
2839   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2840
2841   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2842   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2843   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2844   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
2845   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
2846   my $timestamp                   =   $switch_connection->{'timestamp'};
2847
2848   my $invisible_node = 0; # Count number of invisible node
2849
2850   my %db_building= ();
2851   my %db_switch_line = (); # Number of line drawed on a switch
2852   for my $sw (values %db_switch_global) {
2853      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
2854      $db_building{$building} ||= {};
2855      $db_building{$building}->{$location} ||= {};
2856      $db_building{$building}->{$location}{ $sw->{'hostname'} } = 'y';
2857
2858      $db_switch_line{$sw} = 0;
2859      }
2860
2861
2862   print "digraph G {\n";
2863   print "rankdir=LR;\n";
2864   #print "splines=polyline;\n";
2865
2866   print "site [label=\"site\", color=black, fillcolor=gold, shape=invhouse, style=filled];\n";
2867   print "internet [label=\"internet\", color=black, fillcolor=cyan, shape=house, style=filled];\n";
2868
2869   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
2870   $year += 1900;
2871   $mon++;
2872   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2873   print "\"$date\" [label=\"MAP DATE\\n\\n$date\", color=white, fillcolor=black, shape=polygon, sides=14, style=filled, fontcolor=white];\n";
2874   print "site -> \"$date\" [style=invis];\n";
2875
2876   my $b=0;
2877   for my $building (keys %db_building) {
2878      $b++;
2879
2880      print "\"building$b\" [label=\"$building\", color=black, fillcolor=gold, style=filled];\n";
2881      print "site -> \"building$b\" [len=2, color=firebrick];\n";
2882
2883      my $l = 0;
2884      for my $loc (keys %{$db_building{$building}}) {
2885         $l++;
2886
2887         print "\"location$b-$l\" [label=\"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color=black, fillcolor=orange, style=filled];\n";
2888#         print "\"location$b-$l\" [label=\"$building / $loc\", color=black, fillcolor=orange, style=filled];\n";
2889         print "\"building$b\" -> \"location$b-$l\" [len=2, color=firebrick];\n";
2890
2891         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2892
2893            my $peripheries = 1;
2894            my $color = 'lightblue';
2895            if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2896               $peripheries = 2;
2897               $color = "\"$color:$color\"";
2898               }
2899            print "\"$sw:$db_switch_output_port{$sw}\" [label=\"".format_aggregator4dot($db_switch_output_port{$sw})."\", color=black, fillcolor=lightblue, peripheries=$peripheries, style=filled];\n";
2900
2901            my $swname  = $sw;
2902               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2903            print "\"$sw\" [label=\"$swname\", color=black, fillcolor=palegreen, shape=rect, style=filled];\n";
2904            print "\"location$b-$l\" -> \"$sw\" [len=2, color=firebrick, arrowtail=dot];\n";
2905            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=invdot];\n";
2906
2907
2908            for my $swport (keys %db_switch_connected_on_port) {
2909               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2910               next if not $sw_connect eq $sw;
2911               next if $port_connect eq $db_switch_output_port{$sw};
2912               my $peripheries = 1;
2913               my $color = 'plum';
2914               if ($port_connect =~ m/^(Trk|Br|Po)/) {
2915                  $peripheries = 2;
2916                  $color = "\"$color:$color\"";
2917                  }
2918               print "\"$sw:$port_connect\" [label=\"".format_aggregator4dot($port_connect)."\", color=black, fillcolor=plum, peripheries=$peripheries, style=filled];\n";
2919               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=inv];\n";
2920
2921               #$db_switch_line{$sw}++;
2922               #if ($db_switch_line{$sw} % 9 == 0) {
2923               #   # Create invisible node
2924               #   $invisible_node++;
2925               #   my $invisible = '__Invisible_' . $invisible_node;
2926               #   print "$invisible [shape=none, label=\"\"]\n";
2927               #   print "\"$sw:$port_connect\" -> $invisible [style=invis]\n";
2928               #   print "$invisible            -> \"$sw\"    [style=invis]\n";
2929               #   }
2930              }
2931            }
2932         }
2933      }
2934
2935#   print "Switch output port and parent port connection\n";
2936#   print "---------------------------------------------\n";
2937   for my $sw (sort keys %db_switch_output_port) {
2938      if (exists $db_switch_parent{$sw}) {
2939#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port_id'};
2940         }
2941      else {
2942         my $style = 'solid';
2943         my $color = 'black'; # navyblue
2944         if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2945            $style = 'bold';
2946            $color = "\"$color:invis:$color\"";
2947            }
2948         printf "   \"%s:%s\" -> internet [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw};
2949         }
2950      }
2951   print "\n";
2952
2953#   print "Switch parent and children port inter-connection\n";
2954#   print "------------------------------------------------\n";
2955   for my $swport (sort keys %db_switch_connected_on_port) {
2956      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2957      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2958         my $style = 'solid';
2959         my $color = 'black'; # navyblue
2960         if ($port_connect =~ m/^(Trk|Br|Po)/) {
2961            $style = 'bold';
2962            $color = "\"$color:invis:$color\"";
2963            }
2964         if (exists $db_switch_output_port{$sw}) {
2965            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2966
2967            next if $graph_modulo == 0; # No shift (invisible nodes) in graph
2968            $db_switch_line{$sw_connect}++;
2969            if ($db_switch_line{$sw_connect} % $graph_modulo == 0) {
2970               # Create invisible node
2971               $invisible_node++;
2972               my $invisible = '__Invisible_' . $invisible_node;
2973               print  "   \"$invisible.a\" [shape=none, label=\"\"];\n";
2974               printf "   \"%s:%s\"  -> \"$invisible.a\" [style=invis];\n", $sw, $db_switch_output_port{$sw};
2975               if ($graph_shift == 2) {
2976                  # Two invisible node
2977                  print  "   \"$invisible.b\" [shape=none, label=\"\"];\n";
2978                  print  "   \"$invisible.a\" -> \"$invisible.b\" [style=invis];\n";
2979                  printf "   \"$invisible.b\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
2980                  }
2981               else {
2982                  # One invisible node
2983                  printf "   \"$invisible.a\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
2984                  }
2985               }
2986            }
2987         else {
2988            printf "   \"%s\"   -> \"%s:%s\" [style=$style];\n", $sw, $sw_connect, $port_connect;
2989            }
2990         }
2991      }
2992
2993print "}\n";
2994   return;
2995   }
2996
2997
2998################################################################
2999# documentation
3000################################################################
3001
3002__END__
3003
3004=head1 NAME
3005
3006klask - port and search manager for switches, map management
3007
3008
3009=head1 USAGE
3010
3011 klask version
3012 klask help
3013
3014 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
3015 klask exportdb [--format|-f txt|html]
3016 klask removedb IP* computer*
3017 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3018
3019 klask updatesw [--verbose|-v]
3020 klask exportsw [--format|-f txt|dot]
3021
3022 klask searchdb [--kind|-k host|mac] computer [mac-address]
3023 klask search   computer
3024 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3025
3026 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3027
3028 klask bad-vlan-id [--day|-d days_before_alert]
3029
3030 klask enable  [--verbose|-v] switch port
3031 klask disable [--verbose|-v] switch port
3032 klask status  [--verbose|-v] switch port
3033
3034 klask poe-enable  [--verbose|-v] switch port
3035 klask poe-disable [--verbose|-v] switch port
3036 klask poe-status  [--verbose|-v] switch port
3037
3038 klask vlan-getname switch vlan-id
3039 klask vlan-list switch
3040
3041
3042=head1 DESCRIPTION
3043
3044Klask is a small tool to find where is connected a host in a big network
3045and on which VLAN.
3046Klask mean search in brittany.
3047No hight level protocol like CDL, LLDP are use.
3048Everything is just done with SNMP request on MAC address.
3049
3050Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
3051If you use PVST or MSTP and create loop between VLAN,
3052you have to use C<portignore> functionality on switch port to cut manually loop
3053(see config file below).
3054
3055When you use a management port to administrate a switch,
3056it's not possible to create the map with this switch because it does not have a MAC address,
3057so other switch cannot find the real downlink port...
3058One way to work around this problem is, if you have a computer directly connected on the switch,
3059to put this IP as the fake ip for the switch.
3060The MAC address associated will be use just for the map detection.
3061The C<fake-ip> parameter is defined in the config file.
3062
3063Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
3064
3065
3066=head1 COMMANDS
3067
3068Some command are defined in the source code but are not documented here.
3069Theses could be not well defined, not finished, not well tested...
3070You can read the source code and use them at your own risk
3071(like for all the Klask code).
3072
3073=head2 search
3074
3075 klask search   computer
3076
3077This command takes one or more computer in argument.
3078It search a computer on the network and give the port and the switch on which the computer is connected.
3079
3080=head2 search-mac-on-switch
3081
3082 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3083
3084This command search a MAC address on a switch.
3085To search on all switch, you could put C<'*'> or C<all>.
3086The VLAN parameter could help.
3087
3088
3089=head2 enable
3090
3091 klask enable  [--verbose|-v] switch port
3092
3093This command activate a port (or an agrregate bridge port) on a switch by SNMP.
3094So you need to give the switch name and a port on the command line.
3095See L</ABBREVIATION FOR PORT>.
3096
3097Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3098
3099
3100=head2 disable
3101
3102 klask disable [--verbose|-v] switch port
3103
3104This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
3105So you need to give the switch name and a port on the command line.
3106See L</ABBREVIATION FOR PORT>.
3107
3108Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3109
3110
3111=head2 status
3112
3113 klask status  [--verbose|-v] switch port
3114
3115This command return the status of a port number on a switch by SNMP.
3116The return value could be C<enable> or C<disable> word.
3117So you need to give the switch name and a port on the command line.
3118See L</ABBREVIATION FOR PORT>.
3119
3120If it's not possible to change port status with command L</enable> and L</disable>
3121(SNMP community read write access),
3122it's always possible to have the port status even for bridge agrregate port.
3123
3124
3125=head2 updatedb
3126
3127 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
3128
3129This command will scan networks and update the computer database.
3130To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3131This file is easy to read and write because Klask use YAML format and not XML
3132(see L</CONFIGURATION>).
3133
3134Option are not stable and could be use manually when you have a new kind of switch.
3135Maybe some option will be transfered in a future C<checksw> command!
3136
3137The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3138By default, a network is C<active>.
3139This means that an C<fping> command is done at the beginning on all the IP of the network
3140and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3141will have an C<arping>
3142(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3143In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3144It's good for big subnet with few computer (telephone...).
3145The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3146
3147At the beginning, the command verify that the switch map checksum is always valid.
3148Otherwise, a rebuild procedure will ne done automatically.
3149
3150=head2 exportdb
3151
3152 klask exportdb [--format|-f txt|html]
3153
3154This command print the content of the computer database.
3155There is actually only two format : TXT and HTML.
3156By default, format is TXT.
3157It's very easy to have more format, it's just need times...
3158
3159=head2 removedb
3160
3161 klask removedb IP* computer*
3162
3163This command remove an entry in the database.
3164There is only one kind of parameter, the IP of the computers to be removed.
3165You can put as many IP as you want...
3166
3167Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
3168
3169=head2 cleandb
3170
3171 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3172
3173Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3174Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3175This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3176This functionality could be use when computer define in VLAN 1
3177could have a float IP when they are connected on VLAN 2.
3178In the Klask database, the float DNS entries are less important.
3179
3180When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
3181
3182=head2 updatesw
3183
3184 klask updatesw [--verbose|-v]
3185
3186This command build a map of your manageable switch on your network.
3187The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3188
3189The database has a checksum which depend of all the active switches.
3190It's use when rebuilding the database in case of change in switch configuration (one more for example).
3191
3192=head2 exportsw
3193
3194 klask exportsw [--format|-f txt|dot]
3195
3196This command print the content of the switch database. There is actually two format.
3197One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3198By default, format is TXT.
3199
3200 klask exportsw --format dot > /tmp/map.dot
3201 dot -Tpng /tmp/map.dot > /tmp/map.png
3202
3203
3204=head2 ip-free
3205
3206 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3207
3208This command return IP address that was not use (detected by Klask) at this time.
3209The list returned could be limited to just one VLAN.
3210IP returned could have been never used or no computer have been detected since the number of days specified
3211(2 years by default).
3212This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3213
3214 default:
3215   days-to-death: 730
3216
3217Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3218
3219
3220=head2 bad-vlan-id
3221
3222 klask bad-vlan-id [--day|-d days_before_alert]
3223
3224This command return a list of switch port that are not configure with the good VLAN.
3225Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3226and another prior trace where they had the good IP (good DNS name).
3227The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
3228This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3229
3230 default:
3231   days-before-alert: 15
3232
3233This functionality is not need if your switch use RADIUS 802.1X configuration...
3234
3235
3236=head2 poe-enable
3237
3238 klask poe-enable  [--verbose|-v] switch port
3239
3240This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3241So you need to give the switch name and a port on the command line.
3242See L</ABBREVIATION FOR PORT>.
3243
3244Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3245You need to have the SNMP write access on the switch in order to modify it's configuration.
3246
3247
3248=head2 poe-disable
3249
3250 klask poe-disable [--verbose|-v] switch port
3251
3252This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3253So you need to give the switch name and a port on the command line.
3254See L</ABBREVIATION FOR PORT>.
3255
3256Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3257You need to have the SNMP write access on the switch in order to modify it's configuration.
3258
3259
3260=head2 poe-status
3261
3262 klask poe-status  [--verbose|-v] switch port
3263
3264This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3265The return value could be C<enable> or C<disable> word.
3266So you need to give the switch name and a port on the command line.
3267See L</ABBREVIATION FOR PORT>.
3268
3269If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3270(SNMP community read write access),
3271it's always possible to have the PoE port status.
3272
3273Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3274
3275
3276=head1 CONFIGURATION
3277
3278Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
3279The configuration is done in a F</etc/klask/klask.conf> YAML file.
3280This format have many advantage over XML, it's easier to read and to write !
3281
3282Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3283
3284 default:
3285   community: public
3286   community-rw: private
3287   snmpport: 161
3288   float-regex: '(?^msx: ^float )'
3289   scan-mode: active
3290
3291 network:
3292   labnet:
3293     ip-subnet:
3294       - add: 192.168.1.0/24
3295       - add: 192.168.2.0/24
3296     interface: eth0
3297     vlan-id: 12
3298     main-router: gw1.labnet.local
3299
3300   schoolnet:
3301     ip-subnet:
3302       - add: 192.168.3.0/24
3303       - add: 192.168.4.0/24
3304     interface: eth0.38
3305     vlan-id: 13
3306     main-router: gw2.schoolnet.local
3307     scan-mode: passive
3308
3309   etunet:
3310     ip-subnet:
3311       - add: 192.168.5.0/24
3312     interface: eth2
3313     vlan-id: 14
3314     main-router: gw3.etunet.local
3315     scan-mode: passive
3316
3317 switch:
3318   - hostname: sw1.klask.local
3319     location: BatY / 1 floor / K004
3320     portignore:
3321       - 1
3322       - 2
3323
3324   - hostname: sw2.klask.local
3325     location: BatY / 2 floor / K203
3326     type: HP2424
3327     portignore:
3328       - 1
3329       - 2
3330     fake-ip: 192.168.9.14
3331
3332   - hostname: sw3.klask.local
3333     location: BatY / 2 floor / K203
3334
3335I think it's pretty easy to understand.
3336The default section can be overide in any section, if parameter mean something in theses sections.
3337Network to be scan are define in the network section. You must put an add by network.
3338Maybe I will make a delete line to suppress specific computers.
3339The switch section define your switch.
3340You have to write the port number to ignore, this was important if your switchs are cascades
3341(right now, method C<updatesw> find them automatically)
3342and is still important if you have loop (with PVST or MSTP).
3343Just put the ports numbers between switch.
3344
3345The C<community> parameter is use to get SNMP data on switch.
3346It could be overload for each switch.
3347By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3348Some few command change the switch state as the commands L</enable> and L</disable>.
3349In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3350Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
3351
3352
3353=head1 ABBREVIATION FOR PORT
3354
3355HP Procurve and Nexans switches have a simplistic numbering scheme.
3356It's just number: 1, 2, 3... 24.
3357On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3358Nothing is done on theses ports names.
3359
3360On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3361followed by tree number.
3362In order to have short name,
3363we made the following rules:
3364
3365 Bridge-Aggregation     -> Br
3366 FastEthernet           -> Fa
3367 Forty-GigabitEthernet  -> Fo
3368 FortyGigabitEthernet   -> Fo
3369 GigabitEthernet        -> Gi
3370 Giga                   -> Gi
3371 Port-Channel           -> Po
3372 Ten-GigabitEthernet    -> Te
3373 TenGigabitEthernet     -> Te
3374 Ten                    -> Te
3375
3376All Klask command automatically normalize the port name on standart output
3377and also on input command line.
3378
3379In the case of use an aggregator port (Po, Tk, Br ...),
3380the real ports used are also return.
3381
3382
3383=head1 SWITCH SUPPORTED
3384
3385Here is a list of switches where Klask gives or gave (for old switches) good results.
3386We have only a few manageable switches to actually test Klask.
3387It is quite possible that switches from other brands will work just as well.
3388You just have to do a test on it and add the line of description that goes well in the source code.
3389Contact us for any additional information.
3390
3391In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3392This makes it possible to adjust the code names of the different manufacturers!
3393
3394HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3395J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3396J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F).
3397
3398BayStack: BayStack 350T HW(BS350T)
3399
3400Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3401
3402DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3403
3404H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3405
3406H3C: H3C5500
3407
34083COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
34093CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
34103CR17572-91, 3C17702-US, 3C17700.
3411
3412
3413=head1 FILES
3414
3415 /etc/klask/klask.conf
3416 /var/lib/klask/klaskdb
3417 /var/lib/klask/switchdb
3418
3419
3420=head1 SEE ALSO
3421
3422Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3423
3424=over
3425
3426=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
3427
3428=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3429
3430=back
3431
3432
3433=head1 VERSION
3434
3435$Id: klask 302 2017-10-04 18:48:28Z g7moreau $
3436
3437
3438=head1 AUTHOR
3439
3440Written by Gabriel Moreau, Grenoble - France
3441
3442
3443=head1 LICENSE AND COPYRIGHT
3444
3445GPL version 2 or later and Perl equivalent
3446
3447Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.