source: trunk/klask @ 123

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