source: trunk/klask @ 373

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