Subject: multiple multicast listeners
To: None <tech-net@netbsd.org>
From: David Arnold <arnold@dstc.monash.edu.au>
List: tech-net
Date: 05/09/2001 20:14:52
i'm seeing a problem with some multicast code, and was wondering if
anyone could donate me clue ...

i have a process, listening on a multicast group.  if i start a second
instance of the same program on the same machine, it seems that
multicast packets sent to that group are only delivered to one of the
listeners.

i *think* packets are being delivered to the process that *last sent*
a packet to the group (the processes send a multicast request, and
watch for replies, also multicast).

i've attached a code snippet, but in summary:
- both REUSEADDR and REUSEPORT are set
- the socket is bound to the multicast address and required port
- the socket is joined to INADDR_ANY:port
- loopback is set on.

turning loopback off, binding to INADDR_ANY:port (instead of to the
required class D address), and joining using the ex0 interface's IP
not INADDR_ANY (in the ip_mreq) don't seem to change the result.

tcpdump shows the packets as expected.  ifmcstat shows the group
membership with the correct ethernet MAC address, but the refcount
always seems to be 1 (this seemed odd?)

i'm using 1.5 GENERIC on i386, with a `3Com 3c905-TX 10/100 Ethernet
(rev. 0x0)'.

i'm not seeing this on Solaris, Linux, FreeBSD, etc.  i do have a
report of similar behaviour on OpenBSD.

could anyone suggest what might be causing this?

thanks ...



d

(apologies if this is inappropriate, i scanned the archive: it looked ok?)


-8<----

    struct sockaddr_in name;
    struct hostent *host;
    struct ip_mreq request;
    int value;
    u_char loop;

    /* Construct the socket address */
    memset(&ep->name, 0, sizeof(ep->name));
    ep->name.sin_family = AF_INET;
    ep->name.sin_port = htons(ep->port);

    /* See if the host name is in dot notation */
    if ((ep->name.sin_addr.s_addr = inet_addr(ep->hostname)) == (uint32_t)-1) {
	/* Check with the name service */
	if (! (host = gethostbyname(ep->hostname))) {
	    ELVIN_ERROR_UNIX_GETHOSTBYNAME_FAILED(error,
						  ELVIN_SOCK_H_ERRNO,
						  ep->hostname);
	    return 0;
	}

	ep->name.sin_addr = *(struct in_addr *)host->h_addr;
    }

    /* Create a socket */
    if ((ep->fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
	ELVIN_ERROR_UNIX_SOCKET_FAILED(error, ELVIN_SOCK_ERRNO);
	return 0;
    }

    /* Allow other programs to bind to the socket */
    value = 1;
    if (setsockopt(ep->fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0) {
	ELVIN_ERROR_UNIX_SETSOCKOPT_FAILED(error, ELVIN_SOCK_ERRNO);
	return 0;
    }

#if defined(HAVE_SO_REUSEPORT)
    /* Allow multiple sockets to bind to this interface:port for multicast */
    value = 1;
    if (setsockopt(ep->fd, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)) < 0) {
	ELVIN_ERROR_UNIX_SETSOCKOPT_FAILED(error, ELVIN_SOCK_ERRNO);
	return 0;
    }
#endif

#if defined(HAVE_BROKEN_MCAST_BIND)
    /* For some systems (ie. Irix 6.x) binding the multicast address to
     * the socket succeeds, but then the OS doesn't deliver any packets. 
     * So we simply bind to the default interface as a means of setting
     * the port number, and rely on the marshalling code to discard junk
     * packets. */

    /* Initialize the INADDR_ANY address */
    memset(&name, 0, sizeof(name));
    name.sin_family = AF_INET;
    name.sin_port = htons(ep->port);
    name.sin_addr.s_addr = INADDR_ANY;

    /* Bind socket to the default systems interface */
    if (bind(ep->fd, (struct sockaddr *)&name, sizeof(name)) < 0) {
        ELVIN_ERROR_UNIX_BIND_FAILED(error, ELVIN_SOCK_ERRNO);
        return 0;
    }

#else
    /* Try to bind the multicast address as the socket's interface
     * to prevent stray unicast packets from being delivered (we have to 
     * bind to set the port number).  On some systems this won't work so
     * we fall back to a destination of INADDR_ANY if the first bind fails. */

    if (bind(ep->fd, (struct sockaddr *)&ep->name, sizeof(ep->name)) < 0) {
	/* Initialize the INADDR_ANY address */
	memset(&name, 0, sizeof(name));
	name.sin_family = AF_INET;
	name.sin_port = htons(ep->port);
	name.sin_addr.s_addr = INADDR_ANY;

	/* Try to bind again */
	if (bind(ep->fd, (struct sockaddr *)&name, sizeof(name)) < 0) {
	    ELVIN_ERROR_UNIX_BIND_FAILED(error, ELVIN_SOCK_ERRNO);
	    return 0;
	}
    }
#endif

    /* Join the multicast group */
    request.imr_multiaddr.s_addr = ep->name.sin_addr.s_addr;
    request.imr_interface.s_addr = INADDR_ANY;

    if (setsockopt(ep->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
		   (ELVIN_MCAST_ADDR_TYPE)&request, sizeof(request)) < 0) {
	ELVIN_ERROR_UNIX_SETSOCKOPT_FAILED(error, ELVIN_SOCK_ERRNO);
	return 0;
    }

    /* Set the socket to loop packets back to us */
    loop = 1;

    /* Fails on older WinSock implementations (so don't check result) */
    setsockopt(ep->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));


-8<----