I have two programs, a sender and a receiver. The sender sends an ICMP echo request packet to the receiver using raw sockets. After reading the man-7 page on raw sockets and this blog, my understanding is, if I use IPPROTO_RAW
, I would be enabling the IP_HDRINCL
option, which would tell the OS not to add any IP header as the sender would be supplying it.
However, I still see an unexpected IP header being added when I capture packets using tcpdump.
➜ ~ tcpdump -nt -i lo0 not tcp -X
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo0, link-type NULL (BSD loopback), snapshot length 524288 bytes
IP 127.0.0.1 > 127.0.0.1: ip-proto-255 28
0x0000: 4500 0030 f9a2 0000 40ff 0000 7f00 0001 E..0....@.......
0x0010: 7f00 0001 4500 0000 0000 0000 4001 0000 ....E.......@...
0x0020: 0000 0000 7f00 0001 0800 f7ff 0000 0000 ................
For clarity,
// the unexpected IP header
45 00 00 30 a8 d4 00 00 40 ff 00 00 7f 00 00 01 7f 00 00 01
// my IP header
45 00 00 00 00 00 00 00 40 01 00 00 00 00 00 00 7f 00 00 01
// my ICMP bytes
08 00 f7 ff 00 00 00 00
Things to note
255
for the protocol field which is Reserved. I don't know why it's that and if there's something to infer here.IP_HDRINCL
is enabled, the OS will fill in the identification, source address, checksum, and total length fields. Hence, I've set them to zero in my IP header.This is my sender program
package main
import (
"fmt"
"log"
"syscall"
)
func main() {
fd, _ := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
addr := syscall.SockaddrInet4{
Addr: [4]byte{127, 0, 0, 1},
}
// identification, src address, checksum, and total length will be set by the kernel since IP_HDRINCL is enabled.
ipHeader := []byte{
0x45, // VersionIHL
0x00, // ToS
0x00, 0x00, // total length
0x00, 0x00, // identification
0x00, 0x00, // flags and fragment offset
0x40, // ttl
0x01, // protocol is ICMP
0x00, 0x00, // checksum
0x00, 0x00, 0x00, 0x00, // src address
0x7f, 0x00, 0x00, 0x01, // dest address
}
p := append(ipHeader, 0x08, 0x00, 0xf7, 0xff, 0x00, 0x00, 0x00, 0x00)
err := syscall.Sendto(fd, p, 0, &addr)
if err != nil {
log.Fatal("send to error: ", err)
}
fmt.Printf("sent %d bytes\n", len(p))
}
I'm curious to know why the OS is appending the IP header. Is there any gap in my understanding of raw sockets?
Thanks!
It's lo
on my machine (Ubuntu 6.8.0-35-generic), and I get
# tcpdump -nt -i lo not tcp -X -vv
tcpdump: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP (tos 0x0, ttl 64, id 63468, offset 0, flags [none], proto ICMP (1), length 28)
127.0.0.1 > 127.0.0.1: ICMP echo request, id 0, seq 0, length 8
0x0000: 4500 001c f7ec 0000 4001 84f2 7f00 0001 E.......@.......
0x0010: 7f00 0001 0800 f7ff 0000 0000 ............
What I wonder is why you'll get link type NULL
while I'm getting ETHERNET
. We probably need to know more about the machine you are executing your code on.
Note that when you are on a non-Linux machine, the Linux man pages obviously don't apply. For example macOS ip(4) says:
If proto is 0, the default protocol
IPPROTO_RAW
is used for outgoing packets, and only incoming packets destined for that protocol are received. If proto is non-zero, that protocol number will be used on outgoing packets and to filter incoming packets.Outgoing packets automatically have an IP header prepended to them (based on the destination address and the protocol number the socket is created with), unless the
IP_HDRINCL
option has been set. Incoming packets are received with IP header and options intact.
similarly on FreeBSD you have ip (4):
If proto is 0, the default protocol
IPPROTO_RAW
is used for outgoing packets, and only incoming packets destined for that protocol are received. If proto is non-zero, that protocol number will be used on outgoing packets and to filter incoming packets.Outgoing packets automatically have an IP header prepended to them (based on the destination address and the protocol number the socket is created with), unless the
IP_HDRINCL
option has been set. Unlike in previous BSD releases, incoming packets are received with IP header and options intact, leaving all fields in network byte order.
So IPPROTO_RAW
implying IP_HDRINCL
is not true on every platform, what you are seeing would be fine on a macOS machine - but you are using the wrong documentation for that.