Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions conn_linux_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os/exec"
"os/user"
"slices"
"strings"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -1528,6 +1529,69 @@ func TestIntegrationConntrackGetMultiReplyFollowedByAcknowledgement(t *testing.T
}
}

func TestIntegrationConnExtendedAcknowledgeCapped(t *testing.T) {
for _, capped := range []bool{false, true} {
t.Run(fmt.Sprintf("capped %t", capped), func(t *testing.T) {
c, err := netlink.Dial(unix.NETLINK_ROUTE, nil)
if err != nil {
t.Fatalf("failed to dial netlink: %v", err)
}
defer c.Close()

if err := c.SetOption(netlink.ExtendedAcknowledge, true); err != nil {
if errors.Is(err, unix.ENOPROTOOPT) {
t.Skipf("skipping, extended acknowledge not supported by this kernel: %v", err)
}

t.Fatalf("failed to set extended acknowledge: %v", err)
}

if err := c.SetOption(netlink.CapAcknowledge, capped); err != nil {
t.Fatalf("failed to set cap acknowledge: %v", err)
}

// Send a RTM_GETLINK request with an IFLA_IFNAME attribute longer
// than IFNAMSIZ to trigger an ERANGE error with an extended
// acknowledgement message.
attrs, err := netlink.MarshalAttributes([]netlink.Attribute{{
Type: unix.IFLA_IFNAME,
Data: []byte(strings.Repeat("x", 2*unix.IFNAMSIZ) + "\x00"),
}})
if err != nil {
t.Fatalf("failed to marshal attributes: %v", err)
}

ifi := make([]byte, unix.SizeofIfInfomsg)

_, err = c.Execute(netlink.Message{
Header: netlink.Header{
Type: unix.RTM_GETLINK,
Flags: netlink.Request,
},
Data: append(ifi, attrs...),
})

var oerr *netlink.OpError
if !errors.As(err, &oerr) {
t.Fatalf("expected OpError, but got: %v", err)
}
if !errors.Is(oerr, unix.ERANGE) {
t.Fatalf("expected ERANGE, but got: %v", oerr)
}
if oerr.Message == "" {
t.Fatalf("expected extended acknowledgement message, but got: %v", oerr)
}

// Where the invalid attribute starts within the sent request
ifnameOffset := unix.SizeofNlMsghdr + len(ifi)
if oerr.Offset != ifnameOffset {
t.Fatalf("expected offset %d (IFLA_IFNAME), but got: %d",
ifnameOffset, oerr.Offset)
}
})
}
}

func mustBeTimeoutNetError(t *testing.T, err error) {
t.Helper()

Expand Down
20 changes: 13 additions & 7 deletions debug_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,19 @@ func nlmsgFprintf(fd io.Writer, m Message, colorize bool) {
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
if m.Header.Flags&Capped != 0 {
// Only the request's nlmsghdr was echoed back, so the TLVs
// immediately follow the header.
off = endErrno + nlmsgHeaderLen
} else {
// 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.
Expand Down
25 changes: 15 additions & 10 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,6 @@ func checkMessage(m Message) error {
Sequence: m.Header.Sequence,
}

// TODO(mdlayher): investigate the Capped flag.

if m.Header.Flags&AcknowledgeTLVs == 0 {
// No extended acknowledgement.
return oerr
Expand All @@ -339,14 +337,21 @@ func checkMessage(m Message) error {
return newOpError("receive", errShortErrorMessage)
}

// 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 newOpError("receive", errShortErrorMessage)
if m.Header.Flags&Capped != 0 {
// Only the request's nlmsghdr was echoed back, but its length
// still reflects the full request, so the TLVs immediately follow
// the header.
off = endErrno + nlmsgHeaderLen
} else {
// 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 newOpError("receive", errShortErrorMessage)
}
}
} else {
// There is no nlmsghdr preceding the TLVs, parse them directly.
Expand Down
51 changes: 49 additions & 2 deletions message_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func Test_checkMessageExtendedAcknowledgementTLVs(t *testing.T) {
},
Data: []byte{0xff, 0xff, 0xff, 0xff},
},
false,
// The actual extended acknowledgement TLVs.
[]Attribute{
{
Expand All @@ -100,6 +101,46 @@ func Test_checkMessageExtendedAcknowledgementTLVs(t *testing.T) {
Sequence: 3258842681,
},
},
{
name: "error capped",
m: Message{
Header: Header{
Type: Error,
// The kernel only echoes the request's header, but leaves
// the header's length set to the full request length.
Flags: Capped | AcknowledgeTLVs,
Sequence: 3258842681,
},
Data: packExtACK(
-1,
// A large request, of which only the header is echoed.
&Message{
Header: Header{
Sequence: 3258842681,
},
Data: make([]byte, 1024),
},
true,
[]Attribute{
{
Type: 1,
Data: nlenc.Bytes("bad request"),
},
{
Type: 2,
Data: nlenc.Uint32Bytes(2),
},
},
),
},
err: &OpError{
Op: "receive",
Err: unix.Errno(1),
Message: "bad request",
Offset: 2,
Sequence: 3258842681,
},
},
{
name: "done multi",
m: Message{
Expand All @@ -113,6 +154,7 @@ func Test_checkMessageExtendedAcknowledgementTLVs(t *testing.T) {
-1,
// No message, straight to TLVs.
nil,
false,
[]Attribute{
{
Type: 1,
Expand Down Expand Up @@ -144,8 +186,9 @@ func Test_checkMessageExtendedAcknowledgementTLVs(t *testing.T) {
}
}

// packExtACK packs an extended acknowledgement response.
func packExtACK(errno int32, m *Message, tlvs []Attribute) []byte {
// packExtACK packs an extended acknowledgement response. If capped is set, only
// the header of m is echoed back, as the kernel does with NETLINK_CAP_ACK.
func packExtACK(errno int32, m *Message, capped bool, tlvs []Attribute) []byte {
b := nlenc.Int32Bytes(errno)

if m != nil {
Expand All @@ -156,6 +199,10 @@ func packExtACK(errno int32, m *Message, tlvs []Attribute) []byte {
panicf("failed to marshal message: %v", err)
}

if capped {
mb = mb[:nlmsgHeaderLen]
}

b = append(b, mb...)
}

Expand Down
Loading