source: trunk/klask @ 128

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