Viele Wege führen in den Kernel. Bekannt sind vor allem die Systemcalls und von denen die Funktionen »open()«, »close()«, »read()«, »write()« und »ioctl()«, die alle Daten aufnehmen oder zurückliefern. Die Unix-Urväter haben dem Aufruf »ioctl()« die Rolle einer Universalschnittstelle zugedacht, die Ein- und Ausgabegeräte steuert. Das war mit den übrigen Systemaufrufen nur schwer abzubilden.
Professionelle Programmierer mögen die Ioctls jedoch nicht. Zum einen sind die Aufrufe nicht portabel, zum anderen fehlerträchtig. Nachlässige Programmierer dokumentieren nur selten das Verhalten der Funktion im fehlerfreien und noch seltener im fehlerbehafteten Fall. Anwendungsentwickler gleichen Schlages verweigern ihrerseits hartnäckig das Lesen der Dokumentation.
Seit Langem schon gibt es auch andere, weniger bekannte Pfade ins Innere von Linux: Das Netlink-Interface [1] hat seinen Ursprung im Netzwerk-Subsystem. Hier dient es dazu, dem Kernel Konfigurationsdaten - etwa neue Routen - zu übergeben. Der umgekehrte Weg funktioniert ebenso. Über dieses Interface teilt der Kernel interessierten Applikationen mit, welche Routen er geändert hat.
Es geht aber noch weiter: Netlink ist eine Methode der Interprozess-Kommunikation (IPC), mit der Applikationen Daten mit sich selbst oder dem Kernel und der Kernel wiederum mit den Applikationen und ebenfalls mit sich selbst austauschen (siehe Abbildung 1).
Abbildung 1: Netlink ermöglicht Kommunikation zwischen Komponenten aus Userland und Kernel.
Da das Interface zudem auf BSD-Sockets beruht, arbeitet es asynchron und unterstützt Multicasts. Ein einzelner Kernelthread kann daher gleich eine ganze Reihe von Prozesses mit identischen Daten versorgen. Und nicht zuletzt lassen sich auf diese Weise auch größere Datenmengen einfach transferieren. Angesichts dieser Vorteile verwundert es nicht, dass das Netlink-Interface auch außerhalb des Netzwerk-Subsystems zu Ruhm, Ehre und vor allem zum Einsatz gekommen ist.
Server im Kernel
Die angesprochene Kommunikation über Sockets folgt dem Client-Server-Modell. Im Fall von Netlink liegt der Server typischerweise im Kernel. Analog zur IP-Kommunikation benötigt auch Netlink einen Socket, allerdings wählt der Admin statt »PF_INET« hier »PF_NETLINK« (siehe Abbildung 2). Die Protokollfamilie »PF_NETLINK« hat zur Zeit 20 offizielle Mitglieder. Darunter befinden sich solche zum Handling von Routing-Informationen, zur Konfiguration der Firewall oder von IPsec. Die Headerdatei »netlink.h« listet die offiziellen Varianten auf.
Abbildung 2: Das Schema der Adressierung folgt bei der Protokollfamilie Netlink dem der bekannteren Familien, etwa »PF_INET« mit seinen BSD-Sockets. Was dort IP-Adressen sind, entspricht bei Netlink Prozess-IDs.
Den adressierten Kommunikationspartner spricht der Entwickler über eine eindeutige Kennung an, typischerweise die Prozess-ID. Ein Netlink-Server im Kernel bindet seinen Socket an eine Adresse und hinterlegt eine Callback-Funktion. Das Subsystem ruft die Funktion auf, sobald eine Nachricht mit dem entsprechenden Protokoll und der Zieladresse eingeht. Die Adressen bei Netlink sind jeweils PIDs; wer den Kernel adressieren will, wählt die Null.
Flexibles Protokoll
Prinzipiell besteht eine Nachricht, die zu einem beliebigen Netlink-Protokoll gehört, aus einem Kommando und den zum Kommando gehörigen Attributen (siehe Abbildung 3). Attribute wiederum bestehen aus einem Attribut-Typ und dem Attribut-Wert. Typ kann beispielsweise Integer oder String, der zugehörige Wert 42 oder "Hello World" sein. Makros, die alle den Namensvorsatz »nla_« tragen, dröseln die Nachricht in die verschiedenen Attribute auf.
Abbildung 3: Eine Netlink-Nachricht besteht aus Header und Nutzdaten. Letztere können wiederum einen protokollspezifischen Header und die Attribute enthalten.
Um einen Netlink-Server zu schreiben, definiert der Entwickler ein Netlink-Protokoll. Für Experimente eignen sich »NETLINK_GENERIC« oder eine selbst erdachte Nummer wie in Listing 1, Zeile 10. Soll der Code in den offiziellen Kernel in »include/linux/netlink.h« aufgenommen werden, muss Linus Torvalds freilich ein entsprechendes Patch akzeptieren.
01 #include <linux/socket.h>
02 #include <linux/kernel.h>
03 #include <linux/module.h>
04 #include <linux/netlink.h>
05 #include <net/sock.h>
06 #include <net/tcp_states.h>
07
08 /* Sollte langfristig in
09 .../include/linux/netlink.h stehen */
10 #define NETLINK_TEST 25
11
12 #define MY_NL_COMMAND 0x11
13 #define MY_MESSAGE "Answer from Kernel"
14
15 static struct sock *nl_sk = NULL;
16 static DEFINE_MUTEX(nltest_mutex);
17
18 static void
19 nltest_netlink_server(struct sk_buff *skb)
20 {
21 struct nlmsghdr *nlh, *hdr;
22 u32 pid;
23 struct sk_buff *ret_skb;
24 char *data_ptr;
25
26 printk("nltest_netlink_server(%p)n", skb);
27 BUG_ON(!skb);
28 nlh = nlmsg_hdr(skb);
29 pid = nlh->nlmsg_pid;
30 printk("pid: %d, seq: %d, data: "%s"n",
31 pid, nlh->nlmsg_seq,
32 (char *)nlmsg_data(nlh));
33
34 /* Antwort verschicken */
35 ret_skb = nlmsg_new(NLMSG_GOODSIZE,
26 GFP_KERNEL);
37 if (ret_skb == NULL) {
38 printk("%s: no memn", __FUNCTION__);
39 return;
40 }
41
42 /* Header vorbereiten */
43 hdr = nlmsg_put(ret_skb, 0,
44 nlh->nlmsg_seq, NLMSG_DONE,
45 strlen(MY_MESSAGE), 0);
46 if (IS_ERR(hdr)) {
47 printk("nlmsg_put failedn");
48 nlmsg_free(ret_skb);
49 return;
50 }
51
52 data_ptr = nlmsg_data(hdr);
53 strcpy(data_ptr, MY_MESSAGE);
54 nlmsg_end(ret_skb, nlh);
55 netlink_unicast(nl_sk, ret_skb, pid,
56 MSG_DONTWAIT);
57 }
58
59 static int
60 __init nltest_module_init(void)
61 {
62 printk("nltest_module_initn");
63 nl_sk = netlink_kernel_create(&init_net,
64 NETLINK_TEST, 0,
65 nltest_netlink_server,
66 &nltest_mutex, THIS_MODULE);
67 return 0;
68 }
69
70 static void
71 __exit nltest_module_exit(void)
72 {
73 printk("nltest_module_exitn");
74 if (nl_sk)
75 sock_release(nl_sk->sk_socket);
76 }
77
78 module_init(nltest_module_init);
79 module_exit(nltest_module_exit);
80 MODULE_LICENSE("GPL");
|