source: trunk/klask @ 212

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