mirror of
https://github.com/rwinkhart/sys.git
synced 2026-08-28 04:46:44 -04:00
unix: export Ifreq and add IoctlIfreq function
This CL expands upon CL 340369 by exporting the Ifreq type along with methods for setting and getting data to/from the ifreq union in a type-safe way. ifreqData remains unexported as we can keep adding helpers similar to the IoctlGetEthtoolDrvinfo to expose those operations in a less error-prone way. A test is also added to verify interface index data using IoctlIfreq against the modern rtnetlink API used by the standard library. Change-Id: Ic6980cbcd3792cc341cd614061cce32fa1f851e7 Reviewed-on: https://go-review.googlesource.com/c/sys/+/340370 Trust: Matt Layher <mdlayher@gmail.com> Run-TryBot: Matt Layher <mdlayher@gmail.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
+72
-11
@@ -7,15 +7,29 @@
|
|||||||
|
|
||||||
package unix
|
package unix
|
||||||
|
|
||||||
import "unsafe"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
// Helpers for dealing with ifreq since it contains a union and thus requires a
|
// Helpers for dealing with ifreq since it contains a union and thus requires a
|
||||||
// lot of unsafe.Pointer casts to use properly.
|
// lot of unsafe.Pointer casts to use properly.
|
||||||
|
|
||||||
// newIfreq creates an ifreq with the input network interface name after
|
// An Ifreq is a type-safe wrapper around the raw ifreq struct. An Ifreq
|
||||||
|
// contains an interface name and a union of arbitrary data which can be
|
||||||
|
// accessed using the Ifreq's methods. To create an Ifreq, use the NewIfreq
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// Use the Name method to access the stored interface name. The union data
|
||||||
|
// fields can be get and set using the following methods:
|
||||||
|
// - Uint16/SetUint16: flags
|
||||||
|
// - Uint32/SetUint32: ifindex, metric, mtu
|
||||||
|
type Ifreq struct{ raw ifreq }
|
||||||
|
|
||||||
|
// NewIfreq creates an Ifreq with the input network interface name after
|
||||||
// validating the name does not exceed IFNAMSIZ-1 (trailing NULL required)
|
// validating the name does not exceed IFNAMSIZ-1 (trailing NULL required)
|
||||||
// bytes.
|
// bytes.
|
||||||
func newIfreq(name string) (*ifreq, error) {
|
func NewIfreq(name string) (*Ifreq, error) {
|
||||||
// Leave room for terminating NULL byte.
|
// Leave room for terminating NULL byte.
|
||||||
if len(name) >= IFNAMSIZ {
|
if len(name) >= IFNAMSIZ {
|
||||||
return nil, EINVAL
|
return nil, EINVAL
|
||||||
@@ -24,25 +38,72 @@ func newIfreq(name string) (*ifreq, error) {
|
|||||||
var ifr ifreq
|
var ifr ifreq
|
||||||
copy(ifr.Ifrn[:], name)
|
copy(ifr.Ifrn[:], name)
|
||||||
|
|
||||||
return &ifr, nil
|
return &Ifreq{raw: ifr}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// An ifreqData is an ifreq but with a typed unsafe.Pointer field for data in
|
// TODO(mdlayher): get/set methods for sockaddr, char array, etc.
|
||||||
// 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
|
// Name returns the interface name associated with the Ifreq.
|
||||||
// the byte array of a raw ifreq.
|
func (ifr *Ifreq) Name() string {
|
||||||
|
// BytePtrToString requires a NULL terminator or the program may crash. If
|
||||||
|
// one is not present, just return the empty string.
|
||||||
|
if !bytes.Contains(ifr.raw.Ifrn[:], []byte{0x00}) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return BytePtrToString(&ifr.raw.Ifrn[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint16 returns the Ifreq union data as a C short/Go uint16 value.
|
||||||
|
func (ifr *Ifreq) Uint16() uint16 {
|
||||||
|
return *(*uint16)(unsafe.Pointer(&ifr.raw.Ifru[:2][0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUint16 sets a C short/Go uint16 value as the Ifreq's union data.
|
||||||
|
func (ifr *Ifreq) SetUint16(v uint16) {
|
||||||
|
ifr.clear()
|
||||||
|
*(*uint16)(unsafe.Pointer(&ifr.raw.Ifru[:2][0])) = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 returns the Ifreq union data as a C int/Go uint32 value.
|
||||||
|
func (ifr *Ifreq) Uint32() uint32 {
|
||||||
|
return *(*uint32)(unsafe.Pointer(&ifr.raw.Ifru[:4][0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUint32 sets a C int/Go uint32 value as the Ifreq's union data.
|
||||||
|
func (ifr *Ifreq) SetUint32(v uint32) {
|
||||||
|
ifr.clear()
|
||||||
|
*(*uint32)(unsafe.Pointer(&ifr.raw.Ifru[:4][0])) = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear zeroes the ifreq's union field to prevent trailing garbage data from
|
||||||
|
// being sent to the kernel if an ifreq is reused.
|
||||||
|
func (ifr *Ifreq) clear() {
|
||||||
|
for i := range ifr.raw.Ifru {
|
||||||
|
ifr.raw.Ifru[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mdlayher): export as IfreqData? For now we can provide helpers such as
|
||||||
|
// IoctlGetEthtoolDrvinfo which use these APIs under the hood.
|
||||||
|
|
||||||
|
// An ifreqData is an Ifreq which carries pointer data. To produce an ifreqData,
|
||||||
|
// use the Ifreq.withData method.
|
||||||
type ifreqData struct {
|
type ifreqData struct {
|
||||||
name [IFNAMSIZ]byte
|
name [IFNAMSIZ]byte
|
||||||
|
// A type separate from ifreq 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.
|
||||||
data unsafe.Pointer
|
data unsafe.Pointer
|
||||||
// Pad to the same size as ifreq.
|
// Pad to the same size as ifreq.
|
||||||
_ [len(ifreq{}.Ifru) - SizeofPtr]byte
|
_ [len(ifreq{}.Ifru) - SizeofPtr]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetData produces an ifreqData with the pointer p set for ioctls which require
|
// withData produces an ifreqData with the pointer p set for ioctls which require
|
||||||
// arbitrary pointer data.
|
// arbitrary pointer data.
|
||||||
func (ifr ifreq) SetData(p unsafe.Pointer) ifreqData {
|
func (ifr Ifreq) withData(p unsafe.Pointer) ifreqData {
|
||||||
return ifreqData{
|
return ifreqData{
|
||||||
name: ifr.Ifrn,
|
name: ifr.raw.Ifrn,
|
||||||
data: p,
|
data: p,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// 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 (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An ifreqUnion is shorthand for a byte array matching the
|
||||||
|
// architecture-dependent size of an ifreq's union field.
|
||||||
|
type ifreqUnion = [len(ifreq{}.Ifru)]byte
|
||||||
|
|
||||||
|
func TestNewIfreq(t *testing.T) {
|
||||||
|
// Interface name too long.
|
||||||
|
if _, err := NewIfreq("abcdefghijklmnop"); err != EINVAL {
|
||||||
|
t.Fatalf("expected error EINVAL, but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfreqSize(t *testing.T) {
|
||||||
|
// Ensure ifreq (generated) and Ifreq/ifreqData (hand-written to create a
|
||||||
|
// safe wrapper and store a pointer field) are identical in size.
|
||||||
|
want := unsafe.Sizeof(ifreq{})
|
||||||
|
if got := unsafe.Sizeof(Ifreq{}); want != got {
|
||||||
|
t.Fatalf("unexpected Ifreq size: got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := unsafe.Sizeof(ifreqData{}); want != got {
|
||||||
|
t.Fatalf("unexpected IfreqData size: got: %d, want: %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfreqName(t *testing.T) {
|
||||||
|
// Invalid ifreq (no NULL terminator), so expect empty string.
|
||||||
|
var name [IFNAMSIZ]byte
|
||||||
|
for i := range name {
|
||||||
|
name[i] = 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
bad := &Ifreq{raw: ifreq{Ifrn: name}}
|
||||||
|
if got := bad.Name(); got != "" {
|
||||||
|
t.Fatalf("expected empty ifreq name, but got: %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid ifreq, expect the hard-coded testIfreq name.
|
||||||
|
ifr := testIfreq(t)
|
||||||
|
if want, got := ifreqName, ifr.Name(); want != got {
|
||||||
|
t.Fatalf("unexpected ifreq name: got: %q, want: %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfreqWithData(t *testing.T) {
|
||||||
|
ifr := testIfreq(t)
|
||||||
|
|
||||||
|
// Store pointer data in the ifreq so we can retrieve it and cast back later
|
||||||
|
// for comparison.
|
||||||
|
want := [5]byte{'h', 'e', 'l', 'l', 'o'}
|
||||||
|
ifrd := ifr.withData(unsafe.Pointer(&want[0]))
|
||||||
|
|
||||||
|
// Ensure the memory of the original Ifreq was not modified by SetData.
|
||||||
|
if ifr.raw.Ifru != (ifreqUnion{}) {
|
||||||
|
t.Fatalf("ifreq was unexpectedly modified: % #x", ifr.raw.Ifru)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := *(*[5]byte)(ifrd.data)
|
||||||
|
if want != got {
|
||||||
|
t.Fatalf("unexpected ifreq data bytes:\n got: % #x\nwant: % #x", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfreqUint16(t *testing.T) {
|
||||||
|
ifr := testIfreq(t)
|
||||||
|
const in = 0x0102
|
||||||
|
ifr.SetUint16(in)
|
||||||
|
|
||||||
|
// The layout of the bytes depends on the machine's endianness.
|
||||||
|
var want ifreqUnion
|
||||||
|
if isBigEndian {
|
||||||
|
want[0] = 0x01
|
||||||
|
want[1] = 0x02
|
||||||
|
} else {
|
||||||
|
want[0] = 0x02
|
||||||
|
want[1] = 0x01
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := ifr.raw.Ifru; want != got {
|
||||||
|
t.Fatalf("unexpected ifreq uint16 bytes:\n got: % #x\nwant: % #x", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := ifr.Uint16(); in != got {
|
||||||
|
t.Fatalf("unexpected ifreq uint16: got: %d, want: %d", got, in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIfreqUint32(t *testing.T) {
|
||||||
|
ifr := testIfreq(t)
|
||||||
|
const in = 0x01020304
|
||||||
|
ifr.SetUint32(in)
|
||||||
|
|
||||||
|
// The layout of the bytes depends on the machine's endianness.
|
||||||
|
var want ifreqUnion
|
||||||
|
if isBigEndian {
|
||||||
|
want[0] = 0x01
|
||||||
|
want[1] = 0x02
|
||||||
|
want[2] = 0x03
|
||||||
|
want[3] = 0x04
|
||||||
|
} else {
|
||||||
|
want[0] = 0x04
|
||||||
|
want[1] = 0x03
|
||||||
|
want[2] = 0x02
|
||||||
|
want[3] = 0x01
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := ifr.raw.Ifru; want != got {
|
||||||
|
t.Fatalf("unexpected ifreq uint32 bytes:\n got: % #x\nwant: % #x", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := ifr.Uint32(); in != got {
|
||||||
|
t.Fatalf("unexpected ifreq uint32: got: %d, want: %d", got, in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ifreqName is a hard-coded name for testIfreq.
|
||||||
|
const ifreqName = "eth0"
|
||||||
|
|
||||||
|
// testIfreq returns an Ifreq with a populated interface name.
|
||||||
|
func testIfreq(t *testing.T) *Ifreq {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ifr, err := NewIfreq(ifreqName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create ifreq: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifr
|
||||||
|
}
|
||||||
+21
-3
@@ -48,15 +48,15 @@ func IoctlSetRTCWkAlrm(fd int, value *RTCWkAlrm) error {
|
|||||||
// IoctlGetEthtoolDrvinfo fetches ethtool driver information for the network
|
// IoctlGetEthtoolDrvinfo fetches ethtool driver information for the network
|
||||||
// device specified by ifname.
|
// device specified by ifname.
|
||||||
func IoctlGetEthtoolDrvinfo(fd int, ifname string) (*EthtoolDrvinfo, error) {
|
func IoctlGetEthtoolDrvinfo(fd int, ifname string) (*EthtoolDrvinfo, error) {
|
||||||
ifr, err := newIfreq(ifname)
|
ifr, err := NewIfreq(ifname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
value := EthtoolDrvinfo{Cmd: ETHTOOL_GDRVINFO}
|
value := EthtoolDrvinfo{Cmd: ETHTOOL_GDRVINFO}
|
||||||
ifrd := ifr.SetData(unsafe.Pointer(&value))
|
ifrd := ifr.withData(unsafe.Pointer(&value))
|
||||||
|
|
||||||
err = ioctlPtr(fd, SIOCETHTOOL, unsafe.Pointer(&ifrd))
|
err = ioctlIfreqData(fd, SIOCETHTOOL, &ifrd)
|
||||||
return &value, err
|
return &value, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,3 +176,21 @@ func IoctlHIDGetRawUniq(fd int) (string, error) {
|
|||||||
err := ioctlPtr(fd, _HIDIOCGRAWUNIQ, unsafe.Pointer(&value[0]))
|
err := ioctlPtr(fd, _HIDIOCGRAWUNIQ, unsafe.Pointer(&value[0]))
|
||||||
return ByteSliceToString(value[:]), err
|
return ByteSliceToString(value[:]), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IoctlIfreq performs an ioctl using an Ifreq structure for input and/or
|
||||||
|
// output. See the netdevice(7) man page for details.
|
||||||
|
func IoctlIfreq(fd int, req uint, value *Ifreq) error {
|
||||||
|
// It is possible we will add more fields to *Ifreq itself later to prevent
|
||||||
|
// misuse, so pass the raw *ifreq directly.
|
||||||
|
return ioctlPtr(fd, req, unsafe.Pointer(&value.raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mdlayher): export if and when IfreqData is exported.
|
||||||
|
|
||||||
|
// ioctlIfreqData performs an ioctl using an ifreqData structure for input
|
||||||
|
// and/or output. See the netdevice(7) man page for details.
|
||||||
|
func ioctlIfreqData(fd int, req uint, value *ifreqData) error {
|
||||||
|
// The memory layout of IfreqData (type-safe) and ifreq (not type-safe) are
|
||||||
|
// identical so pass *IfreqData directly.
|
||||||
|
return ioctlPtr(fd, req, unsafe.Pointer(value))
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,14 +14,6 @@ import (
|
|||||||
"unsafe"
|
"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 {
|
func makeProto(proto int) *int {
|
||||||
return &proto
|
return &proto
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,42 @@ func TestIoctlGetRTCWkAlrm(t *testing.T) {
|
|||||||
v.Enabled, v.Time.Year+1900, v.Time.Mon+1, v.Time.Mday, v.Time.Hour, v.Time.Min, v.Time.Sec)
|
v.Enabled, v.Time.Year+1900, v.Time.Mon+1, v.Time.Mday, v.Time.Hour, v.Time.Min, v.Time.Sec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIoctlIfreq(t *testing.T) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare the network interface fetched from rtnetlink with the data from
|
||||||
|
// the equivalent ioctl API.
|
||||||
|
for _, ifi := range ifis {
|
||||||
|
ifr, err := unix.NewIfreq(ifi.Name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create ifreq for %q: %v", ifi.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unix.IoctlIfreq(s, unix.SIOCGIFINDEX, ifr); err != nil {
|
||||||
|
t.Fatalf("failed to get interface index for %q: %v", ifi.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if want, got := ifi.Index, int(ifr.Uint32()); want != got {
|
||||||
|
t.Fatalf("unexpected interface index for %q: got: %d, want: %d",
|
||||||
|
ifi.Name, got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if want, got := ifi.Name, ifr.Name(); want != got {
|
||||||
|
t.Fatalf("unexpected interface name for index %d: got: %q, want: %q",
|
||||||
|
ifi.Index, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPpoll(t *testing.T) {
|
func TestPpoll(t *testing.T) {
|
||||||
if runtime.GOOS == "android" {
|
if runtime.GOOS == "android" {
|
||||||
t.Skip("mkfifo syscall is not available on android, skipping test")
|
t.Skip("mkfifo syscall is not available on android, skipping test")
|
||||||
|
|||||||
Reference in New Issue
Block a user