source: trunk/klask @ 247

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