source: trunk/klask @ 150

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