source: trunk/klask @ 245

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