source: trunk/klask @ 160

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