source: trunk/klask @ 301

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