Golang – Obtener Ip publica y enviarla por medio de un bot a telegram.

Golang – Obtener Ip publica y enviarla por medio de un bot a telegram.

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 en settings.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 en settings.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:

PropiedadDescripción
ip_urlUrl para obtener la IP pública.
data_filenameEl 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_urlEsta 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_idEl 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_messageEste 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:

  1. Lee el archivo settings.json para obtener los valores necesarios para la lógica del programa.
  2. Ejecuta el método getIP para obtener la IP pública. Si la petición falla, el proceso se detiene inmediatamente.
  3. Luego, se ejecuta el método getLastIp para recuperar la IP previamente guardada en el archivo data.txt. En caso de que el archivo no exista o haya un error, se asigna 0.0.0.0 como IP.
  4. 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.
  5. Finalmente, se ejecuta el método saveIp para guardar la nueva IP en data.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.

El código pueden clonarlo desde este repositorio.