source: trunk/klask @ 174

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