Golang. En mi casa, tengo una Raspberry Pi 3 Model B Rev 1.2 que utilizo para diversas funciones, como operar un DNS que bloquea anuncios en toda mi red local, gestionar algunos microservicios e incluso para mantener una VPN que me permite conectarme a mi hogar. Para esta última tarea, necesito la IP pública de mi conexión.
Existen plataformas, como DynDNS o la mítica NoIP, que ofrecen un nombre de dominio que apunta a nuestra IP pública. En caso de que esta cambie, el DNS tipo A se actualiza automáticamente. No obstante, yo no uso esos servicios. DynDNS es de pago y, en el caso de la versión gratuita de NoIP, debo validar el uso del dominio cada 30 días, lo cual me resulta tedioso, además de que presenta anuncios.
En mi caso, utilizo los bots de Telegram para enviarme notificaciones a mi móvil sobre diversas situaciones; además, son gratuitos. En este contexto, lo que hice fue programar el envío de notificaciones con mi IP pública actual, que es la única información que necesito para acceder a la red de mi casa y al puerto SSH 22.
En muchas ocasiones, mi proveedor de internet cierra todos los puertos. Ante esto, he implementado un script que, mediante UPnP, los abre por un lapso determinado, permitiéndome así retomar el acceso. Todo este proceso lo lleva a cabo la Raspberry Pi, la cual opera ininterrumpidamente las 24 horas del día, los 7 días de la semana, tal vez publique sobre el script UPnP en otro momento.
Escribí el código en Golang simplemente para practicar y no olvidar la sintaxis. Ya poseo un script en mi Raspberry que realiza exactamente las mismas funciones, pero está escrito en Python. La versión de Golang que utilicé es la 1.21.1, que básicamente es la más reciente disponible hasta la fecha de esta publicación.
Código
El código en Golang consta de tres archivos: main.go
, settings.go
y settings.json
.
Para obtener la IP pública, empleo un servicio HTTP REST personalizado que desarrollé en C# y .NET (compartiré el código más adelante). Sin embargo, para los propósitos de esta publicación, se puede utilizar el siguiente servicio gratuito: https://api.ipify.org/?format=json.”
La respuesta JSON de este servicio es:
{
"ip": "289.123.123.123"
}
main.go
Es el archivo principal y donde comienza el programa (como todos los archivos main, Golang no es la excepción), ahi hay tres métodos:
saveIp
: Este método guarda la IP en modo texto en un archivo especificado ensettings.json
.getIp
: Este método obtiene la IP pública utilizando el servicio REST mencionado.sendTelegramMessage
: Este método envía la IP mediante un bot de Telegram a un usuario, ambos especificados ensettings.json
.
Dentro tiene una estructura (struct
) que solo sirve para modelar la respuesta del servicio de IP.
settings.go
Este código no tiene complejidades significativas. Incluye una estructura (struct
) para modelar el JSON de settings.json
y un método llamado ReadFileSettings
, que básicamente se encarga de leer el archivo.
settings.json
Este es el archivo JSON en el cual guardo variables importantes, tales como el servicio REST para obtener la IP, el ID de usuario en Telegram y la API del bot de Telegram.
Dicho archivo JSON está ignorado por Git, por lo que necesitarás crearlo manualmente
Ejemplo JSON
{
"ip_url": "https://api.ipify.org/?format=json",
"data_filename": "data.txt",
"bot_url": "https://api.telegram.org/botAQUI_VA_EL_ID_DEL_BOT/sendMessage",
"chat_id": "ID_USUARIO_TELEGRAM",
"telegram_message": "La ip en home ha cambiado a: "
}
El archivo JSON tiene pocas propiedades:
Propiedad | Descripción |
---|---|
ip_url | Url para obtener la IP pública. |
data_filename | El nombre del archivo donde se guardará en texto la última IP obtenida desde el servicio REST, puede ser el nombre o la ruta absoluta del archivo. |
bot_url | Esta es la URL del servicio para enviar mensajes a través del bot. Es necesario crear primero un bot en Telegram. No comparto la mía directamente porque en la URL se incluye el ‘secret’ del bot. |
chat_id | El ID es un identificador que Telegram asigna en su sistema a cada usuario. Este se puede obtener enviando mensajes al bot y revisando las peticiones recibidas; en ellas aparecerá el ID del usuario. Todos los detalles se explican en la documentación oficial. |
telegram_message | Este es simplemente el texto que el bot enviará al usuario. Dentro del código, a este texto le concateno la nueva IP. |
Funcionalidad
La función del código es bastante sencilla:
- Lee el archivo
settings.json
para obtener los valores necesarios para la lógica del programa. - Ejecuta el método
getIP
para obtener la IP pública. Si la petición falla, el proceso se detiene inmediatamente. - Luego, se ejecuta el método
getLastIp
para recuperar la IP previamente guardada en el archivodata.txt
. En caso de que el archivo no exista o haya un error, se asigna 0.0.0.0 como IP. - Se compara la IP obtenida con la IP guardada en el archivo. Si son distintas, se envía un mensaje mediante Telegram para notificar el cambio.
- Finalmente, se ejecuta el método
saveIp
para guardar la nueva IP endata.txt
. Luego, se muestra un mensaje y concluye la ejecución del programa
Si no cuentas con un bot de telegram puedes simular la petición usando otro proyecto que hice en NodeJs
Compilación
Este script debe ejecutarse automáticamente en la Raspberry Pi. Lo primero que necesito hacer es tomar el codigo en Golang y compilarlo para dicha plataforma. Aunque el código en Golang es multiplataforma, como lo desarrollé y probé en Linux x64, necesito realizar una compilación cruzada.
Para compilar en la plataforma correcta para Linux arm64, se tiene que ejecutar el siguiente comando:
GOOS=linux GOARCH=arm64 go build -ldflags "-w -s" -trimpath -o goIp main.go
Lo anterior especifica lo siguiente:
- GOOS: Sistema operativo objetivo, en este caso linux
- GOARCH: Arquitectura del procesador de la Raspberry que es un ARM de 64 bits
- -ldflags: Esta es una opción que le dice al compilador que modifique el comportamiento del enlazador:
-w
: Desactiva la generación de información de depuración en el archivo binario, lo que resulta en un archivo más pequeño.-s
: Desactiva la generación de la tabla de símbolos, lo que también reduce el tamaño del archivo binario.
- -trimpath: Esta opción elimina todos los directorios de archivo que empiezan con la ruta del directorio en el que se invocó el compilador, eliminando así cualquier información del directorio de construcción en la información de depuración. Esto ayuda a que las compilaciones sean reproducibles, asegurando que los binarios sean idénticos independientemente del path donde se compilen.
Los otros parámetros incluyen el archivo main.go
, que sirve como punto de inicio para todo el programa, y el nombre del binario, que en este caso es getIp
.
El binario es el ejecutable destinado para la Raspberry Pi. Para que sea leído correctamente, el archivo settings.json
debe ubicarse en la misma carpeta que el ejecutable o pasarle la ubicación como parametro.
./goIp #ejecución sin parámetros, settings debe estar en la misma ubicación
./goIp -p /ruta/a/settings.json
Configuración
Una vez tengamos el ejecutable en la Raspberry Pi, ahora lo que nos falta o en mi caso lo que necesito es configurarlo para que se ejecute cada 15 minutos, que es lo que necesito.
Para configurar este “timer” lo que tengo que hacer es configurar el archivo crontab para esto se usa el siguiente comando:
crontab -e
Con el comando anterior, se abrirá el archivo crontab del usuario activo. Dentro de este, es necesario configurar tanto el intervalo de tiempo como la ruta hacia el binario. En mi caso, necesito que se ejecute cada 15 minutos, y la configuración quedaría de la siguiente manera:
*/15 * * * * /ruta/goIp -p /ruta/a/settings.json
En la configuracion de crontab debe especificarse la ruta del archivo JSON, dado que la ubicacion de ejecucion es el home del usuario, si no se especifica entonces el settings.json
debe estar en ~/settings.json
Si se quiere capturar los logs como es mi caso para saber que ha sucedido entre ejecuciones, pueden agregar un redireccionamiento a un archivo en la ejecucion del crontab.
*/15 * * * * /ruta/goIp -p /ruta/a/settings.json >> /ruta/log.txt
El archivo log.txt
va a escribir la salida estandar del ejecutable, en el que código son las impresiones a consola por medio de fmt.Println()
Y eso sería todo, para que el binario se ejecute por solo cada 15 minutos y que el bot me notifique si la IP publica ha cambiado.
Recuerda aplicar los permisos de ejecucion al binario:
chmod +x goIp
Tambien se puede configurar el crontab en modo root por si no funciona con un usuario sin privilegios.