Comprobar automáticamente la dirección IP pública de nuestra Raspberry Pi

Hoy quiero explicaros cómo comprobar automáticamente la dirección IP pública de nuestra Raspberry Pi. Además, haremos que la propia Raspberry Pi nos informe por correo electrónico si la dirección IP ha cambiado. También te explicaré cómo configurarla para que ejecute nuestro programa automáticamente cada cierto tiempo. Todo ello programado con Python.

(En realidad, este tutorial no sirve solamente para Raspberry Pi, sino que es útil en cualquier distribución Linux).

¿Por qué es útil esto?

Pues porque ahora que ya hemos aprendido a controlar nuestra Raspberry Pi mediante SSH y hemos configurado un servidor web en nuestra Raspberry Pi, ahora sería muy útil poder acceder a ella desde cualquier sitio, ya sea por SSH o porque queremos ver la web que tenemos alojada en nuestro servidor web.

¿Y cuál es el problema?

Con redireccionar el puerto 22 de nuestro router hacia la dirección IP privada de nuestra Raspberry Pi para las conexiones SSH y el puerto 80 para las conexiones con el servidor web sería suficiente, pensará más de uno. Por supuesto, y eso es necesario si queremos acceder desde fuera de nuestra red a nuestra Raspberry Pi. El problema está en que rara vez (muy rara vez) contaremos con una dirección IP pública estática. Es decir, nuestra dirección IP pública variará cada cierto tiempo. Por tanto, por mucho que sepamos nuestra dirección IP pública, si ésta cambia cuando estamos fuera de casa y no nos damos cuenta, cuando intentemos acceder a nuestra Raspberry desde fuera nos encontraremos con que no podemos.

Algunos conceptos básicos de redes.

Para aquellos que no lo sepan, una de las clasificaciones que se le puede aplicar a las direcciones IP es la de “direcciones públicas” y “direcciones privadas”. Hubo un momento en el que, debido a la rápida expansión de Internet, el número de direcciones IP disponibles para asignar a los diferentes dispositivos conectados a Internet estaba cercano a agotarse por cuestiones matemáticas. Por ello, decidieron reservar un rango de esas direcciones como “direcciones IP privadas”. Otras tantas como “direcciones IP públicas”. Y otras tantas para otro tipo de direcciones IP que ahora mismo no nos interesan.

Diferencia entre IP pública y privada.

La diferencia entre una dirección IP pública y una dirección IP privada es, básicamente, desde dónde podemos acceder a ellas. Todo dispositivo conectado a una red WiFi normal, como la que todos tenemos en casa, tiene una dirección IP privada. Cada dispositivo la suya. Sin embargo, a esa dirección IP solamente se puede acceder desde dentro de la propia red. Si te vas a casa de tu amigo y tratas de acceder al ordenador de tu casa poniendo su dirección IP privada, no podrás acceder a él. De hecho, es posible que en la red de tu amigo esa dirección IP coresponda a uno de los dispositivos que tenga en su casa conectados a su red. Sin embargo, a una dirección IP pública se puede acceder desde cualquier lugar del mundo. En el caso de las redes WiFi de andar por casa, suele corresponderse con la del router WiFi.

La importancia de la IP pública.

Un router WiFi tiene, como mínimo, dos direcciones IP diferentes, de las cuales una es sí o sí, pública. Esta dirección IP es la que nuestro router usa para comunicarse con el resto de dispositivos de Internet. Las otras direcciones IP del router suelen ser privadas. Son las que el router usa para comunicarse con los dispositivos y subredes que haya dentro de su propia red. De esta forma, cualquier comunicación que haga hacia Internet un dispositivo conectado a una red, llevará como dirección IP de origen la dirección IP pública del router, independientemente del dispositivo que sea y de cual sea su dirección IP privada.

Como ya he mencionado antes, rara vez nos encontraremos con que nuestra dirección IP pública se mantenga invariable. Aunque no suele cambiar habitualmente, sí puede pasar cada cierto tiempo, y esto puede ocasionarnos algún problema si queremos acceder a nuestra Raspberry y la dirección ha cambiado sin que lo sepamos. Por ello nos interesa conocer en todo momento cuál es nuestra dirección IP pública, y para eso te va a ser de gran ayuda este tutorial.

El código de este tutorial está completamente auto-explicado gracias a los comentarios. Aún así, daré una pequeña explicación extra de cada uno de los scripts del programa, para mayor claridad. Para los nuevos en programación, que alguno habrá, los comentarios son las partes del texto de color gris que está tras los símbolos “#”.

[ACTUALIZACIÓN 05/12/2017]
Anteriormente este tutorial explicaba el funcionamiento con un único script que contenía todo el código.
Atendiendo a las sugerencias que me llegaron a través de los comentarios en la página de Facebook, decidí
modificar el código para hacerlo modular y también subirlo a GitHub.
Puedes ver el script antiguo haciendo clic en este enlace.

Script “checkPublicIP.py”

Para solucionar este pequeño problema, me dispuse a escribir un pequeño script en Python que obtiene la dirección IP pública del router al que está conectada la Raspberry, la compara con la que tenemos almacenada en un pequeño fichero de texto y, si ha variado, sustituye la antigua por la nueva y nos envia la nueva por correo electrónico. El script principal es el siguiente:

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Importamos el módulo necesario para obtener la dirección IP pública
from getPublicIP import getPublicIP
# Importamos el módulo necesario para comprobar si la dirección IP pública ha cambiado
from checkActualIP import checkActualIP
# Importamos el módulo necesario para enviar correos electrónicos
from sendMail import sendMail
# Importamos el módulo necesario para recibir los parámetros desde consola
import sys

# << Inicio de la definición

def checkPublicIP(src, pwd, dst):
	# Comprobamos si la dirección IP pública ha cambiado
	if (not checkActualIP()):
		# Si la IP actual no coincide con la registrada, enviamos la nueva IP por correo electrónico
		sendMail(src, pwd, dst, "Nueva IP", getPublicIP())

# >> Fin de la definición

# << Inicio de la invocación

checkPublicIP(sys.argv[1], sys.argv[2], sys.argv[3])

# >> Fin de la invocación

Este script no devuelve nada, pero necesita que le introduzcamos ciertos datos por parámetro. Estos datos son: la dirección de correo electrónico desde la que se va a enviar el correo electrónico (en nuestro caso, debe ser únicamente de GMail, más adelante veremos el motivo); la contraseña de dicha cuenta de GMail; y la dirección de correo electrónico en la que queremos recibir el correo. Esta puede ser de cualquier servicio de correo.

Este script es el script principal de nuestro programa. Como podemos ver, en este script importamos tres módulos esenciales para su funcionamiento. El módulo que nos permite obtener la dirección IP pública actual; el que nos permite compararla con la última dirección IP pública almacenada; y el que nos permite enviar un correo electrónico en caso de cambio en nuestra dirección IP pública. En nuestro script principal usamos estos tres módulos para realizar las operaciones necesarias y conseguir nuestro objetivo. Veamos el código de cada uno de estos módulos.

Script “getPublicIP.py”

Este script simplemente obtiene la dirección IP pública actual y la devuelve. Su código es muy sencillo:

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Importamos la librería necesaria para hacer una consulta HTTP
import urllib

def getPublicIP():
	# Solicitamos a 'icanhazip.com' nuestra IP pública y la devolvemos
	return urllib.urlopen('http://icanhazip.com').read()

En él importamos la librería Python urllib, que nos permite hacer consultas HTTP. Haciendo uso de esta librería, hacemos una consulta a la dirección http://icanhazip.com, que nos devuelve nuestra dirección IP pública.

Script “checkActualIP.py”

Este es el script más largo de los cuatro que vamos usamos en este tutorial, pero no por eso es más complejo que los demás. Su código es como sigue:

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Importamos la librería necesaria para acceder a un fichero
import os.path as path
# Importamos el módulo encargado de obtener la dirección IP pública actual
from getPublicIP import getPublicIP

def checkActualIP():
	# Apertura del fichero con la última dirección IP pública registrada
	# Comprobamos que el ficheor existe
	if path.exists('publicIPs.txt'):
		# Si el fichero existe, lo abrimos en modo lectura. El atributo 'r' indica que es de solo lectura
		archivo = open("publicIPs.txt", 'r')
	else:
		# Si el archivo no existe, lo creamos en modo lectura y escritura. El atributo 'w+' abre el fichero en modo lectura y escritura y, si no existe, lo crea
		archivo = open("publicIPs.txt", 'w+')
	# Obtenemos la dirección IP pública actual.
	publicIpActual = getPublicIP().split('\n')[0]
	
	# << Lectura de la última dirección IP pública registrada en el fichero
	# Se leen todas las líneas del fichero, quedándonos únicamente con la última
	linea = archivo.readline()
	publicIP = ""
	while linea != '':
		publicIP = linea
		linea = archivo.readline()
	# >> Cierre del fichero con la última dirección IP pública registrada
	archivo.close()

	# Si en el archivo no existe ninguna dirección IP pública registrada, o la última registrada es diferente a la actual
	if publicIP == "" or publicIP != publicIpActual:
		# Guardamos la dirección IP pública actual en la variable "publicIP"
		publicIP = publicIpActual
		# Escribimos la dirección IP pública actual en el fichero "publicIPs.txt". El atributo 'w' abre el fichero únicamente en modo escritura y, si no existe, lo crea.
		archivo = open("publicIPs.txt", 'w')
		archivo.write(publicIP)
		# Cerramos el fichero
		archivo.close()
		# Devolvemos 'False' para indicar que las direcciones no coincidían
		return False
	else:
		# Devolvemos 'True' para indicar que las direcciones coincidían
		return True

En él, además de importar el módulo getPublicIP que, como hemos visto hace un momento, devuelve la dirección IP actual, importamos la librería path. Esta librería nos permite acceder a un fichero y trabajar con la información contenida en él. Gracias a estos dos módulos, nuestro script obtiene la dirección IP pública actual, accede al fichero que contiene el registro de las últimas direcciones IP públicas que el dispositivo ha tenido y compara la última registrada con la actual. Si no coinciden, almacena la IP pública actual en el fichero y nos devuelve un valor False para indicar que no coinciden. En caso contrario, simplemente devuelve un valor True para indicar que son idénticas.

Script “sendMail.py”

Por último, tenemos el script encargado del envío de correo. Como explicábamos anteriormente, este script no devuelve nada. Únicamente toma tres parámetros (dirección de correo saliente, contraseña de dicha dirección de correo y dirección de correo destino) y ejecuta el envío del correo. El código es éste:

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Importamos la librería necesaria para el envío de correos
import smtplib

def sendMail(src, pwd, dst, sub, msg):
	# << Montaje del correo electrónico
	email = """From: %s
To: %s
MIME-Version: 1.0
Content-type: text/html
Subject: %s
%s
""" %(src, dst, sub, msg)
	# >> Fin del montaje del correo electrónico
	
	# << Envío del correo
	# Apertura del servido SMTP en el servidor de correo electrónico de origen
	try:
		server = smtplib.SMTP('smtp.gmail.com:587')
                server.starttls()
		try:
			# Inicio de sesión en el servidor de correo electrónico de origen.
			server.login(src, pwd)
			try:
				# Envío del correo electrónico
				server.sendmail(src, dst, email)
			except:
				print('Error al enviar el correo electrónico')
		except:
			print('Error al iniciar sesión en GMail')
		# Cierre del servidor SMTP
		server.quit()
	except:
		print('Error al iniciar el servidor SMTP')
	# >> Fin del envío del correo

En este script importamos la librería smtplib, que será la que nos permitirá enviar el correo electrónico. Este código simplemente monta todas las cabeceras y el cuerpo del correo electrónico a enviar con los datos que le hemos pasado por parámetro e intenta el envío. Si falla en algún momento, nos mostrará por consola un indicio de dónde está fallando para que podamos analizar el problema y solucionarlo. Como podemos ver en el código, indicamos que el servidor de correo saliente que usaremos es el servidor ‘smtp.gmail.com:587‘. Es por este motivo por el que el script solo funcionará con una dirección de correo saliente de GMail.

Programando los scripts.

Para escribir este script en nuestra Raspberry Pi no tenemos más que conectarnos a ella mediante SSH (o abrir un terminal si la tenemos conectada a una pantalla y a un teclado) y movernos al directorio donde queramos guardar el programa. Para moverte por los directorios de la Raspberry debes usar el comando cd. Por ejemplo, si queremos movernos al directorio “PythonProjects” que se encuentra dentro de “Documents”, ejecutaremos el siguiente comando:

cd Documents/PythonProjects

Y crearemos un directorio para nuestro programa ejecutando el comando

mkdir [nombreDelDirectorio]

donde [nombreDelDirectorio] es el nombre que queramos darle a la carpeta en la que guardaremos nuestro programa en Python, por ejemplo:

mkdir compruebaPublicIP

Ahora nos movemos dentro del directorio usando de nuevo el comando cd.

cd compruebaPublicIP

Y para programar nuestros scripts, ejecutamos el comando

nano [nombreDelScript].py

donde [nombreDelScript] es el nombre del script que vamos a crear en ese momento. Este comando nos va a abrir un script de Python en blanco. En él escribiremos el código de cada script. Tras escribir el código, guardamos el fichero dejando pulsada la tecla CTRL y pulsando la X. Debemos repetir este proceso para cada uno de los scripts, cambiando [nombreDelScript] por el correspondiente en cada caso. Por ejemplo, para escribir el script principal del programa debo ejecutar el comando

nano checkPublicIP.py

copiar el código correspondiente al script checkPublicIP.py en el script Python en blanco que se nos ha abierto y guardar con CTRL+X. Luego haré lo mismo con getPublicIP.pysendMail.py.

También podemos hacer uso de mi repositorio en GitHub y descargarnos todos los scripts de una vez ejecutando el comando

git clone https://github.com/manucabello/checkPublicIP

estando situados en el directorio en que queramos crear la carpeta con nuestro programa Python. Pueden ver el repositorio del proyecto en GitHub haciendo clic en este enlace.

Probando nuestro programa.

Para comprobar si el programa funciona como esperamos debes ejecutar el siguiente comando mientras tienes la terminal situada en el directorio en el que has creado los scripts:

python [nombreDelScript].py [direccionCorreoSaliente] [contraseñaCorreoSaliente] [direccionCorreoDestino]

Donde debes cambiar [nombreDelScript] por el nombre que le diste al script principal cuando lo creaste [direccionCorreoSaliente] por la dirección de correo desde el que se va enviar el correo (recuerda, de GMail), [contraseñaCorreoSaliente] por la contraseña de la cuenta de GMail desde la que se va a enviar el correo y [direccionCorreoDestino] por la dirección de correo a la que se va a enviar el correo.

Al ejecutar el comando la Raspberry se queda un tiempo parada mientras procesa los scripts. Luego veremos que aparentemente no ha pasado nada. Sin embargo, si miras en la bandeja de entrada (o en la carpeta de spam) de la dirección de correo electrónico que pusiste como destino en el script, verás que hay un correo electrónico indicándote tu dirección IP pública. Además, habrá un nuevo fichero de texto en el directorio donde están los scripts, llamado “publicIPs.txt”. Este fichero contiene la dirección IP pública actual de tu router. Es el fichero que el script checkActualIP.py usará para comprobar si ha cambiado en futuras ocasiones.

Configurando la ejecución automática del script.

Ya tenemos nuestro programa, y sabemos que funciona. Ahora tenemos que indicarle a la Raspberry Pi que lo ejecute periódicamente por su cuenta. Para ello haremos uso del demonio “cron”.

¿Qué es cron?

Según el blog DesdeLinux, cron es un administrador regular de procesos en segundo plano (demonio) que ejecuta procesos o guiones a intervalos regulares (por ejemplo, cada minuto, día, semana o mes). Los procesos que deben ejecutarse y la hora en la que deben hacerlo se especifican en el fichero crontab.

Automatizar el script con cron.

Para automatizar el script con cron en nuestra Raspberry debemos acceder al fichero cronguiente comando:

crontab -e

Si es la primera vez que ejecutamos este comando, puede que la consola nos devuelva algo así:

no crontab for pi - using an empty one

Select an editor. To change later, run 'in/nano <----- easiest
3. /usr/bin/vim.tiny

Choose 1-3 []:

Escogemos el editor que más nos guste. El que hemos estado usando durante este tutorial, y mi favorito, es nano, por lo que tecleamos un 2 y pulsamos Enter. Ahora veremos algo así:

# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
#nual pages of crontab(5) and cron(8)
#
# m h  dom mon dow   command

Esto significa que ya hemos conseguido acceder al fichero crontab. Ahora vamos a configurar la ejecución automática del script. Para ello, debemos añadir al final del fichero la siguiente línea de texto:

0 0 * * *  cd ~/Documents/PythonProjects && python checkPublicIp.py [direccionCorreoSaliente] [contraseñaCorreoSaliente] [direccionCorreoDestino]

Sustituyendo [direccionCorreoSaliente] por la dirección de correo desde el que se va enviar el correo (recuerda, de GMail), [contraseñaCorreoSaliente] por la contraseña de la cuenta de GMail desde la que se va a enviar el correo y [direccionCorreoDestino] por la dirección de correo a la que se va a enviar el correo. Esta línea le indica a cron que debe moverse hasta la ruta “~/Documents/PythonProjects” y ejecutar el script llamado “checkPublicIp.py” a las 0:00h cada día del mes, cada mes y sea cual sea el día de la semana.

El primer 0 indica el minuto en el que debe ejecutarse el script. Podría ser también un asterisco si quisiéramos que se ejecutara a cada minuto. El segundo 0 indica la hora a la que debe ejecutarse el script. Es decir, el script se ejecutaría cada minuto desde las 0:00h hasta las 0:59h si en lugar del primer 0 hubiera un asterisco. Los siguientes tres valores, que en nuestro caso hemos marcado con asterisco, son, de izquierda a derecha, el día del mes, el mes (del 1 al 12) y el día de la semana (del 1 al 7). Si quieres profundizar un poco en la configuración del fichero crontab, mira este enlace.

Y para terminar, reiniciamos el servicio de cron ejecutando el siguiente comando:

sudo service cron restart

¡Y nada más! Si quieres probar ahora mismo si funciona, puedes cambiar el momento en el que cron ejecute el script por un momento más próximo y así te aseguras que todo ha ido bien.

Como siempre, cualquier sugerencia o consulta podéis hacérmela llegar enviando un correo electrónico a sugerencias@manusoft.es o dejando un comentario en la entrada. ¡Muchas gracias por visitar ManuSoft.es!

Publicado en Linux, Raspberry Pi y etiquetado , , , , , .

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *