source: trunk/klask @ 169

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