source: trunk/klask @ 208

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