Strange Linux socket protocols behaviour - c

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.

Related

How to modify IP header options for SYN/ACK packets?

To produce packets with extended IP header setsockopt operation can be performed with level SOL_IP and option IP_OPTIONS:
int ipoption=0xbaadf00d;
int sockfd=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
setsockopt(sockfd, SOL_IP, IP_OPTIONS, &ipoption, sizeof ipoption);
After making this when trying to connect TCP stack produces packets with correct extended header.
Problem is how to do the same for server socket: I expect TCP server socket that answers with SYN/ACK packet with specific IP header extension in response to connect. But making same setsockopt for socket gives no effect. No matter when I call setsockopt - before listen, before accept etc. Is it possible somehow to apply IP option to server socket without switching to RAW sockets?

What is the HOPOPT protocol and how does socket() work?

I'm messing with sockets in C and this protocol continues to show up, I couldn't find anything about it, so what is it used for? What's the difference between HOPOPT and IP?
Also i'm don't get why the last argument of the socket() function should be 0. According to the man page:
The protocol specifies a particular protocol to be used with the socket. Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol can be specified as 0. However, it is possible that many protocols may exist, in which case a particular protocol must be specified in this manner. The protocol number to use is specific to the “communication domain” in which communication is to take place; see protocols(5). See getprotoent(3) on how to map protocol name strings to protocol numbers.
As far as I understand setting the last argument to 0 will let the standard library to decide which protocol to use but in which case would one use a number other than 0?
HOPOPT is the acronym of the Hop-by-Hop IPv6 extension header. It is a header that allows to add even more options to an IPv6 packet. It is normal that IPv6 packets include this header.
socket() is the system call that BSD and others (Linux et al.) provide to create a new socket, that is the internal representation of a network connection. When creating a new socket, the desired protocol must be specified: TCP, UDP, etc., which may go over IPv4, IPv6, etc.
The paragraph that you are citing explains that one or many protocols may exist for each socket type.
If only one exists, the protocol argument must be zero. For instance, SOCK_STREAM sockets are only implemented by TCP:
int sk = socket(AF_INET, SOCK_STREAM, 0);
If more exist, than you must specify which protocol in particular you want to use. For example, the SOCK_SEQPACKET type can be implemented with the SCTP protocol:
int sk = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
So, in conclusion:
If you want to create a socket, choose what protocol to use, for instance TCP over IPv4.
HOPOPT is totally normal in an IPv6 packet. If you see it appear in your traces, because you created an IPv6 socket (using AF_INET6), it is OK.

Receiving multicast UDP packets from a single network interface on macOS

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.

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.

recvfrom with Raw Sockets, get just the data

I'm trying to implement my own transport layer protocol, but I'm perfectly happy to leave the network layer as-is and not need to mess with the actual IP header information.
But of course, when calling recvfrom() on a raw socket, you are given the raw IP datagram, while the sockaddr struct is not filled in.
Is there anyway to coax the stack to fill in those structs and leave the ip header out of the data portion, or does that need to be implemented by hand?
Receiver:
struct sockaddr_in sender;
int sender_len;
raw_socket = socket(AF_INET, SOCK_RAW, 56);
...
if((n = recvfrom(raw_socket, buf, 1024, 0, (struct sockaddr*)&sender, &sender_len)) == -1){
perror("recvfrom");
return -1;
}
The IP header will always be included when receiving on a SOCK_RAW socket.
Per raw(7):
The IPv4 layer generates an IP header when sending a packet unless the IP_HDRINCL socket option is enabled on the socket. When it is enabled, the packet must contain an IP header. For receiving the IP header is always included in the packet.
Reference:
ip(7) man page
SOCK_RAW Demystified
Advanced TCP/IP - THE RAW SOCKET PROGRAM EXAMPLES
Use recvmsg() with the msg[] buffers initialized so that the first one receives the IP header, then the second one will only contain data.

Resources