btcutil: vendor anet instead of depending on a personal fork

In this commit, we vendor the Android networking shim that backs
InterfaceAddrs on android into the btcutil module, rather than
importing it from github.com/kcalvinalvin/anet. The shim works around
golang/go#40569, where net.InterfaceAddrs fails with
"route ip+net: netlinkrib: permission denied" on Android 11 and later
because of the tightened restrictions on NETLINK sockets. btcutil only
ever calls anet.InterfaceAddrs, wired in from net_android.go under the
android build tag.

The motivation is dependency hygiene. The previous import pointed at a
fork hosted under an individual contributor's account, which is not a
home we want a release dependency to live in. By copying the source
into internal/anet we drop the external module entirely: the package is
now unimportable from outside btcutil, and go.mod no longer references
the fork.

The source originates from github.com/wlynxg/anet, and its BSD 3-Clause
LICENSE travels with it. Unlike upstream, this copy does not reach into
the standard library's net package via //go:linkname; it carries its
own ipv6ZoneCache, so no -ldflags "-checklinkname=0" linker flag is
needed to build it. When golang/go#40569 is resolved upstream, both
this package and the net_android.go shim can be removed.
This commit is contained in:
Olaoluwa Osuntokun 2026-06-18 16:06:31 -07:00
parent 280d4d2db6
commit ce4e0f53af
10 changed files with 738 additions and 4 deletions

View file

@ -12,7 +12,6 @@ require (
github.com/btcsuite/btcd/wire/v2 v2.0.0
github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee
github.com/kkdai/bstream v1.0.0
)

View file

@ -20,8 +20,6 @@ github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee h1:FPP9HDkBbPyniu+u7FHZg+kKFX1WW0gxOGteJ0h3AJk=
github.com/kcalvinalvin/anet v0.0.0-20251112173137-d8ddc1f6dbee/go.mod h1:N6sz6HwJAenJ6d+/xmSl0ikfV05ZrVGmjt1ryy/WOtE=
github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8=
github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View file

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2023, wlynxg
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,24 @@
# anet (vendored)
This package is a vendored copy of an Android networking shim. It works around
[golang/go#40569](https://github.com/golang/go/issues/40569), where
`net.Interfaces()` and `net.InterfaceAddrs()` fail with
`route ip+net: netlinkrib: permission denied` on Android 11 and later due to the
tightened restrictions on `NETLINK` sockets (no `bind`, no `RTM_GETLINK`).
`btcutil` only relies on `anet.InterfaceAddrs()`, which is wired in from
`net_android.go` under the `android` build tag.
## Provenance
The source originates from `github.com/wlynxg/anet` (BSD 3-Clause, see
`LICENSE`). It was vendored here, rather than imported as a module dependency,
so that `btcd` does not depend on a fork hosted under a personal account.
Unlike upstream, this copy does **not** use the `//go:linkname` mechanism to
reach into the standard library's `net` package; it carries its own
`ipv6ZoneCache`. As a result, no `-ldflags "-checklinkname=0"` linker flag is
required to build it.
When [golang/go#40569](https://github.com/golang/go/issues/40569) is resolved
upstream, this package and the `net_android.go` shim can be removed.

View file

@ -0,0 +1,7 @@
//go:build !(android && cgo)
package anet
func androidDeviceApiLevel() int {
return -1
}

View file

@ -0,0 +1,23 @@
//go:build android && cgo
package anet
// #include <android/api-level.h>
import "C"
import "sync"
var (
apiLevel int
once sync.Once
)
// Returns the API level of the device we're actually running on, or -1 on failure.
// The returned value is equivalent to the Java Build.VERSION.SDK_INT API.
func androidDeviceApiLevel() int {
once.Do(func() {
apiLevel = int(C.android_get_device_api_level())
})
return apiLevel
}

View file

@ -0,0 +1,30 @@
//go:build !android
// +build !android
package anet
import (
"net"
)
// Interfaces returns a list of the system's network interfaces.
func Interfaces() ([]net.Interface, error) {
return net.Interfaces()
}
// InterfaceAddrs returns a list of the system's unicast interface
// addresses.
//
// The returned list does not identify the associated interface; use
// Interfaces and Interface.Addrs for more detail.
func InterfaceAddrs() ([]net.Addr, error) {
return net.InterfaceAddrs()
}
// InterfaceAddrsByInterface returns a list of the system's unicast
// interface addresses by specific interface.
func InterfaceAddrsByInterface(ifi *net.Interface) ([]net.Addr, error) {
return ifi.Addrs()
}
func SetAndroidVersion(version uint) {}

View file

@ -0,0 +1,446 @@
package anet
import (
"bytes"
"errors"
"net"
"os"
"sync"
"syscall"
"time"
"unsafe"
)
const (
android11ApiLevel = 30
)
var (
customAndroidApiLevel = -1
errInvalidInterface = errors.New("invalid network interface")
errInvalidInterfaceIndex = errors.New("invalid network interface index")
errInvalidInterfaceName = errors.New("invalid network interface name")
errNoSuchInterface = errors.New("no such network interface")
errNoSuchMulticastInterface = errors.New("no such multicast network interface")
)
type ifReq [40]byte
// Interfaces returns a list of the system's network interfaces.
func Interfaces() ([]net.Interface, error) {
if androidApiLevel() < android11ApiLevel {
return net.Interfaces()
}
ift, err := interfaceTable(0)
if err != nil {
return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
}
if len(ift) != 0 {
zoneCache.update(ift, true)
zoneCacheX.update(ift, true)
}
return ift, nil
}
// InterfaceAddrs returns a list of the system's unicast interface
// addresses.
//
// The returned list does not identify the associated interface; use
// Interfaces and Interface.Addrs for more detail.
func InterfaceAddrs() ([]net.Addr, error) {
if androidApiLevel() < android11ApiLevel {
return net.InterfaceAddrs()
}
ifat, err := interfaceAddrTable(nil)
if err != nil {
err = &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
}
return ifat, err
}
// InterfaceByIndex returns the interface specified by index.
//
// On Solaris, it returns one of the logical network interfaces
// sharing the logical data link; for more precision use
// InterfaceByName.
func InterfaceByIndex(index int) (*net.Interface, error) {
if androidApiLevel() < android11ApiLevel {
return net.InterfaceByIndex(index)
}
if index <= 0 {
return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterfaceIndex}
}
ift, err := interfaceTable(index)
if err != nil {
return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
}
ifi, err := interfaceByIndex(ift, index)
if err != nil {
err = &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
}
return ifi, err
}
// InterfaceByName returns the interface specified by name.
func InterfaceByName(name string) (*net.Interface, error) {
if name == "" {
return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterfaceName}
}
ift, err := interfaceTable(0)
if err != nil {
return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
}
if len(ift) != 0 {
zoneCache.update(ift, true)
zoneCacheX.update(ift, true)
}
for _, ifi := range ift {
if name == ifi.Name {
return &ifi, nil
}
}
return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errNoSuchInterface}
}
// InterfaceAddrsByInterface returns a list of the system's unicast
// interface addresses by specific interface.
func InterfaceAddrsByInterface(ifi *net.Interface) ([]net.Addr, error) {
if ifi == nil {
return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: errInvalidInterface}
}
if androidApiLevel() < android11ApiLevel {
return ifi.Addrs()
}
ifat, err := interfaceAddrTable(ifi)
if err != nil {
err = &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: nil, Err: err}
}
return ifat, err
}
// SetAndroidVersion set the Android environment in which the program runs.
// The Android system version number can be obtained through
// `android.os.Build.VERSION.RELEASE` of the Android framework.
// If version is 0 the actual version will be detected automatically if possible.
func SetAndroidVersion(version uint) {
switch {
case version == 0:
customAndroidApiLevel = -1
case version >= 11:
customAndroidApiLevel = android11ApiLevel
default:
customAndroidApiLevel = 0
}
}
func androidApiLevel() int {
if customAndroidApiLevel != -1 {
// user-provided api level should be used
return customAndroidApiLevel
}
// try to autodetect api level
return androidDeviceApiLevel()
}
// An ipv6ZoneCache represents a cache holding partial network
// interface information. It is used for reducing the cost of IPv6
// addressing scope zone resolution.
//
// Multiple names sharing the index are managed by first-come
// first-served basis for consistency.
type ipv6ZoneCache struct {
sync.RWMutex // guard the following
lastFetched time.Time // last time routing information was fetched
toIndex map[string]int // interface name to its index
toName map[int]string // interface index to its name
}
var zoneCache = ipv6ZoneCache{
toIndex: make(map[string]int),
toName: make(map[int]string),
}
var zoneCacheX = ipv6ZoneCache{
toIndex: make(map[string]int),
toName: make(map[int]string),
}
// update refreshes the network interface information if the cache was last
// updated more than 1 minute ago, or if force is set. It reports whether the
// cache was updated.
func (zc *ipv6ZoneCache) update(ift []net.Interface, force bool) (updated bool) {
zc.Lock()
defer zc.Unlock()
now := time.Now()
if !force && zc.lastFetched.After(now.Add(-60*time.Second)) {
return false
}
zc.lastFetched = now
if len(ift) == 0 {
var err error
if ift, err = interfaceTable(0); err != nil {
return false
}
}
zc.toIndex = make(map[string]int, len(ift))
zc.toName = make(map[int]string, len(ift))
for _, ifi := range ift {
zc.toIndex[ifi.Name] = ifi.Index
if _, ok := zc.toName[ifi.Index]; !ok {
zc.toName[ifi.Index] = ifi.Name
}
}
return true
}
// If the ifindex is zero, interfaceTable returns mappings of all
// network interfaces. Otherwise it returns a mapping of a specific
// interface.
func interfaceTable(ifindex int) ([]net.Interface, error) {
tab, err := NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC)
if err != nil {
return nil, os.NewSyscallError("netlinkrib", err)
}
msgs, err := syscall.ParseNetlinkMessage(tab)
if err != nil {
return nil, os.NewSyscallError("parsenetlinkmessage", err)
}
var ift []net.Interface
im := make(map[uint32]struct{})
loop:
for _, m := range msgs {
switch m.Header.Type {
case syscall.NLMSG_DONE:
break loop
case syscall.RTM_NEWADDR:
ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0]))
if _, ok := im[ifam.Index]; ok {
continue
} else {
im[ifam.Index] = struct{}{}
}
if ifindex == 0 || ifindex == int(ifam.Index) {
ifi := newLink(ifam)
if ifi != nil {
ift = append(ift, *ifi)
}
if ifindex == int(ifam.Index) {
break loop
}
}
}
}
return ift, nil
}
func newLink(ifam *syscall.IfAddrmsg) *net.Interface {
ift := &net.Interface{Index: int(ifam.Index)}
name, err := indexToName(ifam.Index)
if err != nil {
return nil
}
ift.Name = name
mtu, err := nameToMTU(name)
if err != nil {
return nil
}
ift.MTU = mtu
flags, err := nameToFlags(name)
if err != nil {
return nil
}
ift.Flags = flags
return ift
}
func linkFlags(rawFlags uint32) net.Flags {
var f net.Flags
if rawFlags&syscall.IFF_UP != 0 {
f |= net.FlagUp
}
if rawFlags&syscall.IFF_RUNNING != 0 {
f |= net.FlagRunning
}
if rawFlags&syscall.IFF_BROADCAST != 0 {
f |= net.FlagBroadcast
}
if rawFlags&syscall.IFF_LOOPBACK != 0 {
f |= net.FlagLoopback
}
if rawFlags&syscall.IFF_POINTOPOINT != 0 {
f |= net.FlagPointToPoint
}
if rawFlags&syscall.IFF_MULTICAST != 0 {
f |= net.FlagMulticast
}
return f
}
// If the ifi is nil, interfaceAddrTable returns addresses for all
// network interfaces. Otherwise it returns addresses for a specific
// interface.
func interfaceAddrTable(ifi *net.Interface) ([]net.Addr, error) {
tab, err := NetlinkRIB(syscall.RTM_GETADDR, syscall.AF_UNSPEC)
if err != nil {
return nil, os.NewSyscallError("netlinkrib", err)
}
msgs, err := syscall.ParseNetlinkMessage(tab)
if err != nil {
return nil, os.NewSyscallError("parsenetlinkmessage", err)
}
var ift []net.Interface
if ifi == nil {
var err error
ift, err = interfaceTable(0)
if err != nil {
return nil, err
}
}
ifat, err := addrTable(ift, ifi, msgs)
if err != nil {
return nil, err
}
return ifat, nil
}
func addrTable(ift []net.Interface, ifi *net.Interface, msgs []syscall.NetlinkMessage) ([]net.Addr, error) {
var ifat []net.Addr
loop:
for _, m := range msgs {
switch m.Header.Type {
case syscall.NLMSG_DONE:
break loop
case syscall.RTM_NEWADDR:
ifam := (*syscall.IfAddrmsg)(unsafe.Pointer(&m.Data[0]))
if len(ift) != 0 || ifi.Index == int(ifam.Index) {
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
if err != nil {
return nil, os.NewSyscallError("parsenetlinkrouteattr", err)
}
ifa := newAddr(ifam, attrs)
if ifa != nil {
ifat = append(ifat, ifa)
}
}
}
}
return ifat, nil
}
func newAddr(ifam *syscall.IfAddrmsg, attrs []syscall.NetlinkRouteAttr) net.Addr {
var ipPointToPoint bool
// Seems like we need to make sure whether the IP interface
// stack consists of IP point-to-point numbered or unnumbered
// addressing.
for _, a := range attrs {
if a.Attr.Type == syscall.IFA_LOCAL {
ipPointToPoint = true
break
}
}
for _, a := range attrs {
if ipPointToPoint && a.Attr.Type == syscall.IFA_ADDRESS {
continue
}
switch ifam.Family {
case syscall.AF_INET:
return &net.IPNet{IP: net.IPv4(a.Value[0], a.Value[1], a.Value[2], a.Value[3]), Mask: net.CIDRMask(int(ifam.Prefixlen), 8*net.IPv4len)}
case syscall.AF_INET6:
ifa := &net.IPNet{IP: make(net.IP, net.IPv6len), Mask: net.CIDRMask(int(ifam.Prefixlen), 8*net.IPv6len)}
copy(ifa.IP, a.Value[:])
return ifa
}
}
return nil
}
func interfaceByIndex(ift []net.Interface, index int) (*net.Interface, error) {
for _, ifi := range ift {
if index == ifi.Index {
return &ifi, nil
}
}
return nil, errNoSuchInterface
}
func ioctl(fd int, req uint, arg unsafe.Pointer) error {
_, _, e1 := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
if e1 != 0 {
return e1
}
return nil
}
func indexToName(index uint32) (string, error) {
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM|syscall.SOCK_CLOEXEC, 0)
if err != nil {
return "", err
}
defer syscall.Close(fd)
var ifr ifReq
*(*uint32)(unsafe.Pointer(&ifr[syscall.IFNAMSIZ])) = index
err = ioctl(fd, syscall.SIOCGIFNAME, unsafe.Pointer(&ifr[0]))
if err != nil {
return "", err
}
return string(bytes.Trim(ifr[:syscall.IFNAMSIZ], "\x00")), nil
}
func nameToMTU(name string) (int, error) {
// Leave room for terminating NULL byte.
if len(name) >= syscall.IFNAMSIZ {
return -1, syscall.EINVAL
}
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM|syscall.SOCK_CLOEXEC, 0)
if err != nil {
return -1, err
}
defer syscall.Close(fd)
var ifr ifReq
copy(ifr[:], name)
err = ioctl(fd, syscall.SIOCGIFMTU, unsafe.Pointer(&ifr[0]))
if err != nil {
return -1, err
}
return int(*(*int32)(unsafe.Pointer(&ifr[syscall.IFNAMSIZ]))), nil
}
func nameToFlags(name string) (net.Flags, error) {
// Leave room for terminating NULL byte.
if len(name) >= syscall.IFNAMSIZ {
return 0, syscall.EINVAL
}
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM|syscall.SOCK_CLOEXEC, 0)
if err != nil {
return 0, err
}
defer syscall.Close(fd)
var ifr ifReq
copy(ifr[:], name)
err = ioctl(fd, syscall.SIOCGIFFLAGS, unsafe.Pointer(&ifr[0]))
if err != nil {
return 0, err
}
return linkFlags(*(*uint32)(unsafe.Pointer(&ifr[syscall.IFNAMSIZ]))), nil
}

View file

@ -0,0 +1,179 @@
// Copyright 2011 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.
// Netlink sockets and messages
package anet
import (
"syscall"
"unsafe"
)
// Round the length of a netlink message up to align it properly.
func nlmAlignOf(msglen int) int {
return (msglen + syscall.NLMSG_ALIGNTO - 1) & ^(syscall.NLMSG_ALIGNTO - 1)
}
// Round the length of a netlink route attribute up to align it
// properly.
func rtaAlignOf(attrlen int) int {
return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1)
}
// NetlinkRouteRequest represents a request message to receive routing
// and link states from the kernel.
type NetlinkRouteRequest struct {
Header syscall.NlMsghdr
Data syscall.RtGenmsg
}
func (rr *NetlinkRouteRequest) toWireFormat() []byte {
b := make([]byte, rr.Header.Len)
*(*uint32)(unsafe.Pointer(&b[0:4][0])) = rr.Header.Len
*(*uint16)(unsafe.Pointer(&b[4:6][0])) = rr.Header.Type
*(*uint16)(unsafe.Pointer(&b[6:8][0])) = rr.Header.Flags
*(*uint32)(unsafe.Pointer(&b[8:12][0])) = rr.Header.Seq
*(*uint32)(unsafe.Pointer(&b[12:16][0])) = rr.Header.Pid
b[16] = byte(rr.Data.Family)
return b
}
func newNetlinkRouteRequest(proto, seq, family int) []byte {
rr := &NetlinkRouteRequest{}
rr.Header.Len = uint32(syscall.NLMSG_HDRLEN + syscall.SizeofRtGenmsg)
rr.Header.Type = uint16(proto)
rr.Header.Flags = syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST
rr.Header.Seq = uint32(seq)
rr.Data.Family = uint8(family)
return rr.toWireFormat()
}
// NetlinkRIB returns routing information base, as known as RIB, which
// consists of network facility information, states and parameters.
func NetlinkRIB(proto, family int) ([]byte, error) {
s, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW|syscall.SOCK_CLOEXEC, syscall.NETLINK_ROUTE)
if err != nil {
return nil, err
}
defer syscall.Close(s)
sa := &syscall.SockaddrNetlink{Family: syscall.AF_NETLINK}
wb := newNetlinkRouteRequest(proto, 1, family)
if err := syscall.Sendto(s, wb, 0, sa); err != nil {
return nil, err
}
lsa, err := syscall.Getsockname(s)
if err != nil {
return nil, err
}
lsanl, ok := lsa.(*syscall.SockaddrNetlink)
if !ok {
return nil, syscall.EINVAL
}
var tab []byte
rbNew := make([]byte, syscall.Getpagesize())
done:
for {
rb := rbNew
nr, _, err := syscall.Recvfrom(s, rb, 0)
if err != nil {
return nil, err
}
if nr < syscall.NLMSG_HDRLEN {
return nil, syscall.EINVAL
}
rb = rb[:nr]
tab = append(tab, rb...)
msgs, err := ParseNetlinkMessage(rb)
if err != nil {
return nil, err
}
for _, m := range msgs {
if m.Header.Seq != 1 || m.Header.Pid != lsanl.Pid {
return nil, syscall.EINVAL
}
if m.Header.Type == syscall.NLMSG_DONE {
break done
}
if m.Header.Type == syscall.NLMSG_ERROR {
return nil, syscall.EINVAL
}
}
}
return tab, nil
}
// NetlinkMessage represents a netlink message.
type NetlinkMessage struct {
Header syscall.NlMsghdr
Data []byte
}
// ParseNetlinkMessage parses b as an array of netlink messages and
// returns the slice containing the NetlinkMessage structures.
func ParseNetlinkMessage(b []byte) ([]NetlinkMessage, error) {
var msgs []NetlinkMessage
for len(b) >= syscall.NLMSG_HDRLEN {
h, dbuf, dlen, err := netlinkMessageHeaderAndData(b)
if err != nil {
return nil, err
}
m := NetlinkMessage{Header: *h, Data: dbuf[:int(h.Len)-syscall.NLMSG_HDRLEN]}
msgs = append(msgs, m)
b = b[dlen:]
}
return msgs, nil
}
func netlinkMessageHeaderAndData(b []byte) (*syscall.NlMsghdr, []byte, int, error) {
h := (*syscall.NlMsghdr)(unsafe.Pointer(&b[0]))
l := nlmAlignOf(int(h.Len))
if int(h.Len) < syscall.NLMSG_HDRLEN || l > len(b) {
return nil, nil, 0, syscall.EINVAL
}
return h, b[syscall.NLMSG_HDRLEN:], l, nil
}
// NetlinkRouteAttr represents a netlink route attribute.
type NetlinkRouteAttr struct {
Attr syscall.RtAttr
Value []byte
}
// ParseNetlinkRouteAttr parses m's payload as an array of netlink
// route attributes and returns the slice containing the
// NetlinkRouteAttr structures.
func ParseNetlinkRouteAttr(m *NetlinkMessage) ([]NetlinkRouteAttr, error) {
var b []byte
switch m.Header.Type {
case syscall.RTM_NEWLINK, syscall.RTM_DELLINK:
b = m.Data[syscall.SizeofIfInfomsg:]
case syscall.RTM_NEWADDR, syscall.RTM_DELADDR:
b = m.Data[syscall.SizeofIfAddrmsg:]
case syscall.RTM_NEWROUTE, syscall.RTM_DELROUTE:
b = m.Data[syscall.SizeofRtMsg:]
default:
return nil, syscall.EINVAL
}
var attrs []NetlinkRouteAttr
for len(b) >= syscall.SizeofRtAttr {
a, vbuf, alen, err := netlinkRouteAttrAndValue(b)
if err != nil {
return nil, err
}
ra := NetlinkRouteAttr{Attr: *a, Value: vbuf[:int(a.Len)-syscall.SizeofRtAttr]}
attrs = append(attrs, ra)
b = b[alen:]
}
return attrs, nil
}
func netlinkRouteAttrAndValue(b []byte) (*syscall.RtAttr, []byte, int, error) {
a := (*syscall.RtAttr)(unsafe.Pointer(&b[0]))
if int(a.Len) < syscall.SizeofRtAttr || int(a.Len) > len(b) {
return nil, nil, 0, syscall.EINVAL
}
return a, b[syscall.SizeofRtAttr:], rtaAlignOf(int(a.Len)), nil
}

View file

@ -10,7 +10,7 @@ package btcutil
import (
"net"
"github.com/kcalvinalvin/anet"
"github.com/btcsuite/btcd/btcutil/v2/internal/anet"
)
// InterfaceAddrs returns a list of the system's network interface addresses.