Gerenciando redes com Perl e SNMP
Muitas empresas utilizam softwares de código aberto nas mais diversas atividades. Em minha carreira deparei-me com sistemas de gerência de rede que não atendiam as necessidades do cliente. Minha intenção aqui é demonstrar de forma prática como o SNMP funciona, como entender as MIBs e principalmente, como usar o Perl para melhorar os sistemas de gerência, ou mesmo, criar o seu próprio.
Parte 4: Criando uma solução SNMP personalizada com o Perl
Você pode decidir criar um gerenciador SNMP para tender uma necessidade em particular. No meu caso, softwares gerenciadores de fabricantes específicos preferem usar os objetos dentro de iso.org.dod.internet.private e como já dissemos este espaço é reservado para características particulares dos fabricantes. Logo um gerenciador da CISCO não funciona perfeitamente com elementos da Siemens e vice-versa. Softwares que ofereçam gerência para elementos de diversos fabricantes podem ser muito caros - pelo menos para meus clientes - e ainda necessitariam da criação de alguns módulos para atender todas as necessidades de uma grande empresa.
Eu decidi por utilizar o Perl nestas tarefas pelos seguintes motivos:
O Perl já vem instalado em máquinas GNU/Linux padrão. Porém para criar uma solução SNMP precisamos instalar o Net::SNMP. Você o encontra no repositório CPAN em http://search.cpan.org/~dtown/Net-SNMP-v6.0.0/lib/Net/SNMP.pm. No CPAN existem diversos módulos para gerenciamento via SNMP. Eu gosto particularmente deste pois é um módulo sem dependências externas. Não temos que instalar nada na máquina que irá executar o programa. Se ela for capaz de rodar o Perl, ela poderá usar o Net::SNMP e a nossa aplicação.
Depois de tanto blá-blá-blá estamos ansiosos para começar a codificar. Uma vez que você baixou e instalou o módulo Net::SNMP você está pronto para começar.
Como programadores organizados e por gostarmos de evitar retrabalho, vamos começar com um módulo genérico que obtém as informações de interface do elemento.
Agora que definimos um nome para nosso pacote e adicionamos as diretivas comuns vamos codificar o construtor do objeto:
O nosso objeto será basicamente um hash que ira armazenar as informações estratégicas das interfaces de um elemento de rede. A chave Session do hashref $self irá armazenar um objeto Net::SNMP.
No método connect, nós criamos um objeto SNMP e criamos a sessão com o elemento definido em IP, que foi passado como argumento ao construtor juntamente com a community. Com isto podemos solicitar ou escrever as primeiras informações do elemento via SNMP:
Observe que o método query_info_system obtém as informações dos objetos ISO.org.dod.internet.mgmt.mib-2.system.name e system.description. Pode parecer um código um pouco longo e pouco prático para se obter apenas duas informações mas ele é bastante flexível. Se você quiser obter outros dados dentro do objeto system, como uptime ou services, basta adicionar mais chaves no hash %mibSystemTable e expressões regulares dentro do loop foreach para associar com a propriedade do objeto.
Agora vamos montar a tabela de interfaces. Como elementos de rede podem ter diferentes números de interfaces de diferentes tipos, nosso método primará pela flexibilidade do programa. Usamos as informações dos objetos mib-2.interfaces.ifNumber que informa o número de interfaces de rede do elemento - tanto físicas quanto lógicas - e mib-2.ifMIB. Usamos estes dados para montar uma tabela hash que relaciona o index da interface com o nome da mesma. Esta tabela ficará armazenada na propriedade InterfaceIndexName do objeto SNMPRouter.
Os nomes de interface são, por exemplo, em um servidor Linux, eth0 e lo, gi5/3 ou se0/1 num roteador CISCO e fe.1.2 ou ge.1.24 num switch Enterasys. Esta é a beleza do SNMP com o Perl. Não importam as diferenças entre os fabricantes, a raça a cor ou o sexo. Podemo obter as informações e formatá-las ao nosso gosto.
Muito bem, já conseguimos os nomes das interfaces, o nome e a descrição do elemento. Agora vamos obter os estados e a descrição da interfaces. Afinal, é importante saber quais são as interfaces dos elementos, mas também é muito importante saber se elas estão ativas e respondendo. Os hash %statusTable contém os valores retornados pelos objetos mib-2.interfaces.ifTable.ifEntry.ifAdminStatus e ifOperStatus relacionados com seu significado. Se você observar um arquivo de definição das mibs verá que a sintaxe dela é INTEGER. Este valor deve ser convertido para um texto com significado.
Falta apenas o método que obtenha os endereços IPs das interfaces de rede. Este é um pouco mais confuso, pois ao contrário dos outros objetos da MIB, mib-2.ip.ipAddrTable.ipAddrEntry.ipAdEntIfIndex não relaciona o número da interface com o IP e sim o contrário. O IP é a referência do OID.
E, para terminar o módulo, não se esqueça de finalizar com 1.
Agora que você sente que teve um trabalhão para criar este módulo, vamos fazer um pequeno script de teste:
Ao executar este script em diferentes elementos de rede teremos saídas um pouco diferentes. Por exemplo, um roteador gera a saída abaixo:
Eu decidi por utilizar o Perl nestas tarefas pelos seguintes motivos:
- É realmente uma linguagem multiplataforma. Inclusive os módulos para criar programas de rede funcionam tanto em Linux, Solaris ou Windows. Nós amamos o Linux. Nossos clientes, nem sempre;
- Fácil de usar e atingiu seu desafio de manter as coisas simples, simples e as coisas difíceis, possíveis;
- O CPAN - repositório do Perl em www.cpan.org - torna as coisas difíceis, simples. São milhares de módulos gratuitos e com o código aberto;
- Gratuito e open source.
O Perl já vem instalado em máquinas GNU/Linux padrão. Porém para criar uma solução SNMP precisamos instalar o Net::SNMP. Você o encontra no repositório CPAN em http://search.cpan.org/~dtown/Net-SNMP-v6.0.0/lib/Net/SNMP.pm. No CPAN existem diversos módulos para gerenciamento via SNMP. Eu gosto particularmente deste pois é um módulo sem dependências externas. Não temos que instalar nada na máquina que irá executar o programa. Se ela for capaz de rodar o Perl, ela poderá usar o Net::SNMP e a nossa aplicação.
Depois de tanto blá-blá-blá estamos ansiosos para começar a codificar. Uma vez que você baixou e instalou o módulo Net::SNMP você está pronto para começar.
Como programadores organizados e por gostarmos de evitar retrabalho, vamos começar com um módulo genérico que obtém as informações de interface do elemento.
1 package SNMPRouter; 2 use strict; 3 use warnings; 4 use Net::SNMP; 5 use Carp qw/croak/;
Agora que definimos um nome para nosso pacote e adicionamos as diretivas comuns vamos codificar o construtor do objeto:
6 sub new{
7
8
9 my ($class, $ip, $community) = @_;
10
11 my $self = {
12 IP=>$ip, # Armazena o IP/nome do elemento
13 Session => undef, # Armazena o Objeto SNMP gerado por connect
14 Community => $community, # Armazena a community de segurança
15 SysName => undef, #
16 SysDescription => undef, #
17 NumberOfInterfaces => undef, # Armazena o número de Interfaces
18 InterfaceIndexNames => {}, # Relaciona o número da interface com o nome
19 InterfaceIPAddress => {}, # Relaciona o número da interface com o IP
20 InterfaceAlias => {}, # Relaciona o número da interface com o Alias
21 InterfaceAdminStatus => {}, #
22 InterfaceOperStatus => {}, #
23
24 };
25
26 bless $self, $class
27 }
O nosso objeto será basicamente um hash que ira armazenar as informações estratégicas das interfaces de um elemento de rede. A chave Session do hashref $self irá armazenar um objeto Net::SNMP.
28 sub connect{
29 my $self = shift;
30 $self->{Session} = Net::SNMP->session(Hostname => $self->{IP},
31 Community => $self->{Community},
32 ) or croak "Unable to create SNMP Session\n";
33 }
No método connect, nós criamos um objeto SNMP e criamos a sessão com o elemento definido em IP, que foi passado como argumento ao construtor juntamente com a community. Com isto podemos solicitar ou escrever as primeiras informações do elemento via SNMP:
34 sub query_system_info{
35 my $self = shift;
36 my $mib2System = '1.3.6.1.2.1.1';
37
38 my %mibSystemTable = (
39 Mib2SysName => $mib2System . '.5',
40 Mib2SysDescription => $mib2System . '.1',
41 );
42
43 my @queryList = values %mibSystemTable;
44
45 my $result = $self->{Session}->get_next_request(Varbindlist => \@queryList);
46 croak "Erro ao tentar receber informações da MIB System\n" unless
47 defined $result;
48
49 foreach my $key(keys %$result){
50 $self->{SysName} = $result->{$key} if
51 $key =~ /^$mibSystemTable{Mib2SysName}/;
52 $self->{SysDescription} = $result->{$key} if
53 $key =~ /^$mibSystemTable{Mib2SysDescription}/;
54 }
55 }
Observe que o método query_info_system obtém as informações dos objetos ISO.org.dod.internet.mgmt.mib-2.system.name e system.description. Pode parecer um código um pouco longo e pouco prático para se obter apenas duas informações mas ele é bastante flexível. Se você quiser obter outros dados dentro do objeto system, como uptime ou services, basta adicionar mais chaves no hash %mibSystemTable e expressões regulares dentro do loop foreach para associar com a propriedade do objeto.
Agora vamos montar a tabela de interfaces. Como elementos de rede podem ter diferentes números de interfaces de diferentes tipos, nosso método primará pela flexibilidade do programa. Usamos as informações dos objetos mib-2.interfaces.ifNumber que informa o número de interfaces de rede do elemento - tanto físicas quanto lógicas - e mib-2.ifMIB. Usamos estes dados para montar uma tabela hash que relaciona o index da interface com o nome da mesma. Esta tabela ficará armazenada na propriedade InterfaceIndexName do objeto SNMPRouter.
Os nomes de interface são, por exemplo, em um servidor Linux, eth0 e lo, gi5/3 ou se0/1 num roteador CISCO e fe.1.2 ou ge.1.24 num switch Enterasys. Esta é a beleza do SNMP com o Perl. Não importam as diferenças entre os fabricantes, a raça a cor ou o sexo. Podemo obter as informações e formatá-las ao nosso gosto.
56 sub create_int_table{
57 my $self = shift;
58 my $mib2ifNumber = '1.3.6.1.2.1.2.1';
59 my $mib2ifIndex = '1.3.6.1.2.1.2.2.1.1';
60 my $mib2ifXMIB = '1.3.6.1.2.1.31.1.1.1';
61 my $mib2ifName = $mib2ifXMIB . '.1';
62
63 my $ifNumber = $self->{Session}->get_next_request(
64 Varbindlist => [$mib2ifNumber]);
65
66 $ifNumber = $ifNumber->{"$mib2ifNumber.0"};
67
68 for(my $i = 1;$i <= $ifNumber; $i++){
69 my $ifIndex = $self->{Session}->get_request(
70 Varbindlist => ["$mib2ifIndex.$i"]);
71
72 if($ifIndex){
73 my $ifName = $self->{Session}->get_request(
74 Varbindlist => ["$mib2ifName.$i"]);
75
76 $ifName = $ifName->{"$mib2ifName.$i"};
77 $self->{InterfaceIndexNames}->{$i} = $ifName;
78 }else{$ifNumber++}
79 }
80 }
Muito bem, já conseguimos os nomes das interfaces, o nome e a descrição do elemento. Agora vamos obter os estados e a descrição da interfaces. Afinal, é importante saber quais são as interfaces dos elementos, mas também é muito importante saber se elas estão ativas e respondendo. Os hash %statusTable contém os valores retornados pelos objetos mib-2.interfaces.ifTable.ifEntry.ifAdminStatus e ifOperStatus relacionados com seu significado. Se você observar um arquivo de definição das mibs verá que a sintaxe dela é INTEGER. Este valor deve ser convertido para um texto com significado.
81 sub query_int_status{
82 my $self = shift;
83
84 croak "Tabela de Interfaces não foi criada para este Objeto"
85 unless $self->{InterfaceIndexNames};
86
87 my $mib2IfAdminStatus = '1.3.6.1.2.1.2.2.1.7';
88 my $mib2IfOperStatus = '1.3.6.1.2.1.2.2.1.8';
89 my $mib2ifAlias = '1.3.6.1.2.1.31.1.1.1.18';
90 my %statusTable = (
91 1=>'up',
92 2=>'down',
93 3=>'testing',
94 4=>'unknow',
95 5=>'dormant',
96 6=>'notPresent',
97 8=>'lowerLayerDown',
98 );
99
100 foreach my $key (keys %{$self->{InterfaceIndexNames}}){
101 my $status = $self->{Session}->get_request(
102 Varbindlist => ["$mib2IfOperStatus.$key",
103 "$mib2IfAdminStatus.$key",
104 "$mib2ifAlias.$key"
105 ]);
106 $self->{InterfaceOperStatus}->{$key} =
107 $statusTable{$status->{"$mib2IfOperStatus.$key"}};
108 $self->{InterfaceAdminStatus}->{$key} =
109 $statusTable{$status->{"$mib2IfAdminStatus.$key"}};
110 $self->{InterfaceAlias}->{$key} = $status->{"$mib2ifAlias.$key"};
111 }
112 }
Falta apenas o método que obtenha os endereços IPs das interfaces de rede. Este é um pouco mais confuso, pois ao contrário dos outros objetos da MIB, mib-2.ip.ipAddrTable.ipAddrEntry.ipAdEntIfIndex não relaciona o número da interface com o IP e sim o contrário. O IP é a referência do OID.
113 sub query_int_ip{
114 my $self = shift;
115 my $mib2IpAddrTable = '1.3.6.1.2.1.4.20';
116 my $mib2IpAddrxIntIndex = '1.3.6.1.2.1.4.20.1.2';
117 my $hshIpTable = $self->{Session}->get_table(Baseoid=>$mib2IpAddrTable)
118 or croak "Erro ao tentar acessar a mib IpAddrTable";
119
120 foreach my $key (keys %{$hshIpTable}){
121 if ($key =~ $mib2IpAddrxIntIndex){
122 $key =~ s/$mib2IpAddrxIntIndex\.//;
123 my $intIndex = $hshIpTable->{$mib2IpAddrxIntIndex . "." . $key};
124 $self->{InterfaceIPAddress}->{$intIndex} = $key;
125
126 }
127 }
128 }
E, para terminar o módulo, não se esqueça de finalizar com 1.
129 1
Agora que você sente que teve um trabalhão para criar este módulo, vamos fazer um pequeno script de teste:
1 use strict;
2 use warnings;
3 use lib qw/./;
4
5 use SNMPRouter;
6
7 print "Digite o HOSTNAME ou IP do ROTEADOR: ";
8 chomp(my $host = <STDIN>);
9 my $community = 'public'; #Altere este valor para a sua community ou solicite ao
10 #usuário para informar.
11 my $router = SNMPRouter->new($host,$community);
12 $router->connect();
13 $router->query_system_info();
14
15 print "Informações do elemento:\n","#"x80,"\n",
16 $router->{SysDescription},"\n",'#'x80,"\n\n\n";
17
18 print "\tElemento ",$router->{SysName},"\tIP: ",$router->{IP}, "\n";
19 print "#"x80,"\n\n";
20
21 $router->create_int_table();
22 $router->query_int_status;
23 $router->query_int_ip();
24
25 foreach (sort{$a<=>$b} keys %{$router->{InterfaceIndexNames}}){
26 print "$router->{InterfaceIndexNames}->{$_}\t",
27 "$router->{InterfaceOperStatus}->{$_}\t",
28 "$router->{InterfaceAdminStatus}->{$_}\t",
29 ($router->{InterfaceIPAddress}->{$_}||"\t"),
30 "\t",($router->{InterfaceAlias}->{$_}||"\t"),
31 "\n";
32 };
33 print '#'x80,"\n\nPressione ENTER para sair\n";
34 <STDIN>
Ao executar este script em diferentes elementos de rede teremos saídas um pouco diferentes. Por exemplo, um roteador gera a saída abaixo:
Digite o HOSTNAME ou IP do ROTEADOR: 10.10.10.1 Informações do elemento: ############################################################ Cisco Internetwork Operating System Software IOS (tm) C2600 Software (C2600-JS-M), Version 12.2(13)T4, RELEASE SOFTWARE (fc2) TAC Support: http://www.cisco.com/tac Copyright (c) 1986-2003 by cisco Systems, Inc. Compiled Thu 01-May-03 03:53 by eaarma ############################################################ Elemento RT01FRODO IP: 10.10.10.1 ############################################################ Fa0/0 up up 10.10.11.254 REDE LOCAL Se0/0 down down RESERVADA Fa0/1 up up 10.10.12.254 REDE LOCAL Se0/1 up up 10.10.13.254 REDE EXTERNA Nu0 up up Lo100 up up 10.10.14.100 INTERFACE ADMINISTRATIVA VoiceOverIpPeer108 up up *** Para ligacoes externas *** VoiceOverIpPeer100 up up *** Para ligacoes DDD *** VoiceEncapPeer6085 up up *** RAMAL do JOAO *** VoiceEncapPeer6086 up up *** RAMAL da ANA *** VoiceEncapPeer6087 up up *** RAMAL do CARLOS *** VoiceEncapPeer6084 up up Vi1 up up Foreign Exchange Station 1/0/0 dormant up Foreign Exchange Station 1/0/1 dormant up Foreign Exchange Station 1/0/2 dormant up ############################################################ Pressione ENTER para sairSe você tiver familiaridade com roteadores CISCO, perceberá que a saída é semelhante a do comando "show interface description". Apesar de otimizado para obter informações de roteadores, como eu disse, o Perl e o SNMP não são preconceituosos. Vamos agora mostrar o que acontece se eu tentar obter informações de um switch de uma concorrente da CISCO, Enterasys:
Digite o HOSTNAME ou IP do ROTEADOR: 10.10.11.1
Informações do elemento:
################################################
Enterasys Networks, Inc. A2H124-24 Rev 02.01.07.0003
################################################
Elemento SW01SkyWalker IP: 10.10.11.1
################################################
fe.1.1 up up
fe.1.2 down up
fe.1.3 down up
fe.1.4 down up
fe.1.5 down up
fe.1.6 down up
fe.1.7 down up
fe.1.8 down up
fe.1.9 down up
fe.1.10 down up
fe.1.11 down up
fe.1.12 up up
fe.1.13 up up
fe.1.14 down up
fe.1.15 up up
fe.1.16 down up
fe.1.17 up up
fe.1.18 down up
fe.1.19 up up
fe.1.20 down up
fe.1.21 up up
fe.1.22 up up
fe.1.23 up up
fe.1.24 down up
ge.1.25 up up
ge.1.26 up up
ge.1.27 down up
ge.1.28 down up
host up up 10.10.11.1
lag.0.1 down up
lag.0.2 down up
lag.0.3 down up
lag.0.4 down up
lag.0.5 down up
lag.0.6 down up
################################################
Pressione ENTER para sair
Estamos às ordens!