Von Jeremys Lösung zu 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;
}
Zunächst einmal sollten Sie Broadcast als obsolet betrachten, insbesondere INADDR_BROADCAST
(255.255.255.255). Ihre Frage hebt genau einen der Gründe hervor, warum die Übertragung ungeeignet ist. Es sollte zusammen mit IPv4 sterben (hoffentlich). Beachten Sie, dass IPv6 nicht einmal ein Broadcast-Konzept hat (stattdessen wird Multicast verwendet).
INADDR_BROADCAST
ist auf den lokalen Link beschränkt. Heutzutage wird es nur für die automatische DHCP-Konfiguration verwendet, da der Client zu diesem Zeitpunkt noch nicht weiß, mit welchem Netzwerk er verbunden ist.
Mit einem einzigen sendto()
, wird nur ein einziges Paket generiert und die ausgehende Schnittstelle wird durch die Routing-Tabelle des Betriebssystems bestimmt (ip route
unter Linux). Sie können keinen einzigen sendto()
haben mehr als ein Paket generieren, müssten Sie über alle Schnittstellen iterieren und entweder Raw-Sockets verwenden oder den Socket mit setsockopt(..., SOL_SOCKET, SO_BINDTODEVICE, "ethX")
an ein Gerät binden um jedes Paket unter Umgehung der OS-Routing-Tabelle zu senden (dies erfordert Root-Rechte). Keine gute Lösung.
Stattdessen seit INADDR_BROADCAST
sowieso nicht geroutet wird, können Sie fast dasselbe erreichen, indem Sie über jede Schnittstelle iterieren und das Paket an seine Broadcast-Adresse senden. Angenommen, Ihre Netzwerke haben beispielsweise 255.255.255.0 (/24)-Masken, lauten die Broadcast-Adressen 192.168.1.255 und 192.168.2.255 . Rufen Sie sendto()
an einmal für jede dieser Adressen und Sie haben Ihr Ziel erreicht.
Bearbeiten: Informationen zu INADDR_BROADCAST
behoben , und ergänzen Sie die Antwort mit Informationen zu SO_BINDTODEVICE
.
Sie können keinen einzigen sendto()
haben ein Paket auf jeder Schnittstelle erzeugen - im Allgemeinen (ungeachtet der Fragmentierung) wird ein Paket für jeden sendto()
übertragen .
Sie müssen das Paket einmal für jede Schnittstelle übertragen und entweder:
-
Verwenden Sie Low-Level (
setsockopt()
?) Aufrufe zur Auswahl der ausgehenden Schnittstelle -
an die spezifische Broadcast-Adresse für jede bekannte Schnittstelle senden
Letzteres ist jedoch nicht geeignet, wenn Sie versuchen, eine Art Erkennungsmechanismus durchzuführen, sodass die Geräte, von denen Sie erwarten, dass sie antworten, nicht richtig mit einer IP-Adresse im selben Subnetz wie die Schnittstelle konfiguriert sind, mit der sie verbunden sind zu.