source: trunk/klask @ 246

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