Raw network sockets are a curious beasts, as unless you have a strong urge to implement your own low-level network protocol, it’s a topic that is probably best left to the (well-paid) experts. That said, you can totally use raw sockets in virtually every operating system, but one should be aware of a few things, the lack of portability being one of them. This is what tripped [Swagnik] up while trying to write a low-level network ping (ICMP) utility, by reading the Linux socket documentation while testing on MacOS. It’s all BSD-style sockets, after all, right?
As it turns out, the network stacks in Linux and MacOS have some subtle differences, which become apparent when you read the friendly manuals. For Linux, the raw(7) man entry for IPv4 sockets make it clear that the IP_HDRINCL
socket option is default by default for IPPROTO_RAW
sockets. This is different from MacOS, which is effectively FreeBSD with glossy makeup. Like FreeBSD, the MacOS man page makes it clear that the IP_HDRINCL
option is not set by default.
So that’s easy, right? Just fire off a setsockopt()
call on the raw socket and that’s done. Not quite. The Linux man page notes that it cannot receive all IP protocols, while the FreeBSD/MacOS version makes no such exceptions. There is also the issue of endianness, which is where [Swagnik]’s blog post seems to err. The claim is that on MacOS the received IPv4 raw socket header is in host (little endian) order, while the documentation clearly notes that these are in network (big endian) order, which the blog post also shows.
Where things get really fun is when moving from IPv4 raw sockets to IPv6 raw sockets, as [Michael F. Schönitzer] covered for Linux back in 2018 already. IPv6 raw sockets drop IP_HDRINCL
and requires a whole different approach. The endianness also changes, as IPv6 raw sockets under Linux must send and will receive data in network byte order, putting it in line with FreeBSD raw sockets.