Skip to content

Allow conversion of some IPv4 header fields to host byte order when serialized#640

Open
i-i-i-i-i-i-i-i-i-i wants to merge 1 commit intogoogle:masterfrom
i-i-i-i-i-i-i-i-i-i:ipv4-byte-order
Open

Allow conversion of some IPv4 header fields to host byte order when serialized#640
i-i-i-i-i-i-i-i-i-i wants to merge 1 commit intogoogle:masterfrom
i-i-i-i-i-i-i-i-i-i:ipv4-byte-order

Conversation

@i-i-i-i-i-i-i-i-i-i
Copy link

@i-i-i-i-i-i-i-i-i-i i-i-i-i-i-i-i-i-i-i commented Apr 22, 2019

Just an unassuming fix for #150.

Just to describe the issue concisely:
darwin, dragonfly, netbsd, and freebsd<11.0 expect native byte ordering (only for length/fragmentation offset fields) when writing serialized IPv4 headers to raw IP sockets, as reflected by a fix for ipv4/header.go.

This strange assumption was removed since freebsd 11.0, but the other systems still have issues with this (as used to be the behaviour in freebsd with IP_HDRINCL, you will likely get EINVAL from the kernel due to incorrect packet length).

It would be nice to have a fix inside SerializeTo, but this would probably require an extra global variable for native byte order along with the magic you see in header.go to determine versions/endianness. I also assume changing such a critical method for this fix is not ideal.

Here is an example from some code that lets you craft empty packets with custom source/destination fields. Later the buffer is used with a raw IP socket (this works on Darwin, whereas I got EINVAL before).

I have tested a good amount with Wireshark to ensure that the length, fragmentation offset, and flag fields are preserved properly in transit.

func craftPacket(p *gopacket.SerializeBuffer, src net.IP, dst net.IP) error {
	opts := gopacket.SerializeOptions{
		FixLengths: true,
	}

	ipv4 := layers.IPv4{
		Version:  4,
		IHL:      5,
		SrcIP:    src,
		DstIP:    dst,
		TTL:      64,
		Protocol: layers.IPProtocolUDP,
	}
	if err := gopacket.SerializeLayers(*p, opts, &ipv4); err != nil {
		return err
	}
	ipv4.RawSocketByteOrder(*p, binary.LittleEndian)

	return nil
}

...

p := gopacket.NewSerializeBuffer()
craftPacket(&p, src.IP, dst.IP)

fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
if err != nil {
	panic(err)
}

if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1); err != nil {
	panic(err)
}

to := &syscall.SockaddrInet4{
	Addr: [4]byte{127, 0, 0, 1},
	Port: 0,
}

if err := syscall.Sendto(fd, p.Bytes(), 0, to); err != nil {
	panic(err)
}

It would be easy enough to include a function that determines host endianness, but maybe that's best left to the user? Please let me know what you think!

@i-i-i-i-i-i-i-i-i-i i-i-i-i-i-i-i-i-i-i changed the title Allow conversion of some IPv4 header fields to host byte order for certain systems Allow conversion of some IPv4 header fields to host byte order Apr 22, 2019
@i-i-i-i-i-i-i-i-i-i i-i-i-i-i-i-i-i-i-i changed the title Allow conversion of some IPv4 header fields to host byte order Allow conversion of some IPv4 header fields to host byte order when serialized Apr 22, 2019
@i-i-i-i-i-i-i-i-i-i
Copy link
Author

i-i-i-i-i-i-i-i-i-i commented Apr 22, 2019

It's worth noting that this will require extra attention on the header deserialization side.

As reflected again in header.go, there are even more OS-dependent interpretations of IPv4 headers that must be accounted for.

I'm happy to implement and test these, but first I'd like to see what approach seems best to others.

I do think getting a serialization fix merged would be a priority for systems that need to construct packets with custom headers (e.g. for transparently forwarding packets with UDP).

@notti
Copy link
Contributor

notti commented Apr 27, 2019

I'm not completely sure about this, since I use those OSes only on rare occasions, but does this affect both bpf sockets and raw sockets? (So far I was under the impression this only affects raw sockets - but I might be mistaken here).

@i-i-i-i-i-i-i-i-i-i
Copy link
Author

Good point - I don't believe this affects BPF sockets. Testing with gopacket's BPF sniffer, I get identical results with Wireshark, implying that maybe this byte order is only imposed by the kernel when reading from raw IP sockets.

Test packet bytes after host endianness conversion, before sending on raw socket (length is 0x001e -> 0x1e00 for little endian):
Screen Shot 2019-04-28 at 20 10 50

Results in Wireshark/gopacket (length is back to big endian):
Screen Shot 2019-04-28 at 19 52 16
Screen Shot 2019-04-28 at 19 52 33

A bugfix in FreeBSD 11 seems to support this:

Before FreeBSD 11.0 packets received on raw IP sockets had the ip_len and
ip_off fields converted to host byte order. Packets written to raw IP
sockets
were expected to have ip_len and ip_off in host byte order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants