source: trunk/klask @ 242

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