server: upstream trains, observed TTL/DSCP/ECN, rate limits, action ids
Types 0x03/0x04/0x05 land with a bounded columnar train buffer (head kept, truncation declared) and grant-free multi-part reports - a report row is smaller than the packet it answers, so $3.4 holds without a grant. The read loop now collects TTL/TOS cmsgs on Linux, replacing the 0xFF stubs in the observation block with what the kernel saw; downtrain gained a dscp parameter, so DSCP survival is measurable in both directions. Rate limiting ($2.5) exists now: per-credential AND per-source buckets, 429 on the control plane, silent drop on the data plane after the HMAC gate and before the replay window. UDP ceilings default above the largest legitimate run - a limit that clips a real measurement produces a confidently wrong number. Every granted packet carries its action_id at payload[8:16]; overlapping actions were unattributable before. Canary DNS logs now honor the stated 24h privacy default. /admin/enroll-tokens answers the spec's JSON shape. protocol_version 1.0.1 (additive). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f6849f8e6a
commit
8118e213ae
@@ -0,0 +1,70 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user