gosocketsnetwork-programmingraw-sockets

OS appending IP header even with raw sockets and IPPROTO_RAW


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

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!


Solution

  • 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.