From 10179127f0d6af9f40d5b12480577ab2e1d214fd Mon Sep 17 00:00:00 2001 From: rootkiller6788 Date: Fri, 21 Aug 2026 20:59:43 +0800 Subject: [PATCH] conn: fix read buffer size for batches with echo messages The enlargeReadBuffer logic was inverted: the default echo read buffer was applied only when no buffered message had the Echo flag set, and skipped when the batch actually contained echo messages. As a result, batches that create rules (which set the Echo flag) were given a read buffer sized at 1024 bytes per message, which can be too small and lead to "recvmsg: no buffer space available" errors, while batches without echo messages wastefully allocated the large default buffer. Extract the buffer-size computation into readBufferSize so that it can be covered by a unit test. --- conn.go | 33 ++++++++++++++++-------- conn_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 conn_test.go diff --git a/conn.go b/conn.go index c986c13..e56eccd 100644 --- a/conn.go +++ b/conn.go @@ -622,12 +622,31 @@ func (cc *Conn) enlargeReadBuffer(conn *netlink.Conn) error { return nil } - var bufferSize int + bufferSize := cc.readBufferSize() + + currSize, err := conn.ReadBuffer() + if err != nil { + return err + } + if currSize < bufferSize { + return conn.SetReadBuffer(bufferSize) + } + return nil +} +// readBufferSize returns the read buffer size required for the currently +// buffered messages. +// +// If any of the buffered messages has the Echo flag set, the buffer size is +// initialized to the default echo read buffer size, since the kernel will echo +// back each created rule. Otherwise, 1024 bytes are allocated per message, just +// like nftables. +func (cc *Conn) readBufferSize() int { // If there are any messages with the Echo flag, we initialize the buffer size // to the default echo read buffer size. + var bufferSize int for _, msg := range cc.messages { - if msg.Header.Flags&netlink.Echo == 0 { + if msg.Header.Flags&netlink.Echo != 0 { bufferSize = cc.getDefaultEchoReadBuffer() break } @@ -638,15 +657,7 @@ func (cc *Conn) enlargeReadBuffer(conn *netlink.Conn) error { if bufferSize < requiredSize { bufferSize = requiredSize } - - currSize, err := conn.ReadBuffer() - if err != nil { - return err - } - if currSize < bufferSize { - return conn.SetReadBuffer(bufferSize) - } - return nil + return bufferSize } // getPortIDUnderLock returns the netlink port ID associated with this diff --git a/conn_test.go b/conn_test.go new file mode 100644 index 0000000..235c9f5 --- /dev/null +++ b/conn_test.go @@ -0,0 +1,72 @@ +// Copyright 2018 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package nftables + +import ( + "testing" + + "github.com/mdlayher/netlink" +) + +func TestReadBufferSize(t *testing.T) { + defaultEcho := (&Conn{}).getDefaultEchoReadBuffer() + + tests := []struct { + name string + messages []netlinkMessage + want int + }{ + { + name: "empty batch", + messages: nil, + want: 0, + }, + { + name: "batch without echo messages", + messages: []netlinkMessage{ + {Header: netlink.Header{Flags: netlink.Request}}, + {Header: netlink.Header{Flags: netlink.Request | netlink.Create}}, + }, + want: 2 * 1024, + }, + { + name: "batch with echo message uses default echo read buffer", + messages: []netlinkMessage{ + {Header: netlink.Header{Flags: netlink.Request | netlink.Create | netlink.Echo}}, + }, + want: defaultEcho, + }, + { + name: "batch with echo message and many messages", + messages: func() []netlinkMessage { + messages := make([]netlinkMessage, 10_000) + for i := range messages { + messages[i].Header.Flags = netlink.Request | netlink.Create | netlink.Echo + } + return messages + }(), + want: max(defaultEcho, 10_000*1024), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cc := &Conn{messages: tt.messages} + if got := cc.readBufferSize(); got != tt.want { + t.Fatalf("readBufferSize() = %d, want %d", got, tt.want) + } + }) + } +}