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 anip_mreqn
or (since Linux 3.5)ip_mreq
structure similar toIP_ADD_MEMBERSHIP
, or anin_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
andSOCK_RAW
socket. The argument is a pointer to an interface index (seenetdevice(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; }