source: trunk/klask @ 328

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