source: trunk/klask @ 241

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