source: trunk/klask @ 300

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