// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later //go:build linux package dataplane import ( "encoding/binary" "net" "syscall" ) // Per-packet TTL and TOS/traffic-class arrive as control messages, and only if asked for at // socket setup. These fill the spec ยง3.3 observation-block fields that were shipped as the 0xFF // sentinel until now โ€” received TTL is path-length evidence, the TOS byte is DSCP/ECN survival. // enableRecvMeta asks the kernel to attach the cmsgs to every received datagram. Both the v4 and // the v6 option sets are attempted on every socket: a dual-stack socket delivers v4-mapped // traffic through the v6 fd, and the kernel refuses whichever set does not apply. Errors are // dropped on purpose โ€” a socket that cannot deliver metadata still serves probes, and the // sentinel already says "not observed" for it. func enableRecvMeta(conn *net.UDPConn) { raw, err := conn.SyscallConn() if err != nil { return } _ = raw.Control(func(fd uintptr) { _ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_RECVTTL, 1) _ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_RECVTOS, 1) _ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_RECVHOPLIMIT, 1) _ = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_RECVTCLASS, 1) }) } // parseMeta extracts TTL and the TOS byte from one datagram's control messages. Anything absent // or unparseable keeps the sentinel โ€” reported as unobserved, never guessed. func parseMeta(oob []byte) pktMeta { m := pktMeta{TTL: metaUnavailable, TOS: metaUnavailable} if len(oob) == 0 { return m } cmsgs, err := syscall.ParseSocketControlMessage(oob) if err != nil { return m } for _, c := range cmsgs { switch { case c.Header.Level == syscall.IPPROTO_IP && c.Header.Type == syscall.IP_TTL, c.Header.Level == syscall.IPPROTO_IPV6 && c.Header.Type == syscall.IPV6_HOPLIMIT: m.TTL = cmsgValue(c.Data) case c.Header.Level == syscall.IPPROTO_IP && c.Header.Type == syscall.IP_TOS, c.Header.Level == syscall.IPPROTO_IPV6 && c.Header.Type == syscall.IPV6_TCLASS: m.TOS = cmsgValue(c.Data) } } return m } // cmsgValue reads a cmsg the kernel encodes either as a native-endian int (IP_TTL, // IPV6_HOPLIMIT, IPV6_TCLASS) or as a single byte (IP_TOS). Both fit a byte by definition. func cmsgValue(data []byte) uint8 { switch { case len(data) >= 4: return uint8(binary.NativeEndian.Uint32(data)) case len(data) >= 1: return data[0] } return metaUnavailable }