source: trunk/klask @ 284

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