source: trunk/klask @ 68

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