original in es Angel
Lopez
es to en Javier
Palacios
en to de Katja Socher
Angel beendet gerade sein Studium in Informatik. Er arbeitet jetzt als Lehrer für Sun Microsystems, wo er Solaris und Netzwerkadministration unterrichtet. Kürzlich hat er als Co-Autor mit Ra-Ma ein Buch mit dem Titel Internetprotokolle. Design und Implementation auf Unixsystemen veröffentlicht. Seine Hauptinteressen sind Netzwerke, Sicherheit, System-/Netzwerk-Unixprogrammierung und seit neuestem verkürzt Linuxkernelhacking seine Zeit fürs Schlafen ;)
Dieser Artikel ist, eine Einführung zu multicast Technologien auf TCP/IP Netzwerken. Er erklärt die theoretischen Konzepte der mulitcast Kommunikation und das Linux API, das wir zum Programmieren vn Mulitcastapplikationen benutzen können. Die Kernelfunktionen, die diese Technologie implementieren, werden auch gezeigt, um den globalen Überblick über die mulitcast Unterstützung unter Linux zu vervollständigen. Der Artikel endet mit einem einfachen C Beispiel zur socket Programmierung, wo die Entstehung einer multicast Applikation illustriert wird.
Wenn du versuchst, einen host (interface, eine Netzwerkschnittstelle) innerhalb eines Netzwerkes zu erreichen, kannst du drei verschiedene Arten von Adressen benutzen:
Multicast Adressen sind nützlich, wenn der Informationsempfänger nicht nur ein host ist und wir keinen Netzwerk-broadcast produzieren wollen. Dieses Szenario ist typisch in Situationen, die das Senden von Multimediainformationen (Echtzeitaudio oder -video, z.B.) zu einigen hosts erfordern. In Bandbreitenausdrücken gedacht, sind diese Fälle nicht das beste, um es als unicast zu jedem Client zu senden, der die Multimediaemmission empfangen möchte. Auch broadcast ist nicht die beste Lösung, hauptsächlich, wenn sich bestimmte Clienten außerhalb des lokalen Intranetzes befinden, von wo der Multicast seinen Ursprung hat.
Wie der Leser wahrscheinlich weiß, ist der IP Adressenraum in drei Adressklassen unterteilt. A,B und C Adreßklassen. Es gibt eine vierte Klasse (D), die für multicast Adressen reserviert ist. IPv4 Adressen zwischen 224.0.0.0 und 239.255.255.255 gehören zur Klasse D.
Die vier höchsten Bits von der IP Adresse erlauben Werte zwischen
224 und 239. Die unteren 28 Bits sind für den
multicast group identifier, wie in der Abbildung unten gezeigt, reserviert:
Auf der Netzwerkebene sollten die IPv4 multicast Adressen über die physikalischen Adressen von dem Typ des Netzwerkes, auf dem wir arbeiten, gemappt werden. Wenn wir mit einer unicast Netzwerkadresse arbeiten, sollten wir die verbundenen physikalischen Adressen durch Benutzen des ARP Protokolls erhalten. Im Fall von multicast Adressen ist ARP nicht brauchbar, und die physikalische Adresse muß auf andere Weise zurückgewonnen werden. Es gibt einige RFC Dokumente, die die Methode zur durchführung dieser Zuordnung behandeln:
Es gibt einige besondere multicast IPv4 Adressen:
Die Tabelle unten zeigt den vollen multicast Adreßraum mit den üblichen Namen für jeden Adreßbereich und ihre dazugehörigen TTLs (time to live Zähler im ip Packet). Unter multicast IPv4 hat das TTL eine doppelte Bedeutung. Wie der Leser wahrscheinlich weiß, steuert es die Lebenszeit eines Diagramms im Netzwerk, um eine unendliche Schleife zu verhindern für den Fall falsch konfigurierter routing Tabellen. Beim Arbeiten mit multicast definiert der TTL Wert auch die Reichweite des Diagramms, z.B. wie weit es innerhalb es Netzwerkes reist. Dies erlaubt eine Reichweitendefinition basierend auf der Kategories des Paketes.
Reichweite | align=CENTER>TTL | Adressenbereich <</TD> | Beschreibung |
Node | 0 | Das Datagramm ist auf den lokalen host beschränkt. Es erreicht keine der Netzwerkschnittstellen. | |
Link | 1 | 224.0.0.0 - 224.0.0.255 | Das Datagramm ist auf das senderhost Unternetz beschränkt und wird nicht über diesen Router hinausgehen. |
Abteilung | < 32 | 239.255.0.0 - 239.255.255.255 | beschränkt auf eine Abteilung der Organisation. |
Organisation | < 64 | 239.192.0.0 - 239.195.255.255 | beschränkt auf eine spezielle Organisation. |
Global | < 255 | 224.0.1.0 - 238.255.255.255 | keine Bschränkung, globale Applikation. |
Innerhalb eins LANs wird eine Netzwerkschnittstelle auf einem host all die
Pakete zu höheren Ebenen senden, die den host als Zielpunkt haben. Diese Pakete
haben als Zieladresse die physikalischen interface Adressen oder eine
broadcast Zieladresse.
Wenn ein host einer Multicastgruppe angehört, erkennt das Netzwerkinterface auch
die Pakete, die an diese Gruppe gerichtet sind: all diejenigen, mit einer
Zieladresse, die zu der Multicastgruppe mit host Mitgliedschaft korrespondiert.
Deshalb werden, wenn ein host interface die typische Adresse 80:C0:F6:A0:4A:B1 hat und einer Multicastgruppe 224.0.1.10 angehört, die zu dem host gehörenden Pakete erkannt, wenn sie eine der folgenden Zieladressen haben:
Die Router schicken auch IGMP Nachrichten zu der Gruppe 224.0.0.1, um all host Information über die Gruppen, an denen sie teilnehmen, anzufordern. Ein host setzt, nachdem er eine solche Meldung erhalten hat, seinen Zähler auf einen Zufallswert und antwortet, wenn der Zähler bei 0 steht. Dies verhindert, daß alle hosts zur selben Zeit antworten und damit eine Netzwerküberlastung verursachen. Wenn der host antwortet, sendet er eine Meldung zur multicast Adresse der Gruppe und jeder andere host mit Gruppenmitgliedschaft sieht die Antwort und antwortet selber nicht, solange ein teilnehmender host für den Unternetzrouter ausreichend ist, um multicast Meldungen für die Gruppe zu handhaben.
Wenn alle hosts, die einer Gruppe angehörten, gekündigt haben, wird keiner antworten und der Router entscheidet, daß kein host tatsächlich an einer solchen Gruppe interessiert ist und wird das Routen der korrespondierenden Meldungen in das Unternetz beenden. Eine andere mit IGMPv2 implementierte Option ist das aussenden der Kündigung, die von dem host kommt, der eine Nachricht zu Adresse 224.0.0.2 schickt.
Mit Leser die sich mit socket Programmierung auskennen finden nur fünf neue Socket Operationen, die multicast Optionen behandeln. Die Funktionen setsockopt() und getsockopt() werden benutzt, um Werte dieser fünf Optionen aufzubauen oder zu lesen. Die Tabelle unten zeigt die verfügbaren Optionen für multicast mit ihren verwalteten Datentypen und einer kurzen Beschreibung:
IPv4 Option | Datentyp | Beschreibung |
IP_ADD_MEMBERSHIP | struct ip_mreq | nimmt an der Multicastgruppe teil. |
IP_DROP_MEMBERSHIP | struct ip_mreq | kündigt die Multicastgruppe. |
IP_MULTICAST_IF | struct ip_mreq | Spezifiziert eine Schnittstelle für die Versendung von multicast Meldungen. |
IP_MULTICAST_TTL | u_char | Spezifiziert eine TTL für die Versendung von multicast Meldungen. |
IP_MULTICAST_LOOP | u_char | Aktiviert oder deaktiviert das multicast messages loopback. |
Die ip_mreq struct ist definiert im header file <linux/in.h> wie unten beschrieben:
struct ip_mreq { struct in_addr imr_multiaddr; /* IP multicast address of group */ struct in_addr imr_interface; /* local IP address of interface */ };Und die multicast Optionen in der Datei sind:
#define IP_MULTICAST_IF 32 #define IP_MULTICAST_TTL 33 #define IP_MULTICAST_LOOP 34 #define IP_ADD_MEMBERSHIP 35 #define IP_DROP_MEMBERSHIP 36
Ein Prozess kann an einer Multicastgruppe teilnehmen, wenn er diese Option über ein Socket mit der Funktion setsockopt() verschickt. Der Parameter ist ein ip_mreq struct. Das erste Strukturfeld, imr_multiaddr, enthält die multicast Adresse, an der wir teilnehmen wollen. Das zweite Feld, imr_interface, enthält die IPv4 Adresse der Schnittstelle, die wir benutzen werden.
Durch Benutzen dieser Option kann ein Prozess einer multicast Gruppe kündigen. Die Felder der ip_mreq struct werden genauso benutzt wie im obigen Fall.
Diese Option erlaubt uns, das Netzwerkinterface zu fixieren, das der socket zum Senden der multicast Meldungen benutzt. Das Interface wird in der ip_mreq übergeben wie in den vorherigen Fällen.
Baut das TTL (Time To Live) für das Datagramm mit den gesendeten multicast Meldungen durch Benutzen des sockets auf. Der Defaultwert ist 1, was bedeutet, daß das Datagramm nicht aus dem lokalen Sub-Netz herausgehen wird.
Wenn ein Prozess eine Meldung für eine Multicastgruppe sendet, empfängt er die Meldungen, wenn sein Interface mit der Gruppe verbunden wurde, auf dieselbe Weise, wie es empfangen wird, wenn sein Ursprung ein anderer Ort im Netzwerk ist. Diese Option erlaubt das Aktivieren und Deaktivieren dieses Verhaltens.
Um die Ideen, die in diesem Artikel gezeigt wurden, zu testen, zeigen wir ein einfaches Beispiel, wo es einen Prozess gibt, der Meldungen zu einer Multicastgruppe schickt und einige Prozesse, verbunden mit dieser Gruppe empfangen die Meldungen und zeigen sie auf dem Bildschirm an.
Der folgende Code implementiert einen Server, der zu der Multicastgruppe 224.0.1.1 alles sendet, das durch seinen Standardinput geht. Wie beobachtet werden kann, besteht keine Notwendigkeit einer speziellen Aktion, um die Informationen zu der Multicastgruppe zu senden. Die Zielortgruppenadressen sind ausreichend. Loopback und die TTL Optionen können verändert werden, wenn ihre Defaultwerte für die zu entwickelnde Applikation nicht angemessen wären.
Der Standardinput wird zur Multicastgruppe 224.0.1.1 gsendet
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #define MAXBUF 256 #define PUERTO 5000 #define GRUPO "224.0.1.1" int main(void) { int s; struct sockaddr_in srv; char buf[MAXBUF]; bzero(&srv, sizeof(srv)); srv.sin_family = AF_INET; srv.sin_port = htons(PUERTO); if (inet_aton(GRUPO, &srv.sin_addr) < 0) { perror("inet_aton"); return 1; } if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); return 1; } while (fgets(buf, MAXBUF, stdin)) { if (sendto(s, buf, strlen(buf), 0, (struct sockaddr *)&srv, sizeof(srv)) < 0) { perror("recvfrom"); } else { fprintf(stdout, "Enviado a %s: %s\n", GRUPO, buf); } } }
Der untenstehende Code ist die Clientseite, die die abgeschickten Informationen zu der Multicastgruppe des Servers, empfängt. Die erhaltenen Meldungen werden im Standardoutput angezeigt. Die einzige Besonderheit dieses Codes ist das Aufbauen einer IP_ADD_MEMBERSHIP Option. Der übrige Code ist der übliche für einen Prozess, der UDP Meldungen empfangen muß.
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #define MAXBUF 256 #define PUERTO 5000 #define GRUPO "224.0.1.1" int main(void) { int s, n, r; struct sockaddr_in srv, cli; struct ip_mreq mreq; char buf[MAXBUF]; bzero(&srv, sizeof(srv)); srv.sin_family = AF_INET; srv.sin_port = htons(PUERTO); if (inet_aton(GRUPO, &srv.sin_addr) < 0) { perror("inet_aton"); return 1; } if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket"); return 1; } if (bind(s, (struct sockaddr *)&srv, sizeof(srv)) < 0) { perror("bind"); return 1; } if (inet_aton(GRUPO, &mreq.imr_multiaddr) < 0) { perror("inet_aton"); return 1; } mreq.imr_interface.s_addr = htonl(INADDR_ANY); if (setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) { perror("setsockopt"); return 1; } n = sizeof(cli); while (1) { if ((r = recvfrom(s, buf, MAXBUF, 0, (struct sockaddr *) &cli, &n)) < 0) { perror("recvfrom"); } else { buf[r] = 0; fprintf(stdout, "Mensaje desde %s: %s\n", inet_ntoa(cli.sin_addr), buf); } } }
Wie wir oben gezeigt haben, benutzt ein Prozess, wenn er an einer Multicastgruppe teilnehmen möchte, die setsockopt() Funktion, um die Option IP_ADD_MEMBERSHIP auf dem IP level aufzubauen. Die tatsächliche Implementation für diese Funktion kann in /usr/src/linux/net/ipv4/ip_sockglue.c gefunden werden. Der innerhalb der Funktion ausgeführte Code, um diese Funktionen oder die IP_DROP_MEMBERSHIP zu setzen, lautet:
struct ip_mreqn mreq; if (optlen < sizeof(struct ip_mreq)) return -EINVAL; if (optlen >= sizeof(struct ip_mreqn)) { if(copy_from_user(&mreq,optval,sizeof(mreq))) return -EFAULT; } else { memset(&mreq, 0, sizeof(mreq)); if (copy_from_user(&mreq,optval,sizeof(struct ip_mreq))) return -EFAULT; } if (optname == IP_ADD_MEMBERSHIP) return ip_mc_join_group(sk,&mreq); else return ip_mc_leave_group(sk,&mreq);
Die allerersten Zeilen des Codes überprüfen, daß der Inputparameter, die ip_mreq struct, eine korrekte Länge hat, und es möglich ist , die Daten von User- zu Kernelspace zu kopieren. Sobald wir den Parameterwert bekommen, wird die Funktion ip_mc_join_group(), um an der Multicastgruppe teilzunehmen oder ip_mc_leave_group(), wenn wir kündigen wollen, aufgerufen.
Der Code für diese Funktionen findet sich in /usr/src/linux/net/ipv4/igmp.c. Um an einer Gruppe teilzunehmen ist der Quellcode unten kommentiert:
int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr) { int err; u32 addr = imr->imr_multiaddr.s_addr; struct ip_mc_socklist, *iml, *i; struct in_device *in_dev; int count = 0;
Ganz zu Beginn überprüfen wir durch Benutzen des Multicastmakros, daß die Gruppenadresse sich innerhalb des für Multicastadressen reservierten Bereichs befindet. Es ist ausreichend zu überprüfen, daß das höchste Byte auf der IPadresse auf 224 gesetzt ist.
if (!MULTICAST(addr)) return -EINVAL; rtnl_shlock();
Nach der Verifizierung wird eine Netzwerkschnittstelle aufgesetzt, um die Multicastgruppe zu handhaben. Wenn der Zugriff nicht möglich ist über den Index zu der Schnittstelle, wie es unter IPv6 sein sollte, wird die Funktion ip_mc_find_dev() aufgerufen, um den device, der mit der speziellen IP-Adresse verbunden ist, zu finden. Wir nehmen für den Rest dieses Artikels an, daß dies der Fall ist, da wir unter IPv4 arbeiten. Wenn die Adresse INADDR_ANY wäre, sollte der Kernel die Netzwerkschnittstelle durch Lesen der Routingtabelle selber finden, um ein besseres Interface zu wählen, während er die Gruppenadresse und die Definition der Routing-Tabellen mitberücksichtigt.
if (!imr->imr_ifindex) in_dev = ip_mc_find_dev(imr); else in_dev = inetdev_by_index(imr->imr_ifindex); if (!in_dev) { iml = NULL; err = -ENODEV; goto done; }
Dann reservieren wir Speicher für ein ip_mc_socklist struct, und jede Gruppenadresse und Schnittstelle, die mit dem socket verbunden ist, werden verglichen. Wenn irgendein früherer Eintrag, der mit dem socket verbunden ist, übereinstimmt, springen wir aus der Funktion heraus, da eine doppelte Verbindung zu der Gruppe und Schnittstelle keinen Sinn macht. Wenn die Netzwerkinterfaceadresse nicht INADDR_ANY wäre, wird der dazugehörige Zähler inkrementiert, bevor die Funktion endet.
iml = (struct ip_mc_socklist *)sock_kmalloc(sk, sizeof(*iml), GFP_KERNEL); err = -EADDRINUSE; for (i=sk->ip_mc_list; i; i=i->next) { if (memcmp(&i->multi, imr, sizeof(*imr)) == 0) { /* New style additions are reference counted */ if (imr->imr_address.s_addr == 0) { i->count++; err = 0; } goto done; } count++; } err = -ENOBUFS; if (iml == NULL || count >= sysctl_igmp_max_memberships) goto done;
Wenn wir zu diesem Punkt gelangen, bedeutet das, daß ein neuer socket zu einer neuen Gruppe verbunden wird, weshalb ein neuer Eintrag erstellt und zu der Liste der Gruppen, die zu dem socket gehören, verbunden werden muß. Der Speicher wurde im voraus reserviert und wir müssen nur die korrekten Werte für die verschiedenen Felder der betroffenen Strukturen setzen.
memcpy(&iml->multi,imr, sizeof(*imr)); iml->next = sk->ip_mc_list; iml->count = 1; sk->ip_mc_list = iml; ip_mc_inc_group(in_dev,addr); iml = NULL; err = 0; done: rtnl_shunlock(); if (iml) sock_kfree_s(sk, iml, sizeof(*iml)); return err; }
Die Funktion ip_mc_leave_group() ist verantwortlich für das Kündigen von einer Multicastgruppe und ist viel einfacher als die vorherige Funktion. Sie nimmt die Interfaceadresse und die Gruppe und sucht sie unter den Einträgen, die zu dem aktuellen Socket gehören. Sobald sie gefunden wurden, wird die Zahl der Referenzen dekrementiert, da es jetzt einen Prozess weniger gibt, der mit der Gruppe verbunden ist. Wenn der neue Wert null ist, wird der Zähler selbst gelöscht..
int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr) { struct ip_mc_socklist *iml, **imlp; for (imlp=&sk->ip_mc_list;(iml=*imlp)!=NULL; imlp=&iml->next) { if (iml->multi.imr_multiaddr.s_addr==imr->imr_multiaddr.s_addr && iml->multi.imr_address.s_addr==imr->imr_address.s_addr && (!imr->imr_ifindex || iml->multi.imr_ifindex==imr->imr_ifindex)) { struct in_device *in_dev; if (--iml->count) return 0; *imlp = iml->next; synchronize_bh(); in_dev = inetdev_by_index(iml->multi.imr_ifindex); if (in_dev) ip_mc_dec_group(in_dev, imr->imr_multiaddr.s_addr); sock_kfree_s(sk, iml, sizeof(*iml)); return 0; } } return -EADDRNOTAVAIL; }
Die anderen Multicastoptionen, die wir oben aufgelistet haben, sind sehr einfach, weil sie nur einige Werte in den Datenfeldern der internen Struktur, die mit dem Socket verbundenen ist, mit dem wir arbeiten, setzen. Diese Aufgaben werden direkt von der Funktion ip_setsockopt() ausgeführt.