source: trunk/klask @ 245

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