source: trunk/klask @ 239

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