source: trunk/klask @ 305

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