source: trunk/klask @ 370

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