source: trunk/klask @ 246

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