Home Map Index Search News Archives Links About LF
[Top bar]
[Bottom bar]
Эта заметка доступна на: English  Castellano  Deutsch  Francais  Nederlands  Russian  Turkce  Arabic  

[Фото автора]
автор Angel Lopez

Об авторе:

Angel закончил факультет Вычислительной Техники. Теперь он работает преподавателем в Sun Microsystems, обучая Solaris и администрированию сетей. Недавно была опубликована книга Internet protocols. Design and implementation on Unix systems где он выступал как соавтор Ra-Ma. Его основными увлечениями являются сети, безопасность, программирование систем/сетей под unix, и в последнее время исследование ядра Linux (очень хорошо помогает при бессоннице ;))


Содержание:

 

Посылка групповых сообщений

[Ilustration]

Резюме:

Эта статья является введением в технологии мультикастинга в сетях TCP/IP. Она описывает теоретическую концепцию мультикастинговых коммуникаций и подробности Linux API, которые можно использовать для программирования приложений, использующих мультикастинг. Для наиболее полного обзора поддержки мультикастинга в Linux в статье показаны функции ядра, обеспечивающие эту технологию. Статья заканчивается простым примером на C с программированием сокетов, где показано создание приложения использующего мультикастинг.



 

Введение

Для того, чтобы обратиться к нужному хосту (интерфейсу) в сети, Вы можете использовать три различных типа адреса:



Групповые адреса очень полезны, когда получателем информации является не единичный хост и мы не хотим генерировать широковещательный запрос. Этот сценарий типичен для тех ситуаций в которых требуется посылка мультимедийной информации (например пересылка аудио или видео информации в реальном времени) некоторым хостам. В случае посылки такого рода информации каждому клиенту по unicast адресу у нас просто может не хватить пропускной способности сети. Передача же по широковещательному адресу тоже не лишена недостатков - клиент может находиться за пределами подсети, которая получает информацию.

 

Групповые адреса

Как читателю вероятно известно, пространство IP-адресов поделено на три класса адресов. Классы - A,B и C. Есть еще четвертый класс (D), зарезервированный для групповых адресов. Адреса IPv4 между 224.0.0.0 и 239.255.255.255 принадлежат классу D.

4 старших бита IP-адеса обозначают значения между 224 и 239. Остальные 28 бит, зарезервированы для идентификатора группы, так как показано на рисунке ниже:

[биты группы]


На сетевом уровне, групповые адреса IPv4 должны отображаться на физические адреса того типа сети с которой мы работаем. Если мы работаем с сетевым unicast-адресом, то мы должны получить соответствующий физический адрес используя протокол ARP. В случае использования нами группового адреса, ARP использовать нельзя, и физический адрес может быть получен другим путём. Существуют несколько RFC-документов, поясняющие методы для выполнения этого отображения:

В наиболее распостранённых сетях Ethernet, отображение производится установкой 24-х старших бит Ethernet-адреса в 01:00:5E. Следующий бит сбрасывается в 0, а 23 оставшихся используют 23 младших бита группового адреса IPv4. Этот процесс показан на рисунке ниже:
[преобразование в Ethernet]

Например, групповой адрес IPv4 224.0.0.5 будет соответствовать физическому ethernet-адресу 01:00:5E:00:00:05.

Вот несколько специальных групповых адресов IPv4:

Существует еще много выделенных групповых адресов, не описанных выше. Полный перечень можно найти в последней доступной версии "Assigned Numbers" RFC.

Таблица ниже демонстрирует полное пространство групповых адресов, с обычными именами для каждой группы адресов и ассоциированные с ними TTL (счётчик времени жизни ip-пакета). При мультикастинге, TTL имеет два значения. Как читатель возможно знает, TTL контролирует время жизни датаграмм в сети для предотвращения любых зацикливаний, полученых в результате неправильной конфигурации таблицы маршрутизации. В случае с мультикастингом, значение TTL также определяет область пересылки датаграм, т. е., насколько далеко она датаграмма может путешевствовать в сети. Это позволяет устанавливать пределы, исходя из категории датаграммы.

Предел TTL Группа адресов Описание
Узел 0   Датаграмма ограничена локальным хостом. Она не достигнет ни одного из интерфейсов сети.
Звено 1 224.0.0.0 - 224.0.0.255 Датаграмма ограничена подсетью хоста ее отославшего, и не будет обработана ни одним маршрутизатором.
Отдел < 32 239.255.0.0 - 239.255.255.255 Ограничена одним отделом некоторой организации.
Организация < 64 239.192.0.0 - 239.195.255.255 Ограничена конкретной организацией.
Глобально < 255 224.0.1.0 - 238.255.255.255 Нет ограничений, глобальное использование.


 

Как это работает

В локальной сети (LAN), сетевой интерфейс хоста будет посылать на верхний уровень все те пакеты которые имеют адресом назначения этот хост. Это те пакеты, у которых адрес назначения является физическим адресом интерфейса, либо широковещательные (broadcast) пакеты.
Если хост включён в multicast-группу, сетевой интерфейс распознает также пакеты предназначеные этой группе: все те у которых адрес назначения соответствует той multicast-группе в которую входит хост.

Следовательно, если интерфейс хоста имеет физический адрес 80:C0:F6:A0:4A:B1 и он является членом multicast-группы 224.0.1.10, то пакеты будут распознаны как принадлежащие этому хосту, только в том случае если они будут иметь следующие адреса назначения:

Для работы в глобальных сетях (WAN), маршрутизаторы должны поддерживать multicast-маршрутизацию. Когда процесс запущеный на хосте присоединяется к multicast-группе, хост посылает IGMP (Internet Group Management Protocol) сообщение всем multicast-маршрутизаторам в подсети, для того, чтобы проинформировать их о том, что multicast-сообщения посланые multicast-группе должны быть посланы в локальную подсеть для того чтобы присоединившийся процесс имел возможность их получить. Маршрутизаторы в свою очередь информируют все остальные multicast-маршрутизаторы, предоставляя им данные о том какие multicast-сообщения должны маршрутизироваться в подсеть.

Маршрутизаторы также посылают IGMP сообщения группе 224.0.0.1 запрашивая информацию со всех хостов о том членами каких групп они состоят. Хост, после того как получит такое сообщение, устанавливает счётчик в случайное значение и отвечает только тогда, когда счётчик станет равным 0. Это необходимо для того чтобы предохранить сеть от перегрузки (если все хосты ответят одновременно). Когда хост отвечает, он посылает сообщение multicast-адресу группы и потому каждый другой хост входящий в эту группу увидит это сообщение. Но не будет на него отвечать, так как после того как хост договорится с маршрутизатором о посылке сообщений для этой группы в свою подсеть, другим просто нет смысла повторять то же самое, поскольку нужные сообщения уже будут приходить в подсеть.

Если все хосты входящие в группу отказались от участия в ней, то ни один из них не ответит и маршрутизатор примет решение о прекращении маршрутизации сообщений этой группы в подсеть. Другим вариантом, реализованым в IGMPv2, является посылка хостом желающим выйти из группы сообщения на адрес 224.0.0.2.

 

Программный интерфейс приложения (API)

Если читатель уже имел опыт программирования сокетов в приложениях, то здесь он дополнительно узнает только пять новых операций с сокетами для работы с опциями многоадресной передачи. Функции setsockopt() и getsockopt() используются для установки или чтения значений этих пяти опций. В таблице приведенной ниже показаны возможные опции для мультикастинга, с типами передаваемых им данных и кратким описанием:

Опции IPv4 Тип данных Описание
IP_ADD_MEMBERSHIP struct ip_mreq Присоединиться к multicast-группе.
IP_DROP_MEMBERSHIP struct ip_mreq Выйти из multicast-группы.
IP_MULTICAST_IF struct ip_mreq Указать интерфейс для получения групповых сообщений.
IP_MULTICAST_TTL u_char Указать TTL для групповых сообщений.
IP_MULTICAST_LOOP u_char Включить/выключить закольцовывание групповых сообщений.


Структура ip_mreq определена в заголовочном файле <linux/in.h> так как приведено ниже:

struct ip_mreq {
   struct in_addr imr_multiaddr; /* IP multicast-адрес группы */
   struct in_addr imr_interface; /* локальный IP-адрес интерфейса */
   };
Там же определены и опции 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

Процесс может присоединиться к multicast-группе послав эту опцию с помощью функции setsockopt(). Параметром является структура ip_mreq. Первое поле структуры imr_multiaddr, содержит групповой адрес к которому мы хотим присоединиться. Второе поле, imr_interface, содержит IPv4-адрес интерфейса который мы будем использовать.

 

IP_DROP_MEMBERSHIP

Используя эту опцию процесс может отказаться от участия в multicast-группе. Поля в структуре ip_mreq используются также как и в предыдущем пункте.

 

IP_MULTICAST_IF

Эта опция позволит нам указать сетевой интерфейс, который сокет будет использовать для посылки multicast-сообщений. Интерфейс передаётся структурой ip_mreq также как и в предыдущих пунктах.

 

IP_MULTICAST_TTL

Устанавливает TTL (Time To Live) для датаграм с групповыми сообщениями послаными с использованием сокета. Значение по умолчанию - 1, предполагает, что датаграмы не будут выходить за пределы локальной подсети.

 

IP_MULTICAST_LOOP

Когда процесс пошлёт сообщение multicast-группе, он будет получать сообщение если его интерфейс подключен к группе, также как и в случае когда он находится в любом другом месте сети. С помощью этой опции можно включить/выключить этот режим.

 

Пример

Для того, чтобы проверить идеи изложеные в этой статье, мы приведем здесь небольшой пример, в котором у нас будет процесс, который посылает сообщения группе, и несколько процессов входящих в эту группу, которые будут получать эти сообщения, отображая их на экране.

Код приведённый ниже представляет собой сервер посылающий группе 224.0.1.1 всё что приходит ему со стандартного входа. Как вы можете видеть не надо производить никаких специфических действий, для того чтобы послать группе информацию. Достаточно знать адрес группы.
Вы можете изменить опции Loopback и TTL, если значения по умолчанию вам не подходят.

 

Сервер

Данные со стандартного входа посылаются группе 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);
    }
  }
}


 

Клиент

Код приведённый ниже, это клиентская часть, которая получает информацию посланую группе сервером. Полученые сообщения передаются на стандартный выход. Единственная особенность этого кода в том, что здесь используется опция IP_ADD_MEMBERSHIP. Оставшаяся часть кода является стандартной для процесса которому необходимо получать UDP-сообщения.

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


 

Ядро и мультикастинг

Как мы говорили выше, когда процесс хочет присоединиться к multicast-группе, он использует функцию setsockopt() для установки опции IP_ADD_MEMBERSHIP на уровне IP-протокола. Реализацию этой функции Вы можете найти в файле /usr/src/linux/net/ipv4/ip_sockglue.c. Вот код который выполняется при установке этой опции или IP_DROP_MEMBERSHIP:

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


В первых строках кода проверяется имеет ли входной параметр верную длинну, и возможно ли его копирование в область данных ядра. После того как мы получим значение параметра, вызывается функция ip_mc_join_group(), если выполняется подключение к группе или ip_mc_leave_group(), если мы хотим отказаться от участия в группе.

Код для этих функций можно найти в файле /usr/src/linux/net/ipv4/igmp.c. Для присоединения к группе, исходный код прокомментирован ниже:

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;


Сначала, используя макрос MULTICAST, мы проверяем входит ли адрес группы в область зарезервированую для multicast-адресов. Для этого достаточно проверить, чтобы старший байт IP-адреса был равен 224.

  if (!MULTICAST(addr))
    return -EINVAL;

    rtnl_shlock();


После проверки, сетевой интерфейс настраивается для работы с multicast-группой. Если невозможно обратиться к нему по индексу (например в IPv6), вызывается функция ip_mc_find_dev() для поиска устройства ассоциированого с этим IP адресом. Мы будем рассматривать этот случай, поскольку предполагается что мы работаем в IPv4. Если переданный адрес - INADDR_ANY, то ядро само ищет сетевой интерфейс, читая таблицу маршрутизации для выбора наилучшего интерфейса.

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


Затем мы выделяем память для структуры ip_mc_socklist, и сверяем каждый адресс группы и интерфейс ассоциированный с сокетом. Если найдётся запись уже связанная с сокетом, то мы выходим из функции, поскольку делать двойную связь к группе и интерфейсу не имеет смысла. Если адрес сетевого интерфейса не INADDR_ANY, то перед выходом из функции увеличивается на еденицу соответствующий счётчик.

  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;


Если мы достигли этого места, значит новый сокет будет подключён к новой группе, для чего будет сделана новая запись и подключена к списку групп принадлежащих сокету. Память у нас уже выделена, и нам остаётся только установить правильные значения для некоторых полей структур.

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


Функция ip_mc_leave_group() вызывается при выходе из multicast-группы, и она намного проще чем предыдущая функция. Она получает адрес интерфейса и группы, и ищет их среди записей относящихся к текущему сокету. Если находит запись, то уменьшает на еденицу число ссылок, так как на один процесс связанный с группой становится меньше. Если новое значение равно нулю, то счётчик удаляется.

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


Остальные multicast-опции перечисленные выше - очень простые, поскольку они всего-лишь устанавливают значения в поля данных внутренних структур связаных с сокетами с которыми мы в данный момент работаем. Эти присваивания выполняются непосредственно через функцию ip_setsockopt().

 

Страница отзывов

У каждой заметки есть страница отзывов. На этой странице вы можете оставить свой комментарий или просмотреть комментарии других читателей.
 talkback page 

Webpages maintained by the LinuxFocus Editor team
© Angel Lopez, FDL
LinuxFocus.org

Click here to report a fault or send a comment to LinuxFocus
Translation information:
es -> -- Angel Lopez
es -> en Javier Palacios
en -> ru Denis Kolobynin

2001-09-01, generated by lfparser version 2.17