How to Multicast with Ipv6 Udp Socket in C/C++ on Linux

How to multicast with ipv6 udp socket in C/C++ on linux?

This code below is right:

The only thing wrong is the address used for the multicast.

Like Jeremy said it, ff0e:: is not correct, I used instead ff12::feed:a:dead:beef and it works.

It is possible to get the name and index of the available interface by using if_nameindex().

Update : I try to remove some code to see if it work without it and I manage to get this :

server :

// OPEN
int fd = socket(AF_INET6, SOCK_DGRAM, 0);

// BIND
struct sockaddr_in6 address = {AF_INET6, htons(4321)};
bind(fd, (struct sockaddr*)&address, sizeof address);

// JOIN MEMBERSHIP
struct ipv6_mreq group;
group.ipv6mr_interface = 0;
inet_pton(AF_INET6, "ff12::1234", &group.ipv6mr_multiaddr);
setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &group, sizeof group);

// READ
char buffer[128];
read(fd, buffer, sizeof buffer);

client :

// OPEN
int fd = socket(AF_INET6, SOCK_DGRAM, 0);

// ADDRESS
struct sockaddr_in6 address = {AF_INET6, htons(4321)};
inet_pton(AF_INET6, "ff12::1234", &address.sin6_addr);

// SEND TO
char buffer[128];
strcpy(buffer, "hello world!");
sendto(fd, buffer, sizeof buffer, 0, (struct sockaddr*)&address, sizeof address);

How to use the options to socket() for IPv6 multicast UDP?

Eventually figured this out. Here's the correct way of doing this:

import socket
import struct
import select
import ipaddress

# .packet is a byte array
mcast_group = ipaddress.IPv6Address("ff22:5eea:675c:d1fc:17c::1")
address = ipaddress.IPv6Address("...ipv6 global scope address of interface...")
port_no = 5555
interface_idx = 3

sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
group = struct.pack("16s i",
mcast_group.packed,
3 #Interface ID
)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)
sock.bind((str(mcast_group), port_no, 0, interface_idx))

recv_socks, _, _ = select.select([sock], [], [], 10)
if recv_socks:
print("Received packet")

Important differences from the code in the question:

  • UDP (SOCK_DGRAM) sockets need to be bound to a port number. AFAICT this is why I was getting ICMP traffic as well as UDP with SOCK_RAW and no traffic at all with SOCK_DGRAM.
  • Instead of binding the socket to a unicast address on the interface of interest, bind it to the multicast group but use the interface index as the scope_id (the fourth in the address tuple passed to socket.bind()).

What are the requirements for ipv6 multicasting using plain sockets?

Bits 12-15 (starting at 0) in an IPv6 multicast address specifies the multicast scope.

A multicast address of the the form ff01::/16 has a scope of 1 which means interface local. Such packets may not be sent over any network link. That's why you can't receive any packets with an address like this from other hosts.

You need to use an address with a different scope value. A scope of 2 may be sent over the local network but not across routers, while a scope of e (15) is globally routeable.

Also, run netstat -ng when your program is running to ensure that you've joined the proper multicast group on the proper interface.

See the Wikipedia page for multicast addresses for more details.

IPv6 multicast interface selection

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

Can a UDP multicast socket be configured so that write() can be called rather than sendto()?

Yes.

Per the documentation man 7 udp

When
connect(2) is called on the socket, the default destination address
is set and datagrams can now be sent using send(2) or write(2)
without specifying a destination address.

and, for generality, the POSIX spec for connect says

If the initiating socket is not connection-mode, then connect() shall set the socket's peer address, and no connection is made. For SOCK_DGRAM sockets, the peer address identifies where all datagrams are sent on subsequent send() functions, and limits the remote sender for subsequent recv() functions.

It's always worth checking the documentation for these, things, it isn't that impenetrable. FWIW I couldn't remember immediately whether you need connect() or bind() for this, and it took me a few seconds to find out.



Related Topics



Leave a reply



Submit