{"id":548,"date":"2023-09-26T20:52:33","date_gmt":"2023-09-27T01:52:33","guid":{"rendered":"https:\/\/80bits.blog\/?p=548"},"modified":"2023-09-27T14:52:18","modified_gmt":"2023-09-27T19:52:18","slug":"golang-obtener-ip-publica-y-enviarla-por-medio-de-un-bot-a-telegram","status":"publish","type":"post","link":"https:\/\/80bits.blog\/index.php\/2023\/09\/26\/golang-obtener-ip-publica-y-enviarla-por-medio-de-un-bot-a-telegram\/","title":{"rendered":"Golang &#8211; Obtener Ip publica y enviarla por medio de un bot a telegram."},"content":{"rendered":"\n<p>Golang. En mi casa, tengo una <a href=\"https:\/\/www.raspberrypi.com\/products\/raspberry-pi-3-model-b\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Raspberry Pi 3 Model B Rev 1.2<\/a> 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 \u00faltima tarea, necesito la IP p\u00fablica de mi conexi\u00f3n.<\/p>\n\n\n\n<p>Existen plataformas, como <a href=\"https:\/\/account.dyn.com\/\" target=\"_blank\" rel=\"noopener\" title=\"\">DynDNS<\/a> o la m\u00edtica <a href=\"https:\/\/www.noip.com\/es-MX\" target=\"_blank\" rel=\"noopener\" title=\"\">NoIP<\/a>, que ofrecen un nombre de dominio que apunta a nuestra IP p\u00fablica. En caso de que esta cambie, el DNS tipo A se actualiza autom\u00e1ticamente. No obstante, yo no uso esos servicios. DynDNS es de pago y, en el caso de la versi\u00f3n gratuita de NoIP, debo validar el uso del dominio cada 30 d\u00edas, lo cual me resulta tedioso, adem\u00e1s de que presenta anuncios.<\/p>\n\n\n\n<p>En mi caso, utilizo los <a href=\"https:\/\/core.telegram.org\/bots\/api\" target=\"_blank\" rel=\"noopener\" title=\"\">bots de Telegram<\/a> para enviarme notificaciones a mi m\u00f3vil sobre diversas situaciones; adem\u00e1s, son gratuitos. En este contexto, lo que hice fue programar el env\u00edo de notificaciones con mi IP p\u00fablica actual, que es la \u00fanica informaci\u00f3n que necesito para acceder a la red de mi casa y al puerto SSH 22.<\/p>\n\n\n\n<p><em>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\u00e9ndome as\u00ed retomar el acceso. Todo este proceso lo lleva a cabo la Raspberry Pi, la cual opera ininterrumpidamente las 24 horas del d\u00eda, los 7 d\u00edas de la semana, tal vez publique sobre el script UPnP en otro momento.<\/em><\/p>\n\n\n\n<p>Escrib\u00ed el c\u00f3digo 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\u00e1 escrito en Python. La versi\u00f3n de <a href=\"https:\/\/go.dev\/\" target=\"_blank\" rel=\"noopener\" title=\"\">Golang que utilic\u00e9 es la 1.21.1<\/a>, que b\u00e1sicamente es la m\u00e1s reciente disponible hasta la fecha de esta publicaci\u00f3n.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">C\u00f3digo<\/h2>\n\n\n\n<p>El c\u00f3digo en Golang consta de tres archivos: <code>main.go<\/code>, <code>settings.go<\/code> y <code>settings.json<\/code>.<\/p>\n\n\n\n<p>Para obtener la IP p\u00fablica, empleo un servicio HTTP REST personalizado que desarroll\u00e9 en C# y .NET (compartir\u00e9 el c\u00f3digo m\u00e1s adelante). Sin embargo, para los prop\u00f3sitos de esta publicaci\u00f3n, se puede utilizar el siguiente servicio gratuito: <a href=\"https:\/\/api.ipify.org\/?format=json\">https:\/\/api.ipify.org\/?format=json<\/a>.&#8221;<\/p>\n\n\n\n<p>La respuesta JSON de este servicio es:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"ip\": \"289.123.123.123\"\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">main.go<\/h3>\n\n\n\n<p>Es el archivo principal y donde comienza el programa (como todos los archivos main, Golang no es la excepci\u00f3n), ahi hay tres m\u00e9todos: <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>saveIp<\/code>: Este m\u00e9todo guarda la IP en modo texto en un archivo especificado en <code>settings.json<\/code>.<\/li>\n\n\n\n<li><code>getIp<\/code>: Este m\u00e9todo obtiene la IP p\u00fablica utilizando el servicio REST mencionado.<\/li>\n\n\n\n<li><code>sendTelegramMessage<\/code>: Este m\u00e9todo env\u00eda la IP mediante un bot de Telegram a un usuario, ambos especificados en <code>settings.json<\/code>.<\/li>\n<\/ul>\n\n\n\n<p>Dentro tiene una estructura (<code>struct<\/code>) que solo sirve para modelar la respuesta del servicio de IP.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">settings.go<\/h3>\n\n\n\n<p>Este c\u00f3digo no tiene complejidades significativas. Incluye una estructura (<code>struct<\/code>) para modelar el JSON de <code>settings.json<\/code> y un m\u00e9todo llamado <code>ReadFileSettings<\/code>, que b\u00e1sicamente se encarga de leer el archivo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">settings.json<\/h3>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>Dicho archivo JSON est\u00e1 ignorado por Git, por lo que necesitar\u00e1s crearlo manualmente<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Ejemplo JSON<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"ip_url\": \"https:\/\/api.ipify.org\/?format=json\",\n    \"data_filename\": \"data.txt\",\n    \"bot_url\": \"https:\/\/api.telegram.org\/botAQUI_VA_EL_ID_DEL_BOT\/sendMessage\",\n    \"chat_id\": \"ID_USUARIO_TELEGRAM\",\n    \"telegram_message\": \"La ip en home ha cambiado a: \"\n}<\/code><\/pre>\n\n\n\n<p>El archivo JSON tiene pocas propiedades:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Propiedad<\/th><th>Descripci\u00f3n<\/th><\/tr><\/thead><tbody><tr><td>ip_url<\/td><td>Url para obtener la IP p\u00fablica.<\/td><\/tr><tr><td>data_filename<\/td><td>El nombre del archivo donde se guardar\u00e1 en texto la \u00faltima IP obtenida desde el servicio REST, puede ser el nombre o la ruta absoluta del archivo.<\/td><\/tr><tr><td>bot_url<\/td><td>Esta es la URL del servicio para enviar mensajes a trav\u00e9s del bot. Es necesario crear primero un bot en Telegram. No comparto la m\u00eda directamente porque en la URL se incluye el &#8216;secret&#8217; del bot.<\/td><\/tr><tr><td>chat_id<\/td><td>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\u00e1 el ID del usuario. Todos los detalles se explican en la documentaci\u00f3n oficial.<\/td><\/tr><tr><td>telegram_message<\/td><td>Este es simplemente el texto que el bot enviar\u00e1 al usuario. Dentro del c\u00f3digo, a este texto le concateno la nueva IP.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Funcionalidad<\/h3>\n\n\n\n<p>La funci\u00f3n del c\u00f3digo es bastante sencilla:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Lee el archivo <code>settings.json<\/code> para obtener los valores necesarios para la l\u00f3gica del programa.<\/li>\n\n\n\n<li>Ejecuta el m\u00e9todo <code>getIP<\/code> para obtener la IP p\u00fablica. Si la petici\u00f3n falla, el proceso se detiene inmediatamente.<\/li>\n\n\n\n<li>Luego, se ejecuta el m\u00e9todo <code>getLastIp<\/code> para recuperar la IP previamente guardada en el archivo <code>data.txt<\/code>. En caso de que el archivo no exista o haya un error, se asigna 0.0.0.0 como IP.<\/li>\n\n\n\n<li>Se compara la IP obtenida con la IP guardada en el archivo. Si son distintas, se env\u00eda un mensaje mediante Telegram para notificar el cambio.<\/li>\n\n\n\n<li>Finalmente, se ejecuta el m\u00e9todo <code>saveIp<\/code> para guardar la nueva IP en <code>data.txt<\/code>. Luego, se muestra un mensaje y concluye la ejecuci\u00f3n del programa<\/li>\n<\/ol>\n\n\n\n<p>Si no cuentas con un bot de telegram puedes simular la petici\u00f3n <a href=\"https:\/\/80bits.blog\/index.php\/2023\/09\/24\/fake-api-generar-servicios-http-rest-en-nodejs-para-simular-micro-servicios\/\" target=\"_blank\" rel=\"noopener\" title=\"\">usando otro proyecto que hice en NodeJs<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Compilaci\u00f3n<\/h2>\n\n\n\n<p>Este script debe ejecutarse autom\u00e1ticamente en la Raspberry Pi. Lo primero que necesito hacer es tomar el codigo en Golang y compilarlo para dicha plataforma. Aunque el c\u00f3digo en Golang es multiplataforma, como lo desarroll\u00e9 y prob\u00e9 en Linux x64, necesito realizar una compilaci\u00f3n cruzada.<\/p>\n\n\n\n<p>Para compilar en la plataforma correcta para Linux arm64, se tiene que ejecutar el siguiente comando:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GOOS=linux GOARCH=arm64 go build -ldflags \"-w -s\" -trimpath -o goIp main.go<\/code><\/pre>\n\n\n\n<p>Lo anterior especifica lo siguiente:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>GOOS: Sistema operativo objetivo, en este caso linux<\/li>\n\n\n\n<li>GOARCH: Arquitectura del procesador de la Raspberry que es un ARM de 64 bits<\/li>\n\n\n\n<li>-ldflags: Esta es una opci\u00f3n que le dice al compilador que modifique el comportamiento del enlazador:\n<ul class=\"wp-block-list\">\n<li><code>-w<\/code>: Desactiva la generaci\u00f3n de informaci\u00f3n de depuraci\u00f3n en el archivo binario, lo que resulta en un archivo m\u00e1s peque\u00f1o.<\/li>\n\n\n\n<li><code>-s<\/code>: Desactiva la generaci\u00f3n de la tabla de s\u00edmbolos, lo que tambi\u00e9n reduce el tama\u00f1o del archivo binario.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>-trimpath: Esta opci\u00f3n elimina todos los directorios de archivo que empiezan con la ruta del directorio en el que se invoc\u00f3 el compilador, eliminando as\u00ed cualquier informaci\u00f3n del directorio de construcci\u00f3n en la informaci\u00f3n de depuraci\u00f3n. Esto ayuda a que las compilaciones sean reproducibles, asegurando que los binarios sean id\u00e9nticos independientemente del path donde se compilen.<\/li>\n<\/ul>\n\n\n\n<p>Los otros par\u00e1metros incluyen el archivo <code>main.go<\/code>, que sirve como punto de inicio para todo el programa, y el nombre del binario, que en este caso es <code>getIp<\/code>.<\/p>\n\n\n\n<p>El binario es el ejecutable destinado para la Raspberry Pi. Para que sea le\u00eddo correctamente, el archivo <code>settings.json<\/code> debe ubicarse en la misma carpeta que el ejecutable o pasarle la ubicaci\u00f3n como parametro.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.\/goIp #ejecuci\u00f3n sin par\u00e1metros, settings debe estar en la misma ubicaci\u00f3n\n.\/goIp -p \/ruta\/a\/settings.json<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Configuraci\u00f3n<\/h2>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>Para configurar este &#8220;timer&#8221; lo que tengo que hacer es configurar el archivo crontab para esto se usa el siguiente comando:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>crontab -e<\/code><\/pre>\n\n\n\n<p>Con el comando anterior, se abrir\u00e1 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\u00f3n quedar\u00eda de la siguiente manera:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>*\/15 * * * * \/ruta\/goIp -p \/ruta\/a\/settings.json<\/code><\/pre>\n\n\n\n<p>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 <code>settings.json<\/code> debe estar en <code>~\/settings.json<\/code><\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>*\/15 * * * * \/ruta\/goIp -p \/ruta\/a\/settings.json &gt;&gt; \/ruta\/log.txt<\/code><\/pre>\n\n\n\n<p>El archivo <code>log.txt<\/code> va a escribir la salida estandar del ejecutable, en el que c\u00f3digo son las impresiones a consola por medio de <code>fmt.Println()<\/code><\/p>\n\n\n\n<p>Y eso ser\u00eda todo, para que el binario se ejecute por solo cada 15 minutos y que el bot me notifique si la IP publica ha cambiado.<\/p>\n\n\n\n<p>Recuerda aplicar los permisos de ejecucion al binario:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>chmod +x goIp<\/code><\/pre>\n\n\n\n<p>Tambien se puede configurar el crontab en modo root por si no funciona con un usuario sin privilegios.<\/p>\n\n\n\n<p><a href=\"https:\/\/github.com\/genitalico\/goIp\" target=\"_blank\" rel=\"noopener\" title=\"\">El c\u00f3digo pueden clonarlo desde este repositorio.<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Script en Golang para obtener la IP publica de tu equipo mediante un servicio rest p\u00fablico y enviarla por medio de un bot de telegram a tu usuario telegram<\/p>\n","protected":false},"author":1,"featured_media":369,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[80,20,66,65,31],"tags":[36,81,82,73,83,5],"class_list":["post-548","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-golang","category-lenguajes-de-programacion","category-linux","category-sistemas-operativos","category-telegram","tag-bot","tag-go","tag-golang","tag-linux","tag-raspberrypi","tag-telegram"],"jetpack_featured_media_url":"https:\/\/80bits.blog\/wp-content\/uploads\/2022\/12\/pexels-photo-5935788.jpeg","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/548","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/comments?post=548"}],"version-history":[{"count":75,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/548\/revisions"}],"predecessor-version":[{"id":639,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/posts\/548\/revisions\/639"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media\/369"}],"wp:attachment":[{"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/media?parent=548"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/categories?post=548"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/80bits.blog\/index.php\/wp-json\/wp\/v2\/tags?post=548"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}