source: trunk/klask @ 250

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