source: trunk/klask @ 243

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