Skip to content
Advertisement

IPv6 multicast interface selection

The setsockopt way to select an interface for the outgoing traffic with IPv4 is IP_MULTICAST_IF, which accepts two arguments. From the ip(4) manual page:

Set the local device for a multicast socket. The argument for setsockopt(2) is an ip_mreqn or (since Linux 3.5) ip_mreq structure similar to IP_ADD_MEMBERSHIP, or an in_addr structure.

When trying to do the analogous operation with IPv6 traffic, the option changes to an interface index. From the ipv6(4) manual page:

Set the device for outgoing multicast packets on the socket. This is allowed only for SOCK_DGRAM and SOCK_RAW socket. The argument is a pointer to an interface index (see netdevice(7)) in an integer.

What happens in a situation where a network interface (e.g. eth0) has multiple addresses assigned to it? Is it the case that IPv6 socket interface has removed the possibility to use each address separately?

Advertisement

Answer

If your interface only has one link-local address (fe80::/10) and one publicly routeable address, the source address for the outgoing packet depends on the scope of the multicast address you’re sending to.

An IPv6 multicast address has the form ffxy::/16, where x is a flag field and y is the scope. If the scope is 1 (interface local) or 2 (link local), then the source address will be the link-local address. If the scope is 3 or higher, the source address will be the publicly routeable address.

If on the other hand your interface has more than one publicly routeable address, you need to use sendmsg when sending datagrams so that you can set the source address using a IPV6_PKTINFO control header.

Below is a full example of how you can do this, assuming you have 2001::1:2:3 and 2002::1:2:3 as IPv6 addresses on one interface and ff03::1:2:3 is the multicast address you send to.

#define _GNU_SOURCE   // needed for some IPv6 datatypes to be visible

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

// multicast address to send to
const char *maddr = "ff03::1:2:3";

// uncomment the line for the source address you want to use
const char *srcaddr = "2001::1:2:3";
//const char *srcaddr = "2002::1:2:3";

int main()
{
    int sock;
    struct sockaddr_in6 dstaddr;

    struct iovec iovec[1];
    struct msghdr msg;
    struct cmsghdr* cmsg;
    char msg_control[1024];
    char udp_packet[] = "this is a test";
    int cmsg_space;
    struct in6_pktinfo *pktinfo;

    dstaddr.sin6_family = AF_INET6;
    inet_pton(AF_INET6, maddr, &dstaddr.sin6_addr);
    dstaddr.sin6_port = htons(5555);
    dstaddr.sin6_flowinfo = 0;
    dstaddr.sin6_scope_id = 0;

    if ((sock=socket(AF_INET6, SOCK_DGRAM, 0)) == -1) {
        perror("socket failed");
        exit(1);
    }

    // set up the msghdr structure with the destination address, 
    // buffer to send, and control info buffer
    iovec[0].iov_base = udp_packet;
    iovec[0].iov_len = strlen(udp_packet);
    msg.msg_name = &dstaddr;
    msg.msg_namelen = sizeof(dstaddr);
    msg.msg_iov = iovec;
    msg.msg_iovlen = sizeof(iovec) / sizeof(*iovec);
    msg.msg_control = msg_control;
    msg.msg_controllen = sizeof(msg_control);
    msg.msg_flags = 0;

    // add IPV6_PKTINFO control message to specify source address
    cmsg_space = 0;
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = IPPROTO_IPV6;
    cmsg->cmsg_type = IPV6_PKTINFO;
    cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
    pktinfo = (struct in6_pktinfo*) CMSG_DATA(cmsg);
    pktinfo->ipi6_ifindex = 0;
    inet_pton(AF_INET6, srcaddr, &pktinfo->ipi6_addr);
    cmsg_space += CMSG_SPACE(sizeof(*pktinfo));
    msg.msg_controllen = cmsg_space;

    // send packet
    if (sendmsg(sock, &msg, 0) == -1) {
        perror("send failed");
    }

    return 0;
}
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement