source: trunk/klask @ 244

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