source: trunk/klask @ 248

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