source: trunk/klask @ 308

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