Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C listen to multicast on all interfaces, respond on same as recieved

I am trying to listen to multicast on all interfaces in system, but responds only on this on which I've received multicast packet.

What I've did is to create a socket for each of the interfaces and here the problems starts.

When I bind interface to INADDR_ANY it will receive packets for all interfaces and send on default one. If I bind port to specific interface it will not receive multicast packet (but it will be able to send it on correct interface).

I've tried setting options like IP_ADD_MEMBERSHIP or IP_MULTICAST_IF but without success.

I think the other options whould be to create one socket to receive on all ifs and senders sockets for all interfaces, but on this approach I have no idea on which ifs packet entered...

Code samples (simplified, without error handling and stuff):

Creating socket:

//here i am looping over all interfaces from getifaddrs
struct sockaddr_in *pAddr = (struct sockaddr_in *)tmp->ifa_addr;

sockets[i] = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);

mreq.imr_multiaddr.s_addr=inet_addr(MDNS_ADDRESS);
mreq.imr_interface.s_addr=pAddr->sin_addr.s_addr;

setsockopt(sockets[i], IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
setsockopt(sockets[i], IPPROTO_IP, IP_MULTICAST_IF, &pAddr, sizeof(struct in_addr));

memset(&my_addr, 0, sizeof(struct sockaddr_in));
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = INADDR_ANY; //or pAddr->sin_addr.s_addr;
my_addr.sin_port = htons(port);

bind(sockets[i], (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1);

Receiving and sending:

recvfrom(sockfd, buf, MAXBUFLEN-1 , 0, (struct sockaddr *)&their_addr, &addr_len);

//do some magic and response (response should be a multicast too)

destination.sin_addr.s_addr = inet_addr(MULTICAST_ADDRESS);
destination.sin_family = AF_INET;
destination.sin_port = htons( port );
sendto(sockfd, buffer, len, 0, (struct sockaddr *)&destination, sizeof destination);

I would like to create something similar in work to mDNS so when packet entered on specific interface program should answer on the same if with some data about this if. It should not send this on other ifs as it may not be relevant for them, but it should send it as multicast so any other host in same network will receive the respond.

like image 985
Pax0r Avatar asked Aug 28 '15 19:08

Pax0r


2 Answers

You should only need one socket for this.

First bind to INADDR_ANY and your port of choice. Then call setsockopt with IP_ADD_MEMBERSHIP on each interface you want to receive multicast on. Finally, call setsockopt with IP_MULTICAST_IF on the interface you want to send multicast on. Make sure to check for errors on each call.

int socket s;
struct sockaddr_in sin;
struct ip_mreq mreq;
struct in_addr out_addr;

bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr=htonl(INADDR_ANY);
sin.sin_port = htons(1044);   // or whatever port you listen on

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

if (bind(s, (struct sockaddr *)&sin, sizeof(sin))==-1) {
    perror("bind failed");
    exit(1);
}

// Do this in a loop for each interface
mreq.imr_multiaddr = inet_addr("230.4.4.1");     // your multicast address
mreq.imr_interface = inet_addr("192.168.1.1");   // your incoming interface IP
if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) == -1) {
    perror("Error joining multicast group");
    exit(1);
}

out_addr.s_addr = inet_addr("192.168.1.1");   // your outgoing interface IP
if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&out_addr,sizeof(out_addr))== -1) {
    perror("Error setting outgoing interface");
    exit(1);
}

When using multicast, you should always bind to the INADDR_ANY address. Failure to do so breaks multicast on Linux systems.

like image 95
dbush Avatar answered Oct 16 '22 17:10

dbush


Initially, I accepted @dbush answer as it allowed me to get on right track. For sake of completeness I post more detailed answer and as suggested by him I accepted my own answer.

Some of the code was found here: Setting the source IP for a UDP socket

I was able to do all of this with single socket and setting IP_PKTINFO.

Code samples (simplified, without error handling and stuff):

Creating socket:

if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1) {
    perror("socket");
    exit(1);
}

if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval) < 0) {
    perror("setsockopt");
    exit(1);
}

optval2 = 1;
if(setsockopt(sockfd, IPPROTO_IP, IP_PKTINFO, &optval2, sizeof(optval2)) < 0) {
    perror("setsockopt");
    exit(1);
}

memset(&my_addr, 0, sizeof(struct sockaddr_in));
my_addr.sin_family = AF_INET;
my_addr.sin_addr.s_addr = INADDR_ANY;
my_addr.sin_port = htons(5353);

if (bind(sockfd, (struct sockaddr *)&my_addr,
    sizeof(struct sockaddr)) == -1) {
    perror("bind");
    exit(1);
}

Receiving and responding:

char buf[MAXBUFLEN];
char cmsgbuf[MAXBUFLEN];

struct iovec iov[1];
iov[0].iov_base=buf;
iov[0].iov_len=sizeof(buf);

struct cmsghdr *cmsg;

struct msghdr message;
message.msg_name=&their_addr;
message.msg_namelen=sizeof(their_addr);
message.msg_iov=iov;
message.msg_iovlen=1;
message.msg_control=cmsgbuf;
message.msg_controllen=MAXBUFLEN;

if ((numbytes = recvmsg(sockfd, &message, 0)) == -1) {
        perror("recvfrom");
        exit(1);
    }
for (cmsg = CMSG_FIRSTHDR(&message); cmsg != NULL; cmsg = CMSG_NXTHDR(&message, 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 = CMSG_DATA(cmsg);
    addr = pi->ipi_spec_dst.s_addr;
}

//DO SOME MAGIC

//HERE WE ARE SETTING ADDR - INTERFACE WITH THIS ADDR WILL SEND MULTICAST
sock_opt_addr.s_addr = addr;
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &sock_opt_addr, sizeof(sock_opt_addr));

sendto(sockfd, buffer, len, 0, (struct sockaddr *)&destination, sizeof destination);
like image 24
Pax0r Avatar answered Oct 16 '22 18:10

Pax0r