Receiving multicast UDP packets from a single network interface on macOS - c

This is a macOS question. I am trying to setup a UDP socket that receives SSDP messages, i.e. UDP packets, sent to multicast addresses. I want to restrict receiving these packets from a single network interface.
I tried
int fd = socket(AF_INET, SOCK_DGRAM, 0);
char* itf = "en0";
int res = setsockopt(fd, SOL_SOCKET, IP_RECVIF, itf, strlen(itf));
The setsockopt call fails with errno 42 (Protocol not available).
I have also found SO_BINDTODEVICE that can be used for the same purpose, but it seems that this is not available on macOS.
Using bind with port and address also does not work. Then no packets sent to the multicast address are received on that socket.

From the OSX documentation on IP multicast...
A host must become a member of a multicast group before it can receive datagrams sent to the group. To join a multicast group, use the IP_ADD_MEMBERSHIP option...
To receive multicast traffic on a specific interface you need to tell the OS that you want to join that multicast group. Follow these steps (you were almost there)...
Create a datagram socket (done).
Bind to INADDR_ANY with the expected port.
Join the multicast group via setsockopt() with the IP_ADD_MEMBERSHIP option. Here you can pass the IP address of the specific network interface you wish to receive multicast traffic on in the ip_mreq struct.

Related

Linux socket use SO_BINDTODEVICE but with specified IP address

I got a program which does
...
/* Only rx/tx packets on the interface */
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, name, strlen(name))) {
...
}
...
for each given ethernet interface and uses UDP.
I want every interface to use the same IP I specified (for example: 50.0.0.1/24).
So that packets coming out from eth0 tell "my source IP is 50.0.0.1"
and packets from eth1 tell the same("source IP: 50.0.0.1").
Both receiving and sending in required.
Is there any way to achieve this?
I've tried
addr.sin_addr.s_addr = inet_addr("50.0.0.1");
...
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
but it won't work (error: cannot assign IP address) unless I set an unrelated interface's IP to 50.0.0.1.
After that, the packets coming out say "source IP: 50.0.0.1" but sending packets with "source IP: 50.0.0.X" to the machine (which runs the above program), it will not receive any.
You can't bind a socket to an interface and an IP at the same time, only one or the other. If an interface is bound, its IP gets used. If an IP is bound, its interface gets used.
And, you can't use bind() to set a source IP that does not belong to the bound interface.
On some platforms (Linux, etc), you can use sendmsg() with IP_PKTINFO to specify a source IP for outgoing packets. However, the OS will lookup and use the interface that belongs to the specified IP, which could be different than the bound interface, so this doesn't address your issue.
So, you will likely have to use a RAW socket and send your UDP packets with a custom IP header, then you can populate that header however you want.
I found the solution.
ip addr add 50.0.0.1/24 dev eth0
ip addr add 50.0.0.1/24 dev eth1
Start the program which does
setsocketopt(fd1, SOL_SOCKET, SO_BINDTODEVICE, "eth0", strlen("eth0"));
...
setsocketopt(fd2, SOL_SOCKET, SO_BINDTODEVICE, "eth1", strlen("eth1"));
Works like a charm.
They all send packets with source IP "50.0.0.1".
And are able receive packets belong to 50.0.0.0/24 network.
The output packets will not go to the wrong interface due to the socket is bond to specific interface(SO_BINDTODEVICE socket option).

Receive same packets on two sockets using SO_REUSEADDR and SO_REUSEPORT

I'd like to have two sockets which, by setsockopt(), are set with SO_REUSEADDR and SO_REUSEPORT.
I'd like both of them to capture the same incoming packet for the address x.x.x.x and port y.
Any idea how to do this, without using a multicast receiver?

Raw Sockets in C

1.
socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
The linux manual page says about this code.
In socket option, if IP_HDRINCL is set, I can make IP header. Am I right?
If it's right, above socket also let me make TCP header, too?
Then, if IP_HDRINCL is not set, what means above socket?
2.
socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
what means above code comparing to number 1 question's code?
I know IPPROTO_RAW can't receive any IP packets. And here, these sockets only can receive TCP packets, and UDP pakcets each.(Can I see IP Header, Ethernet Header also?)
But how about sending?? I don'know exactly about this.
IP_HDRINCL means: I want my data (for send and recv) to include the ip hdr. And if your data include the ip hdr, it means that the tcp hdr follows (just after the ip hdr), and finally the app's message too (the message your normally give to send ...). Without IP_HDRINCL, you have access to apps data only.
Yes, IPPROTO_TCP and IPPROTO_UDP whith SOCK_RAW are just filters as you say, for sending and receiving. Use IPPROTO_RAW to be able to send any TCP/IP packet (no filter). But to also receive packets, you need also to change AF_INET into AF_PACKET.

Strange Linux socket protocols behaviour

I'm a little confused about the difference between the definitions of protocols on Linux when using socket(). I am attempting to listen for connections over TCP using socket(PF_INET, SOCK_STREAM, proto), where proto is (in my mind) disputed, or at least seems odd.
From <netinet/in.h>:
...
IPPROTO_IP = 0, /* Dummy protocol for TCP. */
...
IPPROTO_TCP = 6, /* Transmission Control Protocol. */
...
Agreed with by /etc/protocols:
ip 0 IP # internet protocol, pseudo protocol number
hopopt 0 HOPOPT # hop-by-hop options for ipv6
...
tcp 6 TCP # transmission control protocol
...
I learned from an online tutorial, and also from the man page tcp(7) that you initialise a TCP socket using
tcp_socket = socket(AF_INET, SOCK_STREAM, 0);
which works absolutely fine, and certainly is a TCP socket. One thing about using the above arguments to initialise a socket is that the code
struct timeval timeout = {1, 0};
setsockopt(tcp_socket, 0, SO_RCVTIMEO, &timeout, sizeof(timeout); // 1s timeout
// Exactly the same for SO_SNDTIMEO here
works absolutely fine, but not after replacing all protocol arguments (including in socket()) with IPPROTO_TCP, as opposed to IPPROTO_IP which they have, as above.
So after experimenting with the difference, I've needed to ask a few searching questions:
Why, when I replace all protocol arguments with IPPROTO_TCP, do I get error 92 ("Protocol not available") when setting timeouts, when protocol 0 is apparently just a 'dummy' TCP?
Why does socket() require the information of whether it should be a stream, datagram or raw socket when that information is (always?) implicitly known from the protocol, and vice versa? (i.e. TCP is a stream protocol, UDP is a datagram protocol, ...)
What could be meant by "dummy TCP"?
What is hopopt, and why does it have the same protocol number as 'ip'?
Many thanks.
Giving 0 as protocol to socket just means that you want to use the default protocol for the family/socktype pair. In this case that is TCP, and thus you get the same result as with IPPROTO_TCP.
Your error is in the setsockopt call. The correct one would be
setsockopt(tcp_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); // 1s timeout
the 0 there is not for protocol, but for option level. IPPROTO_TCP is another option level, but you can't combine that with SO_RCVTIMEO. It can only be used together with SOL_SOCKET.
The ones you use with IPPROTO_TCP are the ones listed in tcp(7), e.g. TCP_NODELAY.
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); should work fine.
Passing 0 as the protocol just means, give me the default. Which on every system is TCP for stream sockets and UDP for datagram sockets, when dealing with IP. But socket() can be used for many other things bar giving you a TCP or UDP socket.
socket() is quite general in nature. socket(AF_INET, SOCK_STREAM, 0); just reads as; "give me a streaming socket within the IP protocol family". Passing 0 means you have no preferences over which protocol - though TCP is the obvious choice for any system. But theoretically, it could have given you e.g. an SCTP socket.
Whether you want datagram or streaming sockets is not implicit for protocols. There are many more protocols bar IP based protocols, and many can be used in either datagram or streaming mode such as SCCP used in SS7 networks.
For IP based protocols, SCTP can be used in a datagram based, or streaming fashion. Thus socket(AF_INET,IPPROTO_SCTP); would be ambiguous. And for datagram sockets, there's other choices as well, UDP, DCCP, UDPlite.
socket(AF_INET,SOCK_SEQPACKET,0); is another interesting choice. It cannot return a TCP socket, TCP is not packet based. It cannot return and UDP socket, UDP gives no guarantee of sequential delivery. But an SCTP socket would do, if the system supports it.
I have no explanation for why someone made the comment "dummy TCP" in that the linux netinet/in.h
hopopt is the IPv6 HOP by hop option. In IPv6, the protocol discriminator field is also used as an extension mechanism. In IPv4 packets there is a protocol field which is the protocol discriminator, it'll be set to IPPROTO_TCP if that IPv4 datagram carries TCP. If that IPv4 packet also carries some additional info(options), they are coded by other mechanisms.
IPv6 does this differently, if there is an extension(option), that extension is coded in the protocol field. So if the IPv6 packet needs the hop-by-hop option, IPPROTO_HOPOPTS is placed in the protocol field. The actual hop-by-hop option also have a protocol discriminator, which signals what the next protocol is - which might be IPPROTO_TCP, or yet another option.

UDP sockets in ad hoc network (Ubuntu 9.10)

I am using BSD sockets in Ubuntu 9.10 to send UDP packets in broadcast with the following code:
sock_fd = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP);
//sock_fd=socket(AF_INET,SOCK_DGRAM,0);
receiver_addr.sin_family = PF_INET;
//does not send with broadcast in ad hoc
receiver_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
inet_aton("169.254.255.255",&receiver_addr.sin_addr);
receiver_addr.sin_port = htons(port);
int broadcast = 1;
// this call is what allows broadcast packets to be sent:
if (setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &broadcast,
sizeof broadcast) == -1) {
perror("setsockopt (SO_BROADCAST)");
exit(1);
}
ret=sendto(sock_fd, packet, size, 0,(struct sockaddr*)&receiver_addr,sizeof(receiver_addr));
Note that is not all the code, it is only to have an idea.
The program sends all the data with INADDR_BROADCAST if I am connected to an infrastructure wireless network. However, if my laptop is connected to an ad-hoc network, it is able to receive all the data, but not to send it. I have solved the problem using the 169.254.255.255 broadcast address, but I would like to know what is going on.
Thank you in advance!
Do you have more than one adapter (i.e. wired ethernet, WLAN)?
When you send to the limited broadcast address (INADDR_BROADCAST) your IP stack uses the first adapter and sends the frame. You dont have any control about choosing a specific adapter. The IP stack has done it's job, when it sends to the first adapter found. The frame would be flooded over the whole Internet if this would not be prevented by routers. That's why UDP broadcast are not routable by default. (Exceptions exist as implemented in DHCP relays.)
When you need to send a UDP frame on a specific network, you need the network broadcast address. You did this already, when you send to 169.254.255.255 on a AUTOIP net.
The setsocket(SOL_SOCKET, SO_BROADCAST) just configures the socket to allow sending broadcast at all.

Resources