source: trunk/klask @ 350

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