source: trunk/klask @ 297

Last change on this file since 297 was 297, checked in by g7moreau, 7 years ago
  • Forget one ( !
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 124.9 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 297 2017-10-04 16:26:32Z g7moreau $
6
7use strict;
8use warnings;
9use version; our $VERSION = qv('0.6.7');
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################################################################
885# command
886################################################################
887
888#---------------------------------------------------------------
889sub cmd_help {
890
891print <<'END';
892klask - port and search manager for switches, map management
893
894 klask version
895 klask help
896
897 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
898 klask exportdb [--format|-f txt|html]
899 klask removedb IP* computer*
900 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
901
902 klask updatesw [--verbose|-v]
903 klask exportsw [--format|-f txt|dot]
904
905 klask searchdb [--kind|-k host|mac] computer [mac-address]
906 klask search   computer
907 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
908
909 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
910
911 klask bad-vlan-id [--day|-d days_before_alert]
912
913 klask enable  [--verbose|-v] switch port
914 klask disable [--verbose|-v] switch port
915 klask status  [--verbose|-v] switch port
916
917 klask poe-enable  [--verbose|-v] switch port
918 klask poe-disable [--verbose|-v] switch port
919 klask poe-status  [--verbose|-v] switch port
920
921 klask vlan-getname switch vlan-id
922 klask vlan-list switch
923END
924   return;
925   }
926
927#---------------------------------------------------------------
928sub cmd_version {
929
930print <<'END';
931klask - port and search manager for switches, map management
932Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
933
934END
935   print ' $Id: klask 297 2017-10-04 16:26:32Z g7moreau $'."\n";
936   return;
937   }
938
939#---------------------------------------------------------------
940sub cmd_search {
941   my @computer = @_;
942
943   init_switch_names();    #nomme les switchs
944   fast_ping(@computer);
945
946   LOOP_ON_COMPUTER:
947   for my $clientname (@computer) {
948      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
949      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
950      my $vlan_id   = get_current_vlan_id($vlan_name);
951      my %where     = find_switch_port($resol_arp{'mac_address'}, '', $vlan_id); #retrouve l'emplacement
952
953      next LOOP_ON_COMPUTER if $where{'switch_description'} eq 'unknow' or $resol_arp{'hostname_fq'} eq 'unknow' or $resol_arp{'mac_address'} eq 'unknow';
954
955      printf '%-22s %2s %-30s %-15s %18s',
956         $where{'switch_hostname'},
957         $where{'switch_port_hr'},
958         $resol_arp{'hostname_fq'},
959         $resol_arp{'ipv4_address'},
960         $resol_arp{'mac_address'}."\n";
961      }
962   return;
963   }
964
965#---------------------------------------------------------------
966sub cmd_searchdb {
967   my @ARGV  = @_;
968
969   my $kind;
970
971   GetOptions(
972      'kind=s'   => \$kind,
973      );
974
975   my %possible_search = (
976      host  => \&cmd_searchdb_host,
977      mac   => \&cmd_searchdb_mac,
978      );
979
980   $kind = 'host' if not defined $possible_search{$kind};
981
982   $possible_search{$kind}->(@ARGV);
983   return;
984   }
985
986
987#---------------------------------------------------------------
988sub cmd_searchdb_host {
989   my @computer = @_;
990
991   fast_ping(@computer);
992   my $computerdb = computerdb_load();
993
994   LOOP_ON_COMPUTER:
995   for my $clientname (@computer) {
996      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
997      my $ip = $resol_arp{'ipv4_address'};
998
999      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
1000
1001      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1002      $year += 1900;
1003      $mon++;
1004      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1005
1006      printf "%-22s %2s %-30s %-15s %-18s %s\n",
1007         $computerdb->{$ip}{'switch_hostname'},
1008         $computerdb->{$ip}{'switch_port_hr'},
1009         $computerdb->{$ip}{'hostname_fq'},
1010         $ip,
1011         $computerdb->{$ip}{'mac_address'},
1012         $date;
1013      }
1014   return;
1015   }
1016
1017#---------------------------------------------------------------
1018sub cmd_searchdb_mac {
1019   my @mac = map { normalize_mac_address($_) } @_;
1020
1021   my $computerdb = computerdb_load();
1022
1023   LOOP_ON_MAC:
1024   for my $mac (@mac) {
1025      LOOP_ON_COMPUTER:
1026      for my $ip (keys %{$computerdb}) {
1027         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{'mac_address'};
1028
1029         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1030         $year += 1900;
1031         $mon++;
1032         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1033
1034         printf "%-22s %2s %-30s %-15s %-18s %s\n",
1035            $computerdb->{$ip}{'switch_hostname'},
1036            $computerdb->{$ip}{'switch_port_hr'},
1037            $computerdb->{$ip}{'hostname_fq'},
1038            $ip,
1039            $computerdb->{$ip}{'mac_address'},
1040            $date;
1041         #next LOOP_ON_MAC;
1042         }
1043
1044      }
1045   return;
1046   }
1047
1048#---------------------------------------------------------------
1049sub cmd_updatedb {
1050   @ARGV = @_;
1051
1052   my ($verbose, $verb_description, $check_hostname, $check_location);
1053
1054   GetOptions(
1055      'verbose|v'          => \$verbose,
1056      'verb-description|d' => \$verb_description,
1057      'chk-hostname|h'     => \$check_hostname,
1058      'chk-location|l'     => \$check_location,
1059      );
1060
1061   my @network = @ARGV;
1062      @network = get_list_network() if not @network;
1063
1064   test_switchdb_environnement();
1065
1066   my $computerdb = {};
1067      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
1068   my $timestamp = time;
1069
1070   my %computer_not_detected = ();
1071   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
1072
1073   my $number_of_computer = get_list_ip(@network); # + 1;
1074   my $size_of_database   = keys %{$computerdb};
1075      $size_of_database   = 1 if $size_of_database == 0;
1076   my $i = 0;
1077   my $detected_computer = 0;
1078
1079   init_switch_names('yes', $verb_description, $check_hostname, $check_location);    #nomme les switchs
1080
1081   {
1082   my $switch_checksum = get_switchdb_checksum(%SWITCH_DB);
1083   # Remplis le champs portignore des ports d'inter-connection pour chaque switch
1084   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1085   if ($switch_checksum ne $switch_connection->{'checksum'}) { # Verify checksum
1086      print "ERROR: switch database must be rebuild\n";
1087      }
1088
1089   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1090   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1091   my %db_switch_chained_port = ();
1092   for my $swport (keys %db_switch_connected_on_port) {
1093      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1094      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
1095      }
1096   for my $sw (@SWITCH_LIST) {
1097      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{'hostname'}}  if exists $db_switch_output_port{$sw->{'hostname'}};
1098      if ( exists $db_switch_chained_port{$sw->{'hostname'}} ) {
1099         chop $db_switch_chained_port{$sw->{'hostname'}};
1100         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{'hostname'}};
1101         }
1102#      print "$sw->{'hostname'} ++ @{$sw->{portignore}}\n";
1103      }
1104   }
1105
1106   my %router_mac_ip = ();
1107   DETECT_ALL_ROUTER:
1108#   for my $one_router ('194.254.66.254') {
1109   for my $one_router ( get_list_main_router(@network) ) {
1110      my %resol_arp = resolve_ip_arp_host($one_router);
1111      $router_mac_ip{ $resol_arp{'mac_address'} } = $resol_arp{'ipv4_address'};
1112      }
1113
1114   ALL_NETWORK:
1115   for my $current_net (@network) {
1116
1117      my @computer = get_list_ip($current_net);
1118      my $current_interface = get_current_interface($current_net);
1119
1120      fast_ping(@computer) if get_current_scan_mode($current_net) eq 'active';
1121
1122      LOOP_ON_COMPUTER:
1123      for my $one_computer (@computer) {
1124         $i++;
1125
1126         my $total_percent = int (($i*100)/$number_of_computer);
1127
1128         my $localtime = time - $timestamp;
1129         my ($sec,$min) = localtime $localtime;
1130
1131         my $time_elapse = 0;
1132            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
1133         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
1134
1135         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
1136         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
1137         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
1138         printf ' %-8s %-14s', $current_interface, $one_computer;
1139
1140         my $already_exist = exists $computerdb->{$one_computer} ? 'yes' : 'no';
1141         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface, 'fast', $already_exist);
1142
1143         # do not search on router connection (why ?)
1144         if ( exists $router_mac_ip{$resol_arp{'mac_address'}}) {
1145            $computer_not_detected{$one_computer} = $current_net;
1146            next LOOP_ON_COMPUTER;
1147            }
1148
1149         # do not search on switch inter-connection
1150         if (exists $SWITCH_LEVEL{$resol_arp{'hostname_fq'}}) {
1151            $computer_not_detected{$one_computer} = $current_net;
1152            next LOOP_ON_COMPUTER;
1153            }
1154
1155         my $switch_proposal = q{};
1156         if (exists $computerdb->{$resol_arp{'ipv4_address'}} and exists $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'}) {
1157            $switch_proposal = $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'};
1158            }
1159
1160         # do not have a mac address
1161         if ($resol_arp{'mac_address'} eq 'unknow' or (exists $resol_arp{'timestamps'} and $resol_arp{'timestamps'} < ($timestamp - 3 * 3600))) {
1162            $computer_not_detected{$one_computer} = $current_net;
1163            next LOOP_ON_COMPUTER;
1164            }
1165
1166         my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1167         my $vlan_id   = get_current_vlan_id($vlan_name);
1168         my %where = find_switch_port($resol_arp{'mac_address'}, $switch_proposal, $vlan_id);
1169
1170         #192.168.24.156:
1171         #  arp: 00:0B:DB:D5:F6:65
1172         #  hostname: pcroyon.hmg.priv
1173         #  port: 5
1174         #  switch: sw-batH-legi:hp2524
1175         #  timestamp: 1164355525
1176
1177         # do not have a mac address
1178#         if ($resol_arp{'mac_address'} eq 'unknow') {
1179#            $computer_not_detected{$one_computer} = $current_interface;
1180#            next LOOP_ON_COMPUTER;
1181#            }
1182
1183         # detected on a switch
1184         if ($where{'switch_description'} ne 'unknow') {
1185            $detected_computer++;
1186            $computerdb->{$resol_arp{'ipv4_address'}} = {
1187               hostname_fq        => $resol_arp{'hostname_fq'},
1188               mac_address        => $resol_arp{'mac_address'},
1189               switch_hostname    => $where{'switch_hostname'},
1190               switch_description => $where{'switch_description'},
1191               switch_port_id     => $where{'switch_port_id'},
1192               switch_port_hr     => $where{'switch_port_hr'},
1193               timestamp          => $timestamp,
1194               network            => $current_net,
1195               };
1196            next LOOP_ON_COMPUTER;
1197            }
1198
1199         # new in the database but where it is ?
1200         if (not exists $computerdb->{$resol_arp{'ipv4_address'}}) {
1201            $detected_computer++;
1202            $computerdb->{$resol_arp{'ipv4_address'}} = {
1203               hostname_fq        => $resol_arp{'hostname_fq'},
1204               mac_address        => $resol_arp{'mac_address'},
1205               switch_hostname    => $where{'switch_hostname'},
1206               switch_description => $where{'switch_description'},
1207               switch_port_id     => $where{'switch_port_id'},
1208               switch_port_hr     => $where{'switch_port_hr'},
1209               timestamp          => $resol_arp{'timestamp'},
1210               network            => $current_net,
1211               };
1212            }
1213
1214         # mise a jour du nom de la machine si modification dans le dns
1215         $computerdb->{$resol_arp{'ipv4_address'}}{'hostname_fq'} = $resol_arp{'hostname_fq'};
1216
1217         # mise à jour de la date de détection si détection plus récente par arpwatch
1218         $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'}   = $resol_arp{'timestamp'} if exists $resol_arp{'timestamp'} and $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $resol_arp{'timestamp'};
1219
1220         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
1221#         push @computer_not_detected, $resol_arp{'ipv4_address'} if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1222         $computer_not_detected{$resol_arp{'ipv4_address'}} = $current_net if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1223
1224         }
1225      }
1226
1227   # final end of line at the end of the loop
1228   printf "\n";
1229
1230   my $dirdb = $KLASK_DB_FILE;
1231      $dirdb =~ s{ / [^/]* $}{}xms;
1232   mkdir "$dirdb", 0755 unless -d "$dirdb";
1233   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1234
1235   for my $one_computer (keys %computer_not_detected) {
1236      my $current_net = $computer_not_detected{$one_computer};
1237      my $current_interface = get_current_interface($current_net);
1238      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';
1239      }
1240   return;
1241   }
1242
1243#---------------------------------------------------------------
1244sub cmd_removedb {
1245   my @computer = @_;
1246
1247   test_maindb_environnement();
1248
1249   my $computerdb = computerdb_load();
1250
1251   LOOP_ON_COMPUTER:
1252   for my $one_computer (@computer) {
1253
1254      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
1255            and exists $computerdb->{$one_computer} ) {
1256         delete $computerdb->{$one_computer};
1257         next;
1258         }
1259
1260      my %resol_arp = resolve_ip_arp_host($one_computer);
1261
1262      delete $computerdb->{$resol_arp{'ipv4_address'}} if exists $computerdb->{$resol_arp{'ipv4_address'}};
1263      }
1264
1265   my $dirdb = $KLASK_DB_FILE;
1266      $dirdb =~ s{ / [^/]* $}{}xms;
1267   mkdir "$dirdb", 0755 unless -d "$dirdb";
1268   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1269   return;
1270   }
1271
1272#---------------------------------------------------------------
1273sub cmd_cleandb {
1274   my @ARGV  = @_;
1275
1276   my $days_to_clean = 15;
1277   my $repairdns;
1278   my $verbose;
1279   my $database_has_changed;
1280
1281   GetOptions(
1282      'day|d=i'   => \$days_to_clean,
1283      'verbose|v' => \$verbose,
1284      'repair-dns|r' => \$repairdns,
1285      );
1286
1287   my @vlan_name = get_list_network();
1288
1289   my $computerdb = computerdb_load();
1290   my $timestamp = time;
1291
1292   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
1293   my $timestamp_3month  = 3600 * 24 * 90;
1294
1295   my %mactimedb = ();
1296   ALL_VLAN:
1297   for my $vlan (shuffle @vlan_name) {
1298
1299      my @ip_list   = shuffle get_list_ip($vlan);
1300
1301      LOOP_ON_IP_ADDRESS:
1302      for my $ip (@ip_list) {
1303
1304         next LOOP_ON_IP_ADDRESS if
1305            not exists $computerdb->{$ip};
1306
1307            #&& $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
1308         my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1309         my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1310         my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1311
1312         $mactimedb{$ip_mac} ||= {
1313            ip          => $ip,
1314            timestamp   => $ip_timestamp,
1315            vlan        => $vlan,
1316            hostname_fq => $ip_hostname_fq,
1317            };
1318
1319         if (
1320            ( $mactimedb{$ip_mac}->{'timestamp'} - $ip_timestamp > $timestamp_barrier
1321               or (
1322                  $mactimedb{$ip_mac}->{'timestamp'} > $ip_timestamp
1323                  and $timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_3month
1324                  )
1325            )
1326            and (
1327               not $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1328               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1329               )) {
1330            print "remove ip $ip\n" if $verbose;
1331            delete $computerdb->{$ip};
1332            $database_has_changed++;
1333            }
1334
1335         elsif (
1336            ( $ip_timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_barrier
1337               or (
1338                  $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}
1339                  and $timestamp - $ip_timestamp > $timestamp_3month
1340                  )
1341            )
1342            and (
1343               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1344               or $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1345               )) {
1346            print "remove ip ".$mactimedb{$ip_mac}->{ip}."\n" if $verbose;
1347            delete $computerdb->{$mactimedb{$ip_mac}->{ip}};
1348            $database_has_changed++;
1349            }
1350
1351         if ( $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}) {
1352            $mactimedb{$ip_mac} = {
1353               ip          => $ip,
1354               timestamp   => $ip_timestamp,
1355               vlan        => $vlan,
1356               hostname_fq => $ip_hostname_fq,
1357               };
1358            }
1359         }
1360      }
1361
1362   if ($repairdns) { # Search and update unkown computer in reverse DNS
1363      LOOP_ON_IP_ADDRESS:
1364      for my $ip (keys %{$computerdb}) {
1365         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} ne 'unknow';
1366
1367         my $packed_ip = scalar gethostbyname($ip);
1368         next LOOP_ON_IP_ADDRESS if not defined $packed_ip;
1369
1370         my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
1371         next LOOP_ON_IP_ADDRESS if not defined $hostname_fq;
1372
1373         $computerdb->{$ip}{'hostname_fq'} = $hostname_fq;
1374         $database_has_changed++;
1375         }
1376      }
1377
1378   if ( $database_has_changed ) {
1379      my $dirdb = $KLASK_DB_FILE;
1380         $dirdb =~ s{ / [^/]* $}{}xms;
1381      mkdir "$dirdb", 0755 unless -d "$dirdb";
1382      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1383      }
1384   return;
1385   }
1386
1387#---------------------------------------------------------------
1388sub cmd_exportdb {
1389   @ARGV = @_;
1390
1391   my $format = 'txt';
1392
1393   GetOptions(
1394      'format|f=s'  => \$format,
1395      );
1396
1397   my %possible_format = (
1398      txt  => \&cmd_exportdb_txt,
1399      html => \&cmd_exportdb_html,
1400      );
1401
1402   $format = 'txt' if not defined $possible_format{$format};
1403
1404   $possible_format{$format}->(@ARGV);
1405   return;
1406   }
1407
1408#---------------------------------------------------------------
1409sub cmd_exportdb_txt {
1410   test_maindb_environnement();
1411
1412   my $computerdb = computerdb_load();
1413
1414   printf "%-28s %8s              %-40s %-15s %-18s %-16s %s\n", qw(Switch Port Hostname-FQ IPv4-Address MAC-Address Date VLAN);
1415   print "--------------------------------------------------------------------------------------------------------------------------------------------\n";
1416
1417   LOOP_ON_IP_ADDRESS:
1418   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1419
1420      # to be improve in the future
1421      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1422
1423      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1424      $year += 1900;
1425      $mon++;
1426      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1427
1428      my $vlan = '';
1429      $vlan = $computerdb->{$ip}{'network'}.'('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1430
1431      my $arrow ='<-----------';
1432         $arrow ='<===========' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1433
1434      printf "%-28s %8s %12s %-40s %-15s %-18s %-16s %s\n",
1435         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1436         $computerdb->{$ip}{'switch_port_hr'},
1437         $arrow,
1438         $computerdb->{$ip}{'hostname_fq'},
1439         $ip,
1440         $computerdb->{$ip}{'mac_address'},
1441         $date,
1442         $vlan;
1443      }
1444   return;
1445   }
1446
1447#---------------------------------------------------------------
1448sub cmd_exportdb_html {
1449   test_maindb_environnement();
1450
1451   my $computerdb = computerdb_load();
1452
1453#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1454#<script src="sorttable-klask.js"></script>
1455
1456   print <<'END_HTML';
1457<table class="sortable" summary="Klask Host Database">
1458 <caption>Klask Host Database</caption>
1459 <thead>
1460  <tr>
1461   <th scope="col" class="klask-header-left">Switch</th>
1462   <th scope="col" class="sorttable_nosort">Port</th>
1463   <th scope="col" class="sorttable_nosort">Link</th>
1464   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1465   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1466   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1467   <th scope="col" class="sorttable_alpha">VLAN</th>
1468   <th scope="col" class="klask-header-right">Date</th>
1469  </tr>
1470 </thead>
1471 <tfoot>
1472  <tr>
1473   <th scope="col" class="klask-footer-left">Switch</th>
1474   <th scope="col" class="fklask-port">Port</th>
1475   <th scope="col" class="fklask-link">Link</th>
1476   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1477   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1478   <th scope="col" class="fklask-mac">MAC-Address</th>
1479   <th scope="col" class="fklask-vlan">VLAN</th>
1480   <th scope="col" class="klask-footer-right">Date</th>
1481  </tr>
1482 </tfoot>
1483 <tbody>
1484END_HTML
1485
1486   my %mac_count = ();
1487   LOOP_ON_IP_ADDRESS:
1488   for my $ip (keys %{$computerdb}) {
1489
1490      # to be improve in the future
1491      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1492
1493      $mac_count{$computerdb->{$ip}{'mac_address'}}++;
1494      }
1495
1496   my $typerow = 'even';
1497
1498   LOOP_ON_IP_ADDRESS:
1499   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1500
1501      # to be improve in the future
1502      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1503
1504      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
1505      $year += 1900;
1506      $mon++;
1507      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1508
1509#      $odd_or_even++;
1510#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1511      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1512
1513      #my $arrow ='&#8592;';
1514      #   $arrow ='&#8656;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1515      my $arrow ='&#10229;';
1516         $arrow ='&#10232;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1517
1518      my $switch_hostname = $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'} || 'unkown';
1519      chomp $switch_hostname;
1520      my $switch_hostname_sort = sprintf '%s %06i' ,$switch_hostname, $computerdb->{$ip}{'switch_port_id'}; # Take switch index
1521
1522      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1523
1524      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{'mac_address'}}, $computerdb->{$ip}{'mac_address'};
1525
1526      $computerdb->{$ip}{'hostname_fq'} = 'unknow' if $computerdb->{$ip}{'hostname_fq'} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1527      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{'hostname_fq'};
1528
1529      my $vlan = '';
1530      $vlan = $computerdb->{$ip}{'network'}.' ('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1531
1532      my $parent_port_hr = format_aggregator4html($computerdb->{$ip}{'switch_port_hr'});
1533
1534      print <<"END_HTML";
1535  <tr class="$typerow">
1536   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1537   <td class="bklask-port">$parent_port_hr</td>
1538   <td>$arrow</td>
1539   <td sorttable_customkey="$host_short">$computerdb->{$ip}{'hostname_fq'}</td>
1540   <td sorttable_customkey="$ip_sort">$ip</td>
1541   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{'mac_address'}</td>
1542   <td>$vlan</td>
1543   <td>$date</td>
1544  </tr>
1545END_HTML
1546#   <td colspan="2">$arrow</td>
1547      }
1548
1549   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1550
1551   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1552   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1553   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1554   my %db_switch                   = %{$switch_connection->{'switch_db'}};
1555
1556   # Output switch connection
1557   LOOP_ON_OUTPUT_SWITCH:
1558   for my $sw (sort keys %db_switch_output_port) {
1559
1560      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1561
1562      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1563
1564      #my $arrow ='&#8702;';
1565      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1566      my $arrow ='&#10236;';
1567         $arrow ='&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1568
1569      if (exists $db_switch_parent{$sw}) {
1570         # Link to uplink switch
1571         next LOOP_ON_OUTPUT_SWITCH;
1572
1573         # Do not print anymore
1574         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1575         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1576         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
1577
1578         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1579         $year += 1900;
1580         $mon++;
1581         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1582
1583         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1584
1585         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1586
1587         my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'};
1588
1589         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1590         $vlan .= ' ('.get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}).')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1591
1592         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1593         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
1594
1595         print <<"END_HTML";
1596  <tr class="$typerow">
1597   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1598   <td class="bklask-port">$parent_port_hr</td>
1599   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
1600   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{'switch'}</td>
1601   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1602   <td sorttable_customkey="$mac_sort">$mac_address</td>
1603   <td>$vlan</td>
1604   <td>$date</td>
1605  </tr>
1606END_HTML
1607         }
1608      else {
1609         # Router
1610         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1611
1612         my $host_short = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1613
1614         my $mac_address = $db_switch{$sw}->{'mac_address'};
1615         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1616         my $timestamp = $db_switch{$sw}->{'timestamp'};
1617
1618         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1619         $year += 1900;
1620         $mon++;
1621         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1622
1623         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1624
1625         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1626
1627         my $vlan = $db_switch{$sw}->{'network'};
1628         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1629
1630         my $arrow ='&#10235;';
1631            $arrow ='&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1632
1633         print <<"END_HTML";
1634  <tr class="$typerow">
1635   <td sorttable_customkey="router">router</td>
1636   <td class="bklask-port"></td>
1637   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$parent_port_hr</td></tr></table></td>
1638   <td sorttable_customkey="$host_short">$sw</td>
1639   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1640   <td sorttable_customkey="$mac_sort">$mac_address</td>
1641   <td>$vlan</td>
1642   <td>$date</td>
1643  </tr>
1644END_HTML
1645         #<td>$arrow</td><td>$parent_port_hr</td>
1646
1647         next LOOP_ON_OUTPUT_SWITCH;
1648
1649         # Old print
1650         print <<"END_HTML";
1651  <tr class="$typerow">
1652   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1653   <td class="bklask-port">$parent_port_hr</td>
1654   <td>$arrow</td><td></td>
1655   <td sorttable_customkey="router">router</td>
1656   <td sorttable_customkey="999999999999"></td>
1657   <td sorttable_customkey="99999"></td>
1658   <td></td>
1659   <td></td>
1660  </tr>
1661END_HTML
1662         }
1663      }
1664
1665   # Child switch connection : parent <- child
1666   for my $swport (sort keys %db_switch_connected_on_port) {
1667      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1668      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1669
1670         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1671
1672         my $mac_address = $db_switch{$sw}->{'mac_address'};
1673         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
1674         my $timestamp = $db_switch{$sw}->{'timestamp'};
1675
1676         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1677         $year += 1900;
1678         $mon++;
1679         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1680
1681         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1682
1683         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1684
1685         $typerow = $typerow eq 'even' ? 'odd' : 'even';
1686
1687         #my $arrow ='&#8701;';
1688         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
1689         my $arrow ='&#10235;';
1690            $arrow ='&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
1691
1692         my $vlan = $db_switch{$sw}->{'network'};
1693         $vlan .= ' ('.get_current_vlan_id($db_switch{$sw}->{'network'}).')' if $db_switch{$sw}->{'network'};
1694
1695         if (exists $db_switch_output_port{$sw}) {
1696
1697            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1698
1699            my $parent_port_hr = format_aggregator4html($port_connect);
1700            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
1701
1702            print <<"END_HTML";
1703  <tr class="$typerow">
1704   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1705   <td class="bklask-port">$parent_port_hr</td>
1706   <td><table boder="0" class="noborder"><tr><td>$arrow</td><td>$child_port_hr</td></tr></table></td>
1707   <td sorttable_customkey="$host_short">$sw</td>
1708   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1709   <td sorttable_customkey="$mac_sort">$mac_address</td>
1710   <td>$vlan</td>
1711   <td>$date</td>
1712  </tr>
1713END_HTML
1714            }
1715         else {
1716            my $parent_port_hr = format_aggregator4html($port_connect);
1717
1718            print <<"END_HTML";
1719  <tr class="$typerow">
1720   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1721   <td class="bklask-port">$parent_port_hr</td>
1722   <td>$arrow</td>
1723   <td sorttable_customkey="$sw">$sw</td>
1724   <td sorttable_customkey="">$ipv4_address</td>
1725   <td sorttable_customkey="">$mac_address</td>
1726   <td>$vlan</td>
1727   <td>$date</td>
1728  </tr>
1729END_HTML
1730            }
1731         }
1732      }
1733
1734   print <<'END_HTML';
1735 </tbody>
1736</table>
1737END_HTML
1738   return;
1739   }
1740
1741#---------------------------------------------------------------
1742sub cmd_bad_vlan_id {
1743   @ARGV = @_;
1744
1745   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1746   my $verbose;
1747
1748   GetOptions(
1749      'day|d=i'   => \$days_before_alert,
1750      );
1751
1752   test_maindb_environnement();
1753
1754   my $computerdb = computerdb_load();
1755
1756   # create a database with the most recent computer by switch port
1757   my %switchportdb = ();
1758   LOOP_ON_IP_ADDRESS:
1759   for my $ip (keys %{$computerdb}) {
1760      # to be improve in the future
1761      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1762      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
1763      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port_id'} eq '0';
1764
1765      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1766      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1767      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1768
1769      my $swpt = sprintf "%-28s  %2s",
1770         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1771         $computerdb->{$ip}{'switch_port_hr'};
1772      $switchportdb{$swpt} ||= {
1773         ip          => $ip,
1774         timestamp   => $ip_timestamp,
1775         vlan        => $computerdb->{$ip}{'network'},
1776         hostname_fq => $ip_hostname_fq,
1777         mac_address => $ip_mac,
1778         };
1779
1780      # if float computer, set date 15 day before warning...
1781      my $ip_timestamp_mod = $ip_timestamp;
1782      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
1783      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1784      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1785
1786      if ($ip_timestamp_mod > $ip_timestamp_ref) {
1787         $switchportdb{$swpt} = {
1788            ip          => $ip,
1789            timestamp   => $ip_timestamp,
1790            vlan        => $computerdb->{$ip}{'network'},
1791            hostname_fq => $ip_hostname_fq,
1792            mac_address => $ip_mac,
1793            };
1794         }
1795      }
1796
1797   LOOP_ON_RECENT_COMPUTER:
1798   for my $swpt (keys %switchportdb) {
1799      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
1800      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
1801
1802      my $src_ip = $switchportdb{$swpt}->{ip};
1803      my $src_timestamp = 0;
1804      LOOP_ON_IP_ADDRESS:
1805      for my $ip (keys %{$computerdb}) {
1806         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne  $switchportdb{$swpt}->{'mac_address'};
1807         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
1808         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
1809
1810         $src_ip = $ip;
1811         $src_timestamp = $computerdb->{$ip}{'timestamp'};
1812         }
1813
1814      # keep only if float computer is the most recent
1815      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
1816      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
1817
1818      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
1819      $year += 1900;
1820      $mon++;
1821      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1822
1823      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
1824      $year += 1900;
1825      $mon++;
1826      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1827
1828      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
1829
1830      printf "%s / %-10s +-> %-10s(%i)  %s %s %s %s\n",
1831         $swpt, $switchportdb{$swpt}->{'vlan'}, $computerdb->{$src_ip}{'network'}, $vlan_id,
1832         $date,
1833         $src_date,
1834         $computerdb->{$src_ip}{'mac_address'},
1835         $computerdb->{$src_ip}{'hostname_fq'};
1836      }
1837   }
1838
1839#---------------------------------------------------------------
1840sub cmd_poe_enable {
1841   @ARGV = @_;
1842
1843   my $verbose;
1844   GetOptions(
1845      'verbose|v' => \$verbose,
1846      );
1847
1848   my $switch_name = shift @ARGV || q{};
1849   my $switch_port = shift @ARGV || q{};
1850
1851   if ($switch_name eq q{} or $switch_port eq q{}) {
1852      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
1853      }
1854
1855   for my $sw_name (split /,/, $switch_name) {
1856      if (not defined $SWITCH_DB{$sw_name}) {
1857         die "Switch $sw_name must be defined in klask configuration file\n";
1858         }
1859
1860      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1861
1862      my $sw = $SWITCH_DB{$sw_name};
1863      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1864      print "$error \n" if $error;
1865
1866      my $result = $session->set_request(
1867         -varbindlist => [$oid_search, INTEGER, 8], # Only NEXANS
1868         );
1869      print $session->error()."\n" if $session->error_status();
1870
1871      $session->close;
1872      }
1873   cmd_poe_status($switch_name, $switch_port);
1874   return;
1875   }
1876
1877#---------------------------------------------------------------
1878sub cmd_poe_disable {
1879   @ARGV = @_;
1880
1881   my $verbose;
1882   GetOptions(
1883      'verbose|v' => \$verbose,
1884      );
1885
1886   my $switch_name = shift @ARGV || q{};
1887   my $switch_port = shift @ARGV || q{};
1888
1889   if ($switch_name eq q{} or $switch_port eq q{}) {
1890      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
1891      }
1892
1893   for my $sw_name (split /,/, $switch_name) {
1894      if (not defined $SWITCH_DB{$sw_name}) {
1895         die "Switch $sw_name must be defined in klask configuration file\n";
1896         }
1897
1898      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1899
1900      my $sw = $SWITCH_DB{$sw_name};
1901      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
1902      print "$error \n" if $error;
1903
1904      my $result = $session->set_request(
1905         -varbindlist => [$oid_search, INTEGER, 2], # Only NEXANS
1906         );
1907      print $session->error()."\n" if $session->error_status();
1908
1909      $session->close;
1910      }
1911   cmd_poe_status($switch_name, $switch_port);
1912   return;
1913   }
1914
1915#---------------------------------------------------------------
1916sub cmd_poe_status {
1917   @ARGV = @_;
1918
1919   my $verbose;
1920   GetOptions(
1921      'verbose|v' => \$verbose,
1922      );
1923
1924   my $switch_name = shift @ARGV || q{};
1925   my $switch_port = shift @ARGV || q{};
1926
1927   if ($switch_name eq q{} or $switch_port eq q{}) {
1928      die "Usage: klask poe-status SWITCH_NAME PORT\n";
1929      }
1930
1931   for my $sw_name (split /,/, $switch_name) {
1932      if (not defined $SWITCH_DB{$sw_name}) {
1933         die "Switch $sw_name must be defined in klask configuration file\n";
1934         }
1935
1936      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
1937
1938      my $sw = $SWITCH_DB{$sw_name};
1939      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1940      print "$error \n" if $error;
1941
1942      my $result = $session->get_request(
1943         -varbindlist => [$oid_search],
1944         );
1945
1946      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
1947         my $poe_status = $result->{$oid_search} || 'empty';
1948         $poe_status =~ s/8/enable/;
1949         $poe_status =~ s/2/disable/;
1950         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe_status;
1951         }
1952      else {
1953         print "Klask do not find PoE status on switch $sw_name on port $switch_port\n";
1954         }
1955
1956      $session->close;
1957      }
1958   return;
1959   }
1960
1961#---------------------------------------------------------------
1962# not finish - do not use
1963sub cmd_port_setvlan {
1964   my $switch_name = shift || q{};
1965   my $mac_address = shift || q{};
1966
1967   if ($switch_name eq q{} or $mac_address eq q{}) {
1968      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1969      }
1970
1971   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
1972
1973   for my $sw_name (split /,/, $switch_name) {
1974      if (not defined $SWITCH_DB{$sw_name}) {
1975         die "Switch $sw_name must be defined in klask configuration file\n";
1976         }
1977
1978      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
1979      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. 0 . mac_address_hex2dec($mac_address);
1980      print "Klask search OID $oid_search_port1 on switch $sw_name\n";
1981      print "Klask search OID $oid_search_port2 on switch $sw_name\n";
1982
1983      my $sw = $SWITCH_DB{$sw_name};
1984      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
1985      print "$error \n" if $error;
1986
1987      my $result = $session->get_request(
1988         -varbindlist => [$oid_search_port1]
1989         );
1990      if (not defined $result) {
1991         $result = $session->get_request(
1992            -varbindlist => [$oid_search_port2]
1993            );
1994         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
1995         }
1996
1997      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
1998         my $swport = $result->{$oid_search_port1};
1999         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
2000         }
2001      else {
2002         print "Klask do not find MAC $mac_address on switch $sw_name\n";
2003         }
2004
2005      $session->close;
2006      }
2007   return;
2008   }
2009
2010#---------------------------------------------------------------
2011sub cmd_port_getvlan {
2012   @ARGV = @_;
2013
2014   my $verbose;
2015   GetOptions(
2016      'verbose|v' => \$verbose,
2017      );
2018
2019   my $switch_name = shift @ARGV || q{};
2020   my $switch_port = shift @ARGV || q{};
2021
2022   if ($switch_name eq q{} or $switch_port eq q{}) {
2023      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
2024      }
2025
2026   for my $sw_name (split /,/, $switch_name) {
2027      if (not defined $SWITCH_DB{$sw_name}) {
2028         die "Switch $sw_name must be defined in klask configuration file\n";
2029         }
2030
2031      my $oid_search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
2032
2033      my $sw = $SWITCH_DB{$sw_name};
2034      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2035      print "$error \n" if $error;
2036
2037      my $result = $session->get_request(
2038         -varbindlist => [$oid_search],
2039         );
2040
2041      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2042         my $vlan_id = $result->{$oid_search} || 'empty';
2043         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2044         }
2045      else {
2046         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
2047         }
2048
2049      $session->close;
2050      }
2051   return;
2052   }
2053
2054#---------------------------------------------------------------
2055sub cmd_vlan_setname {
2056   }
2057
2058#---------------------------------------------------------------
2059# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
2060sub cmd_rebootsw {
2061   @ARGV = @_;
2062
2063   my $verbose;
2064   GetOptions(
2065      'verbose|v' => \$verbose,
2066      );
2067
2068   my $switch_name = shift @ARGV || q{};
2069
2070   if ($switch_name eq q{}) {
2071      die "Usage: klask rebootsw SWITCH_NAME\n";
2072      }
2073
2074   for my $sw_name (split /,/, $switch_name) {
2075      if (not defined $SWITCH_DB{$sw_name}) {
2076         die "Switch $sw_name must be defined in klask configuration file\n";
2077         }
2078
2079      my $sw = $SWITCH_DB{$sw_name};
2080      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2081      print "$error \n" if $error;
2082
2083      my $result = $session->set_request(
2084         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
2085         );
2086
2087      $session->close;
2088      }
2089   return;
2090   }
2091
2092#---------------------------------------------------------------
2093sub cmd_vlan_getname {
2094   my $switch_name = shift || q{};
2095   my $vlan_id     = shift || q{};
2096
2097   if ($switch_name eq q{} or $vlan_id eq q{}) {
2098      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
2099      }
2100
2101   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2102
2103   for my $sw_name (split /,/, $switch_name) {
2104      if (not defined $SWITCH_DB{$sw_name}) {
2105         die "Switch $sw_name must be defined in klask configuration file\n";
2106         }
2107
2108      my $oid_search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
2109
2110      my $sw = $SWITCH_DB{$sw_name};
2111      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2112      print "$error \n" if $error;
2113
2114      my $result = $session->get_request(
2115         -varbindlist => [$oid_search_vlan_name]
2116         );
2117
2118      if (defined $result and $result->{$oid_search_vlan_name} ne 'noSuchInstance') {
2119         my $vlan_name = $result->{$oid_search_vlan_name} || 'empty';
2120         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
2121         }
2122      else {
2123         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2124         }
2125
2126      $session->close;
2127      }
2128   return;
2129   }
2130
2131#---------------------------------------------------------------
2132sub cmd_vlan_list {
2133   my $switch_name = shift || q{};
2134
2135   if ($switch_name eq q{}) {
2136      die "Usage: klask vlan-list SWITCH_NAME\n";
2137      }
2138
2139   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*};
2140
2141   for my $sw_name (split /,/, $switch_name) {
2142      if (not defined $SWITCH_DB{$sw_name}) {
2143         die "Switch $sw_name must be defined in klask configuration file\n";
2144         }
2145
2146      my $sw = $SWITCH_DB{$sw_name};
2147      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2148      print "$error \n" if $error;
2149
2150      my %vlandb = snmp_get_vlan_list($session);
2151      $session->close;
2152
2153      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2154      for my $vlan_id (keys %vlandb) {
2155         printf "%7i - %s\n", $vlan_id, $vlandb{$vlan_id};
2156         }
2157      }
2158   return;
2159   }
2160
2161#---------------------------------------------------------------
2162sub cmd_ip_location {
2163   my $computerdb = computerdb_load();
2164
2165   LOOP_ON_IP_ADDRESS:
2166   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
2167
2168      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2169
2170      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
2171      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
2172
2173      my $sw_location = q{};
2174      LOOP_ON_ALL_SWITCH:
2175      for my $sw (@SWITCH_LIST) {
2176         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2177         $sw_location = $sw->{'location'};
2178         last;
2179         }
2180
2181      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
2182      }
2183   return;
2184   }
2185
2186#---------------------------------------------------------------
2187sub cmd_ip_free {
2188   @ARGV = @_;
2189
2190   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
2191   my $format = 'txt';
2192   my $verbose;
2193
2194   GetOptions(
2195      'day|d=i'      => \$days_to_death,
2196      'format|f=s'   => \$format,
2197      'verbose|v'    => \$verbose,
2198      );
2199
2200   my %possible_format = (
2201      txt  => \&cmd_ip_free_txt,
2202      html => \&cmd_ip_free_html,
2203      none => sub {},
2204      );
2205   $format = 'txt' if not defined $possible_format{$format};
2206
2207   my @vlan_name = @ARGV;
2208   @vlan_name = get_list_network() if not @vlan_name;
2209
2210   my $computerdb = {};
2211      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
2212   my $timestamp = time;
2213
2214   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
2215
2216   my %result_ip = ();
2217
2218   ALL_NETWORK:
2219   for my $vlan (@vlan_name) {
2220
2221      my @ip_list = get_list_ip($vlan);
2222
2223      LOOP_ON_IP_ADDRESS:
2224      for my $ip (@ip_list) {
2225
2226         if (exists $computerdb->{$ip}) {
2227            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
2228
2229            my $mac_address = $computerdb->{$ip}{'mac_address'};
2230            LOOP_ON_DATABASE:
2231            for my $ip_db (keys %{$computerdb}) {
2232               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2233               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
2234               }
2235            }
2236
2237         my $ip_date_last_detection = '';
2238         if (exists $computerdb->{$ip}) {
2239            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{'timestamp'};
2240            $year += 1900;
2241            $mon++;
2242            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2243            }
2244
2245         my $packed_ip = scalar gethostbyname($ip);
2246         my $hostname_fq = 'unknown';
2247            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
2248
2249         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2250
2251         $result_ip{$ip} ||= {};
2252         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2253         $result_ip{$ip}->{'hostname_fq'} = $hostname_fq;
2254         $result_ip{$ip}->{'vlan'} = $vlan;
2255
2256         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
2257         }
2258      }
2259
2260   $possible_format{$format}->(%result_ip);
2261   }
2262
2263#---------------------------------------------------------------
2264sub cmd_ip_free_txt {
2265   my %result_ip = @_;
2266
2267   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2268   print "-------------------------------------------------------------------------------\n";
2269   LOOP_ON_IP_ADDRESS:
2270   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2271         my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2272         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
2273      }
2274   }
2275
2276#---------------------------------------------------------------
2277sub cmd_ip_free_html {
2278   my %result_ip = @_;
2279
2280   print <<'END_HTML';
2281<table class="sortable" summary="Klask Free IP Database">
2282 <caption>Klask Free IP Database</caption>
2283 <thead>
2284  <tr>
2285   <th scope="col" class="klask-header-left">IPv4-Address</th>
2286   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2287   <th scope="col" class="sorttable_alpha">VLAN</th>
2288   <th scope="col" class="klask-header-right">Date</th>
2289  </tr>
2290 </thead>
2291 <tfoot>
2292  <tr>
2293   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2294   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2295   <th scope="col" class="fklask-vlan">VLAN</th>
2296   <th scope="col" class="klask-footer-right">Date</th>
2297  </tr>
2298 </tfoot>
2299 <tbody>
2300END_HTML
2301
2302   my $typerow = 'even';
2303
2304   LOOP_ON_IP_ADDRESS:
2305   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2306
2307      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2308
2309      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
2310      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
2311
2312      my $vlan_nameid = $result_ip{$ip}->{'vlan'}.'('.get_current_vlan_id($result_ip{$ip}->{'vlan'}).')';
2313
2314      print <<"END_HTML";
2315  <tr class="$typerow">
2316   <td sorttable_customkey="$ip_sort">$ip</td>
2317   <td sorttable_customkey="$host_short">$result_ip{$ip}->{'hostname_fq'}</td>
2318   <td>$vlan_nameid</td>
2319   <td>$result_ip{$ip}->{'date_last_detection'}</td>
2320  </tr>
2321END_HTML
2322      }
2323   print <<'END_HTML';
2324 </tbody>
2325</table>
2326END_HTML
2327   }
2328
2329#---------------------------------------------------------------
2330sub cmd_enable {
2331   @ARGV = @_;
2332
2333   my $verbose;
2334
2335   GetOptions(
2336      'verbose|v' => \$verbose,
2337      );
2338
2339   my $switch_name = shift @ARGV || q{};
2340   my $port_hr     = shift @ARGV || q{};
2341
2342   if ($switch_name eq q{} or $port_hr eq q{}) {
2343      die "Usage: klask disable SWITCH_NAME PORT\n";
2344      }
2345
2346   if (not defined $SWITCH_DB{$switch_name}) {
2347      die "Switch $switch_name must be defined in klask configuration file\n";
2348      }
2349
2350   my $sw = $SWITCH_DB{$switch_name};
2351   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2352   print "$error \n" if $error;
2353
2354   # Retrieve numeric port value
2355   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2356   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2357
2358   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2359   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2360
2361   my $result = $session->set_request(
2362      -varbindlist => [$oid_search_portstatus, INTEGER, 1],
2363      );
2364   print $session->error()."\n" if $session->error_status();
2365
2366   $session->close;
2367
2368   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2369   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2370   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2371   return;
2372   }
2373
2374#---------------------------------------------------------------
2375sub cmd_disable {
2376   @ARGV = @_;
2377
2378   my $verbose;
2379
2380   GetOptions(
2381      'verbose|v' => \$verbose,
2382      );
2383
2384   my $switch_name = shift @ARGV || q{};
2385   my $port_hr     = shift @ARGV || q{};
2386
2387   if ($switch_name eq q{} or $port_hr eq q{}) {
2388      die "Usage: klask disable SWITCH_NAME PORT\n";
2389      }
2390
2391   if (not defined $SWITCH_DB{$switch_name}) {
2392      die "Switch $switch_name must be defined in klask configuration file\n";
2393      }
2394
2395   my $sw = $SWITCH_DB{$switch_name};
2396   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2397   print "$error \n" if $error;
2398
2399   # Retrieve numeric port value
2400   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2401   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2402
2403   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2404   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2405
2406   my $result = $session->set_request(
2407      -varbindlist => [$oid_search_portstatus, INTEGER, 2],
2408      );
2409   print $session->error()."\n" if $session->error_status();
2410
2411   $session->close;
2412
2413   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2414   return;
2415   }
2416
2417#---------------------------------------------------------------
2418sub cmd_status {
2419   @ARGV = @_;
2420
2421   my $verbose;
2422
2423   GetOptions(
2424      'verbose|v' => \$verbose,
2425      );
2426
2427   my $switch_name = shift @ARGV || q{};
2428   my $port_hr     = shift @ARGV || q{};
2429
2430   if ($switch_name eq q{} or $port_hr eq q{}) {
2431      die "Usage: klask status SWITCH_NAME PORT\n";
2432      }
2433
2434   if (not defined $SWITCH_DB{$switch_name}) {
2435      die "Switch $switch_name must be defined in klask configuration file\n";
2436      }
2437
2438   my $sw = $SWITCH_DB{$switch_name};
2439   my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2440   print "$error \n" if $error;
2441
2442   # Retrieve numeric port value
2443   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2444   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2445
2446   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port_id;
2447   print "Info: switch $switch_name port $port_hr ($port_id) SNMP OID $oid_search_portstatus\n" if $verbose;
2448
2449   my $result = $session->get_request(
2450      -varbindlist => [$oid_search_portstatus]
2451      );
2452   print $session->error()."\n" if $session->error_status();
2453   if (defined $result) {
2454      print "$PORT_UPDOWN{$result->{$oid_search_portstatus}}\n";
2455      }
2456
2457   $session->close;
2458
2459   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
2460   return;
2461   }
2462
2463#---------------------------------------------------------------
2464sub cmd_search_mac_on_switch {
2465   @ARGV = @_;
2466
2467   my $verbose;
2468   my $vlan_id = 0;
2469
2470   GetOptions(
2471      'verbose|v' => \$verbose,
2472      'vlan|l=i'  => \$vlan_id,
2473      );
2474
2475   my $switch_name = shift @ARGV || q{};
2476   my $mac_address = shift @ARGV || q{};
2477
2478   if ($switch_name eq q{} or $mac_address eq q{}) {
2479      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2480      }
2481
2482   $mac_address = normalize_mac_address($mac_address);
2483   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST ) if $switch_name eq q{*} or $switch_name eq q{all};
2484
2485   for my $sw_name (split /,/, $switch_name) {
2486      if (not defined $SWITCH_DB{$sw_name}) {
2487         die "Switch $sw_name must be defined in klask configuration file\n";
2488         }
2489
2490      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2491      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} .'.'. $vlan_id . mac_address_hex2dec($mac_address);
2492      print "Klask search OID $oid_search_port1 on switch $sw_name\n" if $verbose;
2493      print "Klask search OID $oid_search_port2 on switch $sw_name\n" if $verbose;
2494
2495      my $sw = $SWITCH_DB{$sw_name};
2496      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
2497      print "$error \n" if $error;
2498
2499      my $result = $session->get_request(
2500         -varbindlist => [$oid_search_port1]
2501         );
2502      if (not defined $result) {
2503         $result = $session->get_request(
2504            -varbindlist => [$oid_search_port2]
2505            );
2506         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
2507         }
2508
2509      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
2510         my $swport_id = $result->{$oid_search_port1};
2511         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
2512         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
2513         }
2514      else {
2515         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
2516         }
2517
2518      $session->close;
2519      }
2520   return;
2521   }
2522
2523#---------------------------------------------------------------
2524sub cmd_updatesw {
2525   @ARGV = @_;
2526
2527   my $verbose;
2528
2529   GetOptions(
2530      'verbose|v' => \$verbose,
2531      );
2532
2533   init_switch_names('yes');    #nomme les switchs
2534   print "\n";
2535
2536   my %where = ();
2537   my %db_switch_output_port = ();
2538   my %db_switch_ip_hostnamefq = ();
2539
2540   DETECT_ALL_ROUTER:
2541   for my $one_router ( get_list_main_router(get_list_network()) ) {
2542      print "Info: router loop $one_router\n" if $verbose;
2543      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
2544
2545      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
2546      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $verbose;
2547
2548      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
2549      my $vlan_id   = get_current_vlan_id($vlan_name);
2550      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
2551      }
2552
2553   ALL_ROUTER_IP_ADDRESS:
2554   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
2555
2556      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
2557
2558      ALL_SWITCH_CONNECTED:
2559      for my $switch_detected ( keys %{$where{$ip_router}} ) {
2560
2561         my $switch = $where{$ip_router}->{$switch_detected};
2562
2563         next ALL_SWITCH_CONNECTED if $switch->{'port_id'} eq '0';
2564
2565         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
2566         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
2567         }
2568      }
2569
2570   my %db_switch_link_with = ();
2571
2572   my @list_all_switch = ();
2573   my @list_switch_ipv4 = ();
2574   for my $sw (@SWITCH_LIST) {
2575      push @list_all_switch, $sw->{'hostname'};
2576      }
2577
2578   my $timestamp = time;
2579
2580   ALL_SWITCH:
2581   for my $one_switch (@list_all_switch) {
2582      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
2583      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
2584         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
2585         fast_ping($fake_ip);
2586         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $verbose;
2587         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
2588         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
2589            $resol_arp{'mac_address'}   = $resol_arp_alt{'mac_address'};
2590            $resol_arp{'interface'}     = $resol_arp_alt{'interface'};
2591            $resol_arp{'ipv4_address'} .= '*';
2592            # Force a MAC trace on switch
2593            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
2594            }
2595         }
2596      print "Info: switch loop $one_switch\n" if $verbose;
2597      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
2598
2599      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
2600
2601      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
2602      my $vlan_id   = get_current_vlan_id($vlan_name);
2603      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
2604
2605      if ($verbose) {
2606         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
2607         print "VERBOSE_3: $one_switch --- ",
2608            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
2609            "\n";
2610         }
2611
2612      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
2613      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $verbose;
2614
2615      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
2616      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
2617      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
2618      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
2619      }
2620
2621   ALL_SWITCH_IP_ADDRESS:
2622   for my $ip (@list_switch_ipv4) {
2623#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
2624
2625      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2626
2627      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
2628#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
2629
2630      DETECTED_SWITCH:
2631      for my $switch_detected ( keys %{$where{$ip}} ) {
2632
2633         my $switch = $where{$ip}->{$switch_detected};
2634         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $verbose;
2635
2636         next if $switch->{'port_id'}  eq '0';
2637         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
2638         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
2639
2640         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
2641         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{'hostname'} } = $switch->{'port_hr'};
2642         print "VERBOSE_7: +++++\n" if $verbose;
2643         }
2644
2645      }
2646
2647   my %db_switch_connected_on_port = ();
2648   my $maybe_more_than_one_switch_connected = 'yes';
2649   my $cloop = 0;
2650
2651   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2652      $cloop++;
2653      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
2654      for my $sw (keys %db_switch_link_with) {
2655         for my $connect (keys %{$db_switch_link_with{$sw}}) {
2656
2657            my $port_hr = $db_switch_link_with{$sw}->{$connect};
2658
2659            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2660            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
2661            }
2662         }
2663
2664      $maybe_more_than_one_switch_connected  = 'no';
2665
2666      SWITCH_AND_PORT:
2667      for my $swport (keys %db_switch_connected_on_port) {
2668
2669         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
2670
2671         $maybe_more_than_one_switch_connected = 'yes';
2672
2673         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2674         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
2675         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
2676
2677         CONNECTED:
2678         for my $sw_connected (@sw_on_same_port) {
2679
2680            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
2681
2682            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
2683
2684            for my $other_sw (@sw_on_same_port) {
2685               next if $other_sw eq $sw_connected;
2686
2687               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2688               }
2689
2690            # We can not do better for this switch for this loop
2691            next SWITCH_AND_PORT;
2692            }
2693         }
2694      }
2695
2696   my %db_switch_parent =();
2697
2698   for my $sw (keys %db_switch_link_with) {
2699      for my $connect (keys %{$db_switch_link_with{$sw}}) {
2700
2701         my $port_hr = $db_switch_link_with{$sw}->{$connect};
2702
2703         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
2704         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
2705
2706         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
2707         }
2708      }
2709
2710   print "Switch output port and parent port connection\n";
2711   print "---------------------------------------------\n";
2712   for my $sw (sort keys %db_switch_output_port) {
2713      if (exists $db_switch_parent{$sw}) {
2714         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
2715         }
2716      else {
2717         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2718         }
2719      }
2720   print "\n";
2721
2722   print "Switch parent and children port inter-connection\n";
2723   print "------------------------------------------------\n";
2724   for my $swport (sort keys %db_switch_connected_on_port) {
2725      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2726      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2727         if (exists $db_switch_output_port{$sw}) {
2728            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2729            }
2730         else {
2731            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2732            }
2733         }
2734      }
2735
2736   my $switch_connection = {
2737      output_port       => \%db_switch_output_port,
2738      parent            => \%db_switch_parent,
2739      connected_on_port => \%db_switch_connected_on_port,
2740      link_with         => \%db_switch_link_with,
2741      switch_db         => \%SWITCH_DB,
2742      timestamp         => $timestamp,
2743      checksum          => get_switchdb_checksum(%SWITCH_DB),
2744      };
2745
2746   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
2747   return;
2748   }
2749
2750#---------------------------------------------------------------
2751sub cmd_exportsw {
2752   @ARGV = @_;
2753
2754   test_switchdb_environnement();
2755
2756   my $format = 'txt';
2757   my $graph_modulo = 0;
2758   my $graph_shift  = 1;
2759
2760   GetOptions(
2761      'format|f=s'  => \$format,
2762      'modulo|m=i'  => \$graph_modulo,
2763      'shift|s=i'   => \$graph_shift,
2764      );
2765
2766   my %possible_format = (
2767      txt => \&cmd_exportsw_txt,
2768      dot => \&cmd_exportsw_dot,
2769      );
2770
2771   $format = 'txt' if not defined $possible_format{$format};
2772
2773   $possible_format{$format}->($graph_modulo, $graph_shift, @ARGV);
2774   return;
2775   }
2776
2777#---------------------------------------------------------------
2778sub cmd_exportsw_txt {
2779
2780   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2781
2782   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2783   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2784   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2785
2786   print "Switch output port and parent port connection\n";
2787   print "---------------------------------------------\n";
2788   for my $sw (sort keys %db_switch_output_port) {
2789      my $arrow ='-->';
2790         $arrow ='==>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2791      if (exists $db_switch_parent{$sw}) {
2792         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'};
2793         }
2794      else {
2795         printf "%-28s %8s %3s %-8s %-25s\n", $sw, $db_switch_output_port{$sw}, $arrow, '', 'router';
2796         }
2797      }
2798   print "\n";
2799
2800   print "Switch parent and children port inter-connection\n";
2801   print "------------------------------------------------\n";
2802   for my $swport (sort keys %db_switch_connected_on_port) {
2803      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2804      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2805         my $arrow ='<--';
2806            $arrow ='<==' if $port_connect =~ m/^(Trk|Br|Po)/;
2807         if (exists $db_switch_output_port{$sw}) {
2808            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw;
2809            }
2810         else {
2811            printf "%-28s %8s %3s %-8s %-25s\n", $sw_connect, $port_connect, $arrow, '', $sw;
2812            }
2813         }
2814      }
2815   return;
2816   }
2817
2818#---------------------------------------------------------------
2819sub cmd_exportsw_dot {
2820   my $graph_modulo = shift;
2821   my $graph_shift  = shift;
2822
2823   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2824
2825   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
2826   my %db_switch_parent            = %{$switch_connection->{'parent'}};
2827   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
2828   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
2829   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
2830   my $timestamp                   =   $switch_connection->{'timestamp'};
2831
2832   my $invisible_node = 0; # Count number of invisible node
2833
2834   my %db_building= ();
2835   my %db_switch_line = (); # Number of line drawed on a switch
2836   for my $sw (values %db_switch_global) {
2837      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
2838      $db_building{$building} ||= {};
2839      $db_building{$building}->{$location} ||= {};
2840      $db_building{$building}->{$location}{ $sw->{'hostname'} } = 'y';
2841
2842      $db_switch_line{$sw} = 0;
2843      }
2844
2845
2846   print "digraph G {\n";
2847   print "rankdir=LR;\n";
2848   #print "splines=polyline;\n";
2849
2850   print "site [label=\"site\", color=black, fillcolor=gold, shape=invhouse, style=filled];\n";
2851   print "internet [label=\"internet\", color=black, fillcolor=cyan, shape=house, style=filled];\n";
2852
2853   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
2854   $year += 1900;
2855   $mon++;
2856   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2857   print "\"$date\" [label=\"MAP DATE\\n\\n$date\", color=white, fillcolor=black, shape=polygon, sides=14, style=filled, fontcolor=white];\n";
2858   print "site -> \"$date\" [style=invis];\n";
2859
2860   my $b=0;
2861   for my $building (keys %db_building) {
2862      $b++;
2863
2864      print "\"building$b\" [label=\"$building\", color=black, fillcolor=gold, style=filled];\n";
2865      print "site -> \"building$b\" [len=2, color=firebrick];\n";
2866
2867      my $l = 0;
2868      for my $loc (keys %{$db_building{$building}}) {
2869         $l++;
2870
2871         print "\"location$b-$l\" [label=\"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color=black, fillcolor=orange, style=filled];\n";
2872#         print "\"location$b-$l\" [label=\"$building / $loc\", color=black, fillcolor=orange, style=filled];\n";
2873         print "\"building$b\" -> \"location$b-$l\" [len=2, color=firebrick];\n";
2874
2875         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2876
2877            my $peripheries = 1;
2878            my $color = 'lightblue';
2879            if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2880               $peripheries = 2;
2881               $color = "\"$color:$color\"";
2882               }
2883            print "\"$sw:$db_switch_output_port{$sw}\" [label=\"".format_aggregator4dot($db_switch_output_port{$sw})."\", color=black, fillcolor=lightblue, peripheries=$peripheries, style=filled];\n";
2884
2885            my $swname  = $sw;
2886               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2887            print "\"$sw\" [label=\"$swname\", color=black, fillcolor=palegreen, shape=rect, style=filled];\n";
2888            print "\"location$b-$l\" -> \"$sw\" [len=2, color=firebrick, arrowtail=dot];\n";
2889            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=invdot];\n";
2890
2891
2892            for my $swport (keys %db_switch_connected_on_port) {
2893               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2894               next if not $sw_connect eq $sw;
2895               next if $port_connect eq $db_switch_output_port{$sw};
2896               my $peripheries = 1;
2897               my $color = 'plum';
2898               if ($port_connect =~ m/^(Trk|Br|Po)/) {
2899                  $peripheries = 2;
2900                  $color = "\"$color:$color\"";
2901                  }
2902               print "\"$sw:$port_connect\" [label=\"".format_aggregator4dot($port_connect)."\", color=black, fillcolor=plum, peripheries=$peripheries, style=filled];\n";
2903               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=inv];\n";
2904
2905               #$db_switch_line{$sw}++;
2906               #if ($db_switch_line{$sw} % 9 == 0) {
2907               #   # Create invisible node
2908               #   $invisible_node++;
2909               #   my $invisible = '__Invisible_' . $invisible_node;
2910               #   print "$invisible [shape=none, label=\"\"]\n";
2911               #   print "\"$sw:$port_connect\" -> $invisible [style=invis]\n";
2912               #   print "$invisible            -> \"$sw\"    [style=invis]\n";
2913               #   }
2914              }
2915            }
2916         }
2917      }
2918
2919#   print "Switch output port and parent port connection\n";
2920#   print "---------------------------------------------\n";
2921   for my $sw (sort keys %db_switch_output_port) {
2922      if (exists $db_switch_parent{$sw}) {
2923#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port_id'};
2924         }
2925      else {
2926         my $style = 'solid';
2927         my $color = 'black'; # navyblue
2928         if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
2929            $style = 'bold';
2930            $color = "\"$color:invis:$color\"";
2931            }
2932         printf "   \"%s:%s\" -> internet [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw};
2933         }
2934      }
2935   print "\n";
2936
2937#   print "Switch parent and children port inter-connection\n";
2938#   print "------------------------------------------------\n";
2939   for my $swport (sort keys %db_switch_connected_on_port) {
2940      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2941      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2942         my $style = 'solid';
2943         my $color = 'black'; # navyblue
2944         if ($port_connect =~ m/^(Trk|Br|Po)/) {
2945            $style = 'bold';
2946            $color = "\"$color:invis:$color\"";
2947            }
2948         if (exists $db_switch_output_port{$sw}) {
2949            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2950
2951            next if $graph_modulo == 0; # No shift (invisible nodes) in graph
2952            $db_switch_line{$sw_connect}++;
2953            if ($db_switch_line{$sw_connect} % $graph_modulo == 0) {
2954               # Create invisible node
2955               $invisible_node++;
2956               my $invisible = '__Invisible_' . $invisible_node;
2957               print  "   \"$invisible.a\" [shape=none, label=\"\"];\n";
2958               printf "   \"%s:%s\"  -> \"$invisible.a\" [style=invis];\n", $sw, $db_switch_output_port{$sw};
2959               if ($graph_shift == 2) {
2960                  # Two invisible node
2961                  print  "   \"$invisible.b\" [shape=none, label=\"\"];\n";
2962                  print  "   \"$invisible.a\" -> \"$invisible.b\" [style=invis];\n";
2963                  printf "   \"$invisible.b\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
2964                  }
2965               else {
2966                  # One invisible node
2967                  printf "   \"$invisible.a\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
2968                  }
2969               }
2970            }
2971         else {
2972            printf "   \"%s\"   -> \"%s:%s\" [style=$style];\n", $sw, $sw_connect, $port_connect;
2973            }
2974         }
2975      }
2976
2977print "}\n";
2978   return;
2979   }
2980
2981
2982################################################################
2983# documentation
2984################################################################
2985
2986__END__
2987
2988=head1 NAME
2989
2990klask - port and search manager for switches, map management
2991
2992
2993=head1 USAGE
2994
2995 klask version
2996 klask help
2997
2998 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2999 klask exportdb [--format|-f txt|html]
3000 klask removedb IP* computer*
3001 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3002
3003 klask updatesw [--verbose|-v]
3004 klask exportsw [--format|-f txt|dot]
3005
3006 klask searchdb [--kind|-k host|mac] computer [mac-address]
3007 klask search   computer
3008 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3009
3010 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3011
3012 klask bad-vlan-id [--day|-d days_before_alert]
3013
3014 klask enable  [--verbose|-v] switch port
3015 klask disable [--verbose|-v] switch port
3016 klask status  [--verbose|-v] switch port
3017
3018 klask poe-enable  [--verbose|-v] switch port
3019 klask poe-disable [--verbose|-v] switch port
3020 klask poe-status  [--verbose|-v] switch port
3021
3022 klask vlan-getname switch vlan-id
3023 klask vlan-list switch
3024
3025
3026=head1 DESCRIPTION
3027
3028Klask is a small tool to find where is connected a host in a big network
3029and on which VLAN.
3030Klask mean search in brittany.
3031No hight level protocol like CDL, LLDP are use.
3032Everything is just done with SNMP request on MAC address.
3033
3034Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
3035If you use PVST or MSTP and create loop between VLAN,
3036you have to use C<portignore> functionality on switch port to cut manually loop
3037(see config file below).
3038
3039When you use a management port to administrate a switch,
3040it's not possible to create the map with this switch because it does not have a MAC address,
3041so other switch cannot find the real downlink port...
3042One way to work around this problem is, if you have a computer directly connected on the switch,
3043to put this IP as the fake ip for the switch.
3044The MAC address associated will be use just for the map detection.
3045The C<fake-ip> parameter is defined in the config file.
3046
3047Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
3048
3049
3050=head1 COMMANDS
3051
3052Some command are defined in the source code but are not documented here.
3053Theses could be not well defined, not finished, not well tested...
3054You can read the source code and use them at your own risk
3055(like for all the Klask code).
3056
3057=head2 search
3058
3059 klask search   computer
3060
3061This command takes one or more computer in argument.
3062It search a computer on the network and give the port and the switch on which the computer is connected.
3063
3064=head2 search-mac-on-switch
3065
3066 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3067
3068This command search a MAC address on a switch.
3069To search on all switch, you could put C<'*'> or C<all>.
3070The VLAN parameter could help.
3071
3072
3073=head2 enable
3074
3075 klask enable  [--verbose|-v] switch port
3076
3077This command activate a port (or an agrregate bridge port) on a switch by SNMP.
3078So you need to give the switch name and a port on the command line.
3079See L</ABBREVIATION FOR PORT>.
3080
3081Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3082
3083
3084=head2 disable
3085
3086 klask disable [--verbose|-v] switch port
3087
3088This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
3089So you need to give the switch name and a port on the command line.
3090See L</ABBREVIATION FOR PORT>.
3091
3092Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3093
3094
3095=head2 status
3096
3097 klask status  [--verbose|-v] switch port
3098
3099This command return the status of a port number on a switch by SNMP.
3100The return value could be C<enable> or C<disable> word.
3101So you need to give the switch name and a port on the command line.
3102See L</ABBREVIATION FOR PORT>.
3103
3104If it's not possible to change port status with command L</enable> and L</disable>
3105(SNMP community read write access),
3106it's always possible to have the port status even for bridge agrregate port.
3107
3108
3109=head2 updatedb
3110
3111 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
3112
3113This command will scan networks and update the computer database.
3114To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3115This file is easy to read and write because Klask use YAML format and not XML
3116(see L</CONFIGURATION>).
3117
3118Option are not stable and could be use manually when you have a new kind of switch.
3119Maybe some option will be transfered in a future C<checksw> command!
3120
3121The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3122By default, a network is C<active>.
3123This means that an C<fping> command is done at the beginning on all the IP of the network
3124and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3125will have an C<arping>
3126(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3127In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3128It's good for big subnet with few computer (telephone...).
3129The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3130
3131=head2 exportdb
3132
3133 klask exportdb [--format|-f txt|html]
3134
3135This command print the content of the computer database.
3136There is actually only two format : TXT and HTML.
3137By default, format is TXT.
3138It's very easy to have more format, it's just need times...
3139
3140=head2 removedb
3141
3142 klask removedb IP* computer*
3143
3144This command remove an entry in the database.
3145There is only one kind of parameter, the IP of the computers to be removed.
3146You can put as many IP as you want...
3147
3148Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
3149
3150=head2 cleandb
3151
3152 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3153
3154Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3155Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3156This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3157This functionality could be use when computer define in VLAN 1
3158could have a float IP when they are connected on VLAN 2.
3159In the Klask database, the float DNS entries are less important.
3160
3161When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
3162
3163=head2 updatesw
3164
3165 klask updatesw [--verbose|-v]
3166
3167This command build a map of your manageable switch on your network.
3168The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3169
3170
3171=head2 exportsw
3172
3173 klask exportsw [--format|-f txt|dot]
3174
3175This command print the content of the switch database. There is actually two format.
3176One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3177By default, format is TXT.
3178
3179 klask exportsw --format dot > /tmp/map.dot
3180 dot -Tpng /tmp/map.dot > /tmp/map.png
3181
3182
3183=head2 ip-free
3184
3185 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3186
3187This command return IP address that was not use (detected by Klask) at this time.
3188The list returned could be limited to just one VLAN.
3189IP returned could have been never used or no computer have been detected since the number of days specified
3190(2 years by default).
3191This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3192
3193 default:
3194   days-to-death: 730
3195
3196Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3197
3198
3199=head2 bad-vlan-id
3200
3201 klask bad-vlan-id [--day|-d days_before_alert]
3202
3203This command return a list of switch port that are not configure with the good VLAN.
3204Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3205and another prior trace where they had the good IP (good DNS name).
3206The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
3207This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3208
3209 default:
3210   days-before-alert: 15
3211
3212This functionality is not need if your switch use RADIUS 802.1X configuration...
3213
3214
3215=head2 poe-enable
3216
3217 klask poe-enable  [--verbose|-v] switch port
3218
3219This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3220So you need to give the switch name and a port on the command line.
3221See L</ABBREVIATION FOR PORT>.
3222
3223Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3224You need to have the SNMP write access on the switch in order to modify it's configuration.
3225
3226
3227=head2 poe-disable
3228
3229 klask poe-disable [--verbose|-v] switch port
3230
3231This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3232So you need to give the switch name and a port on the command line.
3233See L</ABBREVIATION FOR PORT>.
3234
3235Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3236You need to have the SNMP write access on the switch in order to modify it's configuration.
3237
3238
3239=head2 poe-status
3240
3241 klask poe-status  [--verbose|-v] switch port
3242
3243This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3244The return value could be C<enable> or C<disable> word.
3245So you need to give the switch name and a port on the command line.
3246See L</ABBREVIATION FOR PORT>.
3247
3248If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3249(SNMP community read write access),
3250it's always possible to have the PoE port status.
3251
3252Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3253
3254
3255=head1 CONFIGURATION
3256
3257Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
3258The configuration is done in a F</etc/klask/klask.conf> YAML file.
3259This format have many advantage over XML, it's easier to read and to write !
3260
3261Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3262
3263 default:
3264   community: public
3265   community-rw: private
3266   snmpport: 161
3267   float-regex: '(?^msx: ^float )'
3268   scan-mode: active
3269
3270 network:
3271   labnet:
3272     ip-subnet:
3273       - add: 192.168.1.0/24
3274       - add: 192.168.2.0/24
3275     interface: eth0
3276     vlan-id: 12
3277     main-router: gw1.labnet.local
3278
3279   schoolnet:
3280     ip-subnet:
3281       - add: 192.168.3.0/24
3282       - add: 192.168.4.0/24
3283     interface: eth0.38
3284     vlan-id: 13
3285     main-router: gw2.schoolnet.local
3286     scan-mode: passive
3287
3288   etunet:
3289     ip-subnet:
3290       - add: 192.168.5.0/24
3291     interface: eth2
3292     vlan-id: 14
3293     main-router: gw3.etunet.local
3294     scan-mode: passive
3295
3296 switch:
3297   - hostname: sw1.klask.local
3298     location: BatY / 1 floor / K004
3299     portignore:
3300       - 1
3301       - 2
3302
3303   - hostname: sw2.klask.local
3304     location: BatY / 2 floor / K203
3305     type: HP2424
3306     portignore:
3307       - 1
3308       - 2
3309     fake-ip: 192.168.9.14
3310
3311   - hostname: sw3.klask.local
3312     location: BatY / 2 floor / K203
3313
3314I think it's pretty easy to understand.
3315The default section can be overide in any section, if parameter mean something in theses sections.
3316Network to be scan are define in the network section. You must put an add by network.
3317Maybe I will make a delete line to suppress specific computers.
3318The switch section define your switch.
3319You have to write the port number to ignore, this was important if your switchs are cascades
3320(right now, method C<updatesw> find them automatically)
3321and is still important if you have loop (with PVST or MSTP).
3322Just put the ports numbers between switch.
3323
3324The C<community> parameter is use to get SNMP data on switch.
3325It could be overload for each switch.
3326By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3327Some few command change the switch state as the commands L</enable> and L</disable>.
3328In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3329Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
3330
3331
3332=head1 ABBREVIATION FOR PORT
3333
3334HP Procurve and Nexans switches have a simplistic numbering scheme.
3335It's just number: 1, 2, 3... 24.
3336On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3337Nothing is done on theses ports names.
3338
3339On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3340followed by tree number.
3341In order to have short name,
3342we made the following rules:
3343
3344 Bridge-Aggregation     -> Br
3345 FastEthernet           -> Fa
3346 Forty-GigabitEthernet  -> Fo
3347 FortyGigabitEthernet   -> Fo
3348 GigabitEthernet        -> Gi
3349 Giga                   -> Gi
3350 Port-Channel           -> Po
3351 Ten-GigabitEthernet    -> Te
3352 TenGigabitEthernet     -> Te
3353 Ten                    -> Te
3354
3355All Klask command automatically normalize the port name on standart output
3356and also on input command line.
3357
3358In the case of use an aggregator port (Po, Tk, Br ...),
3359the real ports used are also return.
3360
3361
3362=head1 SWITCH SUPPORTED
3363
3364Here is a list of switches where Klask gives or gave (for old switches) good results.
3365We have only a few manageable switches to actually test Klask.
3366It is quite possible that switches from other brands will work just as well.
3367You just have to do a test on it and add the line of description that goes well in the source code.
3368Contact us for any additional information.
3369
3370In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3371This makes it possible to adjust the code names of the different manufacturers!
3372
3373HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3374J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3375J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F).
3376
3377BayStack: BayStack 350T HW(BS350T)
3378
3379Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3380
3381DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3382
3383H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3384
3385H3C: H3C5500
3386
33873COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
33883CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
33893CR17572-91, 3C17702-US, 3C17700.
3390
3391
3392=head1 FILES
3393
3394 /etc/klask/klask.conf
3395 /var/lib/klask/klaskdb
3396 /var/lib/klask/switchdb
3397
3398
3399=head1 SEE ALSO
3400
3401Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3402
3403=over
3404
3405=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
3406
3407=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3408
3409=back
3410
3411
3412=head1 VERSION
3413
3414$Id: klask 297 2017-10-04 16:26:32Z g7moreau $
3415
3416
3417=head1 AUTHOR
3418
3419Written by Gabriel Moreau, Grenoble - France
3420
3421
3422=head1 LICENSE AND COPYRIGHT
3423
3424GPL version 2 or later and Perl equivalent
3425
3426Copyright (C) 2005-2017 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.