This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
version: "2"
linters:
enable:
# - errorlint
- misspell
- modernize
- revive
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- errcheck
path: _test.go
formatters:
exclusions:
generated: lax
+93 -1
View File
@@ -1,5 +1,97 @@
# CHANGELOG
## v1.11.2
- [Bug Fix]: [#291](https://github.com/mdlayher/netlink/pull/291) fixed a bug
where `netlink.Conn.Receive` would block concurrent calls to
`netlink.Conn.Send` when blocking in `recvmsg`.
- [Improvement]: [#289](https://github.com/mdlayher/netlink/pull/289) updated
`golang.org/x/net` and `golang.org/x/sys` dependencies.
## v1.11.1
- [Bug Fix]: [#283](https://github.com/mdlayher/netlink/pull/283) fixed a bug
where `netlink.Conn.Receive` would reject valid netlink messages whose header
length is not aligned. This is common with nfqueue, nflog and conntrack events.
- [Bug Fix]: [#284](https://github.com/mdlayher/netlink/pull/284) fixed a bug
where `netlink.Message.Data` could be silently overwritten by subsequent
`netlink.Conn.Receive` calls when `netlink.Config.MessageBufferSize` is enabled,
due to pooled buffer reuse.
- [Improvement]: [#282](https://github.com/mdlayher/netlink/pull/282) improved
error handling by usage of `errors` package.
## v1.11.0
**This is the first release of package netlink that only supports Go 1.25+.**
- [Bug Fix]: [#280](https://github.com/mdlayher/netlink/pull/280) fixed a
critical bug where `netlink.Conn.Receive` and `netlink.Conn.ReceiveIter` would
panic if the received message was unaligned.
- [Improvement]: [#277](https://github.com/mdlayher/netlink/pull/277) added
big-endian test fixtures for nlenc and deprecated endian helpers in favor of
binary.NativeEndian.
- [Improvement]: [#276](https://github.com/mdlayher/netlink/pull/276) fixed
skipping of tests on big-endian hosts.
- [Improvement]: [#273](https://github.com/mdlayher/netlink/pull/273) added
golangci-lint to the CI pipeline and fixed all existing lint issues.
- [Improvement]: [#272](https://github.com/mdlayher/netlink/pull/272) updated
dependencies and Go version to 1.25.
## v1.10.0
**Users of this version should upgrade to v1.11.0 as this version contains a
critical bug.**
- [New API]: [#270](https://github.com/mdlayher/netlink/pull/270) added
`MessageBufferSize` option to `netlink.Config` for configuring the size of the
copy buffer used for receiving messages. This can improve performance in
high-throughput applications by reducing the number of syscalls needed to
receive messages.
- [Improvement]: [#219](https://github.com/mdlayher/netlink/pull/219) added
debug logging inspired by libmnl. This is now the new default when setting
`NLDEBUG` in the environment.
- [New API]: [#258](https://github.com/mdlayher/netlink/pull/258) added
`netlink.Conn.ReceiveIter` for iterating over responses without collecting
them into a slice. It also optimized `netlink.Conn.Receive` to use this new
API internally so that less memory is used.
- [Improvement]: [#269](https://github.com/mdlayher/netlink/pull/269) made it so
`nltest.Conn.Receive` can be used to test that multi-part messages are drained
properly.
- [Improvement]: [#266](https://github.com/mdlayher/netlink/pull/266) optimized
the initial parsing of netlink messages in `netlink.Socket.Receive` by adding
an iterator for parsing messages directly from the receive buffer.
- [Improvement]: [#267](https://github.com/mdlayher/netlink/pull/267) added
integration test benchmarks for multi-part dumps.
- [Improvement]: [#215](https://github.com/mdlayher/netlink/pull/215) optimized
the internal peek/allocate logic in `netlink.Socket.Receive`. Messages are
no longer being copied during peeking and the buffer is allocated to the exact
size of the upcoming message.
- [Bug Fix]: [#265](https://github.com/mdlayher/netlink/pull/265) fixed a bug
where concurrent calls to `netlink.Conn.Receive` could race with each other
when handling multi-part messages. Calls to `Receive` are now serialized.
- [Improvement]: [#264](https://github.com/mdlayher/netlink/pull/264) added
handling for truncated messages in `netlink.Socket.Receive`.
## v1.9.0
**This is the first release of package netlink that only supports Go 1.24+.**
- [Improvement]: updated dependencies and Go version to 1.24; tests now run on
Go 1.24-1.26.
- [New API]: [#236](https://github.com/mdlayher/netlink/pull/236) introduced
`Sequence` field to `OpError` for better error correlation.
- [New API]: [#237](https://github.com/mdlayher/netlink/pull/237) added
`netlink.Conn.PID` method to retrieve the connection's PID, also known as port
ID.
- [Improvement]: [#228](https://github.com/mdlayher/netlink/pull/228) fixed
skipping of specific tests on big-endian hosts.
## v1.8.0
- [Improvement]: Updated dependencies, test with Go 1.23 to 1.25.
- [Improvement]: Use Go 1.21's binary.NativeEndian (#220)
- [Improvement]: Expose socket ReadBuffer & WriteBuffer functions (#223)
## v1.7.2
- [Improvement]: updated dependencies, test with Go 1.20.
@@ -29,7 +121,7 @@ Users on older versions of Go must use v1.6.2.**
## v1.6.1
- [Deprecation] [commit](https://github.com/mdlayher/netlink/commit/d1b69ea8697d721415c259ef8513ab699c6d3e96):
- [Deprecation] [commit](https://github.com/mdlayher/netlink/commit/d1b69ea8697d721415c259ef8513ab699c6d3e96):
the `netlink.Socket` interface has been marked as deprecated. The abstraction
is awkward to use properly and disables much of the functionality of the Conn
type when the basic interface is implemented. Do not use.
+1 -1
View File
@@ -1,6 +1,6 @@
# MIT License
Copyright (C) 2016-2022 Matt Layher
Copyright (C) 2016-2026 Matt Layher
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+9 -9
View File
@@ -8,14 +8,14 @@ import "unsafe"
// #define NLMSG_ALIGNTO 4U
const nlmsgAlignTo = 4
// #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
func nlmsgAlign(len int) int {
return ((len) + nlmsgAlignTo - 1) & ^(nlmsgAlignTo - 1)
// #define NLMSG_ALIGN(length) ( ((length)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
func nlmsgAlign(length int) int {
return ((length) + nlmsgAlignTo - 1) & ^(nlmsgAlignTo - 1)
}
// #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
func nlmsgLength(len int) int {
return len + nlmsgHeaderLen
// #define NLMSG_LENGTH(length) ((length) + NLMSG_HDRLEN)
func nlmsgLength(length int) int {
return length + nlmsgHeaderLen
}
// #define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
@@ -24,9 +24,9 @@ var nlmsgHeaderLen = nlmsgAlign(int(unsafe.Sizeof(Header{})))
// #define NLA_ALIGNTO 4
const nlaAlignTo = 4
// #define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
func nlaAlign(len int) int {
return ((len) + nlaAlignTo - 1) & ^(nlaAlignTo - 1)
// #define NLA_ALIGN(length) (((length) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
func nlaAlign(length int) int {
return ((length) + nlaAlignTo - 1) & ^(nlaAlignTo - 1)
}
// Because this package's Attribute type contains a byte slice, unsafe.Sizeof
+5 -5
View File
@@ -34,8 +34,8 @@ func (a *Attribute) marshal(b []byte) (int, error) {
return 0, errInvalidAttribute
}
nlenc.PutUint16(b[0:2], a.Length)
nlenc.PutUint16(b[2:4], a.Type)
binary.NativeEndian.PutUint16(b[0:], a.Length)
binary.NativeEndian.PutUint16(b[2:], a.Type)
n := copy(b[nlaHeaderLen:], a.Data)
return nlaHeaderLen + nlaAlign(n), nil
@@ -47,8 +47,8 @@ func (a *Attribute) unmarshal(b []byte) error {
return errInvalidAttribute
}
a.Length = nlenc.Uint16(b[0:2])
a.Type = nlenc.Uint16(b[2:4])
a.Length = binary.NativeEndian.Uint16(b[0:])
a.Type = binary.NativeEndian.Uint16(b[2:])
if int(a.Length) > len(b) {
return errInvalidAttribute
@@ -243,7 +243,7 @@ func (ad *AttributeDecoder) available() (int, error) {
}
// Extract the length of the attribute.
l := int(nlenc.Uint16(ad.b[i : i+2]))
l := int(binary.NativeEndian.Uint16(ad.b[i:]))
// Ignore zero-length attributes.
if l != 0 {
+172 -60
View File
@@ -1,6 +1,7 @@
package netlink
import (
"iter"
"math/rand"
"sync"
"sync/atomic"
@@ -31,6 +32,12 @@ type Conn struct {
// transaction within Execute.
mu sync.RWMutex
// receiveMu serializes concurrent Receive and ReceiveIter calls to prevent
// races in multi-part message handling and the peek/allocate logic in
// conn_linux.go. It is separate from mu so that Send can proceed
// concurrently with Receive.
receiveMu sync.Mutex
// sock is the operating system-specific implementation of
// a netlink sockets connection.
sock Socket
@@ -53,6 +60,7 @@ type Socket interface {
Send(m Message) error
SendMessages(m []Message) error
Receive() ([]Message, error)
ReceiveIter() iter.Seq2[Message, error]
}
// Dial dials a connection to netlink, using the specified netlink family.
@@ -228,84 +236,130 @@ func (c *Conn) Receive() ([]Message, error) {
c.mu.RLock()
defer c.mu.RUnlock()
// Serialize concurrent Receive calls. See receiveMu for details.
c.receiveMu.Lock()
defer c.receiveMu.Unlock()
return c.lockedReceive()
}
// ReceiveIter returns an iterator which can be used to receive messages from
// netlink. Just like Receive, multi-part messages are handled transparently and
// netlink errors are returned as errors from the iterator.
//
// If the iteration is stopped before all messages have been read and the
// response is multi-part, the remaining messages will be discarded.
func (c *Conn) ReceiveIter() iter.Seq2[Message, error] {
return func(yield func(Message, error) bool) {
// Wait for any concurrent calls to Execute to finish before proceeding.
c.mu.RLock()
defer c.mu.RUnlock()
// Serialize concurrent ReceiveIter calls. See receiveMu for details.
c.receiveMu.Lock()
defer c.receiveMu.Unlock()
for msg, err := range c.lockedReceiveIter() {
if err != nil {
c.debug(func(d *debugger) {
d.debugf(1, "recv: err: %v", err)
})
yield(Message{}, err)
return
}
c.debug(func(d *debugger) {
d.debugf(1, "recv: %+v", msg)
})
if !yield(msg, nil) {
return
}
}
}
}
// lockedReceive implements Receive, but must be called with c.mu acquired for reading.
// We rely on the kernel to deal with concurrent reads and writes to the netlink
// socket itself.
func (c *Conn) lockedReceive() ([]Message, error) {
msgs, err := c.receive()
if err != nil {
var msgs []Message
for m, err := range c.lockedReceiveIter() {
if err != nil {
c.debug(func(d *debugger) {
d.debugf(1, "recv: err: %v", err)
})
return nil, err
}
c.debug(func(d *debugger) {
d.debugf(1, "recv: err: %v", err)
d.debugf(1, "recv: %+v", m)
})
return nil, err
}
c.debug(func(d *debugger) {
for _, m := range msgs {
d.debugf(1, "recv: %+v", m)
}
})
// When using nltest, it's possible for zero messages to be returned by receive.
if len(msgs) == 0 {
return msgs, nil
}
// Trim the final message with multi-part done indicator if
// present.
if m := msgs[len(msgs)-1]; m.Header.Flags&Multi != 0 && m.Header.Type == Done {
return msgs[:len(msgs)-1], nil
msgs = append(msgs, m)
}
return msgs, nil
}
// receive is the internal implementation of Conn.Receive, which can be called
// recursively to handle multi-part messages.
func (c *Conn) receive() ([]Message, error) {
// NB: All non-nil errors returned from this function *must* be of type
// OpError in order to maintain the appropriate contract with callers of
// this package.
//
// This contract also applies to functions called within this function,
// such as checkMessage.
// lockedReceiveIter returns an iterator which can be used to receive messages
// from netlink, but must be called with c.mu acquired for the duration of the
// iteration.
func (c *Conn) lockedReceiveIter() iter.Seq2[Message, error] {
return func(yield func(Message, error) bool) {
// NB: All non-nil errors returned from this function *must* be of type
// OpError in order to maintain the appropriate contract with callers of
// this package.
//
// This contract also applies to functions called within this function,
// such as checkMessage.
var res []Message
for {
msgs, err := c.sock.Receive()
if err != nil {
return nil, newOpError("receive", err)
var more, stopped bool
// send is a helper function to prevent yielding messages after the user
// has stopped iterating
var send = func(m Message, err error) {
if stopped {
return
}
if !yield(m, err) {
stopped = true
}
}
// If this message is multi-part, we will need to continue looping to
// drain all the messages from the socket.
var multi bool
for {
for m, err := range c.sock.ReceiveIter() {
if err != nil {
send(Message{}, newOpError("receive", err))
return
}
for _, m := range msgs {
if err := checkMessage(m); err != nil {
return nil, err
if err := checkMessage(m); err != nil {
send(Message{}, err)
return
}
// Exit early if we encounter a multi-part done message.
// This should be safe to do since messages of type Done should always
// be the last message in a datagram.
if m.Header.Type == Done && m.Header.Flags&Multi != 0 {
return
}
if m.Header.Flags&Multi != 0 {
more = true
}
send(m, nil)
if stopped && !more {
// The user has stopped iterating and there are no more messages
// to read.
return
}
}
// Does this message indicate a multi-part message?
if m.Header.Flags&Multi == 0 {
// No, check the next messages.
continue
if !more {
return
}
// Does this message indicate the last message in a series of
// multi-part messages from a single read?
multi = m.Header.Type != Done
}
res = append(res, msgs...)
if !multi {
// No more messages coming.
return res, nil
}
}
}
@@ -434,17 +488,20 @@ func (c *Conn) SetOption(option ConnOption, enable bool) error {
return newOpError("set-option", conn.SetOption(option, enable))
}
// A bufferSetter is a Socket that supports setting connection buffer sizes.
type bufferSetter interface {
// A bufferedSocket is a Socket that supports getting & setting connection
// buffer sizes.
type bufferedSocket interface {
Socket
SetReadBuffer(bytes int) error
SetWriteBuffer(bytes int) error
ReadBuffer() (int, error)
WriteBuffer() (int, error)
}
// SetReadBuffer sets the size of the operating system's receive buffer
// associated with the Conn.
func (c *Conn) SetReadBuffer(bytes int) error {
conn, ok := c.sock.(bufferSetter)
conn, ok := c.sock.(bufferedSocket)
if !ok {
return notSupported("set-read-buffer")
}
@@ -455,7 +512,7 @@ func (c *Conn) SetReadBuffer(bytes int) error {
// SetWriteBuffer sets the size of the operating system's transmit buffer
// associated with the Conn.
func (c *Conn) SetWriteBuffer(bytes int) error {
conn, ok := c.sock.(bufferSetter)
conn, ok := c.sock.(bufferedSocket)
if !ok {
return notSupported("set-write-buffer")
}
@@ -463,6 +520,44 @@ func (c *Conn) SetWriteBuffer(bytes int) error {
return newOpError("set-write-buffer", conn.SetWriteBuffer(bytes))
}
// ReadBuffer reads the size of the operating system's receive buffer
// associated with the Conn.
func (c *Conn) ReadBuffer() (int, error) {
conn, ok := c.sock.(bufferedSocket)
if !ok {
return 0, notSupported("get-read-buffer")
}
buff, err := conn.ReadBuffer()
if err != nil {
return 0, newOpError("get-read-buffer", err)
}
return buff, nil
}
// WriteBuffer reads the size of the operating system's transmit buffer
// associated with the Conn.
func (c *Conn) WriteBuffer() (int, error) {
conn, ok := c.sock.(bufferedSocket)
if !ok {
return 0, notSupported("get-write-buffer")
}
buff, err := conn.WriteBuffer()
if err != nil {
return 0, newOpError("get-write-buffer", err)
}
return buff, nil
}
// PID returns the PID associated with the Conn. It is also known as
// the port ID in netlink terminology.
// https://docs.kernel.org/userspace-api/netlink/intro.html#nlmsg-pid
func (c *Conn) PID() uint32 {
return c.pid
}
// A syscallConner is a Socket that supports syscall.Conn.
type syscallConner interface {
Socket
@@ -590,4 +685,21 @@ type Config struct {
// When possible, setting Strict to true is recommended for applications
// running on modern Linux kernels.
Strict bool
// MessageBufferSize specifies a fixed buffer size for receiving netlink
// messages. When set, the connection will reuse a single pre-allocated
// buffer of this size instead of peeking at each message to determine
// the exact size needed.
//
// This is useful for high-throughput applications where the overhead of
// peeking at each message is undesirable and the maximum message size
// is known in advance.
//
// If set to 0 (the default), the connection will peek at the upcoming
// message before allocating a buffer for it.
//
// Note: this is not the same as the kernel socket receive buffer which
// can be configured using SetReadBuffer. MessageBufferSize only controls
// the userspace buffer passed to recvmsg.
MessageBufferSize int
}
+99 -38
View File
@@ -1,11 +1,12 @@
//go:build linux
// +build linux
package netlink
import (
"context"
"iter"
"os"
"sync"
"syscall"
"time"
"unsafe"
@@ -19,7 +20,8 @@ var _ Socket = &conn{}
// A conn is the Linux implementation of a netlink sockets connection.
type conn struct {
s *socket.Conn
s *socket.Conn
pool *sync.Pool
}
// dial is the entry point for Dial. dial opens a netlink socket using
@@ -71,6 +73,16 @@ func newConn(s *socket.Conn, config *Config) (*conn, uint32, error) {
}
c := &conn{s: s}
if config.MessageBufferSize > 0 {
c.pool = &sync.Pool{
New: func() any {
b := make([]byte, config.MessageBufferSize)
return &b
},
}
}
if config.Strict {
// The caller has requested the strict option set. Historically we have
// recommended checking for ENOPROTOOPT if the kernel does not support
@@ -121,50 +133,91 @@ func (c *conn) Send(m Message) error {
// Receive receives one or more Messages from netlink.
func (c *conn) Receive() ([]Message, error) {
b := make([]byte, os.Getpagesize())
for {
// Peek at the buffer to see how many bytes are available.
//
// TODO(mdlayher): deal with OOB message data if available, such as
// when PacketInfo ConnOption is true.
n, _, _, _, err := c.s.Recvmsg(context.Background(), b, nil, unix.MSG_PEEK)
var msgs []Message
for msg, err := range c.ReceiveIter() {
if err != nil {
return nil, err
}
// Break when we can read all messages
if n < len(b) {
break
}
// Double in size if not enough bytes
b = make([]byte, len(b)*2)
}
// Read out all available messages
n, _, _, _, err := c.s.Recvmsg(context.Background(), b, nil, 0)
if err != nil {
return nil, err
}
raw, err := syscall.ParseNetlinkMessage(b[:nlmsgAlign(n)])
if err != nil {
return nil, err
}
msgs := make([]Message, 0, len(raw))
for _, r := range raw {
m := Message{
Header: sysToHeader(r.Header),
Data: r.Data,
}
msgs = append(msgs, m)
msgs = append(msgs, msg)
}
return msgs, nil
}
// getBuffer returns the buffer to use for receiving messages and a function to
// release it back to the pool if applicable. If the pool is not configured, a
// new buffer is allocated by peeking the size of the next message to be
// received. The buffer size is aligned to the next multiple of the alignment of a netlink
// message, to avoid panic when parsing messages from the buffer.
func (c *conn) getBuffer() ([]byte, func(), error) {
if c.pool != nil {
bp := c.pool.Get().(*[]byte)
return *bp, func() { c.pool.Put(bp) }, nil
}
n, _, _, _, err := c.s.Recvmsg(context.Background(), nil, nil, unix.MSG_PEEK|unix.MSG_TRUNC)
if err != nil {
return nil, nil, err
}
return make([]byte, nlmsgAlign(n)), func() {}, nil
}
// ownedBuffer trims b to the aligned size of the received datagram and, when
// using pooled buffers, copies it to memory owned by the caller.
func (c *conn) ownedBuffer(b []byte, n int) []byte {
aligned := nlmsgAlign(n)
if c.pool == nil {
return b[:aligned]
}
// A pooled buffer may not have an aligned length. Copy the received bytes
// to an aligned buffer and let the zero-value tail bytes act as padding.
out := make([]byte, aligned)
copy(out, b[:n])
return out
}
// ReceiveIter returns an iterator over Messages received from netlink.
func (c *conn) ReceiveIter() iter.Seq2[Message, error] {
return func(yield func(Message, error) bool) {
b, release, err := c.getBuffer()
if err != nil {
yield(Message{}, err)
return
}
defer release()
// Read out all available messages
// TODO(mdlayher): deal with OOB message data if available, such as
// when PacketInfo ConnOption is true.
n, _, flags, _, err := c.s.Recvmsg(context.Background(), b, nil, 0)
if err != nil {
yield(Message{}, err)
return
}
if flags&unix.MSG_TRUNC != 0 {
// Our buffer was too small to read the entire message,
// this should not happen since we peeked above, but if it does,
// return an error.
yield(Message{}, errMessageTruncated)
return
}
for msg, err := range parseMessagesIter(c.ownedBuffer(b, n)) {
if err != nil {
yield(Message{}, err)
return
}
if !yield(msg, nil) {
return
}
}
}
}
// Close closes the connection.
func (c *conn) Close() error { return c.s.Close() }
@@ -212,6 +265,14 @@ func (c *conn) SetReadBuffer(bytes int) error { return c.s.SetReadBuffer(bytes)
// associated with the Conn.
func (c *conn) SetWriteBuffer(bytes int) error { return c.s.SetWriteBuffer(bytes) }
// ReadBuffer reads the size of the operating system's receive buffer
// associated with the Conn.
func (c *conn) ReadBuffer() (int, error) { return c.s.ReadBuffer() }
// WriteBuffer reads the size of the operating system's transmit buffer
// associated with the Conn.
func (c *conn) WriteBuffer() (int, error) { return c.s.WriteBuffer() }
// SyscallConn returns a raw network connection.
func (c *conn) SyscallConn() (syscall.RawConn, error) { return c.s.SyscallConn() }
+6
View File
@@ -5,6 +5,7 @@ package netlink
import (
"fmt"
"iter"
"runtime"
)
@@ -28,3 +29,8 @@ func (c *conn) Send(_ Message) error { return errUnimplemented }
func (c *conn) SendMessages(_ []Message) error { return errUnimplemented }
func (c *conn) Receive() ([]Message, error) { return nil, errUnimplemented }
func (c *conn) Close() error { return errUnimplemented }
func (c *conn) ReceiveIter() iter.Seq2[Message, error] {
return func(yield func(Message, error) bool) {
yield(Message{}, errUnimplemented)
}
}
+5 -41
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"log"
"os"
"strconv"
"strings"
)
@@ -23,47 +22,12 @@ func init() {
// A debugger is used to provide debugging information about a netlink connection.
type debugger struct {
Log *log.Logger
Level int
Log *log.Logger
Level int
Format string
}
// newDebugger creates a debugger by parsing key=value arguments.
func newDebugger(args []string) *debugger {
d := &debugger{
Log: log.New(os.Stderr, "nl: ", 0),
Level: 1,
}
for _, a := range args {
kv := strings.Split(a, "=")
if len(kv) != 2 {
// Ignore malformed pairs and assume callers wants defaults.
continue
}
switch kv[0] {
// Select the log level for the debugger.
case "level":
level, err := strconv.Atoi(kv[1])
if err != nil {
panicf("netlink: invalid NLDEBUG level: %q", a)
}
d.Level = level
}
}
return d
}
// debugf prints debugging information at the specified level, if d.Level is
// high enough to print the message.
func (d *debugger) debugf(level int, format string, v ...interface{}) {
if d.Level >= level {
d.Log.Printf(format, v...)
}
}
func panicf(format string, a ...interface{}) {
// panicf is a helper to panic with formatted text.
func panicf(format string, a ...any) {
panic(fmt.Sprintf(format, a...))
}
+196
View File
@@ -0,0 +1,196 @@
//go:build linux
package netlink
import (
"encoding/binary"
"fmt"
"io"
"log"
"os"
"strconv"
"strings"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
// newDebugger creates a debugger by parsing key=value arguments.
func newDebugger(args []string) *debugger {
d := &debugger{
Log: log.New(os.Stderr, "nl: ", 0),
Level: 1,
Format: "mnl",
}
for _, a := range args {
kv := strings.Split(a, "=")
if len(kv) != 2 {
continue
}
switch kv[0] {
case "level":
level, err := strconv.Atoi(kv[1])
if err != nil {
panicf("netlink: invalid NLDEBUG level: %q", a)
}
d.Level = level
case "format":
d.Format = kv[1]
}
}
return d
}
// debugf prints debugging information at the specified level, if d.Level is high enough to print the message.
func (d *debugger) debugf(level int, format string, v ...any) {
if d.Level < level {
return
}
switch d.Format {
case "mnl":
colorize := true
_, err := unix.IoctlGetWinsize(int(os.Stdout.Fd()), unix.TIOCGWINSZ)
if err != nil {
colorize = false
}
for _, iface := range v {
if msg, ok := iface.(Message); ok {
nlmsgFprintf(d.Log.Writer(), msg, colorize)
} else {
d.Log.Printf(format, v...)
}
}
default:
d.Log.Printf(format, v...)
}
}
// nlmsgFprintfHeader prints the netlink message header to fd.
func nlmsgFprintfHeader(fd io.Writer, nlh Header) {
fmt.Fprintf(fd, "----------------\t------------------\n")
fmt.Fprintf(fd, "| %010d |\t| message length |\n", nlh.Length)
fmt.Fprintf(fd, "| %05d | %s%s%s%s |\t| type | flags |\n",
nlh.Type,
ternary(nlh.Flags&Request != 0, "R", "-"),
ternary(nlh.Flags&Multi != 0, "M", "-"),
ternary(nlh.Flags&Acknowledge != 0, "A", "-"),
ternary(nlh.Flags&Echo != 0, "E", "-"),
)
fmt.Fprintf(fd, "| %010d |\t| sequence number|\n", nlh.Sequence)
fmt.Fprintf(fd, "| %010d |\t| port ID |\n", nlh.PID)
fmt.Fprintf(fd, "----------------\t------------------\n")
}
// nlmsgFprintf prints a single Message for netlink errors and attributes.
func nlmsgFprintf(fd io.Writer, m Message, colorize bool) {
var hasHeader bool
nlmsgFprintfHeader(fd, m.Header)
switch {
case m.Header.Type == Error:
hasHeader = true
case m.Header.Type == Done && m.Header.Flags&Multi != 0:
if len(m.Data) == 0 {
return
}
default:
// Neither, nothing to do.
}
// Errno occupies 4 bytes.
const endErrno = 4
if len(m.Data) < endErrno {
return
}
c := int32(binary.NativeEndian.Uint32(m.Data[:endErrno]))
if c != 0 {
b := m.Data[0:4]
fmt.Fprintf(fd, "| %.2x %.2x %.2x %.2x |\t",
0xff&b[0], 0xff&b[1],
0xff&b[2], 0xff&b[3])
fmt.Fprintf(fd, "| extra header |\n")
}
// Flags indicate an extended acknowledgement. The type/flags combination
// checked above determines the offset where the TLVs occur.
var off int
if hasHeader {
// There is an nlmsghdr preceding the TLVs.
if len(m.Data) < endErrno+nlmsgHeaderLen {
return
}
// The TLVs should be at the offset indicated by the nlmsghdr.length,
// plus the offset where the header began. But make sure the calculated
// offset is still in-bounds.
h := *(*Header)(unsafe.Pointer(&m.Data[endErrno : endErrno+nlmsgHeaderLen][0]))
off = endErrno + int(h.Length)
if len(m.Data) < off {
return
}
} else {
// There is no nlmsghdr preceding the TLVs, parse them directly.
off = endErrno
}
data := m.Data[off:]
for i := 0; i < len(data); {
// Make sure there's at least a header's worth of data to read on each iteration.
if len(data[i:]) < nlaHeaderLen {
break
}
// Extract the length of the attribute.
l := int(binary.NativeEndian.Uint16(data[i:]))
// extract the type
t := binary.NativeEndian.Uint16(data[i+2:])
// print attribute header
if colorize {
fmt.Fprintf(fd, "|\033[1;31m%05d|\033[1;32m%s%s|\033[1;34m%05d\033[0m|\t",
l,
ternary(t&syscall.NLA_F_NESTED != 0, "N", "-"),
ternary(t&syscall.NLA_F_NET_BYTEORDER != 0, "B", "-"),
t&attrTypeMask)
fmt.Fprintf(fd, "|len |flags| type|\n")
} else {
fmt.Fprintf(fd, "|%05d|%s%s|%05d|\t",
l,
ternary(t&syscall.NLA_F_NESTED != 0, "N", "-"),
ternary(t&syscall.NLA_F_NET_BYTEORDER != 0, "B", "-"),
t&attrTypeMask)
fmt.Fprintf(fd, "|len |flags| type|\n")
}
nextAttr := i + nlaAlign(l)
i += nlaHeaderLen
// Ignore zero-length attributes.
if l == 0 {
continue
}
// If nested check the next attribute
if t&syscall.NLA_F_NESTED != 0 {
continue
}
// Print the remaining attributes bytes
for ; i < nextAttr; i += 4 {
fmt.Fprintf(fd, "| %.2x %.2x %.2x %.2x |\t",
0xff&data[i], 0xff&data[i+1],
0xff&data[i+2], 0xff&data[i+3])
fmt.Fprintf(fd, "| data |")
fmt.Fprintf(fd, "\t %s %s %s %s\n",
ternary(strconv.IsPrint(rune(data[i])), string(data[i]), " "),
ternary(strconv.IsPrint(rune(data[i+1])), string(data[i+1]), " "),
ternary(strconv.IsPrint(rune(data[i+2])), string(data[i+2]), " "),
ternary(strconv.IsPrint(rune(data[i+3])), string(data[i+3]), " "),
)
}
}
fmt.Fprintf(fd, "----------------\t------------------\n")
}
// ternary returns iftrue if cond is true, else iffalse.
func ternary(cond bool, iftrue string, iffalse string) string {
if cond {
return iftrue
}
return iffalse
}
+40
View File
@@ -0,0 +1,40 @@
//go:build !linux
package netlink
import (
"log"
"os"
"strconv"
"strings"
)
// newDebugger creates a debugger by parsing key=value arguments.
func newDebugger(args []string) *debugger {
d := &debugger{
Log: log.New(os.Stderr, "nl: ", 0),
Level: 1,
}
for _, a := range args {
kv := strings.Split(a, "=")
if len(kv) != 2 {
continue
}
switch kv[0] {
case "level":
level, err := strconv.Atoi(kv[1])
if err != nil {
panicf("netlink: invalid NLDEBUG level: %q", a)
}
d.Level = level
}
}
return d
}
// debugf prints debugging information at the specified level, if d.Level is high enough to print the message.
func (d *debugger) debugf(level int, format string, v ...interface{}) {
if d.Level >= level {
d.Log.Printf(format, v...)
}
}
+3
View File
@@ -27,7 +27,10 @@
//
// $ NLDEBUG=level=1 ./nlctl
//
// $ NLDEBUG=level=1,format=mnl ./nlctl
//
// Available key/value debugger options include:
//
// level=N: specify the debugging level (only "1" is currently supported)
// format=mnl: specify the same format used by libmnl (nft --debug=all)
package netlink
+16 -5
View File
@@ -20,6 +20,10 @@ var (
var errNotSupported = errors.New("operation not supported")
// errMessageTruncated is returned when a received message was truncated
// because the receive buffer was too small.
var errMessageTruncated = errors.New("message truncated")
// notSupported provides a concise constructor for "not supported" errors.
func notSupported(op string) error {
return newOpError(op, errNotSupported)
@@ -69,6 +73,11 @@ type OpError struct {
// is not set, both of these fields will be empty.
Message string
Offset int
// Sequence is the netlink message sequence number associated with the
// error. This field is only populated if the error was produced as the
// result of a netlink message; otherwise, it will be zero.
Sequence uint32
}
// newOpError is a small wrapper for creating an OpError. As a convenience, it
@@ -90,11 +99,11 @@ func (e *OpError) Error() string {
}
var sb strings.Builder
_, _ = sb.WriteString(fmt.Sprintf("netlink %s: %v", e.Op, e.Err))
_, _ = fmt.Fprintf(&sb, "netlink %s: %v", e.Op, e.Err)
if e.Message != "" || e.Offset != 0 {
_, _ = sb.WriteString(fmt.Sprintf(", offset: %d, message: %q",
e.Offset, e.Message))
_, _ = fmt.Fprintf(&sb, ", offset: %d, message: %q",
e.Offset, e.Message)
}
return sb.String()
@@ -115,7 +124,8 @@ type timeout interface {
// Timeout reports whether the error was caused by an I/O timeout.
func (e *OpError) Timeout() bool {
if ne, ok := e.Err.(*os.SyscallError); ok {
ne := &os.SyscallError{}
if errors.As(e.Err, &ne) {
t, ok := ne.Err.(timeout)
return ok && t.Timeout()
}
@@ -129,7 +139,8 @@ type temporary interface {
// Temporary reports whether an operation may succeed if retried.
func (e *OpError) Temporary() bool {
if ne, ok := e.Err.(*os.SyscallError); ok {
ne := &os.SyscallError{}
if errors.As(e.Err, &ne) {
t, ok := ne.Err.(temporary)
return ok && t.Temporary()
}
+55 -16
View File
@@ -1,11 +1,11 @@
package netlink
import (
"encoding/binary"
"errors"
"fmt"
"iter"
"unsafe"
"github.com/mdlayher/netlink/nlenc"
)
// Flags which may apply to netlink attribute types when communicating with
@@ -211,11 +211,11 @@ func (m Message) MarshalBinary() ([]byte, error) {
b := make([]byte, ml)
nlenc.PutUint32(b[0:4], m.Header.Length)
nlenc.PutUint16(b[4:6], uint16(m.Header.Type))
nlenc.PutUint16(b[6:8], uint16(m.Header.Flags))
nlenc.PutUint32(b[8:12], m.Header.Sequence)
nlenc.PutUint32(b[12:16], m.Header.PID)
binary.NativeEndian.PutUint32(b[0:], m.Header.Length)
binary.NativeEndian.PutUint16(b[4:], uint16(m.Header.Type))
binary.NativeEndian.PutUint16(b[6:], uint16(m.Header.Flags))
binary.NativeEndian.PutUint32(b[8:], m.Header.Sequence)
binary.NativeEndian.PutUint32(b[12:], m.Header.PID)
copy(b[16:], m.Data)
return b, nil
@@ -231,16 +231,16 @@ func (m *Message) UnmarshalBinary(b []byte) error {
}
// Don't allow misleading length
m.Header.Length = nlenc.Uint32(b[0:4])
if int(m.Header.Length) != len(b) {
m.Header.Length = binary.NativeEndian.Uint32(b[0:])
if int(m.Header.Length) < nlmsgHeaderLen || int(m.Header.Length) > len(b) {
return errShortMessage
}
m.Header.Type = HeaderType(nlenc.Uint16(b[4:6]))
m.Header.Flags = HeaderFlags(nlenc.Uint16(b[6:8]))
m.Header.Sequence = nlenc.Uint32(b[8:12])
m.Header.PID = nlenc.Uint32(b[12:16])
m.Data = b[16:]
m.Header.Type = HeaderType(binary.NativeEndian.Uint16(b[4:]))
m.Header.Flags = HeaderFlags(binary.NativeEndian.Uint16(b[6:]))
m.Header.Sequence = binary.NativeEndian.Uint32(b[8:])
m.Header.PID = binary.NativeEndian.Uint32(b[12:])
m.Data = b[nlmsgHeaderLen:m.Header.Length]
return nil
}
@@ -282,7 +282,7 @@ func checkMessage(m Message) error {
return newOpError("receive", errShortErrorMessage)
}
c := nlenc.Int32(m.Data[:endErrno])
c := int32(binary.NativeEndian.Uint32(m.Data[:endErrno]))
if c == 0 {
// 0 indicates no error.
return nil
@@ -293,7 +293,8 @@ func checkMessage(m Message) error {
// Error code is a negative integer, convert it into an OS-specific raw
// system call error, but do not wrap with os.NewSyscallError to signify
// that this error was produced by a netlink message; not a system call.
Err: newError(-1 * int(c)),
Err: newError(-1 * int(c)),
Sequence: m.Header.Sequence,
}
// TODO(mdlayher): investigate the Capped flag.
@@ -345,3 +346,41 @@ func checkMessage(m Message) error {
// the info we have.
return oerr
}
// parseMessagesIter returns an iterator over netlink messages in b.
// Each iteration yields a Message and any error encountered during parsing.
// The iterator stops on the first error or when all messages are parsed.
//
// If b is less that NLMSG_HDRLEN bytes, no error or messages will be returned.
// The same applies for any trailing bytes whose length is less than
// NLMSG_HDRLEN.
func parseMessagesIter(b []byte) iter.Seq2[Message, error] {
return func(yield func(Message, error) bool) {
for len(b) >= nlmsgHeaderLen {
length := binary.NativeEndian.Uint32(b[0:])
if int(length) < nlmsgHeaderLen {
yield(Message{}, errIncorrectMessageLength)
return
}
alength := nlmsgAlign(int(length))
if alength > len(b) {
yield(Message{}, errShortMessage)
return
}
var message Message
if err := message.UnmarshalBinary(b[:alength]); err != nil {
yield(Message{}, err)
return
}
// Return if the consumer has stopped iterating.
if !yield(message, nil) {
return
}
b = b[alength:]
}
}
}
+2
View File
@@ -7,6 +7,8 @@ import (
)
// NativeEndian returns the native byte order of this system.
//
// Deprecated: Use [binary.NativeEndian] instead.
func NativeEndian() binary.ByteOrder {
// TODO(mdlayher): consider deprecating and removing this function for v2.
return binary.NativeEndian
+36 -16
View File
@@ -1,12 +1,14 @@
package nlenc
import (
"encoding/binary"
"fmt"
"unsafe"
)
// PutUint8 encodes a uint8 into b.
// If b is not exactly 1 byte in length, PutUint8 will panic.
//
// Deprecated: Use inline "b[n] = v" instead.
func PutUint8(b []byte, v uint8) {
if l := len(b); l != 1 {
panic(fmt.Sprintf("PutUint8: unexpected byte slice length: %d", l))
@@ -17,46 +19,56 @@ func PutUint8(b []byte, v uint8) {
// PutUint16 encodes a uint16 into b using the host machine's native endianness.
// If b is not exactly 2 bytes in length, PutUint16 will panic.
//
// Deprecated: Use [binary.NativeEndian.PutUint16] instead.
func PutUint16(b []byte, v uint16) {
if l := len(b); l != 2 {
panic(fmt.Sprintf("PutUint16: unexpected byte slice length: %d", l))
}
*(*uint16)(unsafe.Pointer(&b[0])) = v
binary.NativeEndian.PutUint16(b, v)
}
// PutUint32 encodes a uint32 into b using the host machine's native endianness.
// If b is not exactly 4 bytes in length, PutUint32 will panic.
//
// Deprecated: Use [binary.NativeEndian.PutUint32] instead.
func PutUint32(b []byte, v uint32) {
if l := len(b); l != 4 {
panic(fmt.Sprintf("PutUint32: unexpected byte slice length: %d", l))
}
*(*uint32)(unsafe.Pointer(&b[0])) = v
binary.NativeEndian.PutUint32(b, v)
}
// PutUint64 encodes a uint64 into b using the host machine's native endianness.
// If b is not exactly 8 bytes in length, PutUint64 will panic.
//
// Deprecated: Use [binary.NativeEndian.PutUint64] instead.
func PutUint64(b []byte, v uint64) {
if l := len(b); l != 8 {
panic(fmt.Sprintf("PutUint64: unexpected byte slice length: %d", l))
}
*(*uint64)(unsafe.Pointer(&b[0])) = v
binary.NativeEndian.PutUint64(b, v)
}
// PutInt32 encodes a int32 into b using the host machine's native endianness.
// If b is not exactly 4 bytes in length, PutInt32 will panic.
//
// Deprecated: Use [binary.NativeEndian.PutUint32] instead (with a typecast).
func PutInt32(b []byte, v int32) {
if l := len(b); l != 4 {
panic(fmt.Sprintf("PutInt32: unexpected byte slice length: %d", l))
}
*(*int32)(unsafe.Pointer(&b[0])) = v
binary.NativeEndian.PutUint32(b, uint32(v))
}
// Uint8 decodes a uint8 from b.
// If b is not exactly 1 byte in length, Uint8 will panic.
//
// Deprecated: Use inline "b[n]" instead.
func Uint8(b []byte) uint8 {
if l := len(b); l != 1 {
panic(fmt.Sprintf("Uint8: unexpected byte slice length: %d", l))
@@ -67,50 +79,58 @@ func Uint8(b []byte) uint8 {
// Uint16 decodes a uint16 from b using the host machine's native endianness.
// If b is not exactly 2 bytes in length, Uint16 will panic.
//
// Deprecated: Use [binary.NativeEndian.Uint16] instead.
func Uint16(b []byte) uint16 {
if l := len(b); l != 2 {
panic(fmt.Sprintf("Uint16: unexpected byte slice length: %d", l))
}
return *(*uint16)(unsafe.Pointer(&b[0]))
return binary.NativeEndian.Uint16(b)
}
// Uint32 decodes a uint32 from b using the host machine's native endianness.
// If b is not exactly 4 bytes in length, Uint32 will panic.
//
// Deprecated: Use [binary.NativeEndian.Uint32] instead.
func Uint32(b []byte) uint32 {
if l := len(b); l != 4 {
panic(fmt.Sprintf("Uint32: unexpected byte slice length: %d", l))
}
return *(*uint32)(unsafe.Pointer(&b[0]))
return binary.NativeEndian.Uint32(b)
}
// Uint64 decodes a uint64 from b using the host machine's native endianness.
// If b is not exactly 8 bytes in length, Uint64 will panic.
//
// Deprecated: Use [binary.NativeEndian.Uint64] instead.
func Uint64(b []byte) uint64 {
if l := len(b); l != 8 {
panic(fmt.Sprintf("Uint64: unexpected byte slice length: %d", l))
}
return *(*uint64)(unsafe.Pointer(&b[0]))
return binary.NativeEndian.Uint64(b)
}
// Int32 decodes an int32 from b using the host machine's native endianness.
// If b is not exactly 4 bytes in length, Int32 will panic.
//
// Deprecated: Use [binary.NativeEndian.Uint32] instead (with a typecast).
func Int32(b []byte) int32 {
if l := len(b); l != 4 {
panic(fmt.Sprintf("Int32: unexpected byte slice length: %d", l))
}
return *(*int32)(unsafe.Pointer(&b[0]))
return int32(binary.NativeEndian.Uint32(b))
}
// Uint8Bytes encodes a uint8 into a newly-allocated byte slice. It is a
// shortcut for allocating a new byte slice and filling it using PutUint8.
//
// Deprecated: Use inline "[]byte{v}" instead.
func Uint8Bytes(v uint8) []byte {
b := make([]byte, 1)
PutUint8(b, v)
return b
return []byte{v}
}
// Uint16Bytes encodes a uint16 into a newly-allocated byte slice using the
@@ -118,7 +138,7 @@ func Uint8Bytes(v uint8) []byte {
// byte slice and filling it using PutUint16.
func Uint16Bytes(v uint16) []byte {
b := make([]byte, 2)
PutUint16(b, v)
binary.NativeEndian.PutUint16(b, v)
return b
}
@@ -127,7 +147,7 @@ func Uint16Bytes(v uint16) []byte {
// byte slice and filling it using PutUint32.
func Uint32Bytes(v uint32) []byte {
b := make([]byte, 4)
PutUint32(b, v)
binary.NativeEndian.PutUint32(b, v)
return b
}
@@ -136,7 +156,7 @@ func Uint32Bytes(v uint32) []byte {
// byte slice and filling it using PutUint64.
func Uint64Bytes(v uint64) []byte {
b := make([]byte, 8)
PutUint64(b, v)
binary.NativeEndian.PutUint64(b, v)
return b
}
@@ -145,6 +165,6 @@ func Uint64Bytes(v uint64) []byte {
// byte slice and filling it using PutInt32.
func Int32Bytes(v int32) []byte {
b := make([]byte, 4)
PutInt32(b, v)
binary.NativeEndian.PutUint32(b, uint32(v))
return b
}