blob: 1e79e367e7ff8c1777741920e0cca1b465359bb4 [file] [log] [blame]
package netdump
import (
"errors"
"fmt"
"net"
"runtime"
"unsafe"
"golang.org/x/sys/unix"
)
// @linux//include/uapi/linux:if.h
type ifreq struct {
ifname [16]byte
data uintptr
}
// @linux//include/uapi/linux:ethtool.h ethtool_perm_addr
type ethtoolPermAddr struct {
Cmd uint32
Size uint32
// Make this an array for memory layout reasons (see
// comment on the kernel struct)
Data [32]byte
}
var errNoPermenentHWAddr = errors.New("no permanent hardware address available")
func isAllZeroes(data []byte) bool {
for _, b := range data {
if b != 0 {
return false
}
}
return true
}
// Get permanent hardware address on Linux kernels older than 5.6. On newer
// kernels this is available via normal netlink. Returns errNoPermanentHWAddr
// in case no such address is available.
func getPermanentHWAddrLegacy(ifName string) (net.HardwareAddr, error) {
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
if err != nil {
fd, err = unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, unix.NETLINK_GENERIC)
if err != nil {
return nil, err
}
}
defer unix.Close(fd)
var data ethtoolPermAddr
data.Cmd = unix.ETHTOOL_GPERMADDR
data.Size = uint32(len(data.Data))
var req ifreq
copy(req.ifname[:], ifName)
// See //metropolis/pkg/nvme:cmd_linux.go RawCommand notice on the safety
// of this.
req.data = uintptr(unsafe.Pointer(&data))
for {
_, _, err := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.SIOCETHTOOL, uintptr(unsafe.Pointer(&req)))
if err == unix.EINTR {
continue
}
if err != 0 {
return nil, fmt.Errorf("ioctl(SIOETHTOOL) failed: %w", err)
}
break
}
runtime.KeepAlive(req)
runtime.KeepAlive(data)
// This kernel API is rather old and can indicate the absence of a permanent
// hardware MAC in two ways: a size of zero (in case the driver does not
// implement a permanent hardware address at all) or an all-zero value in
// case the driver has support for returning one but hasn't populated it.
if data.Size == 0 || isAllZeroes(data.Data[:data.Size]) {
return nil, errNoPermenentHWAddr
}
return net.HardwareAddr(data.Data[:data.Size]), nil
}