I am writing a C program which multicasts a packet. The same socket used for multicasting is subscribed to the multicast group on all interfaces. This would mean that the sender would receive is own packet back. I expect to the socket to receive the message twice, once on loopback interface and another on eth0 interface. Here is the code for the same.
#define MULTICAST_PORT 1112
#define MULTICAST_GROUP "239.0.0.1"
int create_multi_receiver()
{
// Send is also through this socket
int fd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in in_servaddr;
struct ip_mreq mreq;
// Bind UDP server
bzero(&in_servaddr, sizeof(in_servaddr));
in_servaddr.sin_family = AF_INET;
in_servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
in_servaddr.sin_port = htons(MULTICAST_PORT);
if (bind(fd, (struct sockaddr *)&in_servaddr, sizeof(in_servaddr)) == -1)
error_exit("bind error in UDP");
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_GROUP);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
error_exit("setsockopt");
int opt = 1;
if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) < 0)
error_exit("setsockfd");
return fd;
}
struct msg *recv_multi_msg(int udpfd)
{
struct msg nmb_msg; /* space to msg into */
struct iovec vector[1]; /* file name from the child */
struct msghdr msg; /* full message */
struct cmsghdr *cmsg; /* control message with the fd */
/* set up the iovec for the file name */
vector[0].iov_base = &nmb_msg;
vector[0].iov_len = sizeof(nmb_msg);
/* the message we're expecting to receive */
memset(&msg, 0, sizeof(msg));
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = vector;
msg.msg_iovlen = 1;
/* overprovisioning buffer for now */
char cmbuf[128];
msg.msg_control = cmbuf;
msg.msg_controllen = sizeof(cmbuf);
printf("Receiving message..\n");
if (recvmsg(udpfd, &msg, 0) == -1)
{
perror("recvmsg multireceiver");
return NULL;
}
printf("received message..\n");
// TODO: Test this part, very high chance of error
for ( // iterate through all the control headers
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg))
{
// ignore the control headers that don't match what we want
if (cmsg->cmsg_level != IPPROTO_IP ||
cmsg->cmsg_type != IP_PKTINFO)
{
continue;
}
struct in_pktinfo pi;
memcpy(&pi, CMSG_DATA(cmsg), sizeof(pi));
printf("Destination IP: %s\n", inet_ntoa(pi.ipi_spec_dst));
// in_addr_t ip = (nmb_msg.mtype) >> 16;
// if (pi.ipi_spec_dst.s_addr != ip)
// return NULL;
// break;
}
return NULL;
// Do some stuff later
}
void send_multi_msg(int udpfd, int udsfd)
{
struct msg msg;
if (recv(udsfd, &msg, sizeof(msg), 0) > 0)
{
struct sockaddr_in addr;
int alen;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(MULTICAST_GROUP);
addr.sin_port = htons(MULTICAST_PORT);
alen = sizeof(addr);
printf("sending %s\n", msg.mtext);
if (sendto(udpfd, &msg, sizeof(msg), 0, (struct sockaddr *)&addr, alen) == -1)
perror("sendto in multicast");
}
}
However, I am receiving the packet only eth0. Why is this so? IP_MULTICAST_LOOP
is enabled by default.
I have tried checking StackOverflow but none of the questions have been answered satisfactorily.
CodePudding user response:
IP packets, that are sent over the network, can be fragmented. The receiving network stack will reassemble the fragments. That's called IP-reassembly.
The fragments are identified by IP identifier and offset. RFC If you send one IP packet, it gets one identifier. Your network stack discards all "fragments" that have already been received. Therefore you receive a packet only once, although it might have multiple paths to your network adapter(s).
CodePudding user response:
When you send a multicast datagram with sendto
, it only sends the packet out on a single interface. So you only receive one packet because you're only sending one packet.
By default, the interface that sendto
will use for multicast will typically be the first non-loopback network interface on the system. You can control which interface is used by setting the IP_MULTICAST_IF
option on the sending socket. So if you want to send on multiple interfaces you'll need to call sendto
multiple times and set IP_MULTICAST_IF
before each send.
You've also used INADDR_ANY
as the interface to join the multicast group on. That will also typically be the first non-loopback interface. If you run your program and then while it's still up run netstat -ng
you can see which interface was joined.
If you want to listen for multicast on multiple interfaces, you need to set the IP_ADD_MEMBERSHIP
option for each interface.