phc/device.go (76 lines of code) (raw):

/* Copyright (c) Facebook, Inc. and its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package phc import ( "fmt" "net" "unsafe" "github.com/vtolstov/go-ioctl" "golang.org/x/sys/unix" ) // Missing from sys/unix package, defined in Linux include/uapi/linux/ptp_clock.h const ( ptpMaxSamples = 25 ptpClkMagic = '=' ) // ioctlPTPSysOffsetExtended is an IOCTL to get extended offset var ioctlPTPSysOffsetExtended = ioctl.IOWR(ptpClkMagic, 9, unsafe.Sizeof(PTPSysOffsetExtended{})) // Ifreq is the request we send with SIOCETHTOOL IOCTL // as per Linux kernel's include/uapi/linux/if.h type Ifreq struct { Name [unix.IFNAMSIZ]byte Data uintptr } // EthtoolTSinfo holds a device's timestamping and PHC association // as per Linux kernel's include/uapi/linux/ethtool.h type EthtoolTSinfo struct { Cmd uint32 SOtimestamping uint32 PHCIndex int32 TXTypes uint32 TXReserved [3]uint32 RXFilters uint32 RXReserved [3]uint32 } // PTPSysOffsetExtended as defined in linux/ptp_clock.h type PTPSysOffsetExtended struct { NSamples uint32 /* Desired number of measurements. */ Reserved [3]uint32 /* Reserved for future use. */ /* * Array of [system, phc, system] time stamps. The kernel will provide * 3*n_samples time stamps. * - system time right before reading the lowest bits of the PHC timestamp * - PHC time * - system time immediately after reading the lowest bits of the PHC timestamp */ TS [ptpMaxSamples][3]PTPClockTime } // IfaceInfo uses SIOCETHTOOL ioctl to get information for the give nic, i.e. eth0. func IfaceInfo(iface string) (*EthtoolTSinfo, error) { fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) if err != nil { return nil, fmt.Errorf("failed to create socket for ioctl: %w", err) } defer unix.Close(fd) // this is what we want to be populated, but we need to provide Cmd first data := &EthtoolTSinfo{ Cmd: unix.ETHTOOL_GET_TS_INFO, } // actual request we send ifreq := &Ifreq{} // set Name in the request copy(ifreq.Name[:unix.IFNAMSIZ-1], iface) // pointer to the data we need to be populated ifreq.Data = uintptr(unsafe.Pointer(data)) _, _, errno := unix.Syscall( unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCETHTOOL), uintptr(unsafe.Pointer(ifreq)), ) if errno != 0 { return nil, fmt.Errorf("failed get phc ID: %s (%d)", unix.ErrnoName(errno), errno) } return data, nil } // IfaceData has both net.Interface and EthtoolTSinfo type IfaceData struct { Iface net.Interface TSInfo EthtoolTSinfo } // IfacesInfo is like net.Interfaces() but with added EthtoolTSinfo func IfacesInfo() ([]IfaceData, error) { ifaces, err := net.Interfaces() if err != nil { return nil, err } res := []IfaceData{} for _, iface := range ifaces { data, err := IfaceInfo(iface.Name) if err != nil { return nil, err } res = append(res, IfaceData{ Iface: iface, TSInfo: *data, }) } return res, nil }