source: trunk/klask @ 263

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