source: trunk/klask @ 150

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