source: trunk/klask @ 174

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