source: trunk/klask @ 166

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