75

I am using multicast UDP between hosts that have multiple network interfaces. I am using boost::asio, and am confused by the 2 operations receivers have to make: bind, then join-group.

Why do you need to specify the local address of an interface, during bind, when you do that with every multicast group that you join?

The sister-question regards the multicast port: Since during sending, you send to a multicast address & port, why, during subscription to a multicast group, you only specify the address, not the port - the port being specified in the confusing call to bind.

Note: the "join-group" is a wrapper over setsockopt(IP_ADD_MEMBERSHIP), which as documented, may be called multiple times on the same socket to subscribe to different groups (over different networks?). It would therefore make perfect sense to ditch the bind call and specify the port every time I subscribe to a group.

From what I see, always binding to "0.0.0.0" and specifying the interface address when joining the group, works very well. Confused.

Yves M.
  • 26,153
  • 20
  • 93
  • 125
haelix
  • 3,405
  • 3
  • 26
  • 44

4 Answers4

65

To bind a UDP socket when receiving multicast means to specify an address and port from which to receive data (NOT a local interface, as is the case for TCP acceptor bind). The address specified in this case has a filtering role, i.e. the socket will only receive datagrams sent to that multicast address & port, no matter what groups are subsequently joined by the socket. This explains why when binding to INADDR_ANY (0.0.0.0) I received datagrams sent to my multicast group, whereas when binding to any of the local interfaces I did not receive anything, even though the datagrams were being sent on the network to which that interface corresponded.

Quoting from UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking API by W.R Stevens. 21.10. Sending and Receiving

[...] We want the receiving socket to bind the multicast group and port, say 239.255.1.2 port 8888. (Recall that we could just bind the wildcard IP address and port 8888, but binding the multicast address prevents the socket from receiving any other datagrams that might arrive destined for port 8888.) We then want the receiving socket to join the multicast group. The sending socket will send datagrams to this same multicast address and port, say 239.255.1.2 port 8888.

haelix
  • 3,405
  • 3
  • 26
  • 44
  • Do this experiment: in same app - create 2 sockets, join each to a different group. Send traffic to both groups (on identical port numbers!) - If you do not set the address when binding you would get traffic for both groups, I think ... – nhed Oct 30 '13 at 19:34
  • 1
    @nhed: On Linux it does not even have to be in the same process. When binding to 0.0.0.0 you will receive all multicast traffic to that port for which you and other processes on the host added a group membership. – Johannes Overmann Nov 22 '13 at 10:30
  • @JohannesOvermann, agreed. I was just proposing a simple test to show that the OP assertion `always binding to "0.0.0.0" and specifying the interface address when joining the group, works very well` is wrong – nhed Nov 22 '13 at 19:31
  • WHy is it wrong? Note: I wasn't very explicit in what I mean by "works very well". What I meant was that binding to "0.0.0.0" helped me receive traffic, as opposed to binding to the local interface IP for the network over which traffic was sent, which did not help. The accepted answer explains why that was the case. – haelix Dec 03 '13 at 16:22
55

The "bind" operation is basically saying, "use this local UDP port for sending and receiving data. In other words, it allocates that UDP port for exclusive use for your application. (Same holds true for TCP sockets).

When you bind to "0.0.0.0" (INADDR_ANY), you are basically telling the TCP/IP layer to use all available adapters for listening and to choose the best adapter for sending. This is standard practice for most socket code. The only time you wouldn't specify 0 for the IP address is when you want to send/receive on a specific network adapter.

Similarly if you specify a port value of 0 during bind, the OS will assign a randomly available port number for that socket. So I would expect for UDP multicast, you bind to INADDR_ANY on a specific port number where multicast traffic is expected to be sent to.

The "join multicast group" operation (IP_ADD_MEMBERSHIP) is needed because it basically tells your network adapter to listen not only for ethernet frames where the destination MAC address is your own, it also tells the ethernet adapter (NIC) to listen for IP multicast traffic as well for the corresponding multicast ethernet address. Each multicast IP maps to a multicast ethernet address. When you use a socket to send to a specific multicast IP, the destination MAC address on the ethernet frame is set to the corresponding multicast MAC address for the multicast IP. When you join a multicast group, you are configuring the NIC to listen for traffic sent to that same MAC address (in addition to its own).

Without the hardware support, multicast wouldn't be any more efficient than plain broadcast IP messages. The join operation also tells your router/gateway to forward multicast traffic from other networks. (Anyone remember MBONE?)

If you join a multicast group, all the multicast traffic for all ports on that IP address will be received by the NIC. Only the traffic destined for your binded listening port will get passed up the TCP/IP stack to your app. In regards to why ports are specified during a multicast subscription - it's because multicast IP is just that - IP only. "ports" are a property of the upper protocols (UDP and TCP).

You can read more about how multicast IP addresses map to multicast ethernet addresses at various sites. The Wikipedia article is about as good as it gets:

The IANA owns the OUI MAC address 01:00:5e, therefore multicast packets are delivered by using the Ethernet MAC address range 01:00:5e:00:00:00 - 01:00:5e:7f:ff:ff. This is 23 bits of available address space. The first octet (01) includes the broadcast/multicast bit. The lower 23 bits of the 28-bit multicast IP address are mapped into the 23 bits of available Ethernet address space.

pynexj
  • 15,152
  • 5
  • 24
  • 45
selbie
  • 82,148
  • 13
  • 83
  • 154
  • 1
    Thanks for your interest, selbie. So the use of bind(0.0.0.0) is relevant because it specifies the interface on which to receive incoming _unicast_ UDP packets? I tried settings other than 0.0.0.0 when binding, nothing works (not even the specific interface to the network over which multicast traffic is sent). I still don't undestand the meaning of the bind() address. – haelix May 22 '12 at 07:22
  • Small note, for TCP it is very clear what `bind(interfAddr, port)` does. It will accept connections only from that specific network, I have verified this to be correct. But for UDP sockets, the bind address seems redundant with the second argument of IP_ADD_MEMBERSHIP. Not exactly redundant since setting it doesn't seem to work - only 0.0.0.0 works. – haelix May 22 '12 at 07:58
  • Not sure what you mean by "nothing works". Post some code demonstrating the problem. As for the redundancy issue - I suppose you could bind to all adapters (INADDR_ANY == 0.0.0.0) and then do a multicast registration on a specific interface. I suspect they just wanted the interface to be flexible. Go read section 6.4 of this link - http://www.tldp.org/HOWTO/Multicast-HOWTO-6.html on a similar discussion about why the interface needs to be passed. – selbie May 22 '12 at 09:30
  • By 'nothing works' I meant that no address other than INADDR_ANY (0.0.0.0) is suitable when binding the UDP socket (not even the address of the interface over which multicast traffic arrives at the machine i.e. the one specified as second argument when joining the group). There is no error during bind, It's just that `read()` yields no result (hangs forever). – haelix May 22 '12 at 11:29
  • I just found another guy puzzled about the same thing but in java: [link](http://stackoverflow.com/questions/9469036/why-datagramsocketimpl-joingroup-method-takes-a-networkinterface-if-the-socket-m). The answers are pretty much in-line, with the addition that you can bind to the multicast address (!). If I were sarcastic I would say that the API gives you the option of redundantly specifying either the local interface address, or the multicast address. Anyway, it's good that it works even if not 100% clear. +1, for now – haelix May 22 '12 at 11:50
  • If you are checking the return call to bind(), and it didn't return -1, then it is likely working. You likely are not sending data to the socket from the network that it expects. – selbie May 22 '12 at 16:37
  • 1
    This seems not right for multicast receiver. The IP here is not an interface. It is either ANY, or the multicast group address. – WiSaGaN Jun 02 '16 at 07:31
  • My understanding from this is, "bind socket to IP address" -> to mention the IP address and port ( or equivalent network interface) where you want to listen to the data (unicast or multicast traffic) . "channel joins a group" -> listen to the data sent to specified multicast ip address and on a network interface which is provided as 2nd param. now the network interface param should be same the interface specified during the bind operation or can it be different, and is a local network interface? – chebus Aug 11 '16 at 13:38
11

Correction for What does it mean to bind a multicast (udp) socket? as long as it partially true at the following quote:

The "bind" operation is basically saying, "use this local UDP port for sending and receiving data. In other words, it allocates that UDP port for exclusive use for your application

There is one exception. Multiple applications can share the same port for listening (usually it has practical value for multicast datagrams), if the SO_REUSEADDR option applied. For example

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);

If several processes did such "reuse binding", then every UDP datagram received on that shared port will be delivered to each of the processes (providing natural joint with multicasts traffic).

Here are further details regarding what happens in a few cases:

  1. attempt of any bind ("exclusive" or "reuse") to free port will be successful

  2. attempt to "exclusive binding" will fail if the port is already "reuse-binded"

  3. attempt to "reuse binding" will fail if some process keeps "exclusive binding"

nbro
  • 12,226
  • 19
  • 85
  • 163
Yury Schkatula
  • 4,650
  • 2
  • 16
  • 35
7

It is also very important to distinguish a SENDING multicast socket from a RECEIVING multicast socket.

I agree with all the answers above regarding RECEIVING multicast sockets. The OP noted that binding a RECEIVING socket to an interface did not help. However, it is necessary to bind a multicast SENDING socket to an interface.

For a SENDING multicast socket on a multi-homed server, it is very important to create a separate socket for each interface you want to send to. A bound SENDING socket should be created for each interface.

  // 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) );
     }

Without this fix, multicast sending will intermittently get sendto() errno 'No route to host'. If anyone can shed light on why unplugging a DHCP gateway causes Mac OS X multicast SENDING sockets to get confused, I would love to hear it.

nbro
  • 12,226
  • 19
  • 85
  • 163
Keith Knauber
  • 734
  • 6
  • 13