Mostrando entradas con la etiqueta munin. Mostrar todas las entradas
Mostrando entradas con la etiqueta munin. Mostrar todas las entradas

domingo, 10 de junio de 2012

Contando mis logs

Tanto tiempo ciertoo??? Esta vez vengo con un problema mas operativo que otra cosa! El problema es el siguiente: me tocó implementar un servidor de logs centralizado donde escriben logs unos mmm, 150-200 hosts, entre servidores, equipos de red, ups etc.

Hay momentos en el día en que la cantidad de logs se puede poner un pooco densa, y por las noches extrañamente el rsyslog colapsa. En principio activé el monitoreo propio de rsyslog, pero este solamente me dice la cantidad de logs en la cola actual, la cantidad encolados desde que inició el servidor, y el máximo de logs que hubo en algún momento en la cola.

##Estadisticas de rsyslog
$ModLoad impstats.so
$PStatInterval 20
$PStatSeverity 7


Aca una salida del monitoreo con impstats.so

Jun 10 20:50:25 bb rsyslogd-pstats: imuxsock: submitted=141 ratelimit.discarded=0 ratelimit.numratelimiters=72
Jun 10 20:50:25 bb rsyslogd-pstats: action 30 queue: size=0 enqueued=213694 full=0 maxqsize=159
Jun 10 20:50:25 bb rsyslogd-pstats: action 31 queue: size=0 enqueued=76427 full=0 maxqsize=18
Jun 10 20:50:25 bb rsyslogd-pstats: main Q: size=3 enqueued=636410 full=0 maxqsize=238

Tengo 3 colas, la principal y dos mas, una para escribir en un rsyslog secundario remoto y otra para escribir en una BD en otro servidor remoto.

Por las noches sucedía que la mainQ colapsaba y perdía logs. Un par de veces el servidor colapsó.

El tamaño default de la cola de mensajes principal es de 10k mensajes, por lo tanto como me sobraba memoria agrandé la cola considerablemente a unos 100k mensajes. Y por las dudas implementé unas mediciones extras para ver qué cantidad de logs están llegando y de qué dispositivos en particular.

Asumciones:
-Que los logs vienen por UDP (recuerdo que algunos servidores están configurador con TCP, pero son minoría).
-Que cada paquete UDP representa un mensaje, es lo mas probable.

La solución fue el siguiente script en perl:

#!/usr/bin/perl -w

open STDIN,"tcpdump -n -s 1024 -tt dst host HOST_IP and dst port 514 2>/dev/null |" or die "Oops";
open STDOUT,">>/dev/null";
open STDERR,">>/dev/null";

$t1=time();
$t2=$t1+300;

#Imprime en "valores" un historico con el siguiente formato
#fecha y hora
#mensajes -> host
#mensajes -> host
#...
#Ademas escribe en total_logs, el valor logs.value mensajes
#este archivo es levantado por munin para graficar la cantidad
#de mensajes recibidos en los ultimos 5 minutos
sub ver_valores
{
        my $i;
        my $s="";
        my $x=0;
        foreach $i (sort (keys %LOGS))
        {
                $x=$x+$LOGS{$i};
                $s=$s."$LOGS{$i} -> $i\n";
        }
        open(F,">>valores");
        print F localtime()."\n$s\n\n";
        close(F);
        open(F,">total_logs");
        print F "logs.value $x";
        close(F);
}


#Pone en 0 todos los valores del hash
sub reiniciar
{
        foreach $i (keys %LOGS)
        {
                $LOGS{$i}=0;
        }
}

while()
{
        my @a=split(' ',$_);
        my @b=split('\.',$a[2]);
        my $c="$b[0].$b[1].$b[2].$b[3]";
        if(defined $LOGS{$c})
        {
                $LOGS{$c}=$LOGS{$c}+1;
        }
        else
        {
                $LOGS{$c}=1;
        }
        $t1=time();
        if($t1 > $t2)#pasaron al menos 300 segundos
        {
                $t2=$t1+300;
                ver_valores();
                reiniciar();
        }
}


El script es bastante sencillo, toma la salida de tcpdump (un tanto filtrada para reducir la carga al script), y por cada linea (mensaje syslog) carga un nuevo valor en el hash o incrementa en 1 el valor si el host ya fue ingresado. Pasados los 300 segundos se llama a la función ver_valores(), que imprime datos en dos archivos diferentes. Luego la función reiniciar, que pone a cero la cantidad de mensajes de todos los hosts de hash, era mas sencillo reiniciar el hash completamente? SI, pero me interesa que el hash crezca a medida que un nuevo host comienza a mandar logs, y si deja de enviar que marque cero.

Filtrado del tcpdump:

-n: sin resolución de nombres.
-s 1024: reducir el tamaño máximo de captura por paquete, para no sobrecargar en vano, con 1024 bytes sobra para reconocer el tipo de paquete (UDP o TCP).
-tt: para que la hora se imprima como timestamp.
dst host HOST_IP and dst port 514: solamente captura los paquetes destinados a a la IP HOST_IP y al puerto 514 (UDP o TCP).

Si bien falta un pequeño detalle (a tratar en otro post, que es hacer de este script un demonio, de momento lo lanzo con screen), con esto logré saber el número de mensajes recibidos en los últimos 5 minutos y tener un historial que muestra la cantidad de logs que envió cada host.



En la imagen podemos ver como si bien hay una tendencia a los 2k mensajes cada 5 minutos, hubo unos picos muy interesantes que pasaron los 20k mensajes!!!

UPDATE: hice un cambio en la función ver_valores(), básicamente simplifiqué la obtención de la hora, ahora es mas simple la función.

sábado, 25 de febrero de 2012

Buscando una IP libre en mi red

Buenas, tanto tiempo!!! Definitivamente me cuesta alcanzar una frecuencia aceptable de entradas al blog, ya no cabe duda.

En fin, hoy les traigo un pequeño aporte que para algunas podría ser muy útil y para otros unas líneas de código casi inútiles xD.

El problema que intenta resolver es el siguiente: cuando uno administra una cantidad considerable de direcciones IP, digamos como mínimo una clase C fragmentada por ejemplo en 4 redes, resulta complicado saber qué direcciones están disponibles para asignar y cuáles están en uso.

Para existen las siguientes soluciones:
  • Tener una memoria perfecta y recordar precisamente todas las IPs libres y las asignadas.
  • Llevar registro con alguna herramienta.
  • Recorrer el rango de direcciones en busca de ellas.
Debo admitir que carezco de memoria perfecta, y no conozco una herramienta sencilla que resuelva el problema, por lo tanto opté por la tercera solución.


#!/usr/bin/perl -w
use Sys::Hostname;
use Socket;

####functions
#turns a number to an IP (dot-decimal notation)
sub pickAddress
{
    my $A = $_[0];
    my $aux;
    my $S="";
   
    $aux=$A & 4278190080;#first byte
    $aux=$aux>>24;
    $S=int($aux).".";#127.
   
    $aux=$A & 16711680;#second byte
    $aux=$aux>>16;
    $S=$S.int($aux).".";#127.0.

    $aux=$A & 65280;#third byte
    $aux=$aux>>8;
    $S=$S.int($aux).".";#127.0.0.
   
    $aux=$A & 255;
    $S=$S.int($aux);#127.0.0.1

    return $S;
}

#returns up if the host answer echo requests
sub state
{
    my $IP = $_[0];
    my $line;
    my $rtt;
    @SALIDA=`ping -nfq -w 1 -c 2 $IP`;
    while ($line=pop(@SALIDA))
    {
        if($line=~/0 received/)
        {
            return extraTest($IP);
        }
    }
    $rtt="UP";
    return $rtt;
}

#a running host might not answer echo requests, so lets try some common open ports.
sub extraTest
{
    my $IP=$_[0];
    @SALIDA=`nmap $IP -p T:25,22,21,53,80,110,443,U:53 2>/dev/null`;
    while ($line=pop(@SALIDA))
    {
        if($line=~/0 hosts up/)
        {
            $rtt="DOWN";
            return $rtt;#so it seems to be down... any other test?
        }
    }
    $rtt="UP";
    return $rtt;
}
####end of functions

####main code
if($< != 0)
{
    print STDERR "You must be root!\n";
    exit(-3);
}

$NET_ADDR="";
$NET_MASK="";
$HOSTS="";
$FIRST="";
$LAST="";

$argc=@ARGV;
if($argc < 1 or $argc > 2)
{
    print STDERR "USE: script [mask_bits]\n";
    exit(-1);
}

$NET_ADDR=$ARGV[0];
$NET_MASK=$ARGV[1];

#is it a real IP address?
if($NET_ADDR!~/^([\d]+)\.([\d]+)\.([\d]+)\.([\d]+)$/)
{
    print STDERR "Invalid network address. Example: 192.168.0.1\n";
    exit(-4);
}

#valid mask?
if($argc == 2 and ($NET_MASK > 30 || $NET_MASK < 1))
{
    print STDERR "Invalid mask. Mask [1 .. 30]\n";
    exit(-2);
}


#from 127.0.0.1 to 01111111 0000000 00000000 00000001
@L=split('\.',$NET_ADDR);
$NET_ADDR=$L[0]<<24;
$NET_ADDR=$NET_ADDR | $L[1]<<16;
$NET_ADDR=$NET_ADDR | $L[2]<<8;
$NET_ADDR=$NET_ADDR | $L[3];

if($argc == 2)
{#subnet check
    $HOSTS=2**(32-$NET_MASK) - 2;
   
    $aux=$NET_MASK;
    $NET_MASK=1;
    for($i=0;$i<32;$i++)
    {
        if($aux>0)
        {
            $NET_MASK=$NET_MASK<<1;
            $NET_MASK=$NET_MASK | 1;   
            $aux-=1;
        }
        else
        {
            $NET_MASK=$NET_MASK<<1;
        }
    }
    #now the real mask is ready, so i get the real net addr just in case
    $NET_ADDR=$NET_MASK & $NET_ADDR;
    $FIRST=$NET_ADDR+1;
    $LAST=$NET_ADDR+$HOSTS;
    print "NET ADDRESS: ".pickAddress($NET_ADDR).". NET MASK: ".pickAddress($NET_MASK).". HOSTS: $HOSTS. FIRST: ".pickAddress($FIRST).". LAST: ".pickAddress($LAST).".\n";

    $counter=1;

    while($counter<=$HOSTS)
    {
        $next_addr=pickAddress($NET_ADDR+$counter);
   
        $name=gethostbyaddr(inet_aton($next_addr),AF_INET);
   
        if(!$name)
        {
            $name="NONAME";
        }
   
        print $next_addr." is ".state($next_addr)." name $name\n";   
   
        $counter+=1;
    }
}
else
{#single host check
    $next_addr=pickAddress($NET_ADDR);
    $name=gethostbyaddr(inet_aton($next_addr),AF_INET);
    if(!$name)
    {
        $name="NONAME";
    }
    print $next_addr." is ".state($next_addr)." name $name\n";   
}


El script es bastante sencillo, recibe como parámetros una dirección IP y opcionalmente, el número de bits de la máscara de red. Debe ser ejecutado como root. Consiste básicamente en 2 pruebas, la primera es ver si el host responde mensajes ICMP, si los responde se concluye con que esa IP está en uso, si no responde se realiza una prueba mas en busca de puertos abiertos si la prueba devuelve algún puerto abierto, asume que la IP está en uso, de lo contrario asume que la IP está libre.

Aca les dejo una pequeña prueba. En busca de hosts en un fragmento de la red:

root@moon:/home/juan/Escritorio/stuff/Dropbox/TFG/scripts# ./chekIPs.pl 192.168.1.1 28
NET ADDRESS: 192.168.1.0. NET MASK: 255.255.255.240. HOSTS: 14. FIRST: 192.168.1.1. LAST: 192.168.1.14.
192.168.1.1 is DOWN name NONAME
192.168.1.2 is DOWN name NONAME
192.168.1.3 is DOWN name NONAME
192.168.1.4 is DOWN name NONAME
192.168.1.5 is DOWN name NONAME
192.168.1.6 is DOWN name NONAME
192.168.1.7 is DOWN name NONAME
192.168.1.8 is DOWN name NONAME
192.168.1.9 is DOWN name NONAME
192.168.1.10 is UP name NONAME
192.168.1.11 is DOWN name NONAME
192.168.1.12 is DOWN name NONAME
192.168.1.13 is DOWN name NONAME
192.168.1.14 is DOWN name NONAME


El script resulta mucho mas explícito cuando se ejecuta en una red que tiene resolución reversa de nombres.

La idea del script es meter el menor tráfico posible en la red, por eso las pruebas se separan en dos, siendo la primera muy sencilla y la segunda ya un poco mas exigente.

Yo lo uso para encontrar fácilmente direcciones IP que no están siendo utilizadas y así poder reasignarlas a nuevos hosts.

PD: si se preguntan porque está comentado en inglés (mi jefe me lo preguntó xD), no lo recuerdo jajaja.