source: trunk/klask @ 206

Last change on this file since 206 was 206, checked in by g7moreau, 7 years ago
  • Rewrite cmd_status
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 92.8 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2017 Gabriel Moreau
4#
5# $Id: klask 206 2017-02-16 18:39:58Z 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 206 2017-02-16 18:39:58Z 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   my $sw = $SWITCH_DB{$switch_name};
2042   my %session = ( -hostname   => $sw->{hostname} );
2043      $session{-version} = $sw->{version}   || 1;
2044      $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
2045      if (exists $sw->{version} and $sw->{version} eq '3') {
2046         $session{-username} = $sw->{username} || 'snmpadmin';
2047         }
2048      else {
2049         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
2050         }
2051   $sw->{local_session} = \%session;
2052
2053   my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
2054   print "$error \n" if $error;
2055
2056   my $oid_portstatus = $OID_NUMBER{'portUpDown'} .'.'. $port;
2057   print "Info: switch $switch_name port $port SNMP OID $oid_portstatus\n" if $verbose;
2058   my $result = $session->get_request(
2059      -varbindlist => [$oid_portstatus]
2060      );
2061   if (defined $result) {
2062      print "$PORT_UPDOWN{$result->{$oid_portstatus}}\n";
2063      }
2064
2065   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
2066   return;
2067   }
2068
2069sub cmd_search_mac_on_switch {
2070   @ARGV = @_;
2071
2072   my $verbose;
2073   my $vlan_id = 0;
2074
2075   GetOptions(
2076      'verbose|v' => \$verbose,
2077      'vlan|l=i'  => \$vlan_id,
2078      );
2079
2080   my $switch_name = shift @ARGV || q{};
2081   my $mac_address = shift @ARGV || q{};
2082
2083   if ($switch_name eq q{} or $mac_address eq q{}) {
2084      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2085      }
2086
2087   $mac_address = normalize_mac_address($mac_address);
2088   $switch_name = join(',', map {$_->{hostname}} @SWITCH_LIST ) if $switch_name eq q{*};
2089
2090   for my $sw_name (split /,/, $switch_name) {
2091      if (not defined $SWITCH_DB{$sw_name}) {
2092         die "Switch $sw_name must be defined in klask configuration file\n";
2093         }
2094
2095      my $sw = $SWITCH_DB{$sw_name};
2096      my %session = ( -hostname => $sw->{hostname} );
2097         $session{-version} = $sw->{version}   || 1;
2098         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
2099      if (exists $sw->{version} and $sw->{version} eq '3') {
2100         $session{-username} = $sw->{username} || 'snmpadmin';
2101         }
2102      else {
2103         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
2104         }
2105
2106      my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
2107      my $research2 = $OID_NUMBER{searchPort2} .'.'. $vlan_id . mac_address_hex_to_dec($mac_address);
2108      print "Klask search OID $research1 on switch $sw_name\n" if $verbose;
2109      print "Klask search OID $research2 on switch $sw_name\n" if $verbose;
2110
2111      my ($session, $error) = Net::SNMP->session( %session );
2112      print "$error \n" if $error;
2113
2114      my $result = $session->get_request(
2115         -varbindlist => [$research1]
2116         );
2117      if (not defined $result) {
2118         $result = $session->get_request(
2119            -varbindlist => [$research2]
2120            );
2121         $result->{$research1} = $result->{$research2} if defined $result;
2122         }
2123
2124      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
2125         my $swport_num = $result->{$research1};
2126         my $swport_hr = get_human_readable_port($sw->{model}, snmp_get_swithport_hr($session, $swport_num));
2127         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
2128         }
2129      else {
2130         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
2131         }
2132
2133      $session->close;
2134      }
2135   return;
2136   }
2137
2138sub cmd_updatesw {
2139   @ARGV = @_;
2140
2141   my $verbose;
2142
2143   GetOptions(
2144      'verbose|v' => \$verbose,
2145      );
2146
2147   init_switch_names('yes');    #nomme les switchs
2148   print "\n";
2149
2150   my %where = ();
2151   my %db_switch_output_port = ();
2152   my %db_switch_ip_hostnamefq = ();
2153
2154   DETECT_ALL_ROUTER:
2155   for my $one_router ( get_list_main_router(get_list_network()) ) {
2156      print "Info: router loop $one_router\n" if $verbose;
2157      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
2158
2159      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
2160      print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
2161
2162      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{interface});
2163      my $vlan_id   = get_current_vlan_id($vlan_name);
2164      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address},$vlan_id); # retrouve les emplacements des routeurs
2165      }
2166
2167   ALL_ROUTER_IP_ADDRESS:
2168   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
2169
2170      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
2171
2172      ALL_SWITCH_CONNECTED:
2173      for my $switch_detected ( keys %{$where{$ip_router}} ) {
2174
2175         my $switch = $where{$ip_router}->{$switch_detected};
2176
2177         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
2178
2179         $db_switch_output_port{$switch->{hostname}} = $switch->{port_hr};
2180         print "VERBOSE_2: output port $switch->{hostname} : $switch->{port_hr}\n" if $verbose;
2181         }
2182      }
2183
2184   my %db_switch_link_with = ();
2185
2186   my @list_all_switch = ();
2187   my @list_switch_ipv4 = ();
2188   for my $sw (@SWITCH_LIST){
2189      push @list_all_switch, $sw->{hostname};
2190      }
2191
2192   my $timestamp = time;
2193
2194   ALL_SWITCH:
2195   for my $one_switch (@list_all_switch) {
2196      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
2197      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
2198         print "WARNING: fake ip on switch $one_switch -> $SWITCH_DB{$one_switch}{'fake-ip'} / $resol_arp{ipv4_address}\n" if $verbose;
2199         my %resol_arp_alt = resolve_ip_arp_host($SWITCH_DB{$one_switch}{'fake-ip'}, q{*}, q{low}); # arp resolution
2200         if ($resol_arp_alt{mac_address} ne 'unknow') {
2201            $resol_arp{mac_address}   = $resol_arp_alt{mac_address};
2202            $resol_arp{interface}     = $resol_arp_alt{interface};
2203            $resol_arp{ipv4_address} .= '*';
2204            }
2205         }
2206      print "Info: switch loop $one_switch\n" if $verbose;
2207      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
2208
2209      push @list_switch_ipv4, $resol_arp{ipv4_address};
2210
2211      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{interface});
2212      my $vlan_id   = get_current_vlan_id($vlan_name);
2213      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address},$vlan_id); # find port on all switch
2214
2215      if ($verbose) {
2216         print "VERBOSE_3: $one_switch $resol_arp{ipv4_address} $resol_arp{mac_address}\n";
2217         print "VERBOSE_3: $one_switch --- ",
2218            join(' + ', keys %{$where{$resol_arp{ipv4_address}}}),
2219            "\n";
2220         }
2221
2222      $db_switch_ip_hostnamefq{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
2223      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{ipv4_address} -> $resol_arp{hostname_fq}\n" if $verbose;
2224
2225      $SWITCH_DB{$one_switch}->{ipv4_address} = $resol_arp{ipv4_address};
2226      $SWITCH_DB{$one_switch}->{mac_address}  = $resol_arp{mac_address};
2227      $SWITCH_DB{$one_switch}->{timestamp}    = $timestamp;
2228      }
2229
2230   ALL_SWITCH_IP_ADDRESS:
2231   for my $ip (@list_switch_ipv4) {
2232#   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
2233
2234      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
2235
2236      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
2237#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
2238
2239      DETECTED_SWITCH:
2240      for my $switch_detected ( keys %{$where{$ip}} ) {
2241
2242         my $switch = $where{$ip}->{$switch_detected};
2243         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{hostname} : $switch->{port_hr}\n" if $verbose;
2244
2245         next if $switch->{port}     eq '0';
2246         next if $switch->{port_hr}  eq $db_switch_output_port{$switch->{hostname}};
2247         next if $switch->{hostname} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{hostname};
2248
2249         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
2250         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{hostname} } = $switch->{port_hr};
2251         print "VERBOSE_7: +++++\n" if $verbose;
2252         }
2253
2254      }
2255
2256   my %db_switch_connected_on_port = ();
2257   my $maybe_more_than_one_switch_connected = 'yes';
2258   my $cloop = 0;
2259
2260   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
2261      $cloop++;
2262      print "VERBOSE_9: cloop reduction step: $cloop\n" if $verbose;
2263      for my $sw (keys %db_switch_link_with) {
2264         for my $connect (keys %{$db_switch_link_with{$sw}}) {
2265
2266            my $port_hr = $db_switch_link_with{$sw}->{$connect};
2267
2268            $db_switch_connected_on_port{"$connect:$port_hr"} ||= {};
2269            $db_switch_connected_on_port{"$connect:$port_hr"}->{$sw}++; # Just to define the key
2270            }
2271         }
2272
2273      $maybe_more_than_one_switch_connected  = 'no';
2274
2275      SWITCH_AND_PORT:
2276      for my $swport (keys %db_switch_connected_on_port) {
2277
2278         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
2279
2280         $maybe_more_than_one_switch_connected = 'yes';
2281
2282         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2283         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
2284         print "VERBOSE_10: $swport -- ".$#sw_on_same_port." -- @sw_on_same_port\n" if $verbose;
2285
2286         CONNECTED:
2287         for my $sw_connected (@sw_on_same_port) {
2288
2289            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
2290
2291            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
2292
2293            for my $other_sw (@sw_on_same_port) {
2294               next if $other_sw eq $sw_connected;
2295
2296               delete $db_switch_link_with{$other_sw}->{$sw_connect};
2297               }
2298
2299            # We can not do better for this switch for this loop
2300            next SWITCH_AND_PORT;
2301            }
2302         }
2303      }
2304
2305   my %db_switch_parent =();
2306
2307   for my $sw (keys %db_switch_link_with) {
2308      for my $connect (keys %{$db_switch_link_with{$sw}}) {
2309
2310         my $port_hr = $db_switch_link_with{$sw}->{$connect};
2311
2312         $db_switch_connected_on_port{"$connect:$port_hr"} ||= {};
2313         $db_switch_connected_on_port{"$connect:$port_hr"}->{$sw} = $port_hr;
2314
2315         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
2316         }
2317      }
2318
2319   print "Switch output port and parent port connection\n";
2320   print "---------------------------------------------\n";
2321   for my $sw (sort keys %db_switch_output_port) {
2322      if (exists $db_switch_parent{$sw}) {
2323         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port_hr}, $db_switch_parent{$sw}->{switch};
2324         }
2325      else {
2326         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2327         }
2328      }
2329   print "\n";
2330
2331   print "Switch parent and children port inter-connection\n";
2332   print "------------------------------------------------\n";
2333   for my $swport (sort keys %db_switch_connected_on_port) {
2334      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2335      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2336         if (exists $db_switch_output_port{$sw}) {
2337            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2338            }
2339         else {
2340            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2341            }
2342         }
2343      }
2344
2345   my $switch_connection = {
2346      output_port       => \%db_switch_output_port,
2347      parent            => \%db_switch_parent,
2348      connected_on_port => \%db_switch_connected_on_port,
2349      link_with         => \%db_switch_link_with,
2350      switch_db         => \%SWITCH_DB,
2351      };
2352
2353   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
2354   return;
2355   }
2356
2357sub cmd_exportsw {
2358   @ARGV = @_;
2359
2360   test_switchdb_environnement();
2361
2362   my $format = 'txt';
2363
2364   GetOptions(
2365      'format|f=s'  => \$format,
2366      );
2367
2368   my %possible_format = (
2369      txt => \&cmd_exportsw_txt,
2370      dot => \&cmd_exportsw_dot,
2371      );
2372
2373   $format = 'txt' if not defined $possible_format{$format};
2374
2375   $possible_format{$format}->(@ARGV);
2376   return;
2377   }
2378
2379sub cmd_exportsw_txt {
2380
2381   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2382
2383   my %db_switch_output_port       = %{$switch_connection->{output_port}};
2384   my %db_switch_parent            = %{$switch_connection->{parent}};
2385   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
2386
2387   print "Switch output port and parent port connection\n";
2388   print "---------------------------------------------\n";
2389   for my $sw (sort keys %db_switch_output_port) {
2390      if (exists $db_switch_parent{$sw}) {
2391         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port_hr}, $db_switch_parent{$sw}->{switch};
2392         }
2393      else {
2394         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2395         }
2396      }
2397   print "\n";
2398
2399   print "Switch parent and children port inter-connection\n";
2400   print "------------------------------------------------\n";
2401   for my $swport (sort keys %db_switch_connected_on_port) {
2402      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2403      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2404         if (exists $db_switch_output_port{$sw}) {
2405            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2406            }
2407         else {
2408            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2409            }
2410         }
2411      }
2412   return;
2413   }
2414
2415sub cmd_exportsw_dot {
2416
2417   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2418
2419   my %db_switch_output_port       = %{$switch_connection->{output_port}};
2420   my %db_switch_parent            = %{$switch_connection->{parent}};
2421   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
2422   my %db_switch_link_with         = %{$switch_connection->{link_with}};
2423   my %db_switch_global            = %{$switch_connection->{switch_db}};
2424
2425   my %db_building= ();
2426   for my $sw (@SWITCH_LIST) {
2427      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
2428      $db_building{$building} ||= {};
2429      $db_building{$building}->{$location} ||= {};
2430      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
2431      }
2432
2433
2434   print "digraph G {\n";
2435   print "rankdir = LR;\n";
2436   #print "splines=polyline;\n";
2437
2438   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2439   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
2440
2441   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime time;
2442   $year += 1900;
2443   $mon++;
2444   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2445   print "\"$date\" [ color = white, fillcolor = black, shape = polygon, sides=14, style = filled, fontcolor = white ]\n";
2446   print "site -> \"$date\" [color = white];\n";
2447
2448   my $b=0;
2449   for my $building (keys %db_building) {
2450      $b++;
2451
2452      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2453      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
2454
2455      my $l = 0;
2456      for my $loc (keys %{$db_building{$building}}) {
2457         $l++;
2458
2459         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
2460#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
2461         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
2462
2463         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2464
2465            print "\"$sw:$db_switch_output_port{$sw}\" [label = \"$db_switch_output_port{$sw}\", color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
2466
2467            my $swname  = $sw;
2468               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2469            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
2470            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2471            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
2472
2473
2474            for my $swport (keys %db_switch_connected_on_port) {
2475               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2476               next if not $sw_connect eq $sw;
2477               next if $port_connect eq $db_switch_output_port{$sw};
2478               print "\"$sw:$port_connect\" [label = \"$port_connect\", color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
2479               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
2480              }
2481            }
2482         }
2483      }
2484
2485#   print "Switch output port and parent port connection\n";
2486#   print "---------------------------------------------\n";
2487   for my $sw (sort keys %db_switch_output_port) {
2488      if (exists $db_switch_parent{$sw}) {
2489#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
2490         }
2491      else {
2492         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
2493         }
2494      }
2495   print "\n";
2496
2497#   print "Switch parent and children port inter-connection\n";
2498#   print "------------------------------------------------\n";
2499   for my $swport (sort keys %db_switch_connected_on_port) {
2500      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2501      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2502         if (exists $db_switch_output_port{$sw}) {
2503            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2504            }
2505         else {
2506            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
2507            }
2508         }
2509      }
2510
2511print "}\n";
2512   return;
2513   }
2514
2515
2516__END__
2517
2518=head1 NAME
2519
2520klask - ports manager and finder for switch
2521
2522
2523=head1 USAGE
2524
2525 klask updatedb [--verbose] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l]
2526 klask exportdb --format [txt|html]
2527 klask removedb computer*
2528 klask cleandb  [--verbose] --day number_of_day --repair-dns
2529
2530 klask updatesw [--verbose]
2531 klask exportsw --format [txt|dot]
2532
2533 klask searchdb --kind [host|mac] computer [mac-address]
2534 klask search   computer
2535 klask search-mac-on-switch [--verbose] [--vlan|-i vlan-id] switch mac_addr
2536
2537 klask ip-free  [--verbose] --day number_of_day --format [txt|html] [vlan_name]
2538
2539 klask enable  switch port
2540 klask disable swith port
2541 klask status  swith port
2542
2543
2544=head1 DESCRIPTION
2545
2546klask is a small tool to find where is a host in a big network. klask mean search in brittany.
2547No hight level protocol like CDL, LLDP are use.
2548Everything is just done with SNMP request on MAC address.
2549
2550Limitation : loop cannot be detected and could be problematic when create the map.
2551If you use PVST or MSTP and create loop between VLAN,
2552you have to use portignore functionality on switch port to cut manually loop
2553(see config file below).
2554
2555When you use a management port to administrate the switch,
2556it's not possible to create the map because the switch doesn't have a MAC address,
2557so other switch cannot find downlink port...
2558A way is if you have a computer directly connected on the switch is to put this IP as the fake ip for the switch.
2559The MAC address associated will be use just for the map detection.
2560The fake-ip parameter is defined in the config file.
2561
2562Klask has now a web site dedicated for it !
2563
2564 http://servforge.legi.grenoble-inp.fr/projects/klask
2565
2566
2567=head1 COMMANDS
2568
2569
2570=head2 search
2571
2572This command takes one or more computer in argument.
2573It search a computer on the network and give the port and the switch on which the computer is connected.
2574
2575
2576=head2 enable
2577
2578This command activate a port on a switch by SNMP.
2579So you need to give the switch and the port number on the command line.
2580
2581
2582=head2 disable
2583
2584This command deactivate a port on a switch by SNMP.
2585So you need to give the switch and the port number on the command line.
2586
2587
2588=head2 status
2589
2590This command return the status of a port number on a switch by SNMP.
2591So you need to give the switch name and the port number on the command line.
2592
2593
2594=head2 updatedb
2595
2596This command will scan networks and update a database.
2597To know which are the cmputer scan, you have to configure the file F</etc/klask/klask.conf>.
2598This file is easy to read and write because klask use YAML format and not XML.
2599
2600
2601=head2 exportdb --format [txt|html]
2602
2603This command print the content of the database. There is actually only one format.
2604It's very easy to have more format, it's just need times...
2605
2606=head2 removedb
2607
2608This command remove an entry in the database.
2609There is only one kind of parameter, the IP of the computers to be removed.
2610You can put as many IP as you want...
2611
2612DNS computer name could also a valid entry because a DNS resolver is done at the beginning.
2613
2614=head2 cleandb
2615
2616Remove double entry (same MAC-Address) when the older one is older than X day (--day) the new one.
2617Computer name beginning by 'float' are not really taken into account but could be remove.
2618
2619When reverse DNS has not be done, option --repair-dns force reverse DNS check on unkown host.
2620
2621=head2 updatesw
2622
2623This command build a map of your manageable switch on your network.
2624The list of the switch must be given in the file F</etc/klask/klask.conf>.
2625
2626
2627=head2 exportsw --format [txt|dot]
2628
2629This command print the content of the switch database. There is actually two format.
2630One is just txt for terminal and the other is the dot format from the graphviz environnement.
2631
2632 klask exportsw --format dot > /tmp/map.dot
2633 dot -Tpng /tmp/map.dot > /tmp/map.png
2634
2635
2636
2637=head1 CONFIGURATION
2638
2639Because klask need many parameters, it's not possible actually to use command line parameters for everything.
2640The configuration is done in a F</etc/klask/klask.conf> YAML file.
2641This format have many advantage over XML, it's easier to read and to write !
2642
2643Here an example, be aware with indent, it's important in YAML, do not use tabulation !
2644
2645 default:
2646   community: public
2647   snmpport: 161
2648
2649 network:
2650   labnet:
2651     ip-subnet:
2652       - add: 192.168.1.0/24
2653       - add: 192.168.2.0/24
2654     interface: eth0
2655     vlan-id: 12
2656     main-router: gw1.labnet.local
2657
2658   schoolnet:
2659     ip-subnet:
2660       - add: 192.168.6.0/24
2661       - add: 192.168.7.0/24
2662     interface: eth0.38
2663     vlan-id: 13
2664     main-router: gw2.schoolnet.local
2665
2666 switch:
2667   - hostname: sw1.klask.local
2668     portignore:
2669       - 1
2670       - 2
2671
2672   - hostname: sw2.klask.local
2673     location: BatK / 2 / K203
2674     type: HP2424
2675     portignore:
2676       - 1
2677       - 2
2678     fake-ip: 192.168.9.14
2679
2680I think it's pretty easy to understand.
2681The default section can be overide in any section, if parameter mean something in theses sections.
2682Network to be scan are define in the network section. You must put a add by network.
2683Maybe i will make a delete line to suppress specific computers.
2684The switch section define your switch.
2685You have to write the port number to ignore, this is important if your switchs are cascade.
2686Juste put the ports numbers between switch.
2687
2688
2689=head1 FILES
2690
2691 /etc/klask/klask.conf
2692 /var/lib/klask/klaskdb
2693 /var/lib/klask/switchdb
2694
2695=head1 SEE ALSO
2696
2697Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
2698
2699
2700=head1 VERSION
2701
2702$Id: klask 206 2017-02-16 18:39:58Z g7moreau $
2703
2704
2705=head1 AUTHOR
2706
2707Written by Gabriel Moreau, Grenoble - France
2708
2709
2710=head1 LICENSE AND COPYRIGHT
2711
2712GPL version 2 or later and Perl equivalent
2713
2714Copyright (C) 2005-2017 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.