receive multicast from specific network interface on Linux - linux

I'm trying to receive a multicast data from specific network interface on CentOS 5.5
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sd, (sockaddr*)&addr, sizeof(sockaddr_in));
setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 5);
But I'm receiving packets from all interfaces.
What wrong?

First, check if any of your calls fail, socket,bind,setsockopt in this case. Printing an error message with the perror() function will help you diagnose problems.
However, for receiving multicast datagrams you might need to specify the ip address of the interface when you join a multicast group using the IP_ADD_MEMBERSHIP socket option
Something like
setsockopt (sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
where the mreq struct is
struct ip_mreq
{
struct in_addr imr_multiaddr; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of interface */
};
More info here.

Related

multicast: Linux *must not* bind socket to a specific # but Windows *must*

I have a mdns service discovery that uses the following code for initialization
void mdnssd_init(struct in_addr host, bool compliant) {
int sock;
int res;
struct ip_mreq mreq;
struct sockaddr_in addr;
sock = socket(AF_INET, SOCK_DGRAM, 0);
char param = 32;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &param, sizeof(param));
int enable = 1
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &enable, sizeof(enable));
param = 1;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (void*) &param, sizeof(param));
#ifndef _WIN32
if (compliant) {
enable = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void*)&enable, sizeof(enable));
}
#endif
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
/*
* Sending *from * 5353 indicates that we do compliant mDNS query. If we chose
* random ports, the ttl will be much shorter.
*/
if (compliant) addr.sin_port = htons(MDNS_PORT);
// Windows must bind this socket to a specific address, others must not (it's *must*)
#ifdef _WIN32
addr.sin_addr.s_addr = host.s_addr;
#else
addr.sin_addr.s_addr = INADDR_ANY;
#endif
socklen_t addrlen = sizeof(addr);
res = bind(sock, (struct sockaddr *) &addr, addrlen);
if (res < 0) return;
// set outgoing interface for multicast (it's optional, INADDR_ANY could be used)
setsockopt (sock, IPPROTO_IP, IP_MULTICAST_IF, (void*) &host.s_addr, sizeof(host.s_addr));
// set multicast groups we are interested by to receive such packets
memset(&mreq, 0, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(MDNS_MULTICAST_ADDRESS);
mreq.imr_interface.s_addr = host.s_addr; // optional, INADDR_ANY can be used
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &mreq, sizeof(mreq));
}
It works on all platforms (Linux, Windows, Solaris, FreeBSD, MacOS) but I really don't understand why I need that binding difference between Linux and Windows.
On Linux, if I bind the socket to the address I want to use to send/receive, multicast traffic is not received. Note that queries that require unicast response are properly answered. I understand that some settings here are optional and INADDR_ANY can be used, letting the OS select what interface to send request and receive response (and it works).
On the contrary, on Windows, if the socket is not bound to a specific address, but set to INADDR_ANY, then no multicast traffic is received. Same, queries requiring unicast responses are received.
So it's very puzzling to me that not both options work. I should be able to bind the socket to the address that will be used for sending/receiving - or not. It should work either way, no?

linux multicast join doesn't, why?

I need to receive multicast data from two sources on one ethernet connection, 224.0.31.132 port 14384 and 224.0.31.130 port 14382.
First I tried to bind with port 14384 and then join both ip addresses, but I only get the data from 224.0.31.132.
If I bind with 14382 and join both, I only get the data from 224.0.31.130.
So then I tried to create two sockets, bind the first to 14384 and join 224.0.31.132, then bind the second to 14382 and join 224.0.31.130.
When I do that, I get the data from 224.0.31.130, but not from 224.0.31.132, and ip maddr show shows that the join to 224.0.31.132 has apparently been deleted by the setting up of the second socket.
What do I do to receive data from these two sources on the single eth port?
Here is code for first approach:
/* Create a datagram socket on which to receive. */
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if(sd < 0)
{
perror("Opening datagram socket error");
exit(1);
}
else
cout<<"Opening datagram socket....OK."<<endl;
/* Enable SO_REUSEADDR to allow other */
/* applications to receive copies of the multicast datagrams. */
{
int reuse = 1;
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0)
{
perror("Setting SO_REUSEADDR error");
close(sd);
exit(1);
}
else
cout<<"Setting SO_REUSEADDR...OK."<<endl;
}
/* Enable SO_REUSEPORT to allow multiple uses of receive port */
{
int reuse = 1;
if(setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, (char *)&reuse, sizeof(reuse)) < 0)
{
perror("Setting SO_REUSEPORT error");
close(sd);
exit(1);
}
else
cout<<"Setting SO_REUSEPORT...OK."<<endl;
}
/* Bind to the proper port number with the IP address */
/* specified as INADDR_ANY. */
struct sockaddr_in localSock;
memset((char *) &localSock, 0, sizeof(localSock));
localSock.sin_family = AF_INET;
localSock.sin_port = htons(14384);
localSock.sin_addr.s_addr = INADDR_ANY;
if(bind(sd, (struct sockaddr*)&localSock, sizeof(localSock)))
{
perror("Binding datagram socket error");
close(sd);
exit(1);
}
else
cout<<"Binding datagram socket...OK."<<endl;
/* Join the multicast groups on the local nic interface. */
struct ip_mreq group;
group.imr_interface.s_addr = inet_addr("0.0.0.0");
int ipcount = stoi(getSetup("mcstcount"));
while(ipcount>0)
{
string which = "listenip" + to_string(ipcount);
cout<<"listen to "<<getSetup(which.c_str())<<endl;
group.imr_multiaddr.s_addr = inet_addr(getSetup(which.c_str()).c_str());
ipcount--;
if(setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0)
{
perror("Adding multicast group error");
close(sd);
exit(1);
}
else
cout<<"Adding multicast group...OK."<<endl;
}
// process Mdp feed
while( !stop_cond )
{
/* Read from the socket. */
const int DBUFSZ = 5000;
uint8_t databuf[DBUFSZ];
int datalen;
datalen=read(sd, databuf, DBUFSZ);
if(datalen < 0)
{
perror("Reading datagram message error");
close(sd);
exit(1);
}
//process data
}
Here is resulting output:
Opening datagram socket....OK.
Setting SO_REUSEADDR...OK.
Setting SO_REUSEPORT...OK.
Binding datagram socket...OK.
listen to 224.0.33.80
Adding multicast group...OK.
listen to 224.0.31.130
Adding multicast group...OK.
listen to 224.0.31.132
Adding multicast group...OK.
But data only comes in from 224.0.31.132.
Concluded one cannot use a single socket as there are multiple ports to receive from. Went back to approach two, using multiple sockets. Got it working, not sure what was wrong the first time.

Can I (IGMP) join a stream on two NICs and answer (IGMP) queries on both NICs in Linux?

I made a Linux application to receive multicast traffic. It works when I connect to one interface. When I connect to a stream, in Wireshark, I see an IGMP join, and when the switch sends IGMP queries, Linux replies with an IGMP report for the stream.
However, I need more bandwidth than my one interface can provide. To have more bandwidth, I have multiple interfaces on the same network. I therefore duplicated my code to have two interfaces connect to a stream. In that case, in Wireshark, I see an IGMP join on both interfaces, but when the switch sends IGMP queries, Linux only replies with an IGMP report on one interface. Therefore, the switch timeout occurs and I lose the stream on the interface that is not reporting.
Here is a reproducible example. It doesn't receive any data, but it is enough to see the problem happen in Wireshark:
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// Create and fill internet socket address structure for both sockets.
struct sockaddr_in internetSocketAdressStructure;
bzero(&internetSocketAdressStructure, sizeof(internetSocketAdressStructure));
internetSocketAdressStructure.sin_family = AF_INET;
internetSocketAdressStructure.sin_addr.s_addr=htonl(INADDR_ANY);
internetSocketAdressStructure.sin_port = htons(10000);
// Create first socket.
int firstSocketToUse;
if ((firstSocketToUse = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("First socket failed");
exit(1);
}
// Set first socket for address reuse.
int reuseAddressForFirstSocket;
reuseAddressForFirstSocket = 1;
if (setsockopt(firstSocketToUse, SOL_SOCKET, SO_REUSEADDR, ( char* )&reuseAddressForFirstSocket, sizeof(reuseAddressForFirstSocket) ) == -1 ) {
perror("Error setting first socket for address reuse");
exit(1);
}
// Bind first socket.
if (bind(firstSocketToUse, (struct sockaddr *)&internetSocketAdressStructure, sizeof(internetSocketAdressStructure))==-1) {
perror("First bind failed");
exit(1);
}
// Join stream on first socket.
struct ip_mreq multicastRequestOnFirstInterface;
multicastRequestOnFirstInterface.imr_multiaddr.s_addr = inet_addr("239.120.15.2");
multicastRequestOnFirstInterface.imr_interface.s_addr = inet_addr("25.25.40.116");
if (setsockopt(firstSocketToUse, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicastRequestOnFirstInterface, sizeof(multicastRequestOnFirstInterface)) == -1) {
perror("Error joining multicast group on Interface 1");
exit(1);
}
// Create second socket.
int secondSocketToUse;
if ((secondSocketToUse = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("Second socket failed");
exit(1);
}
// Set second socket for address reuse.
int reuseAddressForSecondSocket;
reuseAddressForSecondSocket = 1;
if (setsockopt(secondSocketToUse, SOL_SOCKET, SO_REUSEADDR, ( char* )&reuseAddressForSecondSocket, sizeof(reuseAddressForSecondSocket) ) == -1 ) {
perror("Error setting second socket for address reuse");
exit(1);
}
// Bind second socket.
if (bind(secondSocketToUse, (struct sockaddr *)&internetSocketAdressStructure, sizeof(internetSocketAdressStructure))==-1) {
perror("Second bind failed");
exit(1);
}
// Join stream on second socket.
struct ip_mreq multicastRequestOnSecondInterface;
multicastRequestOnSecondInterface.imr_multiaddr.s_addr = inet_addr("239.120.15.2");
multicastRequestOnSecondInterface.imr_interface.s_addr = inet_addr("25.25.40.134");
if (setsockopt(secondSocketToUse, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicastRequestOnSecondInterface, sizeof(multicastRequestOnSecondInterface)) == -1) {
perror("Error joining multicast group on Interface 2");
exit(1);
}
// Wait forever.
while(1) {}
}
I saw a post on Stackoverflow suggesting to do this with only one socket, so I tried that, but the same issue occurs. Here is the code for that:
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
// Create and fill internet socket address structure for the socket.
struct sockaddr_in internetSocketAdressStructure;
bzero(&internetSocketAdressStructure, sizeof(internetSocketAdressStructure));
internetSocketAdressStructure.sin_family = AF_INET;
internetSocketAdressStructure.sin_addr.s_addr=htonl(INADDR_ANY);
internetSocketAdressStructure.sin_port = htons(10000);
// Create socket.
int socketToUse;
if ((socketToUse = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket failed");
exit(1);
}
// Bind socket.
if (bind(socketToUse, (struct sockaddr *)&internetSocketAdressStructure, sizeof(internetSocketAdressStructure))==-1) {
perror("bind failed");
exit(1);
}
// Set first socket for address reuse.
int reuseAddress;
reuseAddress = 1;
if (setsockopt(socketToUse, SOL_SOCKET, SO_REUSEADDR, ( char* )&reuseAddress, sizeof(int) ) == -1 ) {
perror("Error setting socket for address reuse");
exit(1);
}
// Join stream on first interface.
struct ip_mreq multicastRequestOnFirstInterface;
multicastRequestOnFirstInterface.imr_multiaddr.s_addr = inet_addr("239.120.15.2");
multicastRequestOnFirstInterface.imr_interface.s_addr = inet_addr("25.25.40.116");
if (setsockopt(socketToUse, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicastRequestOnFirstInterface, sizeof(multicastRequestOnFirstInterface)) == -1)
{
perror("Error joining multicast group on first interface");
exit(1);
}
// Join stream on second interface.
struct ip_mreq multicastRequestOnSecondInterface;
multicastRequestOnSecondInterface.imr_multiaddr.s_addr = inet_addr("239.120.15.2");
multicastRequestOnSecondInterface.imr_interface.s_addr = inet_addr("25.25.40.134");
if (setsockopt(socketToUse, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicastRequestOnSecondInterface, sizeof(multicastRequestOnSecondInterface)) == -1)
{
perror("Error joining multicast group on second interface");
exit(1);
}
// Wait forever.
while(1) {}
}
I've also tried to put the interfaces in promiscuous mode:
sudo ip link set interface1 promisc on
sudo ip link set interface2 promisc on
and add the noprefixroute option to each interface:
sudo ip addr change 25.25.40.134 dev interface1 noprefixroute
sudo ip addr change 25.25.40.116 dev interface2 noprefixroute
Both those things failed to solve my problem.
That being said, I found sources (1, 2) that might indicate that what I'm trying to do is impossible, though these sources seem somewhat old.
I was able to fix the issue by putting both ports on the switch to different vlans, though that is a clunky/awful solution that might not be acceptable.
Is connecting to multicast streams on the same network through two interfaces and configure the network to answer the IGMP queries on both interfaces possible?
It is explained as clear as day here:
https://access.redhat.com/solutions/53031
And less clearly here:
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
Considering a computer with 2 net interfaces, interfaceA and interfaceB.
Considering that Linux decides to use interfaceB to send packets to ip address X.
Considering a packet that is received on interfaceA from ip address X.
Linux will drop the packet.
Unless you run
sysctl net.ipv4.conf.all.rp_filter=2 in a terminal or add that line to /etc/sysctl.conf.
It enables receiving packets from an ip address on other interfaces than the one it uses to send packets to that ip address!

Using BPF with SOCK_DGRAM on Linux machine

Is it possible to filter packets using BPF on datagram socket?
No error occures when I try to attach a filter, but I don't receive any packet.
I compiled a filter using libpcap, and the filter works with tcpdump.
Here is shortened version of my code:
static const char filter[] = "udp[8] == 0x00";
int sock = socket(AF_INET, SOCK_DGRAM, 0);
pcap_t *pcap = pcap_open_dead(DLT_RAW, 1024);
struct bpf_program bpf_prog;
pcap_compile(pcap, &bpf_prog, filter, 0, PCAP_NETMASK_UNKNOWN);
struct sock_fprog linux_bpf = {
.len = bpf_prog.bf_len,
.filter = (struct sock_filter *) bpf_prog.bf_insns,
};
setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &linux_bpf, sizeof(linux_bpf));
My machine is ubuntu 12.04 x86.
well, after some tests and trials, it is possible.
however, libpcap does not support it directly.
what should be done is open a pcap handler specifying ethernet data type, and then access the bytes in the udp packet as if you access the ethernet packet.
the filter offsets start from the beginning of the packet, but the 'packet' depends on the layer you opened the socket for.
if one opens socket with SOCK_DGRAM, the bpf instruction ldb 0 will load the first byte of the udp header. so when accessing ether[0] in the filter libpcap will compile it to ldb 0 which is what we want.
so, the corrected code should be something like this:
static const char filter[] = "ether[8] == 0x00";
int sock = socket(AF_INET, SOCK_DGRAM, 0);
pcap_t *pcap = pcap_open_dead(DLT_EN10MB, 1024);
struct bpf_program bpf_prog;
pcap_compile(pcap, &bpf_prog, filter, 0, PCAP_NETMASK_UNKNOWN);
struct sock_fprog linux_bpf = {
.len = bpf_prog.bf_len,
.filter = (struct sock_filter *) bpf_prog.bf_insns,
};
setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &linux_bpf, sizeof(linux_bpf));

Linux : how to set default route from C?

How can I set (and replace the existing) default network route from a C program? I'd like to do it without shell commands if possible (this is a low memory embedded system). Also can you set the default route without specifying the gateway IP address? In my application I want to make either ppp0 or eth0 the default route, depending on whether the cable is plugged into eth0 or not.
Thanks,
Fred
You can make IOCTL calls to set the default route from a C program.
void main()
{
int sockfd;
struct rtentry rt;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1)
{
perror("socket creation failed\n");
return;
}
struct sockaddr_in *sockinfo = (struct sockaddr_in *)&rt.rt_gateway;
sockinfo->sin_family = AF_INET;
sockinfo->sin_addr.s_addr = inet_addr("Your Address");
sockinfo = (struct sockaddr_in *)&rt.rt_dst;
sockinfo->sin_family = AF_INET;
sockinfo->sin_addr.s_addr = INADDR_ANY;
sockinfo = (struct sockaddr_in *)&rt.rt_genmask;
sockinfo->sin_family = AF_INET;
sockinfo->sin_addr.s_addr = INADDR_ANY;
rt.rt_flags = RTF_UP | RTF_GATEWAY;
rt.rt_dev = "eth0";
if(ioctl(sockfd, SIOCADDRT, &rt) < 0 )
perror("ioctl");
return;
}
You could strace the route command you are wanting to mimic. This gives you the relevant syscalls useful to change routing.
You may be interested by the proc(5) interface, e.g. its /proc/net/route pseudo-file.
See also ip(7).

Resources