Multicast

ArticleCategory: []

System Administration

AuthorImage:[]

[Photo of the Author]

TranslationInfo:[]

original in es Angel Lopez
es to en Javier Palacios

en to de Katja Socher

AboutTheAuthor:[]

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 ;)

Abstract:[]

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.

ArticleIllustration:[]

[Ilustration]

ArticleBody:[]

Einführung

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.

Multicast Adresse

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:

[grouped bits]


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:

In Ethernet Netzwerken, den am meisten verbreitetsten, wird das Mapping durch Fixieren der 24 bedeutensten Bits der Ethernetadresse zu 01:00:5E durchgeführt. Das nächste Bit wird bei 0 fixiert und die 23 unteren Bits benutzen die 23 unteren Bits der multicast IPv4 Adresse. Der Prozess ist im Diagramm unten abgebildet:
[transform to Ethernet]

Zum Beispiel korrespondiert die multicast IPv4 Adresse 224.0.0.5 mit der physischen Ethernetadresse 01:00:5E:00:00:05.

Es gibt einige besondere multicast IPv4 Adressen:

Es gibt einen großen Betrag an zugeteilten multicast Adressen, zusätzlich zu denen, die hier gezeigt wurden. Die vollständige Referenz findest sich in der aktuellsten Version von "Assigned Numbers" RFC.

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.


Multicast bei der Arbeit

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:

Um mit multicast über ein WAN zu arbeiten, sollten die Router multicastrouting unterstützen. Wenn ein Prozess, der in einem host läuft, zu einer Multicastgruppe dazukommt, sendet der host eine IGMP (Internet Group Management Protocol) Meldung zu jedem multicast router in dem Unternetz, um sie zu informieren, daß die multicast Meldungen, die zu der Multicastgruppe geschickt wurden, zu einem lokalen Unternetz geschickt werden müssen, um den Empfang durch den teilnehmenden Prozess zu erlauben. Die Router ihrerseits informieren jeden anderen multicast Router und lassen ihn wissen, welche multicast Meldungen in das Unternetz geroutet werden müssen.

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.

Die Programmierschnittstelle

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


IP_ADD_MEMBERSHIP

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.

IP_DROP_MEMBERSHIP

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.

IP_MULTICAST_IF

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.

IP_MULTICAST_TTL

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.

IP_MULTICAST_LOOP

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.

Ein praktisches Beispiel

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.

Server

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); 
    } 
  } 
} 


Client

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); 
    } 
  } 
}


Kernel und Multicast

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.