package main import ( "fmt" "unsafe" "golang.org/x/sys/unix" ) const ( hwtstampTxOff = 0 hwtstampFilterAll = 1 ) type hwtstampConfig struct { flags int32 txType int32 rxFilter int32 } // Temporarily bypassed so BCM work can run on the X520, which cannot stamp; // restore to fatal for the product NIC (docs/open-questions.md). func checkTimestamping(fd int, ifname string) checkResult { res := configureTimestamping(fd, ifname) if res.err != nil { res.state = "bypassed: " + res.detail() res.err = nil res.fixed = true } return res } // Receive stamping is filtered by protocol and ours is not PTP, so nothing // narrower than "all" will see our frames. func configureTimestamping(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 return res } if have.txType == hwtstampTxOff && 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: hwtstampTxOff, rxFilter: hwtstampFilterAll} if err := hwtstampCall(unix.SIOCSHWTSTAMP, &want); err != nil { res.err = err res.state = "could not set" return res } if want.txType != hwtstampTxOff || want.rxFilter != hwtstampFilterAll { res.err = fmt.Errorf("driver applied %s instead", desc(want)) return res } res.fixed = true res.state = fmt.Sprintf("was %s, now %s", desc(have), desc(want)) return res } 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) } // Three timespecs, of which the third is the raw hardware clock. A zero there // means the mac did not produce a stamp. const scmTimestampingLen = 3 * int(unsafe.Sizeof(unix.Timespec{})) // A control message's payload starts at its header rounded up to a pointer, and // the next starts at its length rounded up the same way. const ( cmsgAlignMask = unix.SizeofPtr - 1 cmsgDataOff = (unix.SizeofCmsghdr + cmsgAlignMask) &^ cmsgAlignMask ) // Walked by hand rather than through ParseSocketControlMessage, which allocates // per call on a path that runs for every frame received. func hwTimestamp(oob []byte) (int64, bool) { for off := 0; off+unix.SizeofCmsghdr <= len(oob); { h := (*unix.Cmsghdr)(unsafe.Pointer(&oob[off])) n := int(h.Len) if n < unix.SizeofCmsghdr || off+n > len(oob) { return 0, false } if h.Level == unix.SOL_SOCKET && h.Type == unix.SCM_TIMESTAMPING { if cmsgDataOff+scmTimestampingLen > n { return 0, false } ts := (*[3]unix.Timespec)(unsafe.Pointer(&oob[off+cmsgDataOff])) ns := ts[2].Nano() return ns, ns != 0 } off += (n + cmsgAlignMask) &^ cmsgAlignMask } return 0, false }