Что значит связать многоадресный (UDP) сокет?

Я использую многоадресный UDP между узлами, которые имеют несколько сетевых интерфейсов. Я использую boost::asio, и меня смущают 2 операции, которые приходится выполнять получателям: bind, затем join-group.

Зачем вам нужно указывать локальный адрес интерфейса во время связывания, когда вы делаете это с каждой многоадресной группой, к которой вы присоединяетесь?

Вопрос сестры касается порта многоадресной рассылки: поскольку во время отправки вы отправляете адрес и порт многоадресной рассылки, почему при подписке на группу многоадресной рассылки вы указываете только адрес, а не порт - порт, указанный в сбивающем вызове привязывать.

Примечание: "join-group" - это оболочка над setsockopt(IP_ADD_MEMBERSHIP), что, как задокументировано, может вызываться несколько раз в одном сокете для подписки на разные группы (в разных сетях?). Поэтому было бы разумно отказаться от вызова привязки и указывать порт каждый раз, когда я подписываюсь на группу.

Из того, что я вижу, всегда привязка к "0.0.0.0" и указание адреса интерфейса при присоединении к группе работает очень хорошо. Смущенный.

4 ответа

Решение

Привязка сокета UDP при получении многоадресной рассылки означает указание адреса и порта, с которого следует получать данные (НЕ локальный интерфейс, как в случае с привязкой к TCP-получателю). Адрес, указанный в этом случае, играет роль фильтра, т.е. сокет будет принимать только дейтаграммы, отправленные на этот адрес и порт многоадресной рассылки, независимо от того, к каким группам впоследствии присоединяется сокет. Это объясняет, почему при привязке к INADDR_ANY (0.0.0.0) я получал дейтаграммы, отправленные в мою многоадресную группу, тогда как при привязке к любому из локальных интерфейсов я ничего не получал, даже если дейтаграммы отправлялись в сети, к которой этот интерфейс соответствует.

Цитата из UNIX® Network Programming, том 1, третье издание: API-интерфейс для работы с сокетами WR Stevens. 21,10. Отправка и получение

[...] Мы хотим, чтобы принимающий сокет связывал многоадресную группу и порт, скажем, 239.255.1.2, порт 8888. (Напомним, что мы могли бы просто связать универсальный IP-адрес и порт 8888, но привязка многоадресного адреса не позволяет сокету получать любые другие дейтаграммы, которые могли бы прибыть, предназначенные для порта 8888.) Затем мы хотим, чтобы принимающий сокет присоединился к многоадресной группе. Посылающий сокет будет отправлять дейтаграммы на тот же адрес и порт многоадресной рассылки, скажем 239.255.1.2 порт 8888.

Операция "bind" в основном говорит: "используйте этот локальный порт UDP для отправки и получения данных. Другими словами, он выделяет этот порт UDP для исключительного использования для вашего приложения. (То же самое относится и к сокетам TCP).

Когда вы связываете "0.0.0.0" (INADDR_ANY), вы в основном говорите уровню TCP/IP использовать все доступные адаптеры для прослушивания и выбирать лучший адаптер для отправки. Это стандартная практика для большинства кодов сокетов. Единственный раз, когда вы не указали бы 0 для IP-адреса, это когда вы хотите отправлять / получать на конкретном сетевом адаптере.

Точно так же, если вы укажете значение порта 0 во время привязки, ОС назначит произвольно доступный номер порта для этого сокета. Таким образом, я ожидаю, что для многоадресной рассылки UDP вы связываетесь с INADDR_ANY по определенному номеру порта, на который ожидается отправка многоадресного трафика.

Операция "присоединиться к многоадресной группе" (IP_ADD_MEMBERSHIP) необходим, потому что он в основном говорит сетевому адаптеру прослушивать не только кадры Ethernet, где MAC-адрес назначения является вашим собственным, но также и сетевому адаптеру ( NIC) прослушивать IP-трафик многоадресной рассылки, а также соответствующий многоадресный Ethernet-адрес. Каждый многоадресный IP-адрес сопоставляется с многоадресным Ethernet-адресом. Когда вы используете сокет для отправки на определенный IP-адрес многоадресной рассылки, для MAC-адреса назначения в кадре Ethernet устанавливается соответствующий MAC-адрес многоадресной рассылки для IP-адреса многоадресной рассылки. Когда вы присоединяетесь к группе многоадресной рассылки, вы настраиваете NIC для прослушивания трафика, отправляемого на тот же MAC-адрес (в дополнение к его собственному).

Без аппаратной поддержки многоадресная рассылка не была бы более эффективной, чем обычные широковещательные IP-сообщения. Операция объединения также сообщает вашему маршрутизатору / шлюзу о необходимости пересылать многоадресный трафик из других сетей. (Кто-нибудь помнит MBONE?)

Если вы присоединитесь к многоадресной группе, весь многоадресный трафик для всех портов на этом IP-адресе будет получен сетевым адаптером. Только трафик, предназначенный для вашего связанного порта прослушивания, будет передаваться в стек TCP/IP вашему приложению. Относительно того, почему порты указываются во время многоадресной подписки - это потому, что многоадресный IP - это просто - только IP. "порты" являются свойством верхних протоколов (UDP и TCP).

Вы можете узнать больше о том, как многоадресные IP-адреса сопоставляются с многоадресными Ethernet-адресами на разных сайтах. Статья в Википедии хороша:

IANA является владельцем MAC-адреса OUI 01:00:5e, поэтому многоадресные пакеты доставляются с использованием диапазона MAC-адресов Ethernet 01:00:5e:00:00:00 - 01:00:5e:7f:ff:ff. Это 23 бита доступного адресного пространства. Первый октет (01) включает бит широковещательной / многоадресной передачи. Младшие 23 бита 28-битного IP-адреса многоадресной рассылки отображаются в 23 бита доступного адресного пространства Ethernet.

Исправление для Что означает связывание многоадресного (udp) сокета? пока это частично верно в следующей цитате:

Операция "bind" в основном говорит: "используйте этот локальный порт UDP для отправки и получения данных. Другими словами, он выделяет этот порт UDP для исключительного использования для вашего приложения".

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

int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // create UDP socket somehow
...
int set_option_on = 1;
// it is important to do "reuse address" before bind, not after
int res = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*) &set_option_on, 
    sizeof(set_option_on));
res = bind(sock, src_addr, len);

Если несколько процессов выполняли такую ​​"привязку повторного использования", то каждая дейтаграмма UDP, полученная на этот общий порт, будет доставлена ​​каждому из процессов (обеспечивая естественное соединение с многоадресным трафиком).

Вот дальнейшие детали относительно того, что происходит в нескольких случаях:

  1. попытка любого связывания ("эксклюзивный" или "повторное использование") с свободным портом будет успешной

  2. Попытка "исключительного связывания" не удастся, если порт уже "повторно связан"

  3. попытка "повторного использования привязки" не удастся, если какой-то процесс сохранит "исключительную привязку"

Также очень важно отличать многоадресный сокет SENDING от многоадресного сокета RECEIVING.

Я согласен со всеми ответами выше относительно получения многоадресных сокетов. ОП отметил, что привязка разъема RECEIVING к интерфейсу не помогла. Однако необходимо привязать многоадресный сокет SENDING к интерфейсу.

Для многоадресного сокета SENDING на многосетевом сервере очень важно создать отдельный сокет для каждого интерфейса, который вы хотите отправить. Связанный сокет SENDING должен быть создан для каждого интерфейса.

  // This is a fix for that bug that causes Servers to pop offline/online.
  // Servers will intermittently pop offline/online for 10 seconds or so.
  // The bug only happens if the machine had a DHCP gateway, and the gateway is no longer accessible.
  // After several minutes, the route to the DHCP gateway may timeout, at which
  // point the pingponging stops.
  // You need 3 machines, Client machine, server A, and server B
  // Client has both ethernets connected, and both ethernets receiving CITP pings (machine A pinging to en0, machine B pinging to en1)
  // Now turn off the ping from machine B (en1), but leave the network connected.
  // You will notice that the machine transmitting on the interface with
  // the DHCP gateway will fail sendto() with errno 'No route to host'
  if ( theErr == 0 )
  {
     // inspired by 'ping -b' option in man page:      
     //      -b boundif
     //             Bind the socket to interface boundif for sending.
     struct sockaddr_in bindInterfaceAddr;
     bzero(&bindInterfaceAddr, sizeof(bindInterfaceAddr));
     bindInterfaceAddr.sin_len = sizeof(bindInterfaceAddr);
     bindInterfaceAddr.sin_family = AF_INET;
     bindInterfaceAddr.sin_addr.s_addr = htonl(interfaceipaddr);
     bindInterfaceAddr.sin_port = 0; // Allow the kernel to choose a random port number by passing in 0 for the port.
     theErr = bind(mSendSocketID, (struct sockaddr *)&bindInterfaceAddr, sizeof(bindInterfaceAddr));
     struct sockaddr_in serverAddress;
     int namelen = sizeof(serverAddress);  
     if (getsockname(mSendSocketID, (struct sockaddr *)&serverAddress, (socklen_t *)&namelen) < 0) {
        DLogErr(@"ERROR Publishing service... getsockname err");
     }
     else
     {
        DLog( @"socket %d bind, %@ port %d", mSendSocketID, [NSString stringFromIPAddress:htonl(serverAddress.sin_addr.s_addr)], htons(serverAddress.sin_port) );
     }

Без этого исправления многоадресная отправка будет периодически получать sendto() errno 'No route to host'. Если кто-то может пролить свет на то, почему отключение DHCP-шлюза приводит к путанице в многоадресных SENDING-сокетах Mac OS X, я хотел бы услышать это.

Другие вопросы по тегам