source: trunk/klask @ 203

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