source: trunk/klask @ 209

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