Issuu on Google+

Monitoramento de dispositivos de rede Perl

PROGRAMAÇÃO

Luz na escuridão

Dizem que a escuridão é amiga dos larápios, mas um programa em Perl ilumina essa questão, expondo atividades ocultas e alertando o administrador quando coisas estranhas acontecem na rede. por Michael Schilli

O

s usuários normalmente não têm idéia do que ocorre sob os panos em uma LAN. Uma das atividades ocultas nesse meio é o endereçamento de pacotes no último salto de uma rota, que inclui a descoberta do endereço MAC único do dispositivo de acordo com um endereço IP. Essa atividade é domínio do protocolo ARP.

Figura 1: O script lastaccess revela quais dispositivos estiveram ativos na LAN nas últimas 24 horas.

72

Vigiar todos os endereços MAC atualmente em uso pode levar a conclusões interessantes sobre quem está usando ou abusando de uma rede local. O colunista da Linux Magazine Charly Kühnast costuma falar sobre o arpalert [1], um daemon que monitora requisições ARP e compara seus endereços MAC a uma lista. Endereços MAC desconhecidos acionam um alerta. No entanto, podem ocorrer alertas duplicados para um mesmo incidente, e a documentação do daemon deixa muito a desejar. Felizmente, os módulos do CPAN Net::Pcap e NetPacket::Ethernet facilitam muito a criação de um script em Perl para extrair o endereço MAC dos pacotes que circulam em sua LAN. O mapeador de bancos de dados orientado a objetos Rose oferece uma

forma fácil de armazenar no MySQL os dados coletados, os quais podem ser revisados depois, quando o administrador tiver tempo para isso. Se for necessário determinar quais dispositivos estiveram ativos na sua rede, digamos, nas últimas 24 horas, pode-se simplesmente invocar o script lastaccess (mostrado adiante no exemplo 6), que produz a saída mostrada na Figura 1.

Capturando pacotes como root Assim como o capturador gráfico de pacotes de rede capture, o script arpcollect do exemplo 1 primeiro coloca sua placa de rede no modo promíscuo. Nesse modo, ela coleta não apenas todos os pacotes endereçados a ela, como tam-

http://www.linuxmagazine.com.br


Perl na rede | PROGRAMAÇÃO

bém repassa ao script todos os pacotes os endereços dos pacotes que chegam que encontrar. em um hash temporário que, então, é Isso requer privilégios de superusu- transferido para o banco de dados uma ário, e a linha 11 busca se certificar de vez por minuto. que eles estão presentes. Caso contrário, Um contador é incrementado a cada o script simplesmente pára, com uma combinação de IP e MAC, e guardado na coluna counter da tabela activity do mensagem de erro. A função lookupdev chamada na linha 16 banco de dados pelo método cache_flush(). retorna o nome do primeiro dispositivo de O parâmetro flush_interval no construtor rede disponível. Se só houver um dispositivo, do WatchLAN determina com que freele será chamado eth0. A chamada seguin- qüência o cache é descarregado. te a open_live() entra num laço infinito (o A data da próxima operação de descartempo limite foi desativado, com um va- ga é calculada pela adição de flush_inlor de -1), que lê os 128 primeiros bytes de terval à hora atual, e então é guardada todos os pacotes que chegam, chamando na variável next_update. imediatamente a função callback. Essa O exemplo 2 mostra os comandos de função recebe o endereço e a máscara de shell necessários para se criar um novo rede locais em $user_data, e os dados brutos banco de dados no MySQL. Os comandos SQL para configurar do pacote em $pacote_bruto. O módulo NetPacket::Ethernet decodi- todas as tabelas utilizadas (figura 2) estão fica o quadro Ethernet e revela o endereço em um arquivo separado, sql.txt, mosMAC da origem do pacote, em formato trado na figura 3. hexadecimal, na chave src_ip do hash. Conforme a mesma figura, as chaves Como o endereço ainda não inclui os estrangeiras são usadas para vincular a separadores típicos (:) após cada par de tabela principal activity às tabelas dedígitos, a linha 50 utiliza uma expressão vice e ip_address. A tabela device guarda os endereços regular para inseri-los. Nas linhas 62 a 65, o arpcollect faz refe- MAC junto com os dados sobre o disporência ao endereço IP do pacote para veri- sitivo; ip_address simplesmente guarda os ficar se este tem origem em algum disposi- endereços IP e atribui-os a um número tivo da rede local. O programa descobre o sequencial em seguida. endereço IP lendo o conteúdo do pacote Guardar os endereços na tabela princiEthernet que é extraído pela função strip() pal não apenas desperdiçaria espaço, como do módulo NetPacket::Ethernet. O pacote também geraria dados redundantes. IP bruto resultante é desempacotado pela função decode() do módulo NetPacket::IP, e o endereço de origem do pacote é revelado pela chave src_ip do hash. Se uma operação E binária do endereço IP com a máscara da rede retornar o O MySQL não facilita a detecção auendereço da rede, então podemos supor tomática desses relacionamentos pelo que o pacote tenha sido enviado por um carregador Rose::DB. De acordo com dispositivo da rede local, e que é relevante o autor desse módulo, John Siracusa, é continuar processando-o. necessário fornecer cláusulas REFERENCES O método event_add() do objeto de corretas para as declarações de chaves esbanco de dados WatchLAN aceita os en- trangeiras e, também, definir um índice dereços IP e MAC, inserindo-os no banco tanto na coluna que faz a referência quanto de dados para posterior análise. naquela para a qual esta aponta. Quando a definição do SQL for estabelecida, como mostrado, o WatchLAN.pm só precisará invocar o método make_classes O módulo WatchLAN.pm implementa a ca- para que o Rose::DB contate o banco de mada de armazenamento. Seria pouco dados e defina, de forma autônoma, o prático gravar imediatamente todos os pa- envelope completo de objetos para todas cotes no banco de dados; isso envolveria as tabelas e colunas, incluindo aquelas múltiplas operações de gravação a cada referenciadas em tabelas separadas. segundo, até mesmo em redes de baixo tráfego. Além disso, uma tabela com milhões de linhas consumiria grande espaço em disco e recursos computacionais. O módulo WatchLAN invoca o carragaÉ por esses motivos que o WatchLAN. dor do Rose sempre que um aplicativo pm (veja o exemplo 3) primeiro guarda chama use WatchLAN. O WatchLAN guarda

Tratamento especial para o MySQL

Buffer de minuto

O que eu sei?

Linux Magazine #28 | Março de 2007

Exemplo 1: arpcollect 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

#!/usr/bin/perl -w use strict; use Net::Pcap; use NetPacket::IP; use NetPacket::Ethernet; use Socket; use WatchLAN; die “Voce precisa ser root para”, “executar esse programa.\n” if $> != 0; my($err, $netaddr, $netmask); my $dev = Net::Pcap::lookupdev(\$err); Net::Pcap::lookupnet($dev, \$netaddr, \$netmask, \$err) and die “lookupnet $dev falhou ($!)”; my $objeto = Net::Pcap::open_live($dev, 128, 1, -1, \$err); my $db = WatchLAN->new(); Net::Pcap::loop($objeto, -1, \&callback, [ $netaddr, $netmask ]); ############################# sub callback { ############################# my ($user_data, $cabecalho, $pacote_bruto) = @_; my ($netaddr, $netmask) = @$user_data; my $pacote = NetPacket::Ethernet ->decode($pacote_bruto); my $mac_origem = $pacote->{src_mac}; # Add separating colons $mac_origem =~ s/(..)(?!$)/$1:/g; my $edata = NetPacket::Ethernet::strip ($pacote_bruto); my $ip = NetPacket::IP->decode( $edata); # Vindo da rede local? if ( ( inet_aton($ip->{src_ip}) & pack(‘N’, $netmask) ) eq pack(‘N’, $netaddr) ) { $db->event_add($mac_origem,

o esquema do banco de dados em uma abstração orientada a objetos no namespace do Perl sob WatchLAN::. ➧

73


PROGRAMAÇÃO | Perl na rede

Exemplo 2: Novo banco de dados 01 02 03 04 05

#!/bin/sh NOMEBD=watchlan mysqladmin -f -uroot drop $NOMEBD mysqladmin -uroot create $NOMEBD mysql -uroot $NOMEBD <sql.txt

Por outro lado, se seu vizinho tentar roubar um pouco da banda de sua rede sem fio, o arpcollect detectará a intrusão e registrará o endereço MAC na tabela devices, porém deixará o name respectivo vazio. O script de monitoramento arpemail, que será analisado mais adiante, percebe essa irregularidade e notifica o administrador por email. Para registrar o endereço MAC de um dispositivo conhecido da LAN, o script namedev lê as entradas em sua seção DATA, linha por linha. O formato utilizado nesse script é exatamente o que o script arpalert original [1] espera em seu arquivo de configuração.

Quando o método cache_flush() ne- ou modifica a entrada de um disposicessita salvar dados de hash temporários tivo já existente. no banco de dados, o WatchLAN responA chamada a $device->load(speculative de criando um novo objeto $activity da => 1); carrega um registro a partir da taclasse WatchLAN::Activity. bela de dispositivos, em busca do endeIsso não apenas facilita as atualiza- reço MAC que foi especificado antes no ções na tabela activity, mas também construtor do WatchLAN::Device. em tabelas referenciadas, como devices O método load funciona nesse caso e ip_addresses. A estrutura aparentemen- porque definimos a coluna mac_address te inocente: como chave única, utilizando @UNIactivity->device({ mac_address => QUE ao criar o banco de dados. O Rose detecta isso e então nos per$mac }); faz com que ocorram dois eventos mite carregar o registro com base nesse posteriormente, quando o método save() critério. Se fosse diferente, seria necessádo objeto for chamado. Se a tabela de- rio formular uma consulta para procurar vices não contiver uma entrada para o o registro. Após executar o namedev, apenas dispositivo com o endereço MAC dado, O parâmetro speculative especifica dispositivos desconhecidos da LAN uma nova entrada será criada. Na tabela que é aceitável que um registro não exis- terão um valor nulo em name, na taprincipal activity, é acrescentada a ID ta. Se for o caso, uma chamada a save() bela device. Para encontrar todas as do dispositivo C recém-criado, na forma criará tal registro. entradas de activity que referenciem de uma chave estrangeira na coluna entradas de device contendo um camdevice_id. po name nulo, é necessário realizar um Diferentemente da chamada do méJOIN nas duas tabelas. Se precisarmos todo acima, não são necessárias chaves O Rose possui uma abordagem bastante também do endereço IP da entrada, ({) para se criar uma nova entrada na despreocupada em relação a desperdícios não menos do que três tabelas serão tabela activity que não faça referência quando se trata de conexões ao banco envolvidas. O Rose cuida disso por baia outras tabelas. de dados. Cada novo objeto da Uma chamada a $ a c t i v i t y - classe WatchLAN::Activity chama a >counter($counter) fixa o valor da co- função connect() do módulo DBI, luna do contador $counter do registro e o Rose simplesmente esquece activity atual no valor de $counter . a conexão uma vez que o objeto Após invocar save(), na linha 93, esse termine de ser usado. Isso evita valor é descarregado para o banco de efeitos colaterais adversos ao se dados. Nesse momento, cache_flush() é trabalhar com transações de banfinalizado e já pode limpar seu cache cos de dados, mas é obviamente (linha 96). Em seguida, ele calcula a um desperdício de tempo em data da próxima descarga, retornando qualquer outro caso. O simples carregamento do à função que o chamou. A função device_add() realiza tarefas semelhantes: módulo Apache::DBI faz com que ou insere um novo dispositivo junto este interfira com a forma como o com seu respectivo endereço MAC, módulo DBI de Perl lida com as conexões e garante que somente uma conexão persistente com o banco de dados por trás dos panos seja utilizada. A tabela devices não apenas abriga os endereços MAC, como também atribui a eles nomes de dispositivos expressivos. Assim, 00:11:11:5b:ed:46 torna-se “Maquina Linux do Beltrano”. Ao mesmo tempo, a entrada prova que esse é um disposiFigura 2: As três tabelas no esquema do banco tivo confiável pertencente à Figura 3: Esses comandos em SQL criam o banco de dados necessário no MySQL. de dados rede local.

Alerta vermelho no setor B

Evite o desperdício

74

http://www.linuxmagazine.com.br


Perl na rede | PROGRAMAÇÃO

Exemplo 3: WatchLAN.pm 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060

############################# package WatchLAN; ############################# use strict; # compartilihar uma &#250;nica conex&#227;o ao BD use Apache::DBI; use Rose::DB::Object::Loader; use Log::Log4perl qw(:easy); use DateTime; my $loader = Rose::DB::Object::Loader ->new( db_dsn => ‘dbi:mysql:dbname=watchlan’, db_username => ‘root’, db_password => undef, db_options => { AutoCommit => 1, RaiseError => 1 }, class_prefix => ‘WatchLAN’ ); $loader->make_classes(); ############################# sub new { ############################# my ($class) = @_; my $self = { cache => {}, flush_interval => 60, next_update => undef, }; bless $self, $class; $self->cache_flush(); return $self; } ############################# sub event_add { ############################# my ($self, $mac, $ip) = @_; $self->{cache}-> {“$mac,$ip”}++; $self->cache_flush() if time() > $self->{next_update}; } ############################# sub cache_flush { #############################

061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

my ($self) = @_; for my $key ( keys %{ $self->{cache} }) { my ($mac, $ip) = split /,/, $key; my $counter = $self->{cache}->{$key}; my $minute = DateTime->from_epoch( epoch => $self->{next_update} $self->{flush_interval}, time_zone => “local”, ); my $activity = WatchLAN::Activity->new( minute => $minute); $activity->device({ mac_address => $mac }); $activity->ip_address({ string => $ip }); $activity->counter( $counter); $activity->save(); } $self->{cache} = {}; $self->{next_update} = time() - ( time() % $self->{flush_interval}) + $self->{flush_interval}; } ############################# sub device_add { ############################# my ($self, $name, $mac_address) = @_; my $device = WatchLAN::Device->new( mac_address => $mac_address); $device->load( speculative => 1); $device->name($name); $device->save(); } 1;

xo dos panos. O script arpemail (veja tivity, na linha 14 , consulta a tabela o exemplo 5 ) notifica o administrador activity, e o parâmetro with_objects do sistema sempre que um endereço garante que os dados referenciados MAC até então desconhecido é de- nas tabelas device e ip_address também tectado na tabela device. O arpemail serão extraídos. O Rose enumera as usa a classe WatchLAN::Activity::Mana- tabelas como t1 ( activity), t2 ( device) ger para buscar registros, realizando e t3 ( ip_address); por isso, a consulta uma consulta SQL. O método get_ac- SQL abstraída:

Linux Magazine #28 | Março de 2007

query => [ “t2.name” =>undef ]

na linha 19, se refere à tabela device e procura entradas com valor nulo na coluna name. O resultado dessa consulta é uma referência a um vetor de entradas de banco de dados que casam com a consulta, cada uma sendo

75


PROGRAMAÇÃO | Perl na rede

Exemplo 4: namedev

Exemplo 5: arpemail

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

#!/usr/bin/perl use strict; use warnings; use WatchLAN; my $db = WatchLAN->new(); while (<DATA>) { if (/^#\s+(.*)/) { my $name = $1; my $nextline = <DATA>; chomp $nextline; my ($mac, $ip, $ip_change) = split ‘ ‘, $nextline; $db->device_add($name, $mac); } } <i>DATA</i> # Slimbox 00:04:20:03:00:0d 192.168.0.74 ip_change # Laptop Wireless 00:16:6f:8d:58:db 192.168.0.75 ip_change # Laptop Wired 00:15:60:c3:44:10 192.168.0.71 ip_change # Maquina Linux do Beltrano 00:11:11:5b:ed:46 192.168.0.18 ...

um objeto do tipo WatchLAN::Activity, o que fornece um método para se consultar seus próprios valores das colunas e, também, os valores das tabelas referenciadas. O arpemail “se lembra” dos dispositivos que já marcou como suspeitos em um cache baseado em arquivos, do tipo Cache::File. Com isso, ele evita o envio de mensagens repetidas com o mesmo alerta. Se existir uma entrada no cache para o endereço MAC $mac, então a seguinte estrutura retornará um valor falso: !$cache->($mac) && ($cache->set($mac, 0) ➥|| 1);

Se $mac for desconhecido, o método get do cache retornará um valor falso, o

que será negado para verdadeiro, que por sua vez faz a declaração após o E lógico ser executada. O método set subseqüente é usado para adicionar um novo valor ao cache, e o ||1 seguinte o faz retornar um valor verdadeiro, independentemente do valor real de set. O comando grep externo, na linha 24, usa sua lógica confusa e filtra os endereços MAC armazenados no cache a par-

76

#!/usr/bin/perl -w use strict; use WatchLAN; use Mail::Mailer; use Cache::File; use Template; my $cache = Cache::File->new( cache_root => “$ENV{HOME}/.arpemail”); my $events = WatchLAN::Activity::Manager ->get_activity( with_objects => [ ‘device’, ‘ip_address’ ], query => [ “t2.name” => undef ], sort_by => [‘minute’], ); $events = [ grep { my $mac = $_->device() ->mac_address(); !$cache->get($mac) && ($cache->set($mac, 0) || 1); } @$events ]; exit 0 unless @$events; my $mailer = new Mail::Mailer; $mailer->open( { ‘From’ => ‘me@_foo.com’, ‘To’ => ‘oncall@_foo.com’, ‘Subject’ => “*** New MAC detected ***”, } ); my $t = Template->new(); $t->process(\*DATA, { events => $events }, $mailer) or die $t->error(); close($mailer); <i>DATA</i> [% FOREACH e = events %] When: [% e.minute %] IP: [% e.ip_address.string %] MAC: [% e.device.mac_address %] [% END %]

tir da lista de possíveis ladrões de banda guardados em $events. Se o vetor referenciado por $events parecer vazio, o script de vigilância arpemail, que é invocado por um cronjob normal, simplesmente terminará. No caso de haver novos dispositivos a serem reportados, a mensagem de aler-

ta será formatada pelas ferramentas de modelo. O modelo guardado na seção DATA do fim do script recebe uma referência ao vetor de $events, e usa um laço FOREACH para iterar ao longo das entradas. A sintaxe esquisita, porém bastante prática, das ferramentas de modelos nos permite chamar a cadeia de métodos

http://www.linuxmagazine.com.br


Perl na rede | PROGRAMAÇÃO

Exemplo 6: lastaccess 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

um dos eventos ocorridos a partir desse momento, organizados pela hora do evento arredondada para minutos, conforme guardado na coluna minute do banco de dados. O hash %latest guarda somente os últimos eventos de vários endereços MAC; lastaccess sobrescreve continuamente os mesmos endereços MAC com valores atualizados. Seria preferível que tais cálculos fossem deixados a cargo do banco de dados, entretanto o envelope de objetos Rose ainda não suporta funções agregadoras, como MAX() com GROUP BY. A julgar pelo ritmo de desenvolvimento, no entanto, esse recurso pode até já ter sido implementado quando este artigo for publicado. O lastaccess define uma função time_ diff na linha 40 para calcular a diferença de tempo (em formato inteligível para humanos) entre os valores de segundo. A substituição de texto na linha 56 transforma as unidades de tempo plurais em singulares caso o resultado seja apenas uma unidade. A saída do lastaccess assemelha-se à figura 1 . É possível estender o script arpemail para fixar IPs estáticos para dispositivos específicos na tabela device, seguindo o exemplo do arpalert [1], e enviar um alerta caso um dispositivo com IP estático esteja utilizando um endereço diferente. Como sempre, simplesmente não há limites para a engenhosidade do desenvolvedor agora que a plataforma já está constituída e estabelecida e, felizmente, será possível acessar os dados no banco com conforto e segurança. ■

#!/usr/bin/perl -w use strict; use WatchLAN; my $reachback = DateTime->now( time_zone => “local”) ->subtract( minutes => 60 * 24); my $events = WatchLAN::Activity::Manager ->get_activity( query => [ minute => { gt => $reachback }, ], sort_by => [‘minute’], ); my %latest = (); for my $event (@$events) { $latest{ $event->device_id() } = $event; } for my $id (keys %latest) { my $event = $latest{$id}; my $name = $event->device()->name(); $name ||= “unknown (id=$id)”; printf “%23s: %s ago\n”, $name, time_diff( $event->minute()); } ############################# sub time_diff { ############################# my ($dt) = @_; my $duration = DateTime->now( time_zone => “local”) $dt; for ( qw(hours minutes seconds)) { if (my $n = $duration->in_units($_)) { my $unit = $_; $unit =~ s/s$// if $n == 1; return “$n $unit”; } }

Mais Informações [1] Script arpalert original: http://arpalert.org [2] Exemplos deste artigo: http://www.linuxmagazine. com.br/issue/28/perl

O autor

}

$e->ip_address()->string() e.ip_address.string.

com a forma

O arpemail então utiliza o módulo do CPAN Mail::Mailer para conectar-se ao agente de correio local. Em seguida, ele envia a mensagem ao administrador do sistema listado no campo To, na linha 41.

Linux Magazine #28 | Março de 2007

O que houve aqui?

Para saber quais dispositivos visitaram sua LAN nas últimas 24 horas, o script lastaccess usa o módulo do CPAN DateTime para especificar o ocorrido há exatamente 24 horas. O gerenciador Rose depois inicia uma consulta SQL que retorna cada

Michael Schilli (mschilli@perlmeister. com) trabalha como desenvolvedor de software para o Yahoo!, em Sunnyvale, Estados Unidos. É autor de “Perl Power”, publicado pela editora Addison-Wesley. Sua homepage é http://perlmeister.com.

77


http://www.linuxmagazine.com.br/images/uploads/pdf_aberto/LM28_perlrede