source: trunk/klask @ 241

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