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.