...facce da connettività avanzata... |
#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 // // ottiene tutti i network devices configurati struct ifaddrs *addrs; getifaddrs(&addrs); // loop sui network devices configurati int i_alr = 0; struct ifaddrs *tmpaddrs = addrs; while (tmpaddrs) { // test interface if (tmpaddrs->ifa_addr && tmpaddrs->ifa_addr->sa_family == AF_PACKET) { // apre un socket per il test if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { // mostra errore e continua printf("test interfaces: errore socket per la interface %s: %s\n", tmpaddrs->ifa_name, strerror(errno)); continue; } // prepara i dati per 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) { // errore ioctl: chiude il socket e continua printf("test interfaces: errore ioctl per la interface %s: %s\n", tmpaddrs->ifa_name, strerror(errno)); close(sock); continue; } // mostra i risultati e chiude il socket printf("test interfaces: interface %s: %s\n", tmpaddrs->ifa_name, edata.data ? "OK" : "NOK"); close(sock); } // passa al prossimo device tmpaddrs = tmpaddrs->ifa_next; } // libera la devices list freeifaddrs(addrs); // test connettività // // apre un socket per il test if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { // errore socket printf("test di connettività: socket error: %s\n", strerror(errno)); return EXIT_FAILURE; } // set di un timeout per send() (in questo caso in realtà lo usa connect()) per // evitare un blocco su errore di connessione) struct timeval tv; tv.tv_sec = 1; // set timeout in secondi tv.tv_usec = 0; // set timeout in usecondi if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv)) < 0) { // errore ioctl printf("test di connettività: setsockopt error: %s\n", strerror(errno)); close(sock); return EXIT_FAILURE; } // NOTE: alternativa (non portabile) all'uso di SO_SNDTIMEO: //int syn_retries = 1; // send di un totale di 1 SYN packets => timeout ~2s //if (setsockopt(sock, IPPROTO_TCP, TCP_SYNCNT, &syn_retries, sizeof(syn_retries)) < 0) { // ... // prepara la struttura sockaddr_in per il server remoto struct sockaddr_in server; // server (remoto) socket info memset(&server, 0, sizeof(server)); server.sin_family = AF_INET; // set famiglia indirizzi server.sin_addr.s_addr = inet_addr("216.58.214.174"); // set indirizzo server server.sin_port = htons(80); // set numero port server // connessione al server remoto if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) { // errore connect printf("test di connettività: errore connect: %s\n", strerror(errno)); close(sock); return EXIT_FAILURE; } // mostra i risultati ed esce printf("test di connettività: connettività OK\n"); close(sock); return EXIT_SUCCESS; }una premessa: questo codice è pensato per applicazioni Linux Embedded, quindi tutto ciò che segue si riferisce a un intorno Linux (beh, come sempre, ed è inutile che mi dilunghi sull'argomento...). Il codice è ampiamente commentato, così non devo dilungarmi eccessivamente in spiegazioni. In questo esempio il codice di test è scritto direttamente nel main() ma, in un progetto reale, si dovrebbe trasformarlo in una funzione che si può chiamare, periodicamente, nella posizione più opportuna, magari direttamente nel main() della applicazione. Visto il tipo di test che si esegue si dovrebbe chiamare ogni tanto, in base a quanto deve essere immediata la segnalazione di allarme che necessitiamo. Normalmente l'uso è a bassa frequenza (parliamo di secondi, non di millisecondi), se no la nostra funzione si mangerebbe molto tempo di CPU solo per controllare la connettività (e non mi sembra il caso).
Il test avviene in due fasi: test delle interfacce di rete e test di connettività. La prima verifica eventuali problemi, diciamo, Hardware: se ho sulla stessa macchina una connessione WiFi e una Ethernet potrei avere connessione Internet anche se, per esempio, il cavo di rete è staccato, e a me interessa segnalare questa situazione, quindi il solo test di connessione non sarebbe sufficiente. La seconda fase verifica la connettività vera e propria e, nel caso che questa manchi, possiamo sapere se non c'è connettività nonostante che le interfacce di rete siano ben collegate, così isoliamo meglio le possibili cause (insomma, è già un test abbastanza completo, ma si può sofisticare a piacere).
La fase di test delle interfacce si esegue con una funzione (relativamente nuova) della sempre indispensabile (in casi come questo) ioctl(). Grazie alla funzione SIOCETHTOOL possiamo verificare il funzionamento a basso livello di ogni interfaccia, visto che il nostro codice include un loop con cui si analizzano tutte le interfacce che, normalmente, sono due (loopback e Ethernet) e a volte sono di più (il WiFi, un altra scheda di rete, ecc.). L'interfaccia loopback c'è sempre e, nel caso che non ci interessi testarla, si può saltare facendo un semplice test sul nome (che è sempre "lo", ma comunque il nome si può verificare previamente, a scanso di equivoci, nel file /etc/network/interfaces).
Il test di connettività si basa, invece, su una semplice connessione tipo Client verso una direzione "sicura": nell'esempio ho usato IP e Port di google.com, ma si può usare quella che ci sembra più opportuna, ad esempio per un dispositivo collegato solo in rete locale si può usare la connessione a un server della rete, oppure ci si può collegare all'indirizzo del gateway locale, ecc. Dipende anche se quello che necessitiamo testare è la connettività Internet o una semplice connettività locale. Notare che tutto ruota sulla system call connect(), che attua inviando dati e ricevendo una risposta, quindi è un test più che sufficiente (senza ricorrere a un ben più complicato ping). Visto che la connect() si blocca per molto tempo quando non riceve subito una risposta ho inserito un opportuno timeout (leggere il commento nel codice) usando setcsockopt() + SO_SNDTIMEO, ma (leggere l'altro commento) si poteva usare anche il flag TCP_SYNCNT, che però è una soluzione meno ortodossa e meno portabile.
Su un normale PC (invece che su un sistema embedded) con connessione Internet via Ethernet, il risultato in condizioni normali è il seguente:
aldo@mylinux:~/blogtest$ ./conntest test interfaces: interface lo: OK test interfaces: interface enp3s0: OK test di connettività: connettivitá OKe, se forziamo la disconnessione Software (usando, ad esempio, il NetworkManager di Linux) il risultato è:
aldo@mylinux:~/blogtest$ ./conntest test interfaces: interface lo: OK test interfaces: interface enp3s0: OK test di connettività: errore connect: Network is unreachablementre, se forziamo la disconnessione Hardware (staccando il cavo di rete) il risultato è:
aldo@mylinux:~/blogtest$ ./conntest test interfaces: interface lo: OK test interfaces: interface enp3s0: NOK test di connettività: errore connect: Network is unreachableMica male, no? Una funzione semplice semplice però molto utile. E per oggi può bastare, missione compiuta!
Ciao, e al prossimo post!
Nessun commento:
Posta un commento