source: trunk/klask @ 179

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