Эта заметка доступна на: 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 (очень хорошо помогает при бессоннице ;)) Содержание: |
Резюме:
Эта статья является введением в технологии мультикастинга в сетях 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-документов, поясняющие методы для выполнения этого отображения:
Вот несколько специальных групповых адресов IPv4:
Таблица ниже демонстрирует полное пространство групповых адресов, с обычными именами для каждой группы адресов и ассоциированные с ними 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, то пакеты будут распознаны как принадлежащие этому хосту, только в том случае если они будут иметь следующие адреса назначения:
Маршрутизаторы также посылают IGMP сообщения группе 224.0.0.1 запрашивая информацию со всех хостов о том членами каких групп они состоят. Хост, после того как получит такое сообщение, устанавливает счётчик в случайное значение и отвечает только тогда, когда счётчик станет равным 0. Это необходимо для того чтобы предохранить сеть от перегрузки (если все хосты ответят одновременно). Когда хост отвечает, он посылает сообщение multicast-адресу группы и потому каждый другой хост входящий в эту группу увидит это сообщение. Но не будет на него отвечать, так как после того как хост договорится с маршрутизатором о посылке сообщений для этой группы в свою подсеть, другим просто нет смысла повторять то же самое, поскольку нужные сообщения уже будут приходить в подсеть.
Если все хосты входящие в группу отказались от участия в ней, то ни один из них не ответит и маршрутизатор примет решение о прекращении маршрутизации сообщений этой группы в подсеть. Другим вариантом, реализованым в IGMPv2, является посылка хостом желающим выйти из группы сообщения на адрес 224.0.0.2.
Если читатель уже имел опыт программирования сокетов в приложениях, то здесь он дополнительно узнает только пять новых операций с сокетами для работы с опциями многоадресной передачи. Функции 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
Процесс может присоединиться к multicast-группе послав эту опцию с помощью функции setsockopt(). Параметром является структура ip_mreq. Первое поле структуры imr_multiaddr, содержит групповой адрес к которому мы хотим присоединиться. Второе поле, imr_interface, содержит IPv4-адрес интерфейса который мы будем использовать.
Используя эту опцию процесс может отказаться от участия в multicast-группе. Поля в структуре ip_mreq используются также как и в предыдущем пункте.
Эта опция позволит нам указать сетевой интерфейс, который сокет будет использовать для посылки multicast-сообщений. Интерфейс передаётся структурой ip_mreq также как и в предыдущих пунктах.
Устанавливает TTL (Time To Live) для датаграм с групповыми сообщениями послаными с использованием сокета. Значение по умолчанию - 1, предполагает, что датаграмы не будут выходить за пределы локальной подсети.
Когда процесс пошлёт сообщение 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().
|
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:
|
2001-09-01, generated by lfparser version 2.17