Kernel Korner - Come e perchè usare i Netlink Socket

SysAdmin

Da utilizzare in modo bidirezionale, un metodo versatile per passare dati tra il kernel e l'user-space.

A causa della complessità dello svilupo e del mantenimento del kernel, vengono inseriti nel kernel solo i codici più essenziali e quelli critici delle prestazioni. Le altre cose, tipo GUI, gestione e controllo del codice, di solito sono programmate come applicazioni user-space. Questa pratica di dividere l'implementazione di certe caratteristiche tra kernel e user-space è del tutto comune in Linux. Adesso la domanda è come possono il codice del kernel e il codice in user-space comunicare tra di loro ?

La risposta è nei vari metodi IPC che esistono tra il kernel e l'user-space, come le system-call, ioctl, il filesystem /proc o i socket netlink. Questo articolo discute dei socket netlink e rivela i suoi vantaggi come una caratteristica IPC di rete facile da usare.

Introduzione

Il socket netlink è uno speciale IPC usato per trasferire informazioni tra i processi del kernel e quelli dell'user-space. Fornisce un collegamento di comunicazione full-duplex tra i due attraverso lo standard socket API per i processi in user-space e una speciale API del kernel per i suoi moduli. Il socket netlink usa l'indirizzo della famiglia AF_NETLINK, come paragonato a AF_INET usato dal socket TCP/IP. Ogni caratteristica socket netlink definisce i suoi propri tipi di protocolli nel file header del kernel include/linux/netlink.h.

Il seguente è un sottoinsieme di caratteristiche e di loro tipi di protocolli attualmente supportate dai socket netlink:

  • NETLINK_ROUTE: canale di comunicazione tra i demoni di routing dell'user-space, come BGP, OSPF, RIP e il modulo di packet forwarding del kernel. I demoni di routing in user-space aggiornano la tabella di routing del kernel attraverso questo tipo di protocollo netlink.

  • NETLINK_FIREWALL: riceve paccetti inviati dal codice firewall IPv4.

  • NETLINK_NFLOG: canale di comunicazione per il tool di gestione iptable in user-space e i moduli netfilter nel kernel-space.

  • NETLINK_ARPD: per la gestione della tabella arp dall'user-space.

Perchè le suddette caratteristiche usano netlink invece delle system-call, ioctl o il filesystem /proc per le comunicazioni tra i mondi user e kernel ? Non è un lavoro di poco conto aggiungere system-call, ioctl o file in /proc per nuove caratteristiche; si rischia di inquinare il kernel e danneggiare la stabilità del sistema. Il socket netlink è semplice, anche se, occorre soltanto che sia aggiunta a netlink.h una costante, il tipo di protocollo. Poi, il modulo del kernel e l'applicazione possono comunicare immediatamente usando la API socket-style.

Netlink è asincrono perchè, come con ogni altra API socket, fornisce una coda socket per livellare lo scaturire dei messaggi. La system-call per l'invio di un messaggio netlink accoda il messaggio nella coda del netlink ricevente e dopo chiama il gestore delle ricezioni del ricevente. Il ricevente, nel contesto del gestore delle ricezioni , può decidere se processare il messaggio immediatamente o lasciare il messaggio nella coda e processarlo più tardi in un contesto differente. Diversamente da netlink, le system-call richiedono l'elaborazione sincrona. Perciò, se si utilizza una system-call per passare un messaggio dall'user space al kernel, la granularità della schedulazione del kernel può risentirne se il tempo di elaborazione di quel messaggio è lungo.

L'implementazione del codice di una system call nel kernel viene concatenata staticamente nel kernel all'atto della compilazione; perciò, non è appropriato includere il codice di system call in un modulo caricabile, quale è il caso della maggior parte dei driver dei dispositivi. Con il socket netlink, non esiste dipendenza dal momento della compilazione tra il netlink core del kernel di Linux e l'applicazione netlink che stà nei moduli caricabili del kernel.

Socket netlink supporta il multicast, il quale è un ulteriore beneficio sulle system call, ioctl e /proc. Un processo può inviare un messaggio multicast a un gruppo di indirizzi netlink, e ogni numero di altri processi può ascoltare quel gruppo di indirizzi. Questo fornisce un meccanismo quasi perfetto per ogni invio dal kernel all'user space.

Le system-call e ioctl sono semplici IPC nel senso che una sessione per questi IPC può essere iniziata solo da applicazioni user-space. Ma, cosa accade se un modulo del kernel ha un messaggio urgente per un'applicazione user-space ? Non c'è modo di farlo direttamente usando questi IPC. Di solito, le applicazioni periodicamente hanno bisogno di interrogare il kernel per recepire le modifiche dello stato, sebbene le interrogazioni intensive siano dispendiose. Netlink risolve questo problema in modo elegante permettendo anche al kernel di iniziare le sessioni. Questa viene chiamata la caratteristica duplex del socket netlink.

Infine, il socket netlink fornisce una API socket stile BSD che è ben compresa dalla comunità di sviluppo del software. Perciò, i costi di addestramento sono minori se comparati all'utilizzo delle altre criptiche system call API e ioctl.

Relativamente al Socket Routing BSD

Nell'implementazione dello stack TCP/IP BSD, c'è un socket speciale chiamato il routing socket. Esso ha una famiglia di indirizzo di AF_ROUTE, una famiglia di protocollo di PF_ROUTE e un tipo di socket di SOCK_RAW. Il routing socket in BSD è usato dai processi per aggiungere o cancellare route nella tabella di routing del kernel.

In Linux, l'equivalente funzione del routing socket viene fornita dal tipo di protocollo socket netlink NETLINK_ROUTE. Il socket netlink fornisce una funzionalità di super-insieme del routing socket del BSD.

Le API del socket netlink

Le API socket standard - socket(), sendmsg(), recvmsg() e close() - possono essere usate dalle applicazioni in user-space per accedere al socket netlink. Consultare le pagine di manuale per le definizioni dettagliate di queste API. Qui, si parla di come scegliere i parametri per queste API solo nel contesto dei socket netlink. Le API dovrebbero essere familiari a chiunque abbia scritto un'applicazione di rete ordinaria usando i socket TCP/IP.

Per creare un socket con socket(), immettere:

int socket(int domain, int type, int protocol)



Il dominio socket (famiglia di indirizzo) è AF_NETLINK, e il tipo di socket è o SOCK_RAW o SOCK_DGRAM, perchè netlink è un servizio datagram-oriented.

Il protocollo (tipo protocollo) seleziona quale caratteristica di netlink viene usata dal socket. I seguenti sono alcuni dei tipi di protocollo predefiniti di netlink: NETLINK_ROUTE, NETLINK_FIREWALL, NETLINK_ARPD, NETLINK_ROUTE6 e NETLINK_IP6_FW. Si può anche aggiungere facilmente il proprio tipo di protocollo di netlink.

Possono essere definiti fino a 32 gruppi multicast per ogni tipo di protocollo di netlink. Ogni gruppo multicast è rappresentato da un bit mask, 1<<i, dove 0<=i<=31. Questo è estremamente utile quando un gruppo di processi e il processo coordinato dal kernel per implementare la stessa caratteristica di invio di messaggi netlink multicast può ridurre il numero di chiamate di sistema usate e alleviare le applicazioni dal carico della gestione dei membri del gruppo multicast.

bind()

Come per un socket TCP/IP, l'API netlink bind() associa un indirizzo di socket locale (sorgente) con il socket aperto. La struttura dell'indirizzo del netlink è come segue:

struct sockaddr_nl
{
  sa_family_t    nl_family;  /* AF_NETLINK   */
  unsigned short nl_pad;     /* zero         */
  __u32          nl_pid;     /* process pid */
  __u32          nl_groups;  /* mcast groups mask */
} nladdr;



Quando usato con bind(), il campo nl_pid del sockaddr_nl può essere completato con il pid del processo chiamante. Il nl_pid serve qui come indirizzo locale di questo socket netlink. L'applicazione è responsabile di scegliere un intero univoco di 32-bit per immetterlo nel nl_pid:

NL_PID Formula 1:  nl_pid = getpid();



La Formula 1 usa l'ID del processo dell'applicazione come nl_pid, il quale è una scelta naturale se, per il dato tipo di protocollo di netlink, occorre solo un socket netlink per il processo.

Negli scenari dove differenti thread dello stesso processo vogliono avere differenti socket netlink aperti sotto lo stesso protocollo netlink, può essere usata la Formula 2 per generare il nl_pid:


NL_PID Formula 2: pthread_self() << 16 | getpid();



In questo modo, ciascuno dei differenti pthread dello stesso processo piò avere il suo proprio socket netlink per lo stesso tipo di protocoloo di netlink. In fatti, anche dentro un singolo pthread è possibile creare multipli socket netlink per lo stesso tipo di protocollo. Gli sviluppatori hanno bisogno di essere più creativi nella generazione di un unico nl_pid, non si considera essere un caso di normale utilizzo.

Se l'applicazione vuole ricevere i messaggi netlink del tipo di protocollo che sono destinati per certi gruppi multicast, le bitmask di tutti i gruppi multicast interessati dovrebbero essere ORed insieme per formare il il campo nl_groups di sockaddr_nl. Altrimenti, nl_groups dovrebbe essere messo a zero così che l'applicazione receva solo il messaggio netlink unicast del tipo di protocollo destinato per l'applicazione. Dopo aver impostato il nladdr, eseguire il bind come segue:


bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr));



Invio di un messaggio Netlink

Al fine di inviare un messaggio netlink al kernel o ad altri processi user-space, occorre che sia fornita un'altra struct sockaddr_nl nladdr come indirizzo di destinazione, lo stesso come l'invio di un pacchetto UDP con sendmsg(). Se il messaggio è destinato al kernel, entrambi nl_pid e nl_groups dovrebbero essere forniti a 0.

Se il messaggio è un messaggio unicast destinato a un'altro processo, l'nl_pid è il pid dell'altro processo e nl_groups è 0, assumendo che sia usato nlpid Formula 1 nel sistema.

Se il messaggio è un messaggio multicast destinato a uno o più gruppi multicast, le bitmask di tutti i gruppi multicast di destinazione dovrebbero essere ORed insieme per formare il campo nl_groups. Allora si può fornire l'indirizzo netlink alla struct msghdr msg per l'API sendmsg(), come segue:


struct msghdr msg;
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);



Il socket netlink richiede anche il proprio header del messaggio. Questo per fornire un terreno comune per i messaggi netlink di tutti i tipi di protocollo.

Poichè il core netlink del kernel Linux assume l'esistenza del seguente header in ogni messaggio netlink , un'applicazione deve fornire questo header in ogni messaggio netlink che trasmette:


struct nlmsghdr
{
  __u32 nlmsg_len;   /* Length of message */
  __u16 nlmsg_type;  /* Message type*/
  __u16 nlmsg_flags; /* Additional flags */
  __u32 nlmsg_seq;   /* Sequence number */
  __u32 nlmsg_pid;   /* Sending process PID */
};



nlmsg_len deve essere valorizzato con la lunghezza totale del messaggio netlink, includendo l'header, e è richiesto da netlink core. nlmsg_type può essere usato delle applicazioni ed è un valore incomprensibile a netlink core. nlmsg_flags è usato per dare un controllo aggiuntivo a un messaggio; viene letto e aggiornato da netlink core. nlmsg_seq e nlmsg_pid sono usati dalle applicazioni per tracciare il messaggio, e sono incomprensibili anche a netlink core.

Un messaggio netlink consiste così di nlmsghdr e del messaggio payload. Una volta che il messaggio è stato inserito, esso accede a un buffer puntato dal puntatore nlh. Si può inviare il mesaggio anche alla struct msghdr msg:


struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;



Dopo i passi di sopra, una chiamata a sendmsg() emette il messaggio netlink:


sendmsg(fd, &msg, 0);



Ricezione di un messaggio Netlink

Una applicazione ricevente necessita di allocare un buffer grande a sufficenza per tenere gli header del messaggio netlink e i messaggi payload. Essa poi valorizza la struct msghdr msg come mostrato di sotto e usa lo standard recvmsg() per ricevere il messaggio netlink, supponendo che il buffer sia puntato da nlh:


struct sockaddr_nl nladdr;
struct msghdr msg;
struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = MAX_NL_MSG_LEN;
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
recvmsg(fd, &msg, 0);



Dopo che il messaggio è stato ricevuto corretamente, nlh dovrebbe puntare all'header del messaggio netlink appena ricevuto. nladdr dovrebbe contenere l'indirizzo di destinazione del messaggio ricevuto, il quale consiste del pid e dei gruppi multicast ai quali il messaggio è inviato. E, la macro NLMSG_DATA(nlh), definita in netlink.h, ritorna un puntatore al payload del messaggio netlink. Una chiamata a close(fd) chiude il socket netlink identificato dal file descriptor fd.

Le API Netlink in Kernel-Space

L'API netlink in kernel-space è supportata dal netlink core nel kernel, net/core/af_netlink.c. Dalla parte del kernel, l'API è diversa dall'API in user-space. L'API può essere usata dai moduli del kernel per accedere al socket netlink e per comunicare con le applicazioni in user-space. A meno chè si possa modificare l'esistente tipo di protocollo del socket netlink, occorre aggiungere il proprio tipo di protocollo aggiungendo una constante a netlink.h. Per esempio, è possibile aggiungere un tipo di protocollo netlink per testare obiettivi inserendo questa riga in netlink.h:

#define NETLINK_TEST  17



Dopodichè, si può fare riferimento al tipo di protocollo aggiunto in qualiasi parte nel kernel Linux.

In user space, si chiama socket() per creare un socket netlink, ma in kernel space, si chiama l'API seguente:


struct sock *
netlink_kernel_create(int unit, 
           void (*input)(struct sock *sk, int len));



Il parametro unit è, di fatto, il tipo di protocollo netlink, come NETLINK_TEST. La funzione pointer, input, è una funzione di callback invocata quando un messaggio arriva a questo socket netlink.

Dopo che il kernel ha creato un socket netlink per il protocollo NETLINK_TEST, ogni volta che l'user space invia un messaggio netlink del tipo di protocollo NETLINK_TEST al kernel, viene invocata la funzione di callback, input(), la quale è registrata da netlink_kernel_create(). Il seguente è un'implementazione di esempio della funzione di callback input:


void input (struct sock *sk, int len)
{
 struct sk_buff *skb;
 struct nlmsghdr *nlh = NULL;
 u8 *payload = NULL;
 while ((skb = skb_dequeue(&sk->receive_queue)) 
       != NULL) {
 /* process netlink message pointed by skb->data */
 nlh = (struct nlmsghdr *)skb->data;
 payload = NLMSG_DATA(nlh);
 /* process netlink message with header pointed by 
  * nlh and payload pointed by payload
  */
 }   
}



Questa funzione input() viene chiamata nel contesto della system-call sendmsg() invocata dal processo di invio. E' giusto per processare il messaggio netlink dentro input() se è veloce. Quando l'elaborazione del messaggio netlink impiega molto tempo, comunque, si può voler tenerlo fuori da input() per evitare di bloccare le altre system call dall'entrare nel kernel. Invece, si può usare un thread del kernel dedicato per eseguire i seguenti step indefinitamente. Usare skb = skb_recv_datagram(nl_sk) dove nl_sk è il socket netlink ritornato dal netlink_kernel_create(). Poi, processare il messaggio netlink puntato da skb->data.

Questo thread del kernel è dormiente quando non ci sono messaggi netlink in nl_sk. Così, dentro la funzione callback input(), occorre solo attivare il thread del kernel dormiente, come questo:


void input (struct sock *sk, int len)
{
  wake_up_interruptible(sk->sleep);
}



Questo è un modello di comunicazione più scalabile tra l'user space e il kernel. Inoltre migliora la granularità negli switch del contesto.

Invio di messaggi Netlink dal Kernel

Proprio come in user space, l'indirizzo sorgente del netlink e l'indirizzo di destinazione del netlink necessitano di essere impostati quando si invia un messaggio netlink. Presupponendo che il buffer del socket tenga il messaggio netlink per essere inviato sia struct sk_buff *skb, l'indirizzo locale può essere impostato con:


NETLINK_CB(skb).groups = local_groups;
NETLINK_CB(skb).pid = 0;   /* from kernel */



L'indirizzo di destinazione può essere impostato come questo:


NETLINK_CB(skb).dst_groups = dst_groups;
NETLINK_CB(skb).dst_pid = dst_pid;



Tale informazione non è memorizzata in skb->data. Invece, è memorizzata nel blocco di controllo di netlink del buffer del socket, skb.

Per inviare un messaggio unicast, usare:


int 
netlink_unicast(struct sock *ssk, struct sk_buff 
                *skb, u32 pid, int nonblock);



dove ssk è il socket netlink ritornato dal netlink_kernel_create(), skb->data punta al messaggio netlink che deve essere inviato e pid è il pid dell'applicazione ricevente, presupponendo che sia usata la Formula 1 NLPID. nonblock indica se l'API si dovrebbe bloccare quando non è disponibile la ricezione del buffer o ritonare immediatamente un errore.

Si può anche inviare un messaggio multicast. La seguente API consegna un messaggio netlink a entrambi i processi specificati dal pid e dai gruppi multicast specificati da group:


void 
netlink_broadcast(struct sock *ssk, struct sk_buff 
         *skb, u32 pid, u32 group, int allocation);



group è la bitmask ORed di tutti i gruppi multicast riceventi. allocation è il tipo di allocazione di memoria del kernel. Di solito, è usata GFP_ATOMIC se dal contesto dell'interrupt; GFP_KERNEL se dal resto. Questo è dovuto al fatto che l'API può aver bisogno di allocare uno o molti buffer socket per clonare il messagio multicast.

Chiusura di un socket Netlink dal Kernel

Data la struct sock *nl_sk ritornata dal netlink_kernel_create(), si può chiamare la seguente API del kernel per chiudere il socket netlink nel kernel:


sock_release(nl_sk->socket);



Finora, si è mostrato solo la struttura minima di codice per illustrare il concetto di programmazione netlink. Adesso si userà il tipo di protocollo netlink NETLINK_TEST e si presupporrà di averlo già aggiunto al file header del kernel. Il codice del modulo del kernel elencato qui contiene solo la parte rilevante di netlink, così esso dovrebbe essere inserito in uno scheletro di modulo del kernel, il quale può essere trovato da molti altri sorgenti di riferimento.

Comunicazioni Unicast tra Kernel e Applicazioni

In questo esempio, un processo in user-space invia un messaggio netlink ad un modulo del kernel, e il modulo del kernel ripete il messaggio all'indietro al processo che lo ha trasmesso. Ecco il codice user-space:


#include <sys/socket.h>
#include <linux/netlink.h>
#define MAX_PAYLOAD 1024  /* maximum payload size*/
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
void main() {
 sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST);
 memset(&src_addr, 0, sizeof(src_addr));
 src__addr.nl_family = AF_NETLINK;      
 src_addr.nl_pid = getpid();  /* self pid */
 src_addr.nl_groups = 0;  /* not in mcast groups */
 bind(sock_fd, (struct sockaddr*)&src_addr, 
      sizeof(src_addr));
 memset(&dest_addr, 0, sizeof(dest_addr));
 dest_addr.nl_family = AF_NETLINK;
 dest_addr.nl_pid = 0;   /* For Linux Kernel */
 dest_addr.nl_groups = 0; /* unicast */
 nlh=(struct nlmsghdr *)malloc(
                         NLMSG_SPACE(MAX_PAYLOAD));
 /* Fill the netlink message header */
 nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
 nlh->nlmsg_pid = getpid();  /* self pid */
 nlh->nlmsg_flags = 0;
 /* Fill in the netlink message payload */
 strcpy(NLMSG_DATA(nlh), "Hello you!");
 iov.iov_base = (void *)nlh;
 iov.iov_len = nlh->nlmsg_len;
 msg.msg_name = (void *)&dest_addr;
 msg.msg_namelen = sizeof(dest_addr);
 msg.msg_iov = &iov;
 msg.msg_iovlen = 1;
 sendmsg(fd, &msg, 0);
 /* Read message from kernel */
 memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
 recvmsg(fd, &msg, 0);
 printf(" Received message payload: %s\n", 
        NLMSG_DATA(nlh));
    
 /* Close Netlink Socket */
 close(sock_fd);
}    



E, ecco il codice kernel:


struct sock *nl_sk = NULL;
void nl_data_ready (struct sock *sk, int len)
{
  wake_up_interruptible(sk->sleep);
}
void netlink_test() {
 struct sk_buff *skb = NULL;
 struct nlmsghdr *nlh = NULL;
 int err;
 u32 pid;     
 nl_sk = netlink_kernel_create(NETLINK_TEST, 
                                   nl_data_ready);
 /* wait for message coming down from user-space */
 skb = skb_recv_datagram(nl_sk, 0, 0, &err);
 nlh = (struct nlmsghdr *)skb->data;
 printk("%s: received netlink message payload:%s\n", 
        __FUNCTION__, NLMSG_DATA(nlh));
 pid = nlh->nlmsg_pid; /*pid of sending process */
 NETLINK_CB(skb).groups = 0; /* not in mcast group */
 NETLINK_CB(skb).pid = 0;      /* from kernel */
 NETLINK_CB(skb).dst_pid = pid;
 NETLINK_CB(skb).dst_groups = 0;  /* unicast */
 netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
 sock_release(nl_sk->socket);
}    



Dopo aver caricato il modulo del kernel che esegue il codice kernel di sopra, quando si esegue l'eseguibile user-space, si dovrebbe vedere il seguente output dal programma user-space:

Ricevuto messaggio payload: Hello you!



E, il seguente messaggio dovrebbe apparire nell'output di dmesg:

netlink_test: ricevuto messaggio netlink payload: 
Hello you!



Comunicazione Multicast tra il Kernel e le Applicazioni

In questo esempio, due applicazioni user-space sono in ascolto dello stesso gruppo netlink multicast. Il modulo del kernel invia un messaggio attraverso il socket netlink al gruppo multicast, e tutte le applicazioni lo ricevono. Ecco il codice user-space:


#include <sys/socket.h>
#include <linux/netlink.h>
#define MAX_PAYLOAD 1024  /* maximum payload size*/
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
void main() {
 sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
 memset(&src_addr, 0, sizeof(local_addr));
 src_addr.nl_family = AF_NETLINK;       
 src_addr.nl_pid = getpid();  /* self pid */
 /* interested in group 1<<0 */  
 src_addr.nl_groups = 1;
 bind(sock_fd, (struct sockaddr*)&src_addr, 
      sizeof(src_addr));
 memset(&dest_addr, 0, sizeof(dest_addr)); 
 nlh = (struct nlmsghdr *)malloc(
                          NLMSG_SPACE(MAX_PAYLOAD));
 memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));      
    
 iov.iov_base = (void *)nlh;
 iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
 msg.msg_name = (void *)&dest_addr;
 msg.msg_namelen = sizeof(dest_addr);
 msg.msg_iov = &iov;
 msg.msg_iovlen = 1;
 printf("Waiting for message from kernel\n");
 /* Read message from kernel */
 recvmsg(fd, &msg, 0);
 printf(" Received message payload: %s\n", 
        NLMSG_DATA(nlh));
 close(sock_fd);
}    



E, ecco il codice kernel:


#define MAX_PAYLOAD 1024 
struct sock *nl_sk = NULL;
void netlink_test() {
 sturct sk_buff *skb = NULL;
 struct nlmsghdr *nlh;
 int err;
 nl_sk = netlink_kernel_create(NETLINK_TEST, 
                               nl_data_ready);
 skb=alloc_skb(NLMSG_SPACE(MAX_PAYLOAD),GFP_KERNEL);
 nlh = (struct nlmsghdr *)skb->data;
 nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
 nlh->nlmsg_pid = 0;  /* from kernel */
 nlh->nlmsg_flags = 0;
 strcpy(NLMSG_DATA(nlh), "Greeting from kernel!");
 /* sender is in group 1<<0 */
 NETLINK_CB(skb).groups = 1;
 NETLINK_CB(skb).pid = 0;  /* from kernel */
 NETLINK_CB(skb).dst_pid = 0;  /* multicast */
 /* to mcast group 1<<0 */
 NETLINK_CB(skb).dst_groups = 1;
 /*multicast the message to all listening processes*/
 netlink_broadcast(nl_sk, skb, 0, 1, GFP_KERNEL);
 sock_release(nl_sk->socket);
}    



Presupponendo che il codice user-space sia compilato dentro l'eseguibile nl_recv, si può eseguire due istanze di nl_recv:


./nl_recv &
In attesa del messaggio dal kernel
./nl_recv &
In attesa del messaggio dal kernel



Allora, dopo che si è caricato il modulo del kernel che esegue il codice kernel-space, entrambe le istanze di nl_recv dovrebbero ricevere il seguente messaggio:

Ricevuto messaggio payload: Saluti dal kernel!
Ricevuto messaggio payload: Saluti dal kernel!



Conclusione

Il socket Netlink è un'interfaccia flessibile per le comunicazioni tra le applicazioni user-space e i moduli del kernel. Essa fornisce un'API socket facile all'uso per entrambi applicazioni e kernel. Essa fornisce caratteristiche di comunicazione avanzate, come full-duplex, buffered I/O, comunicazioni multicast e asincrone, le quali sono assenti in altri IPC kernel/user-space.

Kevin Kaichuan He ( hek_u5@yahoo.com ) è uno dei principali ingegneri software alla Solustek Corp. Attualmente stà lavorando a sistemi embedded, driver di dispositivi e progetti di protocolli di rete. Le sue precedenti esperienze lavorative includono ingegnere software senior a Cisco Systems e assistente ricercatore a CS, Purdue University. Nel tempo libero, si diverte con la fotografia digitale, giochi PS2 e letteratura.

Traduzione a cura di sacarde@tiscali.it