source: trunk/klask @ 240

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