diff --git a/unix/ifreq_linux.go b/unix/ifreq_linux.go index fd3eeccc..934af313 100644 --- a/unix/ifreq_linux.go +++ b/unix/ifreq_linux.go @@ -41,7 +41,7 @@ func NewIfreq(name string) (*Ifreq, error) { return &Ifreq{raw: ifr}, nil } -// TODO(mdlayher): get/set methods for sockaddr, char array, etc. +// TODO(mdlayher): get/set methods for hardware address sockaddr, char array, etc. // Name returns the interface name associated with the Ifreq. func (ifr *Ifreq) Name() string { @@ -54,6 +54,46 @@ func (ifr *Ifreq) Name() string { return BytePtrToString(&ifr.raw.Ifrn[0]) } +// According to netdevice(7), only AF_INET addresses are returned for numerous +// sockaddr ioctls. For convenience, we expose these as Inet4Addr since the Port +// field and other data is always empty. + +// Inet4Addr returns the Ifreq union data from an embedded sockaddr as a C +// in_addr/Go []byte (4-byte IPv4 address) value. If the sockaddr family is not +// AF_INET, an error is returned. +func (ifr *Ifreq) Inet4Addr() ([]byte, error) { + raw := *(*RawSockaddrInet4)(unsafe.Pointer(&ifr.raw.Ifru[:SizeofSockaddrInet4][0])) + if raw.Family != AF_INET { + // Cannot safely interpret raw.Addr bytes as an IPv4 address. + return nil, EINVAL + } + + return raw.Addr[:], nil +} + +// SetInet4Addr sets a C in_addr/Go []byte (4-byte IPv4 address) value in an +// embedded sockaddr within the Ifreq's union data. v must be 4 bytes in length +// or an error will be returned. +func (ifr *Ifreq) SetInet4Addr(v []byte) error { + if len(v) != 4 { + return EINVAL + } + + var addr [4]byte + copy(addr[:], v) + + ifr.clear() + *(*RawSockaddrInet4)( + unsafe.Pointer(&ifr.raw.Ifru[:SizeofSockaddrInet4][0]), + ) = RawSockaddrInet4{ + // Always set IP family as ioctls would require it anyway. + Family: AF_INET, + Addr: addr, + } + + return nil +} + // 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])) diff --git a/unix/ifreq_linux_test.go b/unix/ifreq_linux_test.go index 52640ac1..7a6d56cf 100644 --- a/unix/ifreq_linux_test.go +++ b/unix/ifreq_linux_test.go @@ -8,6 +8,8 @@ package unix import ( + "bytes" + "net" "testing" "unsafe" ) @@ -74,6 +76,48 @@ func TestIfreqWithData(t *testing.T) { } } +func TestIfreqInet4Addr(t *testing.T) { + ifr := testIfreq(t) + in := net.IPv4(192, 0, 2, 1).To4() + if err := ifr.SetInet4Addr(in); err != nil { + t.Fatalf("failed to set ifreq IPv4 address: %v", err) + } + + // Store fixed offset data (AF_INET, IPv4 address) within underlying + // sockaddr bytes. Everything else should be zeroed. + want := ifreqUnion{4: 192, 5: 0, 6: 2, 7: 1} + if isBigEndian { + want[0] = 0x00 + want[1] = 0x02 + } else { + want[0] = 0x02 + want[1] = 0x00 + } + + if got := ifr.raw.Ifru; want != got { + t.Fatalf("unexpected ifreq sockaddr bytes:\n got: % #x\nwant: % #x", got, want) + } + + got, err := ifr.Inet4Addr() + if err != nil { + t.Fatalf("failed to get ifreq IPv4 address: %v", err) + } + if !bytes.Equal(in, got) { + t.Fatalf("unexpected ifreq IPv4 address:\n got: % #x\nwant: % #x", got, in) + } + + // Invalid input, wrong length. + if err := ifr.SetInet4Addr([]byte{0xff}); err == nil { + t.Fatal("expected an error setting invalid IPv4 address, but none occurred") + } + + // Invalid output, AF_INET is only set by SetInet4Addr input. + ifr.SetUint32(0xffffffff) + if _, err := ifr.Inet4Addr(); err == nil { + t.Fatal("expected an error getting invalid IPv4 address, but none occurred") + } +} + func TestIfreqUint16(t *testing.T) { ifr := testIfreq(t) const in = 0x0102 diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go index 886cd3c1..dd0fa6a8 100644 --- a/unix/syscall_linux_test.go +++ b/unix/syscall_linux_test.go @@ -158,9 +158,59 @@ func TestIoctlIfreq(t *testing.T) { t.Fatalf("unexpected interface name for index %d: got: %q, want: %q", ifi.Index, got, want) } + + wantIP, ok := firstIPv4(t, &ifi) + if err := unix.IoctlIfreq(s, unix.SIOCGIFADDR, ifr); err != nil { + // Interface may have no assigned IPv4 address. + if err != unix.EADDRNOTAVAIL { + t.Fatalf("failed to get IPv4 address for %q: %v", ifi.Name, err) + } + + // But if we found an address via rtnetlink, we should expect the + // ioctl to return one. + if ok { + t.Fatalf("found IPv4 address %q for %q but ioctl returned none", wantIP, ifi.Name) + } + + continue + } + + // Found an address, compare it directly. + addr, err := ifr.Inet4Addr() + if err != nil { + t.Fatalf("failed to get ifreq IPv4 address: %v", err) + } + + if want, got := wantIP, addr; !want.Equal(got) { + t.Fatalf("unexpected first IPv4 address for %q: got: %q, want: %q", + ifi.Name, got, want) + } } } +// firstIPv4 reports whether the interface has an IPv4 address assigned, +// returning the first discovered address. +func firstIPv4(t *testing.T, ifi *net.Interface) (net.IP, bool) { + t.Helper() + + addrs, err := ifi.Addrs() + if err != nil { + t.Fatalf("failed to get interface %q addresses: %v", ifi.Name, err) + } + + for _, a := range addrs { + // Only want valid IPv4 addresses. + ipn, ok := a.(*net.IPNet) + if !ok || ipn.IP.To4() == nil { + continue + } + + return ipn.IP, true + } + + return nil, false +} + func TestPpoll(t *testing.T) { if runtime.GOOS == "android" { t.Skip("mkfifo syscall is not available on android, skipping test")