source: trunk/klask @ 235

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