UDP-Broadcast на всех интерфейсах - PullRequest
17 голосов
/ 26 марта 2009

В системе Linux с проводным и беспроводным интерфейсом (например, подсети 192.168.1.x и 192.168.2.x) я хочу отправить широковещательную рассылку UDP, которая выходит через ВСЕ доступные интерфейсы (т.е. как через проводной, так и через беспроводной интерфейс).

В настоящее время я отправляю () INADDR_BROADCAST, однако кажется, что широковещательная рассылка отправляется только через один из интерфейсов (не всегда одинаковые и последующие широковещательные рассылки могут использовать другой интерфейс).

Есть ли способ, которым я могу отправить широковещательную рассылку UDP, которая выходит через каждый интерфейс?

Ответы [ 3 ]

27 голосов
/ 26 марта 2009

Прежде всего, вы должны считать вещание устаревшим, особенно INADDR_BROADCAST (255.255.255.255). Ваш вопрос выдвигает на первый план именно одну из причин, почему трансляция не подходит. Он должен умереть вместе с IPv4 (надеюсь). Обратите внимание, что IPv6 даже не имеет понятия широковещания (вместо этого используется многоадресная передача).

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

С одним sendto() генерируется только один пакет, и исходящий интерфейс определяется таблицей маршрутизации операционной системы (ip route в linux). У вас не может быть одного sendto(), генерирующего более одного пакета, вам придется перебирать все интерфейсы и либо использовать необработанные сокеты, либо привязывать сокет к устройству, используя setsockopt(..., SOL_SOCKET, SO_BINDTODEVICE, "ethX") для отправки каждого пакета в обход таблицы маршрутизации ОС. (это требует привилегий root). Не хорошее решение.

Вместо этого, поскольку INADDR_BROADCAST в любом случае не маршрутизируется, вы можете достичь почти того же самого, перебирая каждый интерфейс и отправляя пакет на его широковещательный адрес. Например, предполагая, что ваши сети имеют маски 255.255.255.0 (/ 24), широковещательные адреса: 192.168.1.255 и 192.168.2.255 . Позвоните sendto() один раз для каждого из этих адресов, и вы достигнете своей цели.


Редактировать: исправлена ​​информация, касающаяся INADDR_BROADCAST, и дополнить ответ информацией о SO_BINDTODEVICE.

5 голосов
/ 26 марта 2009

С Решение Джереми в UNIX Socket FAQ:

#include <stdio.h>

#ifdef WIN32
# include <windows.h>
# include <winsock.h>
# include <iphlpapi.h>
#else
# include <unistd.h>
# include <stdlib.h>
# include <sys/socket.h>
# include <netdb.h>
# include <netinet/in.h>
# include <net/if.h>
# include <sys/ioctl.h>
#endif

#include <string.h>
#include <sys/stat.h>

typedef unsigned long uint32;

#if defined(__FreeBSD__) || defined(BSD) || defined(__APPLE__) || defined(__linux__)
# define USE_GETIFADDRS 1
# include <ifaddrs.h>
static uint32 SockAddrToUint32(struct sockaddr * a)
{
   return ((a)&&(a->sa_family == AF_INET)) ? ntohl(((struct sockaddr_in *)a)->sin_addr.s_addr) : 0;
}
#endif

// convert a numeric IP address into its string representation
static void Inet_NtoA(uint32 addr, char * ipbuf)
{
   sprintf(ipbuf, "%li.%li.%li.%li", (addr>>24)&0xFF, (addr>>16)&0xFF, (addr>>8)&0xFF, (addr>>0)&0xFF);
}

// convert a string represenation of an IP address into its numeric equivalent
static uint32 Inet_AtoN(const char * buf)
{
   // net_server inexplicably doesn't have this function; so I'll just fake it
   uint32 ret = 0;
   int shift = 24;  // fill out the MSB first
   bool startQuad = true;
   while((shift >= 0)&&(*buf))
   {
      if (startQuad)
      {
         unsigned char quad = (unsigned char) atoi(buf);
         ret |= (((uint32)quad) << shift);
         shift -= 8;
      }
      startQuad = (*buf == '.');
      buf++;
   }
   return ret;
}

static void PrintNetworkInterfaceInfos()
{
#if defined(USE_GETIFADDRS)
   // BSD-style implementation
   struct ifaddrs * ifap;
   if (getifaddrs(&ifap) == 0)
   {
      struct ifaddrs * p = ifap;
      while(p)
      {
         uint32 ifaAddr  = SockAddrToUint32(p->ifa_addr);
         uint32 maskAddr = SockAddrToUint32(p->ifa_netmask);
         uint32 dstAddr  = SockAddrToUint32(p->ifa_dstaddr);
         if (ifaAddr > 0)
         {
            char ifaAddrStr[32];  Inet_NtoA(ifaAddr,  ifaAddrStr);
            char maskAddrStr[32]; Inet_NtoA(maskAddr, maskAddrStr);
            char dstAddrStr[32];  Inet_NtoA(dstAddr,  dstAddrStr);
            printf("  Found interface:  name=[%s] desc=[%s] address=[%s] netmask=[%s] broadcastAddr=[%s]\n", p->ifa_name, "unavailable", ifaAddrStr, maskAddrStr, dstAddrStr);
         }
         p = p->ifa_next;
      }
      freeifaddrs(ifap);
   }
#elif defined(WIN32)
   // Windows XP style implementation

   // Adapted from example code at http://msdn2.microsoft.com/en-us/library/aa365917.aspx
   // Now get Windows' IPv4 addresses table.  Once again, we gotta call GetIpAddrTable()
   // multiple times in order to deal with potential race conditions properly.
   MIB_IPADDRTABLE * ipTable = NULL;
   {
      ULONG bufLen = 0;
      for (int i=0; i<5; i++)
      {
         DWORD ipRet = GetIpAddrTable(ipTable, &bufLen, false);
         if (ipRet == ERROR_INSUFFICIENT_BUFFER)
         {
            free(ipTable);  // in case we had previously allocated it
            ipTable = (MIB_IPADDRTABLE *) malloc(bufLen);
         }
         else if (ipRet == NO_ERROR) break;
         else
         {
            free(ipTable);
            ipTable = NULL;
            break;
         }
     }
   }

   if (ipTable)
   {
      // Try to get the Adapters-info table, so we can given useful names to the IP
      // addresses we are returning.  Gotta call GetAdaptersInfo() up to 5 times to handle
      // the potential race condition between the size-query call and the get-data call.
      // I love a well-designed API :^P
      IP_ADAPTER_INFO * pAdapterInfo = NULL;
      {
         ULONG bufLen = 0;
         for (int i=0; i<5; i++)
         {
            DWORD apRet = GetAdaptersInfo(pAdapterInfo, &bufLen);
            if (apRet == ERROR_BUFFER_OVERFLOW)
            {
               free(pAdapterInfo);  // in case we had previously allocated it
               pAdapterInfo = (IP_ADAPTER_INFO *) malloc(bufLen);
            }
            else if (apRet == ERROR_SUCCESS) break;
            else
            {
               free(pAdapterInfo);
               pAdapterInfo = NULL;
               break;
            }
         }
      }

      for (DWORD i=0; i<ipTable->dwNumEntries; i++)
      {
         const MIB_IPADDRROW & row = ipTable->table[i];

         // Now lookup the appropriate adaptor-name in the pAdaptorInfos, if we can find it
         const char * name = NULL;
         const char * desc = NULL;
         if (pAdapterInfo)
         {
            IP_ADAPTER_INFO * next = pAdapterInfo;
            while((next)&&(name==NULL))
            {
               IP_ADDR_STRING * ipAddr = &next->IpAddressList;
               while(ipAddr)
               {
                  if (Inet_AtoN(ipAddr->IpAddress.String) == ntohl(row.dwAddr))
                  {
                     name = next->AdapterName;
                     desc = next->Description;
                     break;
                  }
                  ipAddr = ipAddr->Next;
               }
               next = next->Next;
            }
         }
         char buf[128];
         if (name == NULL)
         {
            sprintf(buf, "unnamed-%i", i);
            name = buf;
         }

         uint32 ipAddr  = ntohl(row.dwAddr);
         uint32 netmask = ntohl(row.dwMask);
         uint32 baddr   = ipAddr & netmask;
         if (row.dwBCastAddr) baddr |= ~netmask;

         char ifaAddrStr[32];  Inet_NtoA(ipAddr,  ifaAddrStr);
         char maskAddrStr[32]; Inet_NtoA(netmask, maskAddrStr);
         char dstAddrStr[32];  Inet_NtoA(baddr,   dstAddrStr);
         printf("  Found interface:  name=[%s] desc=[%s] address=[%s] netmask=[%s] broadcastAddr=[%s]\n", name, desc?desc:"unavailable", ifaAddrStr, maskAddrStr, dstAddrStr);
      }

      free(pAdapterInfo);
      free(ipTable);
   }
#else
   // Dunno what we're running on here!
#  error "Don't know how to implement PrintNetworkInterfaceInfos() on this OS!"
#endif
}

int main(int, char **)
{
   PrintNetworkInterfaceInfos();
   return 0;
}
4 голосов
/ 26 марта 2009

Вы не можете иметь один sendto(), генерирующий пакет на каждом интерфейсе - в общем (несмотря на фрагментацию) это один пакет, переданный для каждого sendto().

Вам нужно будет передать пакет один раз для каждого интерфейса и либо:

  1. использовать низкоуровневые (setsockopt()?) Вызовы для выбора исходящего интерфейса

  2. отправить на конкретный широковещательный адрес для каждого известного интерфейса

последнее, однако, не подходит, если вы пытаетесь использовать какой-то механизм обнаружения, так что устройства, на которые вы ожидаете ответить, на самом деле не правильно настроены с IP-адресом в той же подсети, что и интерфейс подключен к.

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