Multicast

ArticleCategory:

System Administration

AuthorImage:

[Foto van de Auteur]

TranslationInfo:

original in es Angel Lopez

es to en Javier Palacios

en to nl Tom Uijldert

AboutTheAuthor:

Angel is aan het afstuderen in Computer Engineering. Op dit moment geeft hij les als docent bij Sun Microsystems in Solaris en netwerkbeheer. Hij heeft net als co-auteur met Ra-Ma een boek uitgebracht met als titel Internet design and implementation on Unix systems. Het meest interessant vindt hij netwerken, beveiliging, programmeren onder Unix en, sinds kort, houdt het hacken in de Linux kernel hem wakker ;)

Abstract:

Dit artikel gaat in op de multicast technologie in een TCP/IP netwerk. Het gaat in op de theorie over het gebruik van Multicast communicatie en de Linuxkoppeling die we hiervoor kunnen gebruiken. Om het geheel te completeren wordt ook de kernel-code bekeken die dit implementeert onder Linux. Het wordt afgerond met een voorbeeldprogramma in C dat het gebruik van Multicast in een applicatie illustreert.

ArticleIllustration:

[Illustratie]

ArticleBody:

Inleiding

Als je contact zoekt met een machine in het netwerk dan zijn er drie manieren om dit te doen: Multicast adressen zijn handig als je dezelfde informatie naar meerdere machines wilt sturen maar er niet direct een broadcast van wilt maken. Een mogelijke toepassing is het versturen van multimedia informatie naar meerdere machines. Als we in dit soort gevallen rekening houden met de bandbreedte dan is het herhaald direct adresseren (unicast) van diverse machines niet echt efficiënt. Vooral niet als de uitzendende machine niet in hetzelfde netwerk zit.

Het Multicast adres

Zoals de lezer waarschijnlijk al weet is het adresbereik van IP verdeeld in drie adresklassen, A, B en C. Er is een vierde klasse (D) gereserveerd voor Multicast adressen. De IP versie 4 adressen tussen 224.0.0.0 en 239.255.255.255 zijn klasse D adressen.

De vier meest-significante bits van het adres bepalen de waarde tussen 224 en 239. De andere 28 bits vormen de groepsidentificatie, zoals hieronder weergegeven:

[grouped bits]

Op netwerkniveau moeten de Multicast adressen naar echte adressen worden vertaald, afhankelijk van het soort netwerk. Voor unicast kan hiervoor het ARP protocol worden gebruikt. Dit kan echter niet voor Multicast adressen en dus zal dit op een andere manier moeten. Een aantal RFC documenten gaan in op het vertalen van een Multicast adres naar echte adressen:

In de -alomtegenwoordige- Ethernet netwerken vertaalt men een Multicast adres door de 24 meest significante bits vast te zetten op 01:00:5E. Het volgende bit staat op 0 en de 23 resterende bits worden op de 23 laagste bits van het IP versie 4 Multicast adres geprojecteerd. Zie hiervoor onderstaand figuur:
[transform to Ethernet]

Het IP-adres 224.0.0.5 zal dus gelijk worden gesteld aan het Ethernet adres 01:00:5E:00:00:05.

Er zijn een aantal bijzondere Multicast adressen:

Naast de hierboven beschreven adressen zijn er nog een hele hoop Multicast adressen gereserveerd. Het complete overzicht hiervan is te vinden in de laatste versie van de "Assigned Numbers" RFC.

Onderstaande tabel geeft een overzicht van het complete Multicast adresbereik met de gebruikelijke benoeming van ieder bereik en de bijbehorende TTL teller (Time To Live teller in ieder IP-pakket). In IP versie 4 heeft deze teller een dubbele betekenis. Zoals waarschijnlijk bekend bepaalt deze teller de levensduur van een datagram in het netwerk om oneindige lussen door verkeerde configuratie van routing-tabellen tegen te gaan. Binnen Multicast bepaalt de teller ook het bereik van het datagram (oftewel hoe ver het pakket in het netwerk mag gaan). Hiermee wordt het mogelijk een bereik te definiëren op basis van de categorie van een datagram.

Bereik TTL Adres bereik Beschrijving
Machine 0   Het verkeer is beperkt tot de lokale machine, het zal niet via een koppeling het netwerk op gaan.
Link 1 224.0.0.0 - 224.0.0.255 Dit verkeer blijft in het lokale netwerk en zal niet langs een router komen.
Afdeling < 32 239.255.0.0 - 239.255.255.255 Verkeer beperkt tot de afdeling van een organisatie.
Organisatie < 64 239.192.0.0 - 239.195.255.255 Beperkt tot een bepaalde organisatie.
Globaal < 255 224.0.1.0 - 238.255.255.255 Geen beperking.

Multicast in actie

In een LAN zal de netwerkkoppeling van een machine alle pakketten doorsturen die deze machine als bestemming hebben. Dit zijn zowel het adres wat overeenkomt met het echte adres van de machine alsook alle broadcast adressen.
Indien een machine zich ook heeft aangesloten bij een Multicast groep dan zullen ook pakketten met dat adres door de koppeling door worden gegeven.

Aldus zal de koppeling met fysiek adres 80:C0:F6:A0:4A:B1 die zich bij Multicast groep 224.0.1.10 heeft aangesloten, alle pakketten doorgeven, gericht aan één van de volgende adressen:

Voor het werken met Multicast over een WAN heb je routers nodig die Multicast routering ondersteunen. Als een proces op een machine zich aan wil sluiten bij een Multicast groep dan zal de machine een IGMP-pakket uitsturen (Internet Group Management Protocol) naar alle routers in zijn netwerk om duidelijk te maken dat verkeer voor deze groep door moet worden gestuurd naar dit netwerk. Op hun beurt zullen de routers andere routers buiten het net laten weten dat verkeer voor deze groep door moet worden gestuurd.

Routers zenden ook IGMP-pakketten naar groep 224.0.0.1 om machines te vragen op welke groepen ze geabonneerd zijn. Machines zullen niet direct antwoorden maar een teller op een willekeurige waarde zetten. Pas als deze teller op nul staat zal een machine antwoorden. Dit om te voorkomen dat alle machines tegelijk antwoorden en zo teveel verkeer op het netwerk genereren. Een machine die aldus antwoordt zal de adressen van de groep uitzenden waarop hij is geabonneerd. Andere machines met hetzelfde abonnement vangen dit op en zullen dit niet meer antwoorden. Eén antwoord van een machine is voor een router voldoende om te weten welk Multicast-verkeer hij door moet sturen.

Wanneer alle machines van de groep zijn afgekoppeld zal er geen antwoord meer komen op dit soort verkeer en zullen routers dus concluderen dat niemand meer op de groep is geabonneerd. Het doorgeven van Multicast verkeer voor dit adres zal dan worden gestopt. Een andere mogelijkheid in IGMP versie 2 is het versturen van een pakket vanuit de machine waarmee men expliciet het abonnement opzegt via adres 224.0.0.2.

De koppeling

Voor diegenen die bekend zijn met het programmeren van sockets komen er slechts 5 nieuwe mogelijkheden bij voor het omgaan met Multicast. De functies setsockopt() en getsockopt() verzorgen deze extra mogelijkheden. Onderstaande tabel geeft details van de vijf mogelijkheden voor Multicast, tezamen met de datatypen en een korte omschrijving.
IPv4 Optie Data type Beschrijving
IP_ADD_MEMBERSHIP struct ip_mreq Abonneer je op de Multicast groep.
IP_DROP_MEMBERSHIP struct ip_mreq Zeg het abonnement op.
IP_MULTICAST_IF struct ip_mreq Geef een bepaalde koppeling op voor het versturen van Multicast berichten.
IP_MULTICAST_TTL u_char Geef een TTL (Time To Live) op voor de berichten.
IP_MULTICAST_LOOP u_char Activeren/Deactiveren van de test-loopback voor Multicast berichten.
Het record ip_mreq is gedefinieerd in het bestand <linux/in.h>, zoals hieronder weergegeven:
struct ip_mreq {
   struct in_addr imr_multiaddr; /* IP multicast address of group */
   struct in_addr imr_interface; /* local IP address of interface */
   };
De volgende opties gelden voor Multicast:
#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

Een proces kan zich op een groep abonneren met het versturen van deze optie via setsockopt(). Als parameter wordt een ip_mreq meegegeven. Het eerste veld, imr_multiaddr, geeft de groep waarop we ons willen abonneren. Het tweede veld, imr_interface, geeft het IP-adres van de koppeling die we hiervoor zullen gebruiken.

IP_DROP_MEMBERSHIP

Met deze optie kan het abonnement op worden gezegd. De velden in ip_mreq worden op dezelfde manier gebruikt als hierboven omschreven.

IP_MULTICAST_IF

Deze bepaalt de netwerk koppeling die zal worden gebruikt voor het versturen/ontvangen van Multicast verkeer.

IP_MULTICAST_TTL

Stel de TTL (Time To Live) in van de pakketten (staat normaal op 1, oftewel alleen in het eigen netwerk).

IP_MULTICAST_LOOP

Wanneer een proces een pakket uitzendt voor de groep dan zal het dit pakket ook ontvangen als het geabonneerd is op die groep, op dezelfde manier alsof het door een andere machine in de groep is uitgezonden. Deze optie zet dit gedrag aan of uit.

Een praktijkvoorbeeld

Om de theorie in dit artikel te illustreren zullen we een eenvoudig voorbeeld maken met een proces wat naar een Multicast groep uitzend en andere processen, geabonneerd op die groep, die dit ontvangen en op het scherm weergeven.

Het volgende programma is een server die alles wat aan de invoer wordt aangeboden (stdin) naar de Multicast groep 224.0.1.1 uitzend. Zoals te zien hoeven er geen bijzondere handelingen te worden verricht. Het opgeven van het adres voldoet.
Opties aangaande loopback en TTL kunnen worden veranderd mochten de standaard waarden niet voldoen.

Server

De invoer (stdin) wordt doorgestuurd naar Multicast groep 224.0.1.1:
#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

Onderstaande code is de client-kant, die de uitgezonden informatie ontvangt. Dit wordt naar de standaard uitvoer (stdout) doorgestuurd. Het enige nieuwe aan deze code is het gebruik van de optie IP_ADD_MEMBERSHIP. De rest van de code is de gebruikelijke voor ontvangst van udp pakketjes.
#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 en Multicast

Een proces dat zich wil abonneren op een Multicast zal, zoals hierboven te zien, de setsockopt() functie gebruiken met de optie IP_ADD_MEMBERSHIP. De code voor het uitvoeren van deze functie kun je vinden in /usr/src/linux/net/ipv4/ip_sockglue.c. De code om deze optie te activeren (of te deactiveren met IP_DROP_MEMBERSHIP) is als volgt:
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); 
De eerste regels code controleren of de parameter ip_mreq de juiste lengte heeft om te kunnen kopiëren naar kernelgeheugen. Afhankelijk van de gegeven optie wordt dan ip_mc_join_group() of ip_mc_leave_group() aangeroepen als we ons op een groep willen abonneren respectievelijk op willen zeggen.

De code voor deze functies kun je vinden in /usr/src/linux/net/ipv4/igmp.c. Voor het abonneren op een groep is de code als volgt:

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; 
Allereerst wordt met de MULTICAST macro gecontroleerd of het adres wel een echt Multicast adres is. Controle op de waarde 224 in het hoogste byte van het adres volstaat.
  if (!MULTICAST(addr)) 
    return -EINVAL; 

    rtnl_shlock(); 
Vervolgens wordt een netwerkkoppeling aangemaakt die de groep moet afhandelen. Als dit niet via een index kan, zoals onder IP versie 6 mogelijk moet zijn, dan wordt ip_mc_find_dev() aangeroepen voor het vinden van de juiste koppeling. Voor dit artikel zullen we aannemen dat we met IP versie 4 werken. Als het adres de waarde INADDR_ANY heeft dan gaat de kernel zelf op zoek naar de beste koppeling om dit verkeer af te handelen, wat afhankelijk is van het groepsadres zelf en de inhoud van de routing-tabellen.
  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; 
  }
Vervolgens wordt geheugen gereserveerd voor een struct ip_mc_socklist en ieder groepsadres wordt vergeleken met de koppeling. Als we er reeds een vinden dan verlaten we de functie omdat het geen zin heeft een dubbele verbinding op te zetten naar de koppeling. Als de optie INADDR_ANY niet wordt gebruikt zal de betreffende teller worden opgehoogd voordat de functie beëindigt.
  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; 
Op dit punt aangekomen moeten we een nieuwe socket met een nieuwe groep aanmaken. Er moet dus een nieuwe inschrijving worden aangemaakt en in de lijst van groepen worden gezet die met deze socket worden geassocieerd. Het geheugen hiervoor was al gereserveerd en dus moeten we alleen de desbetreffende velden op correcte wijze invullen.
  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; 
}
De functie ip_mc_leave_group() gaat over het afmelden van een groep en is een stuk eenvoudiger dan de vorige functie. Deze zoekt de lijsten af op de juiste inschrijving en verlaagt de betreffende groepsteller. Als deze op nul staat wordt de inschrijving zélf weggegooid.
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; 
}
De andere Multicast opties die we boven hebben besproken zijn heel eenvoudig omdat ze alleen maar wat waarden veranderen in de velden die bij de groepsstructuur horen. Deze worden direct uitgevoerd door de functie ip_setsockopt().