net/dump: init

This adds netdump, a library which dumps the network configuration of a
Linux host via netlink and generates a Net specification from it.

Change-Id: I78007df1da4fbf24ba9128eb2e966df0dea9274e
Reviewed-on: https://review.monogon.dev/c/monogon/+/1231
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Leopold Schabel <leo@monogon.tech>
diff --git a/net/dump/hwaddr_compat.go b/net/dump/hwaddr_compat.go
new file mode 100644
index 0000000..1e79e36
--- /dev/null
+++ b/net/dump/hwaddr_compat.go
@@ -0,0 +1,79 @@
+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
+}