source: trunk/klask @ 207

Last change on this file since 207 was 207, checked in by g7moreau, 7 years ago
  • More robust parameter in status method
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 93.0 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2017 Gabriel Moreau
4#
5# $Id: klask 207 2017-02-16 18:46:22Z g7moreau $
6
7use strict;
8use warnings;
9use version; our $VERSION = qv('0.5.9');
10
11use Readonly;
12use FileHandle;
13use Net::SNMP;
14#use YAML;
15use YAML::Syck;
16use Net::Netmask;
17use Net::CIDR::Lite;
18use NetAddr::IP;
19use Getopt::Long qw(GetOptions);
20use Socket;
21use List::Util 'shuffle';
22
23# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
24# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
25# arping net-tools fping bind9-host arpwatch
26
27my $KLASK_VAR      = '/var/lib/klask';
28my $KLASK_CFG_FILE = '/etc/klask/klask.conf';
29my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
30my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
31
32test_running_environnement();
33
34my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
35
36my %DEFAULT     = %{$KLASK_CFG->{default}};
37my @SWITCH_LIST = @{$KLASK_CFG->{switch}};
38
39my %SWITCH_LEVEL = ();
40my %SWITCH_DB    = ();
41LEVEL_OF_EACH_SWITCH:
42for my $sw (@SWITCH_LIST){
43   $SWITCH_LEVEL{$sw->{hostname}} = $sw->{level} || $DEFAULT{switch_level}  || 2;
44   $SWITCH_DB{$sw->{hostname}} = $sw;
45   }
46@SWITCH_LIST = reverse sort { $SWITCH_LEVEL{$a->{hostname}} <=> $SWITCH_LEVEL{$b->{hostname}} } @{$KLASK_CFG->{switch}};
47
48#my %SWITCH_PORT_COUNT = ();
49
50my %CMD_DB = (
51   'help'                 => \&cmd_help,
52   'version'              => \&cmd_version,
53   'exportdb'             => \&cmd_exportdb,
54   'updatedb'             => \&cmd_updatedb,
55   'searchdb'             => \&cmd_searchdb,
56   'removedb'             => \&cmd_removedb,
57   'cleandb'              => \&cmd_cleandb,
58   'search'               => \&cmd_search,
59   'enable'               => \&cmd_enable,
60   'disable'              => \&cmd_disable,
61   'status'               => \&cmd_status,
62   'updatesw'             => \&cmd_updatesw,
63   'exportsw'             => \&cmd_exportsw,
64   'iplocation'           => \&cmd_ip_location,
65   'ip-free'              => \&cmd_ip_free,
66   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
67   'bad-vlan-id'          => \&cmd_bad_vlan_id,
68   'set-vlan-port'        => \&cmd_set_vlan_port,
69   'get-vlan-port'        => \&cmd_get_vlan_port,
70   'set-vlan-name'        => \&cmd_set_vlan_name,
71   'get-vlan-name'        => \&cmd_get_vlan_name,
72   'rebootsw'             => \&cmd_rebootsw,
73   );
74
75Readonly my %INTERNAL_PORT_MAP => (
76   0 => 'A',
77   1 => 'B',
78   2 => 'C',
79   3 => 'D',
80   4 => 'E',
81   5 => 'F',
82   6 => 'G',
83   7 => 'H',
84   );
85Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
86
87Readonly my %SWITCH_KIND => (
88   # HP
89   J3299A           => { type => 1, model => 'HP224M',         match => 'HP J3299A ProCurve Switch 224M'       },
90   J4120A           => { type => 1, model => 'HP1600M',        match => 'HP J4120A ProCurve Switch 1600M'      },
91   J9029A           => { type => 1, model => 'HP1800-8G',      match => 'PROCURVE J9029A'                      },
92   J9449A           => { type => 1, model => 'HP1810-8G',      match => 'HP ProCurve 1810G - 8 GE'             },
93   J4093A           => { type => 1, model => 'HP2424M',        match => 'HP J4093A ProCurve Switch 2424M'      },
94   J9279A           => { type => 1, model => 'HP2510G-24',     match => 'ProCurve J9279A Switch 2510G-24'      },
95   J9280A           => { type => 1, model => 'HP2510G-48',     match => 'ProCurve J9280A Switch 2510G-48'      },
96   J4813A           => { type => 1, model => 'HP2524',         match => 'HP J4813A ProCurve Switch 2524'       },
97   J4900A           => { type => 1, model => 'HP2626A',        match => 'HP J4900A ProCurve Switch 2626'       },
98   J4900B           => { type => 1, model => 'HP2626B',        match => 'J4900B.+?Switch 2626'                 }, # ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
99   J4899B           => { type => 1, model => 'HP2650',         match => 'ProCurve J4899B Switch 2650'          },
100   J9021A           => { type => 1, model => 'HP2810-24G',     match => 'ProCurve J9021A Switch 2810-24G'      },
101   J9022A           => { type => 1, model => 'HP2810-48G',     match => 'ProCurve J9022A Switch 2810-48G'      },
102   J8692A           => { type => 1, model => 'HP3500-24G',     match => 'J8692A Switch 3500yl-24G'             },
103   J4903A           => { type => 1, model => 'HP2824',         match => 'J4903A.+?Switch 2824,'                },
104   J4110A           => { type => 1, model => 'HP8000M',        match => 'HP J4110A ProCurve Switch 8000M'      },
105   JE074A           => { type => 2, model => 'HP5120-24G',     match => 'HP Comware.+?A5120-24G EI Switch'     },
106   JE069A           => { type => 2, model => 'HP5120-48G',     match => 'HP Comware.+?A5120-48G EI Switch'     },
107   JD377A           => { type => 2, model => 'HP5500-24G',     match => 'HP Comware.+?A5500-24G EI Switch'     },
108   JD374A           => { type => 2, model => 'HP5500-24F',     match => 'HP Comware.+?A5500-24G-SFP EI Switch' },
109   # BayStack
110   BS350T           => { type => 1, model => 'BS350T',         match => 'BayStack 350T HW'                     },
111   # Nexans
112   N3483G           => { type => 2, model => 'NA3483-6G',      match => 'GigaSwitch V3 TP SFP-I 48V ES3'       },
113   # DELL
114   PC7024           => { type => 2, model => 'DPC7024',        match => 'PowerConnect 7024,.+?VxWorks'         },
115   N2048            => { type => 2, model => 'DN2048',         match => 'Dell Networking N2048,'               },
116   N4032F           => { type => 2, model => 'DN4032F',        match => 'Dell Networking N4032F,'              },
117   N4064F           => { type => 2, model => 'DN4064F',        match => 'Dell Networking N4064F,'              },
118   # 3COM
119   'H3C5500'        => { type => 1, model => 'H3C5500',        match => 'H3C S5500-SI Series'                  },
120   '3C17203'        => { type => 1, model => '3C17203',        match => '3Com SuperStack 3 24-Port'            },
121   '3C17204'        => { type => 1, model => '3C17204',        match => '3Com SuperStack 3 48-Port'            },
122   '3CR17562-91'    => { type => 1, model => '3CR17562-91',    match => '3Com Switch 4500 50-Port'             },
123   '3CR17255-91'    => { type => 1, model => '3CR17255-91',    match => '3Com Switch 5500G-EI 48-Port'         },
124   '3CR17251-91'    => { type => 1, model => '3CR17251-91',    match => '3Com Switch 5500G-EI 48-Port'         },
125   '3CR17571-91'    => { type => 1, model => '3CR17571-91',    match => '3Com Switch 4500 PWR 26-Port'         },
126   '3CRWX220095A'   => { type => 1, model => '3CRWX220095A',   match => '3Com Wireless LAN Controller'         },
127   '3CR17254-91'    => { type => 1, model => '3CR17254-91',    match => '3Com Switch 5500G-EI 24-Port'         },
128   '3CRS48G-24S-91' => { type => 1, model => '3CRS48G-24S-91', match => '3Com Switch 4800G 24-Port'            },
129   '3CRS48G-48S-91' => { type => 1, model => '3CRS48G-48S-91', match => '3Com Switch 4800G 48-Port'            },
130   '3C17708'        => { type => 1, model => '3C17708',        match => '3Com Switch 4050'                     },
131   '3C17709'        => { type => 1, model => '3C17709',        match => '3Com Switch 4060'                     },
132   '3C17707'        => { type => 1, model => '3C17707',        match => '3Com Switch 4070'                     },
133   '3CR17258-91'    => { type => 1, model => '3CR17258-91',    match => '3Com Switch 5500G-EI 24-Port SFP'     },
134   '3CR17181-91'    => { type => 1, model => '3CR17181-91',    match => '3Com Switch 5500-EI 28-Port FX'       },
135   '3CR17252-91'    => { type => 1, model => '3CR17252-91',    match => '3Com Switch 5500G-EI PWR 24-Port'     },
136   '3CR17253-91'    => { type => 1, model => '3CR17253-91',    match => '3Com Switch 5500G-EI PWR 48-Port'     },
137   '3CR17250-91'    => { type => 1, model => '3CR17250-91',    match => '3Com Switch 5500G-EI 24-Port'         },
138   '3CR17561-91'    => { type => 1, model => '3CR17561-91',    match => '3Com Switch 4500 26-Port'             },
139   '3CR17572-91'    => { type => 1, model => '3CR17572-91',    match => '3Com Switch 4500 PWR 50-Port'         },
140   '3C17702-US'     => { type => 1, model => '3C17702-US',     match => '3Com Switch 4900 SX'                  },
141   '3C17700'        => { type => 1, model => '3C17700',        match => '3Com Switch 4900'                     },
142   );
143
144Readonly my %OID_NUMBER => (
145   sysDescription  => '1.3.6.1.2.1.1.1.0',
146   sysName         => '1.3.6.1.2.1.1.5.0',
147   sysContact      => '1.3.6.1.2.1.1.4.0',
148   sysLocation     => '1.3.6.1.2.1.1.6.0',
149   searchPort1     => '1.3.6.1.2.1.17.4.3.1.2',       # BRIDGE-MIB (802.1D).
150   searchPort2     => '1.3.6.1.2.1.17.7.1.2.2.1.2',   # Q-BRIDGE-MIB (802.1Q) add 0 if unknown vlan id
151   vlanPortDefault => '1.3.6.1.2.1.17.7.1.4.5.1.1',   # dot1qPvid
152   vlanStatus      => '1.3.6.1.2.1.17.7.1.4.3.1.5',   # integer 4 Create, 6 Destroy
153   vlanName        => '1.3.6.1.2.1.17.7.1.4.3.1.1',   # string
154   hpicfReset      => '1.3.6.1.4.1.11.2.14.11.1.4.1', # HP reboot switch
155   ifIndex         => '1.3.6.1.2.1.17.1.4.1.2',       # dot1dBasePortIfIndex - Interface index redirection
156   ifName          => '1.3.6.1.2.1.31.1.1.1.1',       # Interface name (give port number)
157   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)
158   );
159
160Readonly my %PORT_UPDOWN => (
161   1 => 'enable',
162   2 => 'disable',
163   );
164
165Readonly 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;
166Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
167
168Readonly my $RE_FLOAT_HOSTNAME => qr{ ^float }xms;
169
170################
171# main program
172################
173
174my $cmd = shift @ARGV || 'help';
175if (defined $CMD_DB{$cmd}) {
176   $CMD_DB{$cmd}->(@ARGV);
177   }
178else {
179   print {*STDERR} "klask: command $cmd not found\n\n";
180   $CMD_DB{help}->();
181   exit 1;
182   }
183
184exit;
185
186################
187# subroutine
188################
189
190sub test_running_environnement {
191   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
192   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
193   return;
194   }
195
196sub test_switchdb_environnement {
197   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
198   return;
199   }
200
201sub test_maindb_environnement {
202   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
203   return;
204   }
205
206###
207# fast ping dont l'objectif est de remplir la table arp de la machine
208sub fast_ping {
209   # Launch this command without waiting...
210   system "fping -q -c 1 @_ >/dev/null 2>&1 &";
211   return;
212   }
213
214sub shell_command {
215   my $cmd = shift;
216
217   my $fh     = new FileHandle;
218   my $result = '';
219   open $fh, q{-|}, "LANG=C $cmd" or die "Can't exec $cmd\n";
220   $result .= <$fh>;
221   close $fh;
222   chomp $result;
223   return $result;
224   }
225
226###
227# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
228sub resolve_ip_arp_host {
229   my $param_ip_or_host = shift;
230   my $interface = shift || q{*};
231   my $type      = shift || q{fast};
232   my $already   = shift || q{yes};
233
234   my %ret = (
235      hostname_fq  => 'unknow',
236      ipv4_address => '0.0.0.0',
237      mac_address  => 'unknow',
238      );
239
240   # perl -MSocket -E 'say inet_ntoa(scalar gethostbyname("tech7meylan.hmg.inpg.fr"))'
241   my $packed_ip = scalar gethostbyname($param_ip_or_host);
242   return %ret if not defined $packed_ip;
243   $ret{ipv4_address} = inet_ntoa($packed_ip);
244   #if ($ret{ipv4_address} !~ m/$RE_IPv4_ADDRESS/) {
245   #   print "Error: for computer $param_ip_or_host on interface $interface, IP $ret{ipv4_address} is not valide\n";
246   #   return %ret;
247   #   }
248
249   # perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("194.254.66.240"), AF_INET)'
250   my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) if $already eq 'yes';
251   $ret{hostname_fq} = $hostname_fq if defined $hostname_fq;
252
253   # my $cmd = q{grep  -he '\b} . $param_ip_or_host . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
254   #my $cmd = q{grep  -he '\b} . $ret{ipv4_address} . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
255   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';
256   #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
257
258   my $cmd_arpwatch = shell_command $cmd;
259   #my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
260   my ($interface2, $arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
261
262   $ret{interface}    = $interface2 || $interface;
263   $ret{mac_address}  = $arp       if $arp;
264   $ret{timestamp}    = $timestamp if $timestamp;
265
266   my $nowtimestamp = time;
267
268   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 45 * 60 ) ) ) { # 45 min
269      $ret{mac_address} = 'unknow';
270      return %ret;
271      }
272
273   # ARP result
274   #
275   # LANG=C arp -a 194.254.66.62 -i eth331
276   # gw66-62.legi.grenoble-inp.fr (194.254.66.62) at 00:08:7c:bb:0f:c0 [ether] on eth331
277   #
278   # LANG=C ip neigh show to 194.254.66.62 dev eth331
279   # 194.254.66.62 lladdr 00:08:7c:bb:0f:c0 REACHABLE
280   # LANG=C ip neigh show to 194.254.66.62
281   # 194.254.66.62 dev eth331 lladdr 00:08:7c:bb:0f:c0 REACHABLE
282#   my $cmd_arp  = shell_command "arp -a $param_ip_or_host -i $ret{interface}";
283#   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
284#      ( $ret{hostname_fq}, $ret{ipv4_address}, $ret{mac_address} )  = ($1, $2, $3);
285#      }
286   if ($ret{mac_address} eq 'unknow') {
287      # Last chance to have the mac_address
288      if ($ret{interface} eq '*') {
289         my $cmd_arp  = shell_command "ip neigh show to $ret{ipv4_address}";
290         if ( $cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s dev \s ([\w\d\.\:]+) \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms ) {
291            ($ret{interface}, $ret{mac_address}) = ($1, $2);
292            }
293         }
294      else {
295         my $cmd_arp  = shell_command "ip neigh show to $ret{ipv4_address} dev $ret{interface}";
296         if ( $cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms ) {
297            $ret{mac_address} = $1;
298            }
299         }
300      }
301
302   # Normalize MAC Address
303   if ($ret{mac_address} ne 'unknow') {
304      my @paquets = ();
305      for ( split m/ : /xms, $ret{mac_address} ) {
306         my @chars = split m//xms, uc "00$_";
307         push @paquets, "$chars[-2]$chars[-1]";
308         }
309      $ret{mac_address} = join q{:}, @paquets;
310      }
311
312   return %ret;
313   }
314
315# Find Surname of a switch
316sub get_switch_model {
317   my $sw_snmp_description = shift || 'unknow';
318   $sw_snmp_description =~ s/[\n\r]/ /g;
319
320   for my $sw_kind (keys %SWITCH_KIND) {
321      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/ms; # option xms break search, why ?
322
323      return $SWITCH_KIND{$sw_kind}->{model};
324      }
325
326   return $sw_snmp_description;
327   }
328
329###
330# va rechercher le nom des switchs pour savoir qui est qui
331sub init_switch_names {
332   my ($verbose, $verb_description, $check_hostname, $check_location) = @_;
333
334   printf "%-26s                %-25s %s\n",'Switch','Description','Type' if $verbose;
335   print "------------------------------------------------------------------------------\n" if $verbose;
336
337   INIT_EACH_SWITCH:
338   for my $sw (@SWITCH_LIST) {
339      my %session = ( -hostname   => $sw->{hostname} );
340         $session{-version} = $sw->{version}   || 1;
341         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
342         if (exists $sw->{version} and $sw->{version} eq '3') {
343            $session{-username} = $sw->{username} || 'snmpadmin';
344            }
345         else {
346            $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
347            }
348
349      $sw->{local_session} = \%session;
350
351      my ($session, $error) = Net::SNMP->session( %{$sw->{local_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->{local_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->{local_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 207 2017-02-16 18:46:22Z 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 $sw = $SWITCH_DB{$sw_name};
1659      my %session = ( -hostname => $sw->{hostname} );
1660         $session{-version} = $sw->{version}   || 1;
1661         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1662      if (exists $sw->{version} and $sw->{version} eq '3') {
1663         $session{-username} = $sw->{username} || 'snmpadmin';
1664         }
1665      else {
1666         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1667         }
1668
1669      my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
1670      my $research2 = $OID_NUMBER{searchPort2} .'.'. 0 . mac_address_hex_to_dec($mac_address);
1671      print "Klask search OID $research1 on switch $sw_name\n";
1672      print "Klask search OID $research2 on switch $sw_name\n";
1673
1674      my ($session, $error) = Net::SNMP->session( %session );
1675      print "$error \n" if $error;
1676
1677      my $result = $session->get_request(
1678         -varbindlist => [$research1]
1679         );
1680      if (not defined $result) {
1681         $result = $session->get_request(
1682            -varbindlist => [$research2]
1683            );
1684         $result->{$research1} = $result->{$research2} if defined $result;
1685         }
1686
1687      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1688         my $swport = $result->{$research1};
1689         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1690         }
1691      else {
1692         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1693         }
1694
1695      $session->close;
1696      }
1697   return;
1698   }
1699
1700sub cmd_get_vlan_port {
1701   @ARGV = @_;
1702
1703   my $verbose;
1704   GetOptions(
1705      'verbose|v' => \$verbose,
1706      );
1707
1708   my $switch_name = shift @ARGV || q{};
1709   my $switch_port = shift @ARGV || q{};
1710
1711   if ($switch_name eq q{} or $switch_port eq q{}) {
1712      die "Usage: klask get-vlan-port SWITCH_NAME PORT\n";
1713      }
1714
1715   for my $sw_name (split /,/, $switch_name) {
1716      if (not defined $SWITCH_DB{$sw_name}) {
1717         die "Switch $sw_name must be defined in klask configuration file\n";
1718         }
1719
1720      my $sw = $SWITCH_DB{$sw_name};
1721      my %session = ( -hostname => $sw->{hostname} );
1722         $session{-version} = $sw->{version}   || 1;
1723         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1724      if (exists $sw->{version} and $sw->{version} eq '3') {
1725         $session{-username} = $sw->{username} || 'snmpadmin';
1726         }
1727      else {
1728         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1729         }
1730
1731      my $search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
1732
1733      my ($session, $error) = Net::SNMP->session( %session );
1734      print "$error \n" if $error;
1735
1736      my $result = $session->get_request(
1737         -varbindlist => [$search],
1738         );
1739
1740      if (defined $result and $result->{$search} ne 'noSuchInstance') {
1741         my $vlan_id = $result->{$search} || 'empty';
1742         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
1743         }
1744      else {
1745         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
1746         }
1747
1748      $session->close;
1749      }
1750   return;
1751   }
1752
1753sub cmd_set_vlan_name {
1754   }
1755
1756# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'hpicfReset'}.0" i 2;
1757sub cmd_rebootsw {
1758   @ARGV = @_;
1759
1760   my $verbose;
1761   GetOptions(
1762      'verbose|v' => \$verbose,
1763      );
1764
1765   my $switch_name = shift @ARGV || q{};
1766
1767   if ($switch_name eq q{}) {
1768      die "Usage: klask rebootsw SWITCH_NAME\n";
1769      }
1770
1771   for my $sw_name (split /,/, $switch_name) {
1772      if (not defined $SWITCH_DB{$sw_name}) {
1773         die "Switch $sw_name must be defined in klask configuration file\n";
1774         }
1775
1776      my $sw = $SWITCH_DB{$sw_name};
1777      my %session = ( -hostname => $sw->{hostname} );
1778         $session{-version} = $sw->{version}   || 1;
1779         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1780      if (exists $sw->{version} and $sw->{version} eq '3') {
1781         $session{-username} = $sw->{username} || 'snmpadmin';
1782         }
1783      else {
1784         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1785         }
1786
1787      my ($session, $error) = Net::SNMP->session( %session );
1788      print "$error \n" if $error;
1789
1790      my $result = $session->set_request(
1791         -varbindlist => ["$OID_NUMBER{'hpicfReset'}.0", INTEGER, 2],
1792         );
1793
1794      $session->close;
1795      }
1796   return;
1797   }
1798
1799sub cmd_get_vlan_name {
1800   my $switch_name = shift || q{};
1801   my $vlan_id     = shift || q{};
1802
1803   if ($switch_name eq q{} or $vlan_id eq q{}) {
1804      die "Usage: klask get-vlan-name SWITCH_NAME VLAN_ID\n";
1805      }
1806
1807   $switch_name = join(',', map {$_->{hostname}} @SWITCH_LIST ) if $switch_name eq q{*};
1808
1809   for my $sw_name (split /,/, $switch_name) {
1810      if (not defined $SWITCH_DB{$sw_name}) {
1811         die "Switch $sw_name must be defined in klask configuration file\n";
1812         }
1813
1814      my $sw = $SWITCH_DB{$sw_name};
1815      my %session = ( -hostname => $sw->{hostname} );
1816         $session{-version} = $sw->{version}   || 1;
1817         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1818      if (exists $sw->{version} and $sw->{version} eq '3') {
1819         $session{-username} = $sw->{username} || 'snmpadmin';
1820         }
1821      else {
1822         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1823         }
1824
1825      my $search_vlan_name = $OID_NUMBER{vlanName} . ".$vlan_id";
1826
1827      my ($session, $error) = Net::SNMP->session( %session );
1828      print "$error \n" if $error;
1829
1830      my $result = $session->get_request(
1831         -varbindlist => [$search_vlan_name]
1832         );
1833
1834      if (defined $result and $result->{$search_vlan_name} ne 'noSuchInstance') {
1835         my $vlan_name = $result->{$search_vlan_name} || 'empty';
1836         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
1837         }
1838      else {
1839         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
1840         }
1841
1842      $session->close;
1843      }
1844   return;
1845   }
1846
1847sub cmd_ip_location {
1848   my $computerdb = computerdb_load();
1849
1850   LOOP_ON_IP_ADDRESS:
1851   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1852
1853      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1854
1855      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1856      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
1857
1858      my $sw_location = q{};
1859      LOOP_ON_ALL_SWITCH:
1860      for my $sw (@SWITCH_LIST) {
1861         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{hostname};
1862         $sw_location = $sw->{location};
1863         last;
1864         }
1865
1866      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1867      }
1868   return;
1869   }
1870
1871sub cmd_ip_free {
1872   @ARGV = @_; # VLAN name with option
1873
1874   my $days_to_dead = 365 * 2;
1875   my $format = 'txt';
1876   my $verbose;
1877
1878   GetOptions(
1879      'day|d=i'      => \$days_to_dead,
1880      'format|f=s'   => \$format,
1881      'verbose|v'    => \$verbose,
1882      );
1883
1884   my %possible_format = (
1885      txt  => \&cmd_ip_free_txt,
1886      html => \&cmd_ip_free_html,
1887      none => sub {},
1888      );
1889   $format = 'txt' if not defined $possible_format{$format};
1890
1891   my @vlan_name = @ARGV;
1892   @vlan_name = get_list_network() if not @vlan_name;
1893
1894   my $computerdb = {};
1895      $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
1896   my $timestamp = time;
1897
1898   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_dead );
1899
1900   my %result_ip = ();
1901
1902   ALL_NETWORK:
1903   for my $vlan (@vlan_name) {
1904
1905      my @ip_list = get_list_ip($vlan);
1906
1907      LOOP_ON_IP_ADDRESS:
1908      for my $ip (@ip_list) {
1909
1910         if (exists $computerdb->{$ip}) {
1911            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{timestamp} > $timestamp_barrier;
1912
1913            my $mac_address = $computerdb->{$ip}{mac_address};
1914            LOOP_ON_DATABASE:
1915            for my $ip_db (keys %{$computerdb}) {
1916               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{mac_address} ne $mac_address;
1917               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{timestamp} > $timestamp_barrier;
1918               }
1919            }
1920
1921         my $ip_date_last_detection = '';
1922         if (exists $computerdb->{$ip}) {
1923            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1924            $year += 1900;
1925            $mon++;
1926            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1927            }
1928
1929         my $packed_ip = scalar gethostbyname($ip);
1930         my $hostname_fq = 'unknown';
1931            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
1932
1933         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1934
1935         $result_ip{$ip} ||= {};
1936         $result_ip{$ip}->{date_last_detection} = $ip_date_last_detection;
1937         $result_ip{$ip}->{hostname_fq} = $hostname_fq;
1938         $result_ip{$ip}->{vlan} = $vlan;
1939
1940         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
1941         }
1942      }
1943
1944   $possible_format{$format}->(%result_ip);
1945   }
1946
1947sub cmd_ip_free_txt {
1948   my %result_ip = @_;
1949
1950   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
1951   print "-------------------------------------------------------------------------------\n";
1952   LOOP_ON_IP_ADDRESS:
1953   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1954         my $vlan_nameid = $result_ip{$ip}->{vlan}.'('.get_current_vlan_id($result_ip{$ip}->{vlan}).')';
1955         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{hostname_fq}, $result_ip{$ip}->{date_last_detection}, $vlan_nameid;
1956      }
1957   }
1958
1959sub cmd_ip_free_html {
1960   my %result_ip = @_;
1961
1962   print <<'END_HTML';
1963<table class="sortable" summary="Klask Free IP Database">
1964 <caption>Klask Free IP Database</caption>
1965 <thead>
1966  <tr>
1967   <th scope="col" class="klask-header-left">IPv4-Address</th>
1968   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1969   <th scope="col" class="sorttable_alpha">VLAN</th>
1970   <th scope="col" class="klask-header-right">Date</th>
1971  </tr>
1972 </thead>
1973 <tfoot>
1974  <tr>
1975   <th scope="col" class="klask-footer-left">IPv4-Address</th>
1976   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1977   <th scope="col" class="fklask-vlan">VLAN</th>
1978   <th scope="col" class="klask-footer-right">Date</th>
1979  </tr>
1980 </tfoot>
1981 <tbody>
1982END_HTML
1983
1984   my $typerow = 'even';
1985
1986   LOOP_ON_IP_ADDRESS:
1987   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1988
1989      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1990
1991      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1992      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{hostname_fq};
1993
1994      my $vlan_nameid = $result_ip{$ip}->{vlan}.'('.get_current_vlan_id($result_ip{$ip}->{vlan}).')';
1995
1996      print <<"END_HTML";
1997  <tr class="$typerow">
1998   <td sorttable_customkey="$ip_sort">$ip</td>
1999   <td sorttable_customkey="$host_short">$result_ip{$ip}->{hostname_fq}</td>
2000   <td>$vlan_nameid</td>
2001   <td>$result_ip{$ip}->{date_last_detection}</td>
2002  </tr>
2003END_HTML
2004      }
2005   print <<'END_HTML';
2006 </tbody>
2007</table>
2008END_HTML
2009   }
2010
2011sub cmd_enable {
2012   my $switch = shift;
2013   my $port   = shift;
2014
2015   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2016   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2017   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2018   return;
2019   }
2020
2021sub cmd_disable {
2022   my $switch = shift;
2023   my $port   = shift;
2024
2025   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2026   return;
2027   }
2028
2029sub cmd_status {
2030   @ARGV = @_;
2031
2032   my $verbose;
2033
2034   GetOptions(
2035      'verbose|v' => \$verbose,
2036      );
2037
2038   my $switch_name = shift @ARGV || q{};
2039   my $port        = shift @ARGV || q{};
2040
2041   if ($switch_name eq q{} or $port eq q{}) {
2042      die "Usage: klask status SWITCH_NAME PORT\n";
2043      }
2044
2045   if (not defined $SWITCH_DB{$switch_name}) {
2046      die "Switch $switch_name must be defined in klask configuration file\n";
2047      }
2048
2049   my $sw = $SWITCH_DB{$switch_name};
2050   my %session = ( -hostname   => $sw->{hostname} );
2051      $session{-version} = $sw->{version}   || 1;
2052      $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
2053      if (exists $sw->{version} and $sw->{version} eq '3') {
2054         $session{-username} = $sw->{username} || 'snmpadmin';
2055         }
2056      else {
2057         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
2058         }
2059   $sw->{local_session} = \%session;
2060
2061   my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
2062   print "$error \n" if $error;
2063
2064   my $oid_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port;
2065   print "Info: switch $switch_name port $port SNMP OID $oid_portstatus\n" if $verbose;
2066   my $result = $session->get_request(
2067      -varbindlist => [$oid_portstatus]
2068      );
2069   if (defined $result) {
2070      print "$PORT_UPDOWN{$result->{$oid_portstatus}}\n";
2071      }
2072
2073   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
2074   return;
2075   }
2076
2077sub cmd_search_mac_on_switch {
2078   @ARGV = @_;
2079
2080   my $verbose;
2081   my $vlan_id = 0;
2082
2083   GetOptions(
2084      'verbose|v' => \$verbose,
2085      'vlan|l=i'  => \$vlan_id,
2086      );
2087
2088   my $switch_name = shift @ARGV || q{};
2089   my $mac_address = shift @ARGV || q{};
2090
2091   if ($switch_name eq q{} or $mac_address eq q{}) {
2092      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2093      }
2094
2095   $mac_address = normalize_mac_address($mac_address);
2096   $switch_name = join(',', map {$_->{hostname}} @SWITCH_LIST ) if $switch_name eq q{*};
2097
2098   for my $sw_name (split /,/, $switch_name) {
2099      if (not defined $SWITCH_DB{$sw_name}) {
2100         die "Switch $sw_name must be defined in klask configuration file\n";
2101         }
2102
2103      my $sw = $SWITCH_DB{$sw_name};
2104      my %session = ( -hostname => $sw->{hostname} );
2105         $session{-version} = $sw->{version}   || 1;
2106         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
2107      if (exists $sw->{version} and $sw->{version} eq '3') {
2108         $session{-username} = $sw->{username} || 'snmpadmin';
2109         }
2110      else {
2111         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
2112         }
2113
2114      my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
2115      my $research2 = $OID_NUMBER{searchPort2} .'.'. $vlan_id . mac_address_hex_to_dec($mac_address);
2116      print "Klask search OID $research1 on switch $sw_name\n" if $verbose;
2117      print "Klask search OID $research2 on switch $sw_name\n" if $verbose;
2118
2119      my ($session, $error) = Net::SNMP->session( %session );
2120      print "$error \n" if $error;
2121
2122      my $result = $session->get_request(
2123         -varbindlist => [$research1]
2124         );
2125      if (not defined $result) {
2126         $result = $session->get_request(
2127            -varbindlist => [$research2]
2128            );
2129         $result->{$research1} = $result->{$research2} if defined $result;
2130         }
2131
2132      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
2133         my $swport_num = $result->{$research1};
2134         my $swport_hr = get_human_readable_port($sw->{model}, snmp_get_swithport_hr($session, $swport_num));
2135         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
2136         }
2137      else {
2138         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
2139         }
2140
2141      $session->close;
2142      }
2143   return;
2144   }
2145
2146sub cmd_updatesw {
2147   @ARGV = @_;
2148
2149   my $verbose;
2150
2151   GetOptions(
2152      'verbose|v' => \$verbose,
2153      );
2154
2155   init_switch_names('yes');    #nomme les switchs
2156   print "\n";
2157
2158   my %where = ();
2159   my %db_switch_output_port = ();
2160   my %db_switch_ip_hostnamefq = ();
2161
2162   DETECT_ALL_ROUTER:
2163   for my $one_router ( get_list_main_router(get_list_network()) ) {
2164      print "Info: router loop $one_router\n" if $verbose;
2165      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
2166
2167      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
2168      print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
2169
2170      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{interface});
2171      my $vlan_id   = get_current_vlan_id($vlan_name);
2172      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address},$vlan_id); # retrouve les emplacements des routeurs
2173      }
2174
2175   ALL_ROUTER_IP_ADDRESS:
2176   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
2177
2178      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
2179
2180      ALL_SWITCH_CONNECTED:
2181      for my $switch_detected ( keys %{$where{$ip_router}} ) {
2182
2183         my $switch = $where{$ip_router}->{$switch_detected};
2184
2185         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
2186
2187         $db_switch_output_port{$switch->{hostname}} = $switch->{port_hr};
2188         print "VERBOSE_2: output port $switch->{hostname} : $switch->{port_hr}\n" if $verbose;
2189         }
2190      }
2191
2192   my %db_switch_link_with = ();
2193
2194   my @list_all_switch = ();
2195   my @list_switch_ipv4 = ();
2196   for my $sw (@SWITCH_LIST){
2197      push @list_all_switch, $sw->{hostname};
2198      }
2199
2200   my $timestamp = time;
2201
2202   ALL_SWITCH:
2203   for my $one_switch (@list_all_switch) {
2204      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
2205      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
2206         print "WARNING: fake ip on switch $one_switch -> $SWITCH_DB{$one_switch}{'fake-ip'} / $resol_arp{ipv4_address}\n" if $verbose;
2207         my %resol_arp_alt = resolve_ip_arp_host($SWITCH_DB{$one_switch}{'fake-ip'}, q{*}, q{low}); # arp resolution
2208         if ($resol_arp_alt{mac_address} ne 'unknow') {
2209            $resol_arp{mac_address}   = $resol_arp_alt{mac_address};
2210            $resol_arp{interface}     = $resol_arp_alt{interface};
2211            $resol_arp{ipv4_address} .= '*';
2212            }
2213         }
2214      print "Info: switch loop $one_switch\n" if $verbose;
2215      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
2216
2217      push @list_switch_ipv4, $resol_arp{ipv4_address};
2218
2219      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{interface});
2220      my $vlan_id   = get_current_vlan_id($vlan_name);
2221      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address},$vlan_id); # find port on all switch
2222
2223      if ($verbose) {
2224         print "VERBOSE_3: $one_switch $resol_arp{ipv4_address} $resol_arp{mac_address}\n";
2225         print "VERBOSE_3: $one_switch --- ",
2226            join(' + ', keys %{$where{$resol_arp{ipv4_address}}}),
2227            "\n";
2228         }
2229
2230      $db_switch_ip_hostnamefq{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
2231      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{ipv4_address} -> $resol_arp{hostname_fq}\n" if $verbose;
2232
2233      $SWITCH_DB{$one_switch}->{ipv4_address} = $resol_arp{ipv4_address};
2234      $SWITCH_DB{$one_switch}->{mac_address}  = $resol_arp{mac_address};
2235      $SWITCH_DB{$one_switch}->{timestamp}    = $timestamp;
2236      }
2237
2238   ALL_SWITCH_IP_ADDRESS:
2239   for my $ip (@list_switch_ipv4) {
2240#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
2241
2242      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2243
2244      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
2245#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
2246
2247      DETECTED_SWITCH:
2248      for my $switch_detected ( keys %{$where{$ip}} ) {
2249
2250         my $switch = $where{$ip}->{$switch_detected};
2251         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{hostname} : $switch->{port_hr}\n" if $verbose;
2252
2253         next if $switch->{port}     eq '0';
2254         next if $switch->{port_hr}  eq $db_switch_output_port{$switch->{hostname}};
2255         next if $switch->{hostname} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{hostname};
2256
2257         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
2258         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{hostname} } = $switch->{port_hr};
2259         print "VERBOSE_7: +++++\n" if $verbose;
2260         }
2261
2262      }
2263
2264   my %db_switch_connected_on_port = ();
2265   my $maybe_more_than_one_switch_connected = 'yes';
2266   my $cloop = 0;
2267
2268   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2269      $cloop++;
2270      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
2271      for my $sw (keys %db_switch_link_with) {
2272         for my $connect (keys %{$db_switch_link_with{$sw}}) {
2273
2274            my $port_hr = $db_switch_link_with{$sw}->{$connect};
2275
2276            $db_switch_connected_on_port{"$connect:$port_hr"} ||= {};
2277            $db_switch_connected_on_port{"$connect:$port_hr"}->{$sw}++; # Just to define the key
2278            }
2279         }
2280
2281      $maybe_more_than_one_switch_connected  = 'no';
2282
2283      SWITCH_AND_PORT:
2284      for my $swport (keys %db_switch_connected_on_port) {
2285
2286         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
2287
2288         $maybe_more_than_one_switch_connected = 'yes';
2289
2290         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2291         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
2292         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
2293
2294         CONNECTED:
2295         for my $sw_connected (@sw_on_same_port) {
2296
2297            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
2298
2299            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
2300
2301            for my $other_sw (@sw_on_same_port) {
2302               next if $other_sw eq $sw_connected;
2303
2304               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2305               }
2306
2307            # We can not do better for this switch for this loop
2308            next SWITCH_AND_PORT;
2309            }
2310         }
2311      }
2312
2313   my %db_switch_parent =();
2314
2315   for my $sw (keys %db_switch_link_with) {
2316      for my $connect (keys %{$db_switch_link_with{$sw}}) {
2317
2318         my $port_hr = $db_switch_link_with{$sw}->{$connect};
2319
2320         $db_switch_connected_on_port{"$connect:$port_hr"} ||= {};
2321         $db_switch_connected_on_port{"$connect:$port_hr"}->{$sw} = $port_hr;
2322
2323         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
2324         }
2325      }
2326
2327   print "Switch output port and parent port connection\n";
2328   print "---------------------------------------------\n";
2329   for my $sw (sort keys %db_switch_output_port) {
2330      if (exists $db_switch_parent{$sw}) {
2331         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port_hr}, $db_switch_parent{$sw}->{switch};
2332         }
2333      else {
2334         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2335         }
2336      }
2337   print "\n";
2338
2339   print "Switch parent and children port inter-connection\n";
2340   print "------------------------------------------------\n";
2341   for my $swport (sort keys %db_switch_connected_on_port) {
2342      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2343      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2344         if (exists $db_switch_output_port{$sw}) {
2345            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2346            }
2347         else {
2348            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2349            }
2350         }
2351      }
2352
2353   my $switch_connection = {
2354      output_port       => \%db_switch_output_port,
2355      parent            => \%db_switch_parent,
2356      connected_on_port => \%db_switch_connected_on_port,
2357      link_with         => \%db_switch_link_with,
2358      switch_db         => \%SWITCH_DB,
2359      };
2360
2361   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
2362   return;
2363   }
2364
2365sub cmd_exportsw {
2366   @ARGV = @_;
2367
2368   test_switchdb_environnement();
2369
2370   my $format = 'txt';
2371
2372   GetOptions(
2373      'format|f=s'  => \$format,
2374      );
2375
2376   my %possible_format = (
2377      txt => \&cmd_exportsw_txt,
2378      dot => \&cmd_exportsw_dot,
2379      );
2380
2381   $format = 'txt' if not defined $possible_format{$format};
2382
2383   $possible_format{$format}->(@ARGV);
2384   return;
2385   }
2386
2387sub cmd_exportsw_txt {
2388
2389   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2390
2391   my %db_switch_output_port       = %{$switch_connection->{output_port}};
2392   my %db_switch_parent            = %{$switch_connection->{parent}};
2393   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
2394
2395   print "Switch output port and parent port connection\n";
2396   print "---------------------------------------------\n";
2397   for my $sw (sort keys %db_switch_output_port) {
2398      if (exists $db_switch_parent{$sw}) {
2399         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port_hr}, $db_switch_parent{$sw}->{switch};
2400         }
2401      else {
2402         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2403         }
2404      }
2405   print "\n";
2406
2407   print "Switch parent and children port inter-connection\n";
2408   print "------------------------------------------------\n";
2409   for my $swport (sort keys %db_switch_connected_on_port) {
2410      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2411      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2412         if (exists $db_switch_output_port{$sw}) {
2413            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2414            }
2415         else {
2416            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2417            }
2418         }
2419      }
2420   return;
2421   }
2422
2423sub cmd_exportsw_dot {
2424
2425   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2426
2427   my %db_switch_output_port       = %{$switch_connection->{output_port}};
2428   my %db_switch_parent            = %{$switch_connection->{parent}};
2429   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
2430   my %db_switch_link_with         = %{$switch_connection->{link_with}};
2431   my %db_switch_global            = %{$switch_connection->{switch_db}};
2432
2433   my %db_building= ();
2434   for my $sw (@SWITCH_LIST) {
2435      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
2436      $db_building{$building} ||= {};
2437      $db_building{$building}->{$location} ||= {};
2438      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
2439      }
2440
2441
2442   print "digraph G {\n";
2443   print "rankdir = LR;\n";
2444   #print "splines=polyline;\n";
2445
2446   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2447   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
2448
2449   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime time;
2450   $year += 1900;
2451   $mon++;
2452   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2453   print "\"$date\" [ color = white, fillcolor = black, shape = polygon, sides=14, style = filled, fontcolor = white ]\n";
2454   print "site -> \"$date\" [color = white];\n";
2455
2456   my $b=0;
2457   for my $building (keys %db_building) {
2458      $b++;
2459
2460      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2461      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
2462
2463      my $l = 0;
2464      for my $loc (keys %{$db_building{$building}}) {
2465         $l++;
2466
2467         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
2468#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
2469         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
2470
2471         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2472
2473            print "\"$sw:$db_switch_output_port{$sw}\" [label = \"$db_switch_output_port{$sw}\", color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
2474
2475            my $swname  = $sw;
2476               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2477            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
2478            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2479            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
2480
2481
2482            for my $swport (keys %db_switch_connected_on_port) {
2483               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2484               next if not $sw_connect eq $sw;
2485               next if $port_connect eq $db_switch_output_port{$sw};
2486               print "\"$sw:$port_connect\" [label = \"$port_connect\", color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
2487               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
2488              }
2489            }
2490         }
2491      }
2492
2493#   print "Switch output port and parent port connection\n";
2494#   print "---------------------------------------------\n";
2495   for my $sw (sort keys %db_switch_output_port) {
2496      if (exists $db_switch_parent{$sw}) {
2497#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
2498         }
2499      else {
2500         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
2501         }
2502      }
2503   print "\n";
2504
2505#   print "Switch parent and children port inter-connection\n";
2506#   print "------------------------------------------------\n";
2507   for my $swport (sort keys %db_switch_connected_on_port) {
2508      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2509      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2510         if (exists $db_switch_output_port{$sw}) {
2511            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2512            }
2513         else {
2514            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
2515            }
2516         }
2517      }
2518
2519print "}\n";
2520   return;
2521   }
2522
2523
2524__END__
2525
2526=head1 NAME
2527
2528klask - ports manager and finder for switch
2529
2530
2531=head1 USAGE
2532
2533 klask updatedb [--verbose] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2534 klask exportdb --format [txt|html]
2535 klask removedb computer*
2536 klask cleandb  [--verbose] --day number_of_day --repair-dns
2537
2538 klask updatesw [--verbose]
2539 klask exportsw --format [txt|dot]
2540
2541 klask searchdb --kind [host|mac] computer [mac-address]
2542 klask search   computer
2543 klask search-mac-on-switch [--verbose] [--vlan|-i vlan-id] switch mac_addr
2544
2545 klask ip-free  [--verbose] --day number_of_day --format [txt|html] [vlan_name]
2546
2547 klask enable  switch port
2548 klask disable swith port
2549 klask status  swith port
2550
2551
2552=head1 DESCRIPTION
2553
2554klask is a small tool to find where is a host in a big network. klask mean search in brittany.
2555No hight level protocol like CDL, LLDP are use.
2556Everything is just done with SNMP request on MAC address.
2557
2558Limitation : loop cannot be detected and could be problematic when create the map.
2559If you use PVST or MSTP and create loop between VLAN,
2560you have to use portignore functionality on switch port to cut manually loop
2561(see config file below).
2562
2563When you use a management port to administrate the switch,
2564it's not possible to create the map because the switch doesn't have a MAC address,
2565so other switch cannot find downlink port...
2566A way is if you have a computer directly connected on the switch is to put this IP as the fake ip for the switch.
2567The MAC address associated will be use just for the map detection.
2568The fake-ip parameter is defined in the config file.
2569
2570Klask has now a web site dedicated for it !
2571
2572 http://servforge.legi.grenoble-inp.fr/projects/klask
2573
2574
2575=head1 COMMANDS
2576
2577
2578=head2 search
2579
2580This command takes one or more computer in argument.
2581It search a computer on the network and give the port and the switch on which the computer is connected.
2582
2583
2584=head2 enable
2585
2586This command activate a port on a switch by SNMP.
2587So you need to give the switch and the port number on the command line.
2588
2589
2590=head2 disable
2591
2592This command deactivate a port on a switch by SNMP.
2593So you need to give the switch and the port number on the command line.
2594
2595
2596=head2 status
2597
2598This command return the status of a port number on a switch by SNMP.
2599So you need to give the switch name and the port number on the command line.
2600
2601
2602=head2 updatedb
2603
2604This command will scan networks and update a database.
2605To know which are the cmputer scan, you have to configure the file F</etc/klask/klask.conf>.
2606This file is easy to read and write because klask use YAML format and not XML.
2607
2608
2609=head2 exportdb --format [txt|html]
2610
2611This command print the content of the database. There is actually only one format.
2612It's very easy to have more format, it's just need times...
2613
2614=head2 removedb
2615
2616This command remove an entry in the database.
2617There is only one kind of parameter, the IP of the computers to be removed.
2618You can put as many IP as you want...
2619
2620DNS computer name could also a valid entry because a DNS resolver is done at the beginning.
2621
2622=head2 cleandb
2623
2624Remove double entry (same MAC-Address) when the older one is older than X day (--day) the new one.
2625Computer name beginning by 'float' are not really taken into account but could be remove.
2626
2627When reverse DNS has not be done, option --repair-dns force reverse DNS check on unkown host.
2628
2629=head2 updatesw
2630
2631This command build a map of your manageable switch on your network.
2632The list of the switch must be given in the file F</etc/klask/klask.conf>.
2633
2634
2635=head2 exportsw --format [txt|dot]
2636
2637This command print the content of the switch database. There is actually two format.
2638One is just txt for terminal and the other is the dot format from the graphviz environnement.
2639
2640 klask exportsw --format dot > /tmp/map.dot
2641 dot -Tpng /tmp/map.dot > /tmp/map.png
2642
2643
2644
2645=head1 CONFIGURATION
2646
2647Because klask need many parameters, it's not possible actually to use command line parameters for everything.
2648The configuration is done in a F</etc/klask/klask.conf> YAML file.
2649This format have many advantage over XML, it's easier to read and to write !
2650
2651Here an example, be aware with indent, it's important in YAML, do not use tabulation !
2652
2653 default:
2654   community: public
2655   snmpport: 161
2656
2657 network:
2658   labnet:
2659     ip-subnet:
2660       - add: 192.168.1.0/24
2661       - add: 192.168.2.0/24
2662     interface: eth0
2663     vlan-id: 12
2664     main-router: gw1.labnet.local
2665
2666   schoolnet:
2667     ip-subnet:
2668       - add: 192.168.6.0/24
2669       - add: 192.168.7.0/24
2670     interface: eth0.38
2671     vlan-id: 13
2672     main-router: gw2.schoolnet.local
2673
2674 switch:
2675   - hostname: sw1.klask.local
2676     portignore:
2677       - 1
2678       - 2
2679
2680   - hostname: sw2.klask.local
2681     location: BatK / 2 / K203
2682     type: HP2424
2683     portignore:
2684       - 1
2685       - 2
2686     fake-ip: 192.168.9.14
2687
2688I think it's pretty easy to understand.
2689The default section can be overide in any section, if parameter mean something in theses sections.
2690Network to be scan are define in the network section. You must put a add by network.
2691Maybe i will make a delete line to suppress specific computers.
2692The switch section define your switch.
2693You have to write the port number to ignore, this is important if your switchs are cascade.
2694Juste put the ports numbers between switch.
2695
2696
2697=head1 FILES
2698
2699 /etc/klask/klask.conf
2700 /var/lib/klask/klaskdb
2701 /var/lib/klask/switchdb
2702
2703=head1 SEE ALSO
2704
2705Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
2706
2707
2708=head1 VERSION
2709
2710$Id: klask 207 2017-02-16 18:46:22Z g7moreau $
2711
2712
2713=head1 AUTHOR
2714
2715Written by Gabriel Moreau, Grenoble - France
2716
2717
2718=head1 LICENSE AND COPYRIGHT
2719
2720GPL version 2 or later and Perl equivalent
2721
2722Copyright (C) 2005-2017 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.