How to Multicast (send) to first NIC? - c

I found recently that if I have a dial-up connection (this is for a kiosk) and a local area network connection, when the dial-up connection is established (with internet access), my multicast sendto would default to the dial-up rather than my LAN NIC. This made the multicast go out to the dial-up connection instead rather than to my LAN which has several multicast subscribers.
I understand that I need to use IP_MULTICAST_IF to set the interface on my multicast socket. Question is how do I enumerate the interfaces and how do I use IP_MULTICAST_IF in setsockopt?
On the kiosk Windows XP Embedded, there's always going to be just one local area connection NIC. How do I get this interface and pass its IP address (is this what IP_MULTICAST_IF is expecting??) to setsockopt?

Apparently setsockopt and IP_MULTICAST_IF don't work if wsock32.dll is used instead of ws2_32.dll. I thought I was doing it wrong when I kept getting 1.0.0.0 as the IP address even when it was something else that I've set with setsockopt. Funny thing is, before the call to IP_MULTICAST_IF, it would return 0.0.0.0, so setsockopt` did change something, just not correctly.
Someone else who had this same problem way back in 2004 - http://us.generation-nt.com/ip-multicast-problem-help-37595922.html. When we #include "winsock2.h" we need to use ws2_32.dll. However, with C++ Builder, it's impossible to use ws2_32.dll when we use winsock2.h - the RTL implicitly links in wsock32.dll and you can't link ws2_32.dll even if you explicitly specify #pragma comment(lib, "ws2_32.lib"). Embarcadero really need to fix this! Someone in the RTL team must've decided it's clever to implicitly include wsock32.dll. The only 'clever' thing it did was users didn't have to include one line in their code - #pragma comment(lib, "wsock32.lib"). While they're at that, they might as well include every single DLL files known to mankind.

Use GetAdaptersAddresses() to enumerate all available interface IP addresses.
IP_MULTICAST_IF is for IPv4 addresses only. It expects you to pass a DWORD value containing the desired IPv4 address (in network byte order) to setsockopt(), eg:
DWORD dwIP = ...;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&dwIP, sizeof(dwIP));

Related

How to filter a multicast receiving socket by interface?

I need to create two sockets listening on the same IP:port but on different interfaces:
socket0 receives UDP traffic sent to 224.2.2.2:5000 on interface eth0
socket1 receives UDP traffic sent to 224.2.2.2:5000 on interface eth1
It seemed pretty straight forward until I realized that Linux merges all of that into the same traffic. For example, say there's only traffic on eth1 and there's no activity on eth0. When I first create socket0 it won't be receiving any data but as soon as I create socket1 (and join the multicast group) then socket0 will also start receiving the same data. I found this link that explains this.
Now this actually makes sense to me because the only moment when I specify the network interface is when joining the multicast group setsockopt(socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,...) with ip_mreq.imr_interface.s_addr. I believe this specifies which interface joins the group but has nothing to do with from which interface your socket will receive from.
What I tried so far is binding the sockets to the multicast address and port, which behaves like mentioned above. I've tried binding to the interface address but that doesn't work on Linux (it seems to do so on Windows though), you don't receive any traffic on the socket. And finally, I've tried binding to INADDR_ANY but this isn't what I want since I will receive any other data sent to the port regardless of the destination IP, say unicast for example, and it will still not stop multicast data from other interfaces.
I cannot use SO_BINDTODEVICE since it requires root privileges.
So what I want to know is if this is possible at all. If it can't be done then that's fine, I'll take that as an answer and move on, I just haven't been able to find any way around it. Oh, and I've tagged the question as C because that's what we're using, but I'm thinking it really might not be specific to the language.
I haven't included the code for this because I believe it's more of a theoretical question rather than a problem with the source code. We've been working with sockets (multicast or otherwise) for a while now without any problems, it's just this is the first time we've had to deal with multiple interfaces. But if you think it might help I can write some minimal working example.
Edit about the possible duplicate:
I think the usecase I'm trying to achieve here is different. The socket is supposed to receive data from the same multicast group and port (224.2.2.2:5000 in the example above) but only from one specific interface. To put it another way, both interfaces are receiving data from the same multicast group (but different networks, so data is different) and I need each socket to only listen on one interface.
I think that question is about multiple groups on same port, rather than same group from different interfaces. Unless there's something I'm not seeing there that might actually help me with this.
Yes, you can do what you want on Linux, without root privileges:
Bind to INADDR_ANY and set the IP_PKTINFO socket option. You then have to use recvmsg() to receive your multicast UDP packets and to scan for the IP_PKTINFO control message. This gives you some side band information of the received UDP packet:
struct in_pktinfo {
unsigned int ipi_ifindex; /* Interface index */
struct in_addr ipi_spec_dst; /* Local address */
struct in_addr ipi_addr; /* Header Destination address */
};
The ipi_ifindex is the interface index the packet was received on. (You can turn this into an interface name using if_indextoname() or the other way round with if_nametoindex().
As you said on Windows the same network functions have different semantics, especially for UDP and even more for multicast.
The Linux bind() semantics for the IP address for UDP sockets are mostly useless. It is essentially just a destination address filter. You will almost always want to bind to INADDR_ANY for UDP sockets since you either do not care to which address a packet was sent or you want to receive packets for multiple addresses (e.g. receiving unicast and multicast).

setsockopt() SOL_IP API level and IPT_SO_SET_REPLACE switch

What exactly does SOL_IP mean as the API level of the setsockopt function
and what exactly does the IPT_SO_SET_REPLACE switch do?
I tried to search for both in Google but I found nothing.
Please help me understand them (if you can expand and explain with examples I'd really appreciate it)
SOL_IP is the network layer being addressed by the socket option. For example, an ordinary TCP socket encompasses the TCP layer, then the IP layer under it, and so forth. setsockopt is used to pass miscellaneous instructions down to a particular layer to request some service, feature or operation: basically anything that you might need to configure that doesn't directly match up with a system call. (The "API level" referred to in the man page is basically the same thing that I'm calling "layer" here.)
Some that you often see in linux programs (and examples of use) include:
SOL_PACKET (configure packet ring, add/drop multicast group memberships)
SOL_IP (set/configure various IP packet options, IP layer behaviors, [as here] netfilter module options)
SOL_TCP (TCP_NODELAY, TCP-specific keepalive params)
SOL_SOCKET (REUSEADDR, keepalives)
The layers you can address in a setsockopt depend on the kind of socket that you created. Here, it's the IP layer being addressed.
In this case, the option being passed down is IPT_SO_SET_REPLACE -- it's not a "core" IP option, but is provided by the IP Tables module, which (IIUC) links itself into the network stack via the "netfilters" interface. I'm not familiar with IP Tables details, but the option appears to be an instruction to IP Tables to replace a set of rule table entries. I think using it would require pretty intimate knowledge of IP Tables to use this socket option.
Just to focus on IPT_SO_SET_REPLACE option.
It is to replace the iptable with a new chain rule (see -R option):
https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html
http://ipset.netfilter.org/iptables.man.html
The above are the command line option to use. If you want to see the C program, just "apt-get source iptables" to get the source code, and then view libiptc/libiptc.c to see how SO_SET_REPLACE is used:
ret = setsockopt(handle->sockfd, TC_IPPROTO, SO_SET_REPLACE, repl,
sizeof(*repl) + repl->size);
if (ret < 0)
goto out_free_newcounters;
Which is essentially is to replace the new chain rule, by creating and compiling a new iptable.
More info:
https://en.wikipedia.org/wiki/Iptables

With C sockets, what determines the appearance of a socket's port?

When I create a socket, and bind it to a port, it will tell me that the socket is listening on the assigned port. For example,
Create socket.
Bind to port 7006
However, when I connect to another location, the port number appears to change from the outside only. As in, everything works as it should, it just looks like the port is different, and I want to know why.
A server program that took a connection from this socket, bound to 7006, said the address was
localhost:42566
A SOCK_DGRAM socket I made and bound to port 7008 read this address from recvfrom
localhost:a3fs-fileserver
I figure the a3fs-fileserver is just interpreted that way because it happened to have the port typically associated with a3fs-fileserver and getnameinfo tries to get and english service name when possible.
But why does it look different outside my program than inside? Note: This behavior works the same way between machines as well, I just happened to be testing on one for convenience.

Is there a way to to set a socket option to indicate which interface the packets go to

Currently, this is my predicament.
I have 2 fd's : x and y. When a write(x) happens, it must go to x-tunnel and a write(y) should go to y-tunnel. I cannot create a routing rule for each connection (Reasons not mentioned here)
Is there a sock opt which I can set when I accept a connection or is there any other way to do it?
Thanks
you can control in sock binding on specific interface for x, y tunnels
check for SO_DONTROUTE option in setsockopt
Your description is sketchy, I think setsockopt() by optname of SO_REUSEADDR and SO_REUSEPORT may help you, but I'm not sure.It can't 'indicate' which interface the packets go to,but you can indicate them by your implement.
Here some reference on Unix Network Programming, if you want to know more detail,you can read them and examples in chapter7.5.
The SO_REUSEADDR socket option serves four different purposes:
SO_REUSEADDR allows a listening server to start and bind its well-known port,
even if previously established connections exist that use this port as their local
port.
SO_REUSEADDR allows a new server to be started on the same port as an
existing server that is bound to the wildcard address, as long as each instance
binds a different local IP address.
SO_REUSEADDR allows a single process to bind the same port to multiple
sockets, as long as each bind specifies a different local IP address.
SO_REUSEADDR allows completely duplicate bindings: a bind of an IP address
and port, when that same IP address and port are already bound to another
socket, if the transport protocol supports it. Normally this feature is supported
only for UDP sockets.

How to listen on all IPV6 addresses using C sockets API

I maintain GPSD, a widely-deployed open-source service daemon that monitors GPSes and other geodetic sensors. It listens for client-application connections on port 2947 on both IPv4 and IPv6. For security and privacy it normally listens only on the loopback address, but there is a -G option to the daemon that is intended to cause it to listen on any address.
The problem: the -G option works in IPv4, but I can't figure out how to make it work with IPv6. The method that should work based on various tutorial examples does not, producing instead an error suggesting the address is already in use. I'm seeking help to fix this from people experienced with IPv6 network programming.
Relevant code is at http://git.berlios.de/cgi-bin/gitweb.cgi?p=gpsd;a=blob;f=gpsd.c;h=ee2156caf03ca23405f57f3e04e9ef306a75686f;hb=HEAD
This code operates correctly in both the -G and non -G cases under IPv4, as is easily verified with netstat -l.
Now look around line 398 after "case AF_INET6:". The listen_global option is set by -G; when false, the code succeeds. There is presently a following comment, inherited from an unknown contributor, that reads like this:
/* else */
/* BAD: sat.sa_in6.sin6_addr = in6addr_any;
* the simple assignment will not work (except as an initializer)
* because sin6_addr is an array not a simple type
* we could do something like this:
* memcpy(sat.sa_in6.sin6_addr, in6addr_any, sizeof(sin6_addr));
* BUT, all zeros is IPv6 wildcard, and we just zeroed the array
* so really nothing to do here
*/
According to various tutorial examples I have looked up, the assignment "sat.sa_in6.sin6_addr = in6addr_any;" is (despite the comment) correct, and it does compile. However, startup with -G fails claiming the listen address is already in use.
Is the assignment "sat.sa_in6.sin6_addr = in6addr_any;" nominally correct here? What else, if anything, am I missing?
The reason the address is already in use is because on many IPv6 networking stacks, by default an IPv6 socket will listen to both IPv4 and IPv6 at the same time. IPv4 connections will be handled transparently and mapped to a subset of the IPv6 space. However, this means you cannot bind to an IPv6 socket on the same port as an IPv4 socket without changing the settings on the IPv6 socket. Make sense?
Just do this before your call to bind (this is taken from one of my projects):
int on = 1;
if (addr->sa_family == AF_INET6) {
r = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
if (r)
/* error */
}
Unfortunately, there is no default value across platforms for IPV6_V6ONLY -- which basically means you always need to turn it either on or off explicitly if you care, unless you don't care about other platforms. Linux leaves it off by default, Windows leaves it on by default...
From a look in a random Linux system's include files, in6addr_any is declared like so:
extern const struct in6_addr in6addr_any; /* :: */
extern const struct in6_addr in6addr_loopback; /* ::1 */
#define IN6ADDR_ANY_INIT { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } }
So, perhaps the nearness to the INIT array confused whoever left that comment in GPSD's sources. The actual type is clearly struct in6_addr, which is assignable.
I did look around, and found some hints that suggested that if IPv4 is already listening to the "any" address, IPv6 can't also. Perhaps that's what's biting you.

Resources