|
Ogni qual volta ci sia la necessità di scambiare dati tra il kernel e lo spazio utente si può pensare di definire una chiamata di sistema ad hoc, questo però genera non poche difficoltà poiché le chiamate di sistema dovrebbero assolvere a compiti molto generali (si pensi ad esempio alla read() o alla write()) e non dovrebbero servire per soddisfare compiti specifici.
Una soluzione a questo problema viene proposta dall'RFC3549 introducendo i «Linux Netlink as an IP Services Protocol».
Cosa sono
I netlink sono dei particolari tipi di socket che permettono di scambiare dati tra applicazioni e il kernel, ad esempio possono servire per scambiare dati tra un processo e il sottosistema di networking di Linux per controllarne lo stato ed impostarne certi parametri di funzionamento.
I netlink possono però anche essere usati per scambiare dati tra processi.
Col passare del tempo i netlink sono diventati molto popolari all'interno di Linux e ne sono nate diverse tipologie (o famiglie), ognuna delle quali per assolvere ad un ben determinato scopo. Questo ha fatto ha causato la nascita di un gran numero di tipi diversi di socket netlink all'interno del kernel il che (intorno al kernel 2.6.19) ha portato alla definizione di una famiglia generica (generic netlink) per assolvere a tutti quei compiti particolari e di poco interesse generale.
Una applicazione che fa largo uso dei netlink per comunicare con il kernel è iproute2. In questo articolo ci soffermeremo sull'uso dei socket AF_NETLINK per comunicare con il kernel e per gestire/monitorare il sottosistema di rete.
Come si usano
Utilizzare i netlink è abbastanza facile, basta aprire un socket nel domain AF_NETLINK e quindi specificare a quale famiglia siamo interessati. Le pagine di man di socket() dicono:
NAME socket - create an endpoint for communication
SYNOPSIS #include <sys/types.h> /* See NOTES */ #include <sys/socket.h>
int socket(int domain, int type, int protocol);
DESCRIPTION socket() creates an endpoint for communication and returns a descrip- tor.
The domain argument specifies a communication domain; this selects the protocol family which will be used for communication. These families are defined in <sys/socket.h>. The currently understood formats include:
Name Purpose Man page AF_UNIX, AF_LOCAL Local communication unix(7) AF_INET IPv4 Internet protocols ip(7) AF_INET6 IPv6 Internet protocols ipv6(7) AF_IPX IPX - Novell protocols AF_NETLINK Kernel user interface device netlink(7) AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7) AF_AX25 Amateur radio AX.25 protocol AF_ATMPVC Access to raw ATM PVCs AF_APPLETALK Appletalk ddp(7) AF_PACKET Low level packet interface packet(7)
Quindi dalle pagine di netlink(7) si legge:
NAME netlink - Communication between kernel and userspace (AF_NETLINK)
SYNOPSIS #include <asm/types.h> #include <sys/socket.h> #include <linux/netlink.h>
netlink_socket = socket(AF_NETLINK, socket_type, netlink_family);
DESCRIPTION Netlink is used to transfer information between kernel and userspace processes. It consists of a standard sockets-based interface for userspace processes and an internal kernel API for kernel modules. The internal kernel interface is not documented in this manual page. There is also an obsolete netlink interface via netlink character devices; this interface is not documented here and is only provided for back- wards compatibility.
Netlink is a datagram-oriented service. Both SOCK_RAW and SOCK_DGRAM are valid values for socket_type. However, the netlink protocol does not distinguish between datagram and raw sockets.
netlink_family selects the kernel module or netlink group to communi- cate with. The currently assigned netlink families are:
NETLINK_ROUTE Receives routing and link updates and may be used to modify the routing tables (both IPv4 and IPv6), IP addresses, link parame- ters, neighbor setups, queueing disciplines, traffic classes and packet classifiers (see rtnetlink(7)).
...
Ad esempio, per aprire un canale dove ottenere tutti gli eventi relativi alla creazione, cancellazione, attivazione e disattivazione di una interfaccia di rete nonché gli eventi relativi alla creazione e cancellazione di indirizzi IP si può fare:
struct sockaddr_nl sa;
memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); bind(sock, (struct sockaddr *) &sa, sizeof(sa));
I valori possibili per nl_groups sono riportati nel file linux/include/linux/rtnetlink.h.
A questo punto per inviare un messaggio al kernel ( individuato dal pid 0) si può fare:
struct nlmsghdr *nh; /* The nlmsghdr with payload to send. */ struct sockaddr_nl sa; struct iovec iov = { (void *) nh, nh->nlmsg_len }; struct msghdr msg = { (void *) &sa, sizeof(sa), &iov, 1, NULL, 0, 0 }; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; nh->nlmsg_pid = 0; nh->nlmsg_seq = ++sequence_number;
/* Request an ack from kernel by setting NLM_F_ACK. */ nh->nlmsg_flags |= NLM_F_ACK;
sendmsg(sock, &msg, 0);
mentre per la ricezione si ha:
int len; char buf[4096]; struct iovec iov = { buf, sizeof(buf) }; struct sockaddr_nl sa; struct nlmsghdr *nh; struct msghdr msg = { (void *) &sa, sizeof(sa), &iov, 1, NULL, 0, 0 };
len = recvmsg(sock, &msg, 0); for (nh = (struct nlmsghdr *) buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT (nh, len)) { /* The end of multipart message. */ if (nh->nlmsg_type == NLMSG_DONE) return;
if (nh->nlmsg_type == NLMSG_ERROR) /* Do some error handling. */ ...
/* Continue with parsing payload. */ ... }
Ma facciamo un esempio pratico: supponiamo di voler monitorare lo stato degli indirizzi di rete definiiti sulle interfaccie di rete del nostro sistema; sapere quindi, ad esempio, quando ad una interfaccia di rete viene cambiato l'indirizzo IP.
Riprendendo quanto appena detto si ha allora che una possibile soluzione potrebbe essere la seguente:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <asm/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <linux/netlink.h> #include <linux/rtnetlink.h> #include <net/if.h>
int main(int argc, char *argv[]) { int sock, len; char buf[4096]; struct iovec iov = { buf, sizeof(buf) }; struct sockaddr_nl sa; struct msghdr msg = { (void *) &sa, sizeof(sa), &iov, 1, NULL, 0, 0 }; struct nlmsghdr *nlh = (struct nlmsghdr *) buf; struct nlmsghdr *nh; struct ifaddrmsg *ifa; struct rtattr *rth; int rtl; char name[IFNAMSIZ]; unsigned int ipaddr;
memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_groups = RTMGRP_IPV4_IFADDR;
sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); bind(sock, (struct sockaddr *) &sa, sizeof(sa));
printf("---\n"); while (1) { len = recvmsg(sock, &msg, 0); if (len < 0) { perror("recvmsg"); exit(EXIT_FAILURE); }
for (nh = (struct nlmsghdr *) buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT (nh, len)) { /* The end of multipart message. */ if (nh->nlmsg_type == NLMSG_DONE) break;
if (nh->nlmsg_type == NLMSG_ERROR) { fprintf(stderr, "netlink error!"); break; }
printf("new event arrived\n");
ifa = (struct ifaddrmsg *) NLMSG_DATA(nlh); rth = IFA_RTA(ifa); rtl = IFA_PAYLOAD(nlh); for ( ;rtl && RTA_OK(rth, rtl); rth = RTA_NEXT(rth,rtl)) { if (rth->rta_type != IFA_LOCAL) continue; ipaddr = *((unsigned int *) RTA_DATA(rth)); ipaddr = htonl(ipaddr);
printf("%s is now %X rta_type: %d\n", if_indextoname(ifa->ifa_index,name), ipaddr, rth->rta_type); } } }
return 0; }
Una volta salvato nel file netlink.c, compilato ed eseguito si ha:
$ ./netlink ---
Il processo inizializza il canale di comunicazione come descritto prima e quindi si mette in attesa di dati. Se ora andiamo a modificare l'indirizzo IP di un interfaccia di rete del sistema con i comandi:
# ifconfig wlan0 192.168.1.1 # ifconfig wlan0 192.168.1.2 # ifconfig wlan0 192.168.1.3
dal nostro programma si ottiene:
new event arrived wlan0 is now C0A80104 rta_type: 2 new event arrived wlan0 is now C0A80101 rta_type: 2 new event arrived wlan0 is now C0A80101 rta_type: 2 new event arrived wlan0 is now C0A80102 rta_type: 2 new event arrived wlan0 is now C0A80102 rta_type: 2 new event arrived wlan0 is now C0A80103 rta_type: 2
Questa dispensa del corso Programmazione Linux è opera di Rodolfo Giometti (Copyright © 2010) ed è rilasciata dall'autore «as is» (così com'è) e distribuita sotto licenza Creative Commons Attribuzione – Condividi allo stesso modo 2.5 Italia.
|