My Winsock Delphi application should listen on all network interfaces for multicast UDP/IP stream. It listened normally until I tried it on another PC with different network adapters' priority order.
Then I started to research problem and found on some forums that INADDR_ANY
(or 0.0.0.0
) has different meaning in Windows and Linux:
0.0.0.1
for second one). Citation: "If this member specifies an IPv4 address of 0.0.0.0, the default IPv4 multicast interface is used" - without mentioning whether it is for listening or for sending.Could you confirm or deny this?
How to listen really on all interfaces?
Here is a little piece of my code:
TMulticastListener = class(TThread)
private
mreq: ip_mreq;
............
end;
constructor TMulticastListener.Create;
var err: Integer;
wData: WsaData;
reuse: Integer;
begin
inherited Create(true);
err := WSAStartup(MAKEWORD(2, 2), wData);
if err = SOCKET_ERROR then begin
// Tell the user that we could not find a usable Winsock DLL
perror('WSAStartup');
Exit;
end;
// create what looks like an ordinary UDP socket
fd := socket(AF_INET, SOCK_DGRAM, 0);
if fd = INVALID_SOCKET then begin
perror('socket');
Exit;
end;
reuse := 1;
// allow multiple sockets to use the same PORT number
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, Pointer(@reuse), SizeOf(reuse)) < 0) then begin
perror('Reusing ADDR failed');
Exit;
end;
// set up destination address
FillChar(addr, sizeof(addr), 0);
addr.sin_family := AF_INET;
addr.sin_addr.s_addr := htonl(INADDR_ANY); // N.B.: differs from sender
addr.sin_port := htons(HELLO_PORT);
// bind to receive address
if (bind(fd, addr, sizeof(addr)) < 0) then begin
perror('bind');
Exit;
end;
// use setsockopt() to request that the kernel join a multicast group
mreq.imr_multiaddr.s_addr := inet_addr(HELLO_GROUP);
mreq.imr_interface.s_addr := htonl(INADDR_ANY); //inet_addr('0.0.0.0');
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, @mreq, sizeof(mreq)) < 0) then begin
perror('setsockopt');
Exit;
end;
end;
This is an IP address that is used when we don't want to bind a socket to any specific IP. Basically, while implementing communication, we need to bind our socket to an IP address. When we don't know the IP address of our machine, we can use the special IP address INADDR_ANY .
The bind() function binds a unique local name to the socket with descriptor socket. After calling socket(), a descriptor does not have a name associated with it. However, it does belong to a particular address family as specified when socket() is called. The exact format of a name depends on the address family.
Macro: uint32_t INADDR_LOOPBACK. You can use this constant to stand for “the address of this machine,” instead of finding its actual address. It is the IPv4 Internet address ' 127.0. 0.1 ', which is usually called ' localhost '.
AF_INET is an address family that is used to designate the type of addresses that your socket can communicate with (in this case, Internet Protocol v4 addresses). When you create a socket, you have to specify its address family, and then you can only use addresses of that type with the socket.
When INADDR_ANY is specified in the bind call, the socket will be bound to all local interfaces. When listen (2) is called on an unbound socket, the socket is automatically bound to a random free port with the local address set to INADDR_ANY.
This means that on Linux and macOS, you can generally use all of the general purpose file functions with socket handles (e.g. read (), write () ). On Windows, socket handles can only be used with special socket functions. On Windows you can set some socket features with the ioctlsocket () function.
On Windows 64 bits, the SOCKET type is twice larger in size. The socket descriptor on BSD does not differ from the file descriptor. It means that some system calls accept descriptors of sockets and files simultaneously (for example, such commonly used calls as close (), fcntl (), and ioctl () ).
bind () of INADDR_ANY does NOT "generate a random IP". It binds the socket to all available interfaces. For a server, you typically want to bind to all interfaces - not just "localhost".
Windows and Linux actually behave the same regarding the use of INADDR_ANY
. The confusion here is because the two links you provide are being used in different contexts.
When using the bind
function to bind to an address/port, specifying INADDR_ANY
means that the socket will be able to receive packets on the given port from any interface. However, doing so does not set up anything regarding multicast.
In the context of the IP_ADD_MEMBERSHIP
call to setsockopt
, setting the interface to INADDR_ANY
will have the system join the given multicast group on the default network interface.
The Linux link you gave refers to bind
, while the Windows link refers to setsockopt
and IP_ADD_MEMBERSHIP
.
If you want to join the multicast group on all interfaces, you need to retrieve the list of interfaces on the system and join each one. On Windows, the GetAdaptersAddresses()
function will give you the list of interfaces.
On Linux, use the getifaddrs()
function.
Here's an example of how to use the GetAdaptersAddresses()
function in C:
struct iflist {
char name[50];
struct sockaddr_in sin;
int isloopback;
int ismulti;
int ifidx;
};
void getiflist(struct iflist *list, int *len)
{
IP_ADAPTER_ADDRESSES *head, *curr;
IP_ADAPTER_UNICAST_ADDRESS *uni;
char *buf;
int buflen, err, i;
buflen = 100000;
buf = calloc(buflen, 1);
head = (IP_ADAPTER_ADDRESSES *)buf;
if ((err = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, head,
&buflen)) != ERROR_SUCCESS) {
char errbuf[300];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err,
0, errbuf, sizeof(errbuf), NULL);
printf("GetAdaptersAddresses failed: (%d) %s", err, errbuf);
free(buf);
return;
}
for (*len = 0, curr = head; curr; curr = curr->Next) {
if (curr->IfType == IF_TYPE_TUNNEL) continue;
for (uni = curr->FirstUnicastAddress; uni; uni = uni->Next) {
if (curr->OperStatus == IfOperStatusUp) {
memset(&list[*len], 0, sizeof(struct iflist));
strncpy(list[*len].name, (char *)curr->AdapterName,
sizeof(list[i].name) - 1);
memcpy(&list[*len].sin, uni->Address.lpSockaddr,
uni->Address.iSockaddrLength);
list[*len].isloopback =
(curr->IfType == IF_TYPE_SOFTWARE_LOOPBACK);
list[*len].ismulti =
((curr->Flags & IP_ADAPTER_NO_MULTICAST) == 0);
if (uni->Address.lpSockaddr->sa_family == AF_INET6) {
list[*len].ifidx = curr->Ipv6IfIndex;
} else {
list[*len].ifidx = curr->IfIndex;
}
(*len)++;
}
}
}
free(buf);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With