source: trunk/klask @ 240

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