Measure cable length from hardware transmit and receive timestamps

This commit is contained in:
flamingcow
2026-07-25 22:38:52 -07:00
parent 09411aebdf
commit 1b322bb32b
6 changed files with 454 additions and 18 deletions
+110
View File
@@ -0,0 +1,110 @@
package main
import (
"fmt"
"unsafe"
"golang.org/x/sys/unix"
)
const (
hwtstampTxOn = 1
hwtstampFilterAll = 1
)
// Read and written through the ifreq data pointer.
type hwtstampConfig struct {
flags int32
txType int32
rxFilter int32
}
// Hardware timestamping is a property of the interface, not the socket: the MAC
// has to be told to stamp on transmit and on receive before any socket can ask
// for the values. Receive stamping is filtered by protocol and ours is not PTP,
// so nothing narrower than "all" will see our frames.
func checkTimestamping(fd int, ifname string) checkResult {
res := checkResult{item: ifname + " hw timestamps"}
desc := func(c hwtstampConfig) string {
return fmt.Sprintf("tx_type=%d rx_filter=%d", c.txType, c.rxFilter)
}
hwtstampCall := func(req uintptr, cfg *hwtstampConfig) error {
var ifr dataIfreq
copy(ifr.name[:], ifname)
ifr.data = unsafe.Pointer(cfg)
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), req,
uintptr(unsafe.Pointer(&ifr))); errno != 0 {
return errno
}
return nil
}
var have hwtstampConfig
if err := hwtstampCall(unix.SIOCGHWTSTAMP, &have); err != nil {
res.err = err
res.fatal = true
return res
}
if have.txType == hwtstampTxOn && have.rxFilter == hwtstampFilterAll {
res.state = desc(have)
return res
}
// The ioctl reports back what the driver actually applied, which can be
// narrower than what was asked for.
want := hwtstampConfig{txType: hwtstampTxOn, rxFilter: hwtstampFilterAll}
if err := hwtstampCall(unix.SIOCSHWTSTAMP, &want); err != nil {
res.err = err
res.state = "could not set"
res.fatal = true
return res
}
if want.txType != hwtstampTxOn || want.rxFilter != hwtstampFilterAll {
res.err = fmt.Errorf("driver applied %s instead", desc(want))
res.fatal = true
return res
}
res.fixed = true
res.state = fmt.Sprintf("was %s, now %s", desc(have), desc(want))
return res
}
// The socket asks for the values the MAC is now producing. Only the raw
// hardware clock is wanted; the software stamps would just be more control
// message to copy.
func enableTxTimestamps(fd int) error {
return unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_TIMESTAMPING,
unix.SOF_TIMESTAMPING_TX_HARDWARE|
unix.SOF_TIMESTAMPING_RAW_HARDWARE|
unix.SOF_TIMESTAMPING_OPT_TSONLY)
}
func enableRxTimestamps(fd int) error {
return unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_TIMESTAMPING,
unix.SOF_TIMESTAMPING_RX_HARDWARE|unix.SOF_TIMESTAMPING_RAW_HARDWARE)
}
// SCM_TIMESTAMPING carries three timespecs and the third is the raw hardware
// clock; the first two are software clocks we did not ask for and which arrive
// zeroed. A zero hardware stamp means the MAC did not produce one.
const scmTimestampingLen = 3 * int(unsafe.Sizeof(unix.Timespec{}))
func hwTimestamp(oob []byte) (int64, bool) {
msgs, err := unix.ParseSocketControlMessage(oob)
if err != nil {
return 0, false
}
for _, m := range msgs {
if m.Header.Level != unix.SOL_SOCKET || m.Header.Type != unix.SCM_TIMESTAMPING {
continue
}
if len(m.Data) < scmTimestampingLen {
return 0, false
}
ts := (*[3]unix.Timespec)(unsafe.Pointer(&m.Data[0]))
ns := ts[2].Nano()
return ns, ns != 0
}
return 0, false
}