source: trunk/klask @ 368

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