jueves, mayo 25, 2006

Los problemas de la IP dinamica en la Internet actual

Esta muy bien tener tus servidores en casa y poder usarlos desde cualquier lado. El CVS, un apache, SSH, ... vamos cosas utiles. El problema viene cuando tienes IP dinamica. Hay que hacer algún apaño para que eso no nos impida acceder a nuestros servidores cuando queramos y aqui es donde entra el tema de hoy.

¿Como encontrar nuestra IP dinamica en Internet?


Reflexionando un poco sobre como llevar esto a cabo se llega rapidamente a la conclusión de que hace falta un "punto fijo". Tiene que haber algo que se pueda encontrar siempre. Una idea que no llegue a desarrollar fue usar por ejemplo una dirección de correo. Pensar en como quedaría la bandeja de entrada fue suficiente para aparcar la idea.

La primera intentona que puse en práctica fue usar uno de esos servicios gratuitos que ofrecen DNS dinamico como DynDns o No-IP. Los resultados no fueron buenos. Cada vez que necesitaba acceder a mis archivos desde remoto tenía la duda de si podría hacerlo, y a menudo la respuesta era un no rotundo. Unas veces el programa cliente que se ejecuta en mi maquina no se habia arrancado como estaba programado, otras se había muerto de repente, otras era incapaz de contactar. Pero lo más sorprendente me lo encontre cuando el sistema fallaba en la parte del servidor, la configuración de mi cuenta desaparecia. No quiero dar mala imagen a estos servicios, es posible que fuera yo mismo, realizando excesivas peticiones de actualización en poco tiempo el que provocase este efecto. El resultado sin embargo es el mismo, estos servicios no me valian.

Me dió por el bricolaje y me monté un sistema propio. La solución consistia en dos partes al igual que los metodos de DNS dinanicos, una dirección bien conocida y un programa cliente.

Como dirección conocida use una página web que me regalaron con la conexión y que hasta ese momento tenia muerta de risa. Dicha página es todo un logro minimalista, consiste en cuatro números separados por puntos. Esos números son mi dirección IP actual.

Eso nos lleva a la parte cliente, que es el responsable de que la dirección IP que aparece en la página sea, efectivamente, mi dirección. Desgraciadamente, la web de regalo que tengo no admite php lo que complica mucho el tema. Así pues, una vez más he de recurrir a terceros para continuar.

Existen multitud de páginas web que te informan de la IP desde la que te conectas. Una simple búsqueda en Google ofrece un gran abanico para elegir. Nuestro programita cliente puede usar cualquiera de ellas para obtener la información que búscamos. Una vez que conoce ese dato crea la página web oportuna y la sube al servidor.

Ahora bien, al ser la IP dinamica puede cambiar con el tiempo así que es necesario ejecutar el programa de vez en vez. Si esto se hace a mano posiblemente cuando nos haga falta que este actualizado sera la vez que se nos olvido actualizar, aparte de lo incomodo que resultaría. Pero aquí es donde cron viene en nuestro rescate. Para los que no lo sepan cron es un demonio que corre en todos los Linux y se encarga de ejecutar tareas en momentos concretos. Basta con crear una tarea que invoque a nuestro programilla tres o cuatro veces al día para completar el último vertice de la solución.

Si se aloja la página en un servidor que admita php, la consulta para hallar la IP la podemos realizar contra una página nuestra y convertir este esquema en uno del tipo Juan Palomo.

En efecto, esto es un metodo rebuscado y cicatero de poder conectar con tu ordenador en cualquier momento. Los dominios son baratos y los servicios de DNS dinamicos suelen funcionar. Pero como ya he dicho, hoy tengo el día Bricomanía.

Para terminar os pongo el codigo fuente (en C) del programita. Mirandolo con calma, se ve que un shell script podria hacer lo mismo usando programas ya existentes y manejo de expresiones regulares. Hay dos motivos por los que no sea un script. La primera: Sería muy complicado hacer un script multiplataforma. En este blog hablo de mi ordenador, el cual usa GNU/Linux casi todo el tiempo, pero aun así tengo cierto afan de universalidad. Si puedo evitarlo prefiero no discriminar a nadie por el sistema operativo que use. La segunda: Me apetecía trastear un poco con los protocolos de internet.


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_IP <servidor web>
#define MAX_COM 2048
#define CMD_USER "USER <usuario>"
#define CMD_PASSWD "PASS <password>"
#define CMD_TYPE "TYPE I"
#define CMD_PASV "PASV"
#define CMD_STOR "STOR /<pagina web>"
#define CR 13
#define LF 10
#define HTTP_IP <servidor dice IP>
#define GET "GET <pagina dice IP>"
#define HD_HOST "Host: <servidor dice IP>:80"
#define HD_CLOSE "Connection: close"
#define HD_CACHE "Cache-Control: No-cache"

long puerto_datos;

int conectar_ftp( long puerto );
int comunicar( int fd );
char comando( int fd, char* cmd, char** respuesta );
void obtener_puerto( char* buffer );
int determinar_ip( int fd, char** ip );
void encontrar_ip( char bruto[], char** ip );
int actualizar_ip( char* ip, int fd_datos );
int conectar( char* nombre, long puerto );

int main() {
int error = 0;
char* ip;
int fd_control;
int fd_datos;
int fd_http;
char fin;

ip = (char *)malloc( 16 );

fd_http = conectar( HTTP_IP, 80 );
if( fd_http < 0 ) {
printf("No se puede conectar
mediante http\n");
exit( -1 );
}

determinar_ip( fd_http, &ip );
close( fd_http );

if( strlen( ip ) > 6 ) {
fd_control = conectar( SERVER_IP, 21 );
if( fd_control < 0 ) {
printf("No se puede conectar al
ftp (control)\n");
exit( -1 );
}
error = comunicar( fd_control );
if( error < 0 ) {
printf("Fallo en la comunicacion
con FTP\n");
}
fd_datos = conectar( SERVER_IP, puerto_datos );
if( fd_datos < 0 ) {
printf("No se puede conectar al
ftp (datos)\n");
exit( -1 );
}
error = send( fd_datos, ip, strlen( ip ), 0 );
if( error < 0 ) {
printf("No se puede escribir en el
ftp (datos)\n");
exit( -1 );
}
/* cerramos fds */
close( fd_datos );
close( fd_control );
}

if( error < 0 ) {
printf("No se ha podido actualizar la ip\n");
} else {
if( strlen( ip ) > 6 ) {
printf("Se ha actualizado la ip\n");
} else {
printf("Se ha mantenido la ip
anterior\n");
}
}

exit( error );
}


int comunicar( int fd ) {
char buffer[MAX_COM];
int numbytes;
char resultado;
int error = 0;
char* res;

res = (char *)malloc( MAX_COM );
numbytes = recv( fd, buffer, MAX_COM, 0 );
if( numbytes == -1){
printf("Error en recv() \n");
return(-1);
}

if( buffer[0] !='2' ) {
printf("No se puede conectar\n");
return( -1 );
}

resultado = comando( fd, CMD_USER, &res );
if( resultado !='3' ) {
printf("Nombre de usuario no valido\n");
return( -1 );
}

resultado = comando( fd, CMD_PASSWD, &res );
if( resultado !='2' ) {
printf("Contraseña incorrecta\n");
return( -1 );
}

resultado = comando( fd, CMD_TYPE, &res );
if( resultado !='2' ) {
return( -1 );
}

resultado = comando( fd, CMD_PASV, &res );
if( resultado !='2' ) {
printf("No admite modo pasivo\n");
return( -1 );
}

obtener_puerto( res );

resultado = comando( fd, CMD_STOR, &res );
if( resultado =='4' || resultado == '5' ) {
printf("No se puede subir el fichero\n");
return( -1 );
}

return 0;
}

char comando( int fd, char* cmd, char** respuesta ) {
char buffer[MAX_COM];
int numbytes;

bzero( buffer, sizeof( buffer) );
strcpy( buffer, cmd );
buffer[strlen( cmd )] = CR;
buffer[strlen( cmd ) + 1] = LF;
numbytes = send( fd, &buffer, strlen( cmd ) + 2, 0 );
if( numbytes == -1){
printf("Error en send() \n");
return(-1);
}

if( strcmp( cmd , CMD_STOR ) != 0 ) {
numbytes = recv( fd, buffer, MAX_COM, 0);
if( numbytes == -1){
printf("Error en recv() \n");
return(-1);
}
}

strcpy( *respuesta, buffer );

return buffer[0];
}

void obtener_puerto( char* buffer ) {
char c;
int i;
int acumulado;
long puerto_g;
long puerto_p;

i = 0;
c = buffer[i];
while( c !='(' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
while( c !=',' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
while( c !=',' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
while( c !=',' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
while( c !=',' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
acumulado = 0;
while( c !=',' ) {
acumulado = (acumulado * 10) + ( c - '0' );
++i;
c = buffer[i];
}
puerto_g = acumulado;
++i;
c = buffer[i];
acumulado = 0;
while( c !=')' ) {
acumulado = (acumulado * 10) + ( c - '0' );
++i;
c = buffer[i];
}
puerto_p = acumulado;
puerto_datos = ((puerto_g * 256) + puerto_p);
}

int conectar( char* nombre, long puerto ) {
int fd;
struct sockaddr* addr;

/* estructura que recibir�informaci� sobre el nodo remoto */
struct hostent *he;

/* informaci� sobre la direcci� del servidor */
struct sockaddr_in server;

int error = 0;

he = gethostbyname( nombre );
if( he == NULL ) {
/* llamada a gethostbyname() */
printf("gethostbyname() error\n");
return(-1);
}

fd = socket(AF_INET, SOCK_STREAM, 0);
if( fd == -1 ) {
printf("socket() error\n");
return(-1);
}

server.sin_family = AF_INET;
server.sin_port = htons(puerto);
/*he->h_addr pasa la informaci� de ``*he'' a "h_addr" */
server.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(server.sin_zero),8);

if(connect(fd, (struct sockaddr *)&server,
sizeof(struct sockaddr))==-1){
printf("connect() error\n");
return(-1);
}

return fd;
}

int determinar_ip( int fd, char** ip ) {
char buffer[MAX_COM];
int numbytes;

bzero( buffer, sizeof( buffer) );
sprintf( buffer, "%s%c%c%s%c%c%s%c%c%s%c%c%c%c", GET, CR,
LF, HD_HOST, CR, LF, HD_CLOSE, CR, LF,
HD_CACHE, CR, LF,CR, LF );
numbytes = send( fd, &buffer, strlen( buffer ), 0 );
if( numbytes == -1){
printf("Error en send() \n");
return(-1);
}

numbytes = recv( fd, buffer, MAX_COM, 0);
if( numbytes == -1){
printf("Error en recv() \n");
return(-1);
}

encontrar_ip( buffer, ip );

return 0;
}

void encontrar_ip( char bruto[], char** ip ) {
int estado = 0; //No se ha encontrado nada
char str_ip[16];
int indice = 0;
int indice_ip = 0;
char actual;

actual = bruto[indice];

while( estado != 4 && actual != 0 ) {
switch( estado ) {
case 0:
if( actual == 'I' ) {
estado = 1;
}
break;
case 1:
if( actual == 'P' ) {
estado = 2;
} else {
estado = 0;
}
break;
case 2:
if( actual == ':' ) {
estado = 3;
} else {
estado = 0;
}
break;
case 3:
if( actual == '.' ) {
str_ip[indice_ip] = actual;
indice_ip++;
break;
}
if( actual >= '0' && actual <= '9' ) {
str_ip[indice_ip] = actual;
indice_ip++;
break;
}
if( actual == ' ' ) {
break;
}
estado = 4;
break;
case 4:
break;
}
indice++;
actual = bruto[indice];
}
str_ip[indice_ip] = 0;
memcpy( *ip, str_ip, strlen( str_ip ) + 1 );
}

1 comentario:

fortran dijo...

tío, hacer eso en C es un castigo para la vista xD

¿no te valía un script en python, que también es multiplataforma?