END_HTML
}
sub cmd_enable {
my $switch = shift;
my $port = shift;
#snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
#snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
return;
}
sub cmd_disable {
my $switch = shift;
my $port = shift;
system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
return;
}
sub cmd_status {
my $switch = shift;
my $port = shift;
system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
return;
}
sub cmd_search_mac_on_switch {
my $switch_name = shift || q{};
my $mac_address = shift || q{};
if ($switch_name eq q{} or $mac_address eq q{}) {
die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
}
if (not defined $SWITCH_DB{$switch_name}) {
die "Switch $switch_name must be defined in klask configuration file\n";
}
my $sw = $SWITCH_DB{$switch_name};
my %session = ( -hostname => $sw->{hostname} );
$session{-version} = $sw->{version} || 1;
$session{-port} = $sw->{snmpport} || $DEFAULT{snmpport} || 161;
if (exists $sw->{version} and $sw->{version} eq '3') {
$session{-username} = $sw->{username} || 'snmpadmin';
}
else {
$session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
}
my $research = '1.3.6.1.2.1.17.4.3.1.2' . arp_hex_to_dec($mac_address);
print "Klask search OID $research on switch $switch_name\n";
my ($session, $error) = Net::SNMP->session( %session );
print "$error \n" if $error;
my $result = $session->get_request(
-varbindlist => [$research]
);
if (not defined $result or $result->{$research} eq 'noSuchInstance') {
print "Klask do not find MAC $mac_address on switch $switch_name\n";
$session->close;
}
my $swport = $result->{$research};
$session->close;
print "Klask find MAC $mac_address on switch $switch_name port $swport\n";
return;
}
sub cmd_updatesw {
my @options = @_;
my $verbose;
my $ret = GetOptionsFromArray(\@options,
'verbose|v' => \$verbose,
);
init_switch_names('yes'); #nomme les switchs
print "\n";
my %where = ();
my %db_switch_output_port = ();
my %db_switch_ip_hostname = ();
DETECT_ALL_ROUTER:
# for my $one_computer ('194.254.66.254') {
for my $one_router ( get_list_main_router(get_list_network()) ) {
my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
$where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
}
ALL_ROUTER_IP_ADDRESS:
for my $ip (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip}; # /a priori/ idiot car ne sers à rien...
ALL_SWITCH_CONNECTED:
for my $switch_detected ( keys %{$where{$ip}} ) {
my $switch = $where{$ip}->{$switch_detected};
next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
$db_switch_output_port{$switch->{hostname}} = $switch->{port};
print "VERBOSE_2: output port $switch->{hostname} : $switch->{port}\n" if $verbose;
}
}
my %db_switch_link_with = ();
my @list_switch_ip = ();
my @list_switch_ipv4 = ();
for my $sw (@SWITCH){
push @list_switch_ip, $sw->{hostname};
}
my $timestamp = time;
ALL_SWITCH:
for my $one_computer (@list_switch_ip) {
my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
push @list_switch_ipv4,$resol_arp{ipv4_address};
$where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
if ($verbose) {
my @l = ();
for my $c ( keys %{$where{$resol_arp{ipv4_address}}} ) {
push @l, "$c -+ "; #$where{$resol_arp{ipv4_address}}->{$c}{hostname};
}
print "VERBOSE_3 $one_computer $resol_arp{ipv4_address} $resol_arp{mac_address} --- @l\n";
}
$db_switch_ip_hostname{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
$SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
$SWITCH_DB{$one_computer}->{mac_address} = $resol_arp{mac_address};
$SWITCH_DB{$one_computer}->{timestamp} = $timestamp;
}
ALL_SWITCH_IP_ADDRESS:
for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
DETECTED_SWITCH:
for my $switch_detected ( keys %{$where{$ip}} ) {
next DETECTED_SWITCH if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostname{$ip} };
my $switch = $where{$ip}->{$switch_detected};
#print "DEBUG1 : $db_switch_ip_hostname{$ip} / $switch->{hostname} : $switch->{port}\n" if $switch->{hostname} =~ m/sw3-batA0-3s/;
next if $switch->{port} eq '0';
next if $switch->{port} eq $db_switch_output_port{$switch->{hostname}};
next if $switch->{hostname} eq $db_switch_ip_hostname{$ip}; # $computerdb->{$ip}{hostname};
$db_switch_link_with{ $db_switch_ip_hostname{$ip} } ||= {};
$db_switch_link_with{ $db_switch_ip_hostname{$ip} }->{ $switch->{hostname} } = $switch->{port};
print "VERBOSE_4: $db_switch_ip_hostname{$ip} -> $switch->{hostname} : $switch->{port}\n" if $verbose;
}
}
my %db_switch_connected_on_port = ();
my $maybe_more_than_one_switch_connected = 'yes';
while ($maybe_more_than_one_switch_connected eq 'yes') {
for my $sw (keys %db_switch_link_with) {
for my $connect (keys %{$db_switch_link_with{$sw}}) {
my $port = $db_switch_link_with{$sw}->{$connect};
$db_switch_connected_on_port{"$connect:$port"} ||= {};
$db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
}
}
$maybe_more_than_one_switch_connected = 'no';
SWITCH_AND_PORT:
for my $swport (keys %db_switch_connected_on_port) {
next if keys %{$db_switch_connected_on_port{$swport}} == 1;
$maybe_more_than_one_switch_connected = 'yes';
my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
CONNECTED:
for my $sw_connected (@sw_on_same_port) {
next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
$db_switch_connected_on_port{$swport} = {$sw_connected => 1};
for my $other_sw (@sw_on_same_port) {
next if $other_sw eq $sw_connected;
delete $db_switch_link_with{$other_sw}->{$sw_connect};
}
# We can not do better for this switch for this loop
next SWITCH_AND_PORT;
}
}
}
my %db_switch_parent =();
for my $sw (keys %db_switch_link_with) {
for my $connect (keys %{$db_switch_link_with{$sw}}) {
my $port = $db_switch_link_with{$sw}->{$connect};
$db_switch_connected_on_port{"$connect:$port"} ||= {};
$db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
$db_switch_parent{$sw} = {switch => $connect, port => $port};
}
}
print "Switch output port and parent port connection\n";
print "---------------------------------------------\n";
for my $sw (sort keys %db_switch_output_port) {
if (exists $db_switch_parent{$sw}) {
printf "%-27s %2s +--> %2s %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
}
else {
printf "%-27s %2s +--> router\n", $sw, $db_switch_output_port{$sw};
}
}
print "\n";
print "Switch parent and children port inter-connection\n";
print "------------------------------------------------\n";
for my $swport (sort keys %db_switch_connected_on_port) {
my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
if (exists $db_switch_output_port{$sw}) {
printf "%-27s %2s <--+ %2s %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
}
else {
printf "%-27s %2s <--+ %-25s\n", $sw_connect, $port_connect, $sw;
}
}
}
my $switch_connection = {
output_port => \%db_switch_output_port,
parent => \%db_switch_parent,
connected_on_port => \%db_switch_connected_on_port,
link_with => \%db_switch_link_with,
switch_db => \%SWITCH_DB,
};
YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
return;
}
sub cmd_exportsw {
my @ARGV = @_;
test_switchdb_environnement();
my $format = 'txt';
my $ret = GetOptions(
'format|f=s' => \$format,
);
my %possible_format = (
txt => \&cmd_exportsw_txt,
dot => \&cmd_exportsw_dot,
);
$format = 'txt' if not defined $possible_format{$format};
$possible_format{$format}->(@ARGV);
return;
}
sub cmd_exportsw_txt {
my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
my %db_switch_output_port = %{$switch_connection->{output_port}};
my %db_switch_parent = %{$switch_connection->{parent}};
my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
print "Switch output port and parent port connection\n";
print "---------------------------------------------\n";
for my $sw (sort keys %db_switch_output_port) {
if (exists $db_switch_parent{$sw}) {
printf "%-27s %2s +--> %2s %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
}
else {
printf "%-27s %2s +--> router\n", $sw, $db_switch_output_port{$sw};
}
}
print "\n";
print "Switch parent and children port inter-connection\n";
print "------------------------------------------------\n";
for my $swport (sort keys %db_switch_connected_on_port) {
my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
if (exists $db_switch_output_port{$sw}) {
printf "%-27s %2s <--+ %2s %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
}
else {
printf "%-27s %2s <--+ %-25s\n", $sw_connect, $port_connect, $sw;
}
}
}
return;
}
sub cmd_exportsw_dot {
my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
my %db_switch_output_port = %{$switch_connection->{output_port}};
my %db_switch_parent = %{$switch_connection->{parent}};
my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
my %db_switch_link_with = %{$switch_connection->{link_with}};
my %db_switch_global = %{$switch_connection->{switch_db}};
my %db_building= ();
for my $sw (@SWITCH) {
my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
$db_building{$building} ||= {};
$db_building{$building}->{$location} ||= {};
$db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
}
print "digraph G {\n";
print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
my $b=0;
for my $building (keys %db_building) {
$b++;
print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
print "site -> \"building$b\" [len = 2, color = firebrick];\n";
my $l = 0;
for my $loc (keys %{$db_building{$building}}) {
$l++;
print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
# print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
for my $sw (keys %{$db_building{$building}->{$loc}}) {
print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue, peripheries = 2, style = filled];\n";
my $swname = $sw;
$swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
for my $swport (keys %db_switch_connected_on_port) {
my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
next if not $sw_connect eq $sw;
next if $port_connect eq $db_switch_output_port{$sw};
print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum, peripheries = 1, style = filled];\n";
print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
}
}
}
}
# print "Switch output port and parent port connection\n";
# print "---------------------------------------------\n";
for my $sw (sort keys %db_switch_output_port) {
if (exists $db_switch_parent{$sw}) {
# printf " \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
}
else {
printf " \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
}
}
print "\n";
# print "Switch parent and children port inter-connection\n";
# print "------------------------------------------------\n";
for my $swport (sort keys %db_switch_connected_on_port) {
my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
if (exists $db_switch_output_port{$sw}) {
printf " \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
}
else {
printf " \"%s\" -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
}
}
}
print "}\n";
return;
}
__END__
=head1 NAME
klask - ports manager and finder for switch
=head1 USAGE
klask updatedb
klask exportdb --format [txt|html]
klask removedb computer*
klask cleandb --day number_of_day --verbose
klask updatesw
klask exportsw --format [txt|dot]
klask searchdb computer
klask search computer
klask search-mac-on-switch switch mac_addr
klask ip-free --day number_of_day --format [txt|html] [vlan_name]
klask enable switch port
klask disable swith port
klask status swith port
=head1 DESCRIPTION
klask is a small tool to find where is a host in a big network. klask mean search in brittany.
Klask has now a web site dedicated for it !
http://servforge.legi.inpg.fr/projects/klask
=head1 COMMANDS
=head2 search
This 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.
=head2 enable
This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
=head2 disable
This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
=head2 status
This 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.
=head2 updatedb
This 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.
=head2 exportdb
This 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...
=head2 updatesw
This 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.
=head2 exportsw --format [txt|dot]
This 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.
klask exportsw --format dot > /tmp/map.dot
dot -Tpng /tmp/map.dot > /tmp/map.png
=head1 CONFIGURATION
Because 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 !
Here an example, be aware with indent, it's important in YAML, do not use tabulation !
default:
community: public
snmpport: 161
network:
labnet:
ip-subnet:
- add: 192.168.1.0/24
- add: 192.168.2.0/24
interface: eth0
main-router: gw1.labnet.local
schoolnet:
ip-subnet:
- add: 192.168.6.0/24
- add: 192.168.7.0/24
interface: eth0.38
main-router: gw2.schoolnet.local
switch:
- hostname: sw1.klask.local
portignore:
- 1
- 2
- hostname: sw2.klask.local
location: BatK / 2 / K203
type: HP2424
portignore:
- 1
- 2
I 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.
=head1 FILES
/etc/klask.conf
/var/cache/klask/klaskdb
/var/cache/klask/switchdb
=head1 SEE ALSO
Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
=head1 VERSION
$Id: klask 80 2011-03-29 15:57:13Z g7moreau $
=head1 AUTHOR
Written by Gabriel Moreau, Grenoble - France
=head1 LICENSE AND COPYRIGHT
GPL version 2 or later and Perl equivalent
Copyright (C) 2005-2009 Gabriel Moreau.