source: trunk/klask @ 182

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