source: trunk/klask @ 183

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