unix: add ifreq and ifreqData helpers for Linux

ifreq is difficult to use in Go due to the union field in particular. This
situation is made worse due to the need to comply with Go's unsafe.Pointer
rules. This CL generates the raw ifreq type and also adds an ifreqData type
of the same size which is specialized for use with unsafe.Pointer.

We also replace the existing ifreqEthtool (which was not padded to the correct
size) with the new APIs and add a test to verify that IoctlGetEthtoolDrvinfo
functions properly by checking the name of the driver for each network interface.

Future uses of ifreq in package unix can expand upon this type with additional
getter and setter methods to deal with the unsafe casts to and from the union
byte array. We may also consider exporting ifreq and ifreqData if necessary.

Change-Id: Ibf73a10e774b4336815c674bb867bbb7ec1b9c71
Reviewed-on: https://go-review.googlesource.com/c/sys/+/340369
Run-TryBot: Matt Layher <mdlayher@gmail.com>
Trust: Matt Layher <mdlayher@gmail.com>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
diff --git a/unix/ifreq_linux.go b/unix/ifreq_linux.go
new file mode 100644
index 0000000..cb07859
--- /dev/null
+++ b/unix/ifreq_linux.go
@@ -0,0 +1,48 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+// +build linux
+
+package unix
+
+import "unsafe"
+
+// Helpers for dealing with ifreq since it contains a union and thus requires a
+// lot of unsafe.Pointer casts to use properly.
+
+// newIfreq creates an ifreq with the input network interface name after
+// validating the name does not exceed IFNAMSIZ-1 (trailing NULL required)
+// bytes.
+func newIfreq(name string) (*ifreq, error) {
+	// Leave room for terminating NULL byte.
+	if len(name) >= IFNAMSIZ {
+		return nil, EINVAL
+	}
+
+	var ifr ifreq
+	copy(ifr.Ifrn[:], name)
+
+	return &ifr, nil
+}
+
+// An ifreqData is an ifreq but with a typed unsafe.Pointer field for data in
+// the union. This is required in order to comply with the unsafe.Pointer rules
+// since the "pointer-ness" of data would not be preserved if it were cast into
+// the byte array of a raw ifreq.
+type ifreqData struct {
+	name [IFNAMSIZ]byte
+	data unsafe.Pointer
+	// Pad to the same size as ifreq.
+	_ [len(ifreq{}.Ifru) - SizeofPtr]byte
+}
+
+// SetData produces an ifreqData with the pointer p set for ioctls which require
+// arbitrary pointer data.
+func (ifr ifreq) SetData(p unsafe.Pointer) ifreqData {
+	return ifreqData{
+		name: ifr.Ifrn,
+		data: p,
+	}
+}
diff --git a/unix/ioctl_linux.go b/unix/ioctl_linux.go
index 48773f7..7e4f64a 100644
--- a/unix/ioctl_linux.go
+++ b/unix/ioctl_linux.go
@@ -50,28 +50,19 @@
 	return err
 }
 
-type ifreqEthtool struct {
-	name [IFNAMSIZ]byte
-	data unsafe.Pointer
-}
-
 // IoctlGetEthtoolDrvinfo fetches ethtool driver information for the network
 // device specified by ifname.
 func IoctlGetEthtoolDrvinfo(fd int, ifname string) (*EthtoolDrvinfo, error) {
-	// Leave room for terminating NULL byte.
-	if len(ifname) >= IFNAMSIZ {
-		return nil, EINVAL
+	ifr, err := newIfreq(ifname)
+	if err != nil {
+		return nil, err
 	}
 
-	value := EthtoolDrvinfo{
-		Cmd: ETHTOOL_GDRVINFO,
-	}
-	ifreq := ifreqEthtool{
-		data: unsafe.Pointer(&value),
-	}
-	copy(ifreq.name[:], ifname)
-	err := ioctl(fd, SIOCETHTOOL, uintptr(unsafe.Pointer(&ifreq)))
-	runtime.KeepAlive(ifreq)
+	value := EthtoolDrvinfo{Cmd: ETHTOOL_GDRVINFO}
+	ifrd := ifr.SetData(unsafe.Pointer(&value))
+
+	err = ioctl(fd, SIOCETHTOOL, uintptr(unsafe.Pointer(&ifrd)))
+	runtime.KeepAlive(ifrd)
 	return &value, err
 }
 
diff --git a/unix/linux/types.go b/unix/linux/types.go
index 41081f2..cc884ad 100644
--- a/unix/linux/types.go
+++ b/unix/linux/types.go
@@ -634,6 +634,8 @@
 
 type CanFilter C.struct_can_filter
 
+type ifreq C.struct_ifreq
+
 const (
 	SizeofSockaddrInet4     = C.sizeof_struct_sockaddr_in
 	SizeofSockaddrInet6     = C.sizeof_struct_sockaddr_in6
diff --git a/unix/syscall_internal_linux_test.go b/unix/syscall_internal_linux_test.go
index 7ec21ca..9ca15fc 100644
--- a/unix/syscall_internal_linux_test.go
+++ b/unix/syscall_internal_linux_test.go
@@ -14,6 +14,14 @@
 	"unsafe"
 )
 
+func Test_ifreqSize(t *testing.T) {
+	// Ensure ifreq (generated) and ifreqData (hand-written due to
+	// unsafe.Pointer field) are identical in size.
+	if want, got := unsafe.Sizeof(ifreq{}), unsafe.Sizeof(ifreqData{}); want != got {
+		t.Fatalf("unexpected ifreq size: got: %d, want: %d", got, want)
+	}
+}
+
 func makeProto(proto int) *int {
 	return &proto
 }
diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go
index ccc6f19..fe96bbe 100644
--- a/unix/syscall_linux_test.go
+++ b/unix/syscall_linux_test.go
@@ -13,6 +13,7 @@
 	"errors"
 	"fmt"
 	"io/ioutil"
+	"net"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -26,6 +27,39 @@
 	"golang.org/x/sys/unix"
 )
 
+func TestIoctlGetEthtoolDrvinfo(t *testing.T) {
+	if runtime.GOOS == "android" {
+		t.Skip("ethtool driver info is not available on android, skipping test")
+	}
+
+	s, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM, 0)
+	if err != nil {
+		t.Fatalf("failed to open socket: %v", err)
+	}
+	defer unix.Close(s)
+
+	ifis, err := net.Interfaces()
+	if err != nil {
+		t.Fatalf("failed to get network interfaces: %v", err)
+	}
+
+	// Print the interface name and associated driver information for each
+	// network interface supported by ethtool.
+	for _, ifi := range ifis {
+		drv, err := unix.IoctlGetEthtoolDrvinfo(s, ifi.Name)
+		if err != nil {
+			if err == unix.EOPNOTSUPP {
+				continue
+			}
+
+			t.Fatalf("failed to get ethtool driver info for %q: %v", ifi.Name, err)
+		}
+
+		// Trim trailing NULLs.
+		t.Logf("%s: %q", ifi.Name, string(bytes.TrimRight(drv.Driver[:], "\x00")))
+	}
+}
+
 func TestIoctlGetInt(t *testing.T) {
 	f, err := os.Open("/dev/random")
 	if err != nil {
diff --git a/unix/ztypes_linux_386.go b/unix/ztypes_linux_386.go
index 235c62e..f4a435f 100644
--- a/unix/ztypes_linux_386.go
+++ b/unix/ztypes_linux_386.go
@@ -630,3 +630,8 @@
 	PPS_GETCAP    = 0x800470a3
 	PPS_FETCH     = 0xc00470a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [16]byte
+}
diff --git a/unix/ztypes_linux_amd64.go b/unix/ztypes_linux_amd64.go
index 99b1e5b..a8996da 100644
--- a/unix/ztypes_linux_amd64.go
+++ b/unix/ztypes_linux_amd64.go
@@ -648,3 +648,8 @@
 	PPS_GETCAP    = 0x800870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}
diff --git a/unix/ztypes_linux_arm.go b/unix/ztypes_linux_arm.go
index cc8bba7..e9d9632 100644
--- a/unix/ztypes_linux_arm.go
+++ b/unix/ztypes_linux_arm.go
@@ -625,3 +625,8 @@
 	PPS_GETCAP    = 0x800470a3
 	PPS_FETCH     = 0xc00470a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [16]byte
+}
diff --git a/unix/ztypes_linux_arm64.go b/unix/ztypes_linux_arm64.go
index fa8fe3a..fbf5270 100644
--- a/unix/ztypes_linux_arm64.go
+++ b/unix/ztypes_linux_arm64.go
@@ -627,3 +627,8 @@
 	PPS_GETCAP    = 0x800870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}
diff --git a/unix/ztypes_linux_mips.go b/unix/ztypes_linux_mips.go
index e7fb8d9..bc6992f 100644
--- a/unix/ztypes_linux_mips.go
+++ b/unix/ztypes_linux_mips.go
@@ -631,3 +631,8 @@
 	PPS_GETCAP    = 0x400470a3
 	PPS_FETCH     = 0xc00470a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [16]byte
+}
diff --git a/unix/ztypes_linux_mips64.go b/unix/ztypes_linux_mips64.go
index 2fa61d5..9163f88 100644
--- a/unix/ztypes_linux_mips64.go
+++ b/unix/ztypes_linux_mips64.go
@@ -630,3 +630,8 @@
 	PPS_GETCAP    = 0x400870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}
diff --git a/unix/ztypes_linux_mips64le.go b/unix/ztypes_linux_mips64le.go
index 7f36399..7ab4cba 100644
--- a/unix/ztypes_linux_mips64le.go
+++ b/unix/ztypes_linux_mips64le.go
@@ -630,3 +630,8 @@
 	PPS_GETCAP    = 0x400870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}
diff --git a/unix/ztypes_linux_mipsle.go b/unix/ztypes_linux_mipsle.go
index f3c20cb..f44f319 100644
--- a/unix/ztypes_linux_mipsle.go
+++ b/unix/ztypes_linux_mipsle.go
@@ -631,3 +631,8 @@
 	PPS_GETCAP    = 0x400470a3
 	PPS_FETCH     = 0xc00470a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [16]byte
+}
diff --git a/unix/ztypes_linux_ppc.go b/unix/ztypes_linux_ppc.go
index 885d279..0d586a8 100644
--- a/unix/ztypes_linux_ppc.go
+++ b/unix/ztypes_linux_ppc.go
@@ -637,3 +637,8 @@
 	PPS_GETCAP    = 0x400470a3
 	PPS_FETCH     = 0xc00470a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [16]byte
+}
diff --git a/unix/ztypes_linux_ppc64.go b/unix/ztypes_linux_ppc64.go
index a94eb8e..9ed158c 100644
--- a/unix/ztypes_linux_ppc64.go
+++ b/unix/ztypes_linux_ppc64.go
@@ -637,3 +637,8 @@
 	PPS_GETCAP    = 0x400870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}
diff --git a/unix/ztypes_linux_ppc64le.go b/unix/ztypes_linux_ppc64le.go
index 659e32e..7e299ce 100644
--- a/unix/ztypes_linux_ppc64le.go
+++ b/unix/ztypes_linux_ppc64le.go
@@ -637,3 +637,8 @@
 	PPS_GETCAP    = 0x400870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}
diff --git a/unix/ztypes_linux_riscv64.go b/unix/ztypes_linux_riscv64.go
index ab8ec60..60e71be 100644
--- a/unix/ztypes_linux_riscv64.go
+++ b/unix/ztypes_linux_riscv64.go
@@ -655,3 +655,8 @@
 	PPS_GETCAP    = 0x800870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}
diff --git a/unix/ztypes_linux_s390x.go b/unix/ztypes_linux_s390x.go
index 3ec0823..351911b 100644
--- a/unix/ztypes_linux_s390x.go
+++ b/unix/ztypes_linux_s390x.go
@@ -651,3 +651,8 @@
 	PPS_GETCAP    = 0x800870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}
diff --git a/unix/ztypes_linux_sparc64.go b/unix/ztypes_linux_sparc64.go
index 23d4744..7ca1613 100644
--- a/unix/ztypes_linux_sparc64.go
+++ b/unix/ztypes_linux_sparc64.go
@@ -632,3 +632,8 @@
 	PPS_GETCAP    = 0x400870a3
 	PPS_FETCH     = 0xc00870a4
 )
+
+type ifreq struct {
+	Ifrn [16]byte
+	Ifru [24]byte
+}