source: trunk/klask @ 295

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