...caras de conectividad avanzada... |
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <ifaddrs.h> #include <net/if.h> #include <sys/ioctl.h> #include <linux/ethtool.h> #include <linux/sockios.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int sock; // test interfaces // // get de todos los network devices configurados struct ifaddrs *addrs; getifaddrs(&addrs); // loop sobre los network devices configurados int i_alr = 0; struct ifaddrs *tmpaddrs = addrs; while (tmpaddrs) { // test interface if (tmpaddrs->ifa_addr && tmpaddrs->ifa_addr->sa_family == AF_PACKET) { // abre un socket para el test if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { // enseña error y continua printf("test interfaces: error socket para la interface %s: %s\n", tmpaddrs->ifa_name, strerror(errno)); continue; } // prepara los dati para ioctl() struct ethtool_value edata; edata.cmd = ETHTOOL_GLINK; struct ifreq ifr; strncpy(ifr.ifr_name, tmpaddrs->ifa_name, sizeof(ifr.ifr_name) - 1); ifr.ifr_data = (char *)&edata; // esegue ioctl() if (ioctl(sock, SIOCETHTOOL, &ifr) == -1) { // error ioctl: cierra el socket y continua printf("test interfaces: error ioctl para la interface %s: %s\n", tmpaddrs->ifa_name, strerror(errno)); close(sock); continue; } // enseña los resultados y cierra el socket printf("test interfaces: interface %s: %s\n", tmpaddrs->ifa_name, edata.data ? "OK" : "NOK"); close(sock); } // pasa al proximo device tmpaddrs = tmpaddrs->ifa_next; } // libera la devices list freeifaddrs(addrs); // test conectividad // // abre un socket para el test if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { // error socket printf("test de conectividad: socket error: %s\n", strerror(errno)); return EXIT_FAILURE; } // set de un timeout para send() (en este caso en realidad lo usa connect()) para evitar // un bloqueo sobre error de conexion) struct timeval tv; tv.tv_sec = 1; // set timeout en segundos tv.tv_usec = 0; // set timeout en usegundos if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv)) < 0) { // error ioctl printf("test de conectividad: setsockopt error: %s\n", strerror(errno)); close(sock); return EXIT_FAILURE; } // NOTE: alternativa (non portable) a el uso de SO_SNDTIMEO: //int syn_retries = 1; // send de un total de 1 SYN packets => timeout ~2s //if (setsockopt(sock, IPPROTO_TCP, TCP_SYNCNT, &syn_retries, sizeof(syn_retries)) < 0) { // ... // prepara la estructura sockaddr_in para el server remoto struct sockaddr_in server; // server (remoto) socket info memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; // set familia direcciones server.sin_addr.s_addr = inet_addr("216.58.214.174"); // set direccion server server.sin_port = htons(80); // set numero port server // conexion al server remoto if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) { // error connect printf("test de conectividad: error connect: %s\n", strerror(errno)); close(sock); return EXIT_FAILURE; } // enseña los resultados ed esce printf("test de conectividad: conectividad OK\n"); close(sock); return EXIT_SUCCESS; }una premisa: este código está destinado a aplicaciones Linux Embedded, por lo que todo lo que sigue se refiere a un entorno Linux (bueno, como siempre, y es inútil explicar otra vez el tema...). El código es ampliamente comentado, así que no tengo que escribir demasiadas explicaciones. En este ejemplo, el código de test se escribe directamente en el main() pero, en un proyecto real, debería transformarse en una función que se puede llamar periódicamente en la posición más adecuada, tal vez directamente en el main() de la aplicación. Dado el tipo de prueba que se realiza, se debe llamar ocasionalmente, en función de cuanto de inmediata debe ser la señal de alarma que necesitamos. Normalmente, el uso es de baja frecuencia (hablamos de segundos, no de milisegundos), de lo contrario, nuestra función consumiría mucho tiempo de CPU solo para verificar la conectividad (y este no parece ser el caso).
el test se lleva a cabo en dos fases: test de las interfaces de red y test de conectividad. La primera comprueba eventuales problemas, digamos, Hardware: si tengo una conexión WiFi y una Ethernet en la misma máquina podría tener una conexión a Internet incluso si, por ejemplo, el cable de red está desconectado, y me interesa informar de esta situación, por lo que un único test de conexión no sería suficiente. La segunda fase verifica la conectividad real y, en caso de que falte, podemos saber si no hay conectividad a pesar de que las interfaces de red están bien conectadas, así de aislar mejor las posibles causas (en resumen, es un test bastante completo, pero se puede sofisticar aun más).
La fase de test de las interfaces se realiza con una función (relativamente nueva) de la siempre indispensable (en casos como este) ioctl(). Gracias a la función SIOCETHTOOL podemos verificar el funcionamiento de bajo nivel de cada interfaz, ya que nuestro código incluye un loop con el que analizamos todas las interfaces que, normalmente, son dos (loopback y Ethernet) y, a veces, más (WiFi, otra tarjeta de red, etc.). La interfaz de loopback está siempre presente y, si no queremos probarla, se puede omitir haciendo un simple test sobre el nombre (que siempre es "lo", pero en cualquier caso, el nombre puede verificarse con antelación, para evitar malentendidos, en el file /etc/network/interfaces).
El test de conectividad se basa, en vez, en una simple conexión de tipo Client a una dirección "segura": En el ejemplo he usado IP y Port de google.com, pero se puede usar la que parece más apropiada, por ejemplo, para un dispositivo conectado solo a la red local, se puede usar la conexión a un servidor en la red, o bien se puede conectar a la dirección del gateway local, etc. También depende de si lo que tenemos que probar es conectividad a Internet o una simple conectividad local. Tener en cuenta que todo funciona usando la system call connect(), que implementa el envío de datos y la recepción de una respuesta, por lo que es un test más que suficiente (sin recurrir a un mucho más complicado ping). Ya que connect() se bloquea durante mucho tiempo cuando no recibe una respuesta inmediata, he añadido un oportuno timeout (leer el comentario en el código) utilizando setcsockopt() + SO_SNDTIMEO, pero (leer el otro comentario) también se puede usar el flag TCP_SYNCNT, que es, pero, una solución menos ortodoxa y menos portátil.
En un normal PC (en lugar de un sistema embedded) con una conexión a Internet a través de Ethernet, el resultado en condiciones normales es el siguiente:
aldo@mylinux:~/blogtest$ ./conntest test interfaces: interface lo: OK test interfaces: interface enp3s0: OK test de conectividad: conectividad OKy, si forzamos la desconexión del Software (utilizando, por ejemplo, el NetworkManager de Linux) el resultado es:
aldo@mylinux:~/blogtest$ ./conntest test interfaces: interface lo: OK test interfaces: interface enp3s0: OK test de conectividad: error connect: Network is unreachablemientras que si forzamos la desconexión Hardware (desconectando el cable de red) el resultado es:
aldo@mylinux:~/blogtest$ ./conntest test interfaces: interface lo: OK test interfaces: interface enp3s0: NOK test de conectividad: error connect: Network is unreachableNo está mal, ¿verdad? Una función simple simple pero muy útil. ¡Y por hoy ya està, misión cumplida!
¡Hasta el próximo post!