diff --git a/overlay/tio/queueset_gso_linux.go b/overlay/tio/queueset_gso_linux.go index 8d04e34e..c422fb65 100644 --- a/overlay/tio/queueset_gso_linux.go +++ b/overlay/tio/queueset_gso_linux.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "errors" "fmt" + "log/slog" "sync/atomic" "golang.org/x/sys/unix" @@ -21,11 +22,13 @@ type offloadQueueSet struct { // Queues created by Add inherit this and surface it via Offload.USOSupported so coalescers can gate USO emission. usoEnabled bool closed atomic.Bool + // l is handed to each queue for its bad-vnet-header drop logging. + l *slog.Logger } // NewOffloadQueueSet creates a QueueSet that uses virtio_net_hdr to do TSO segmentation. // usoEnabled tells downstream queues whether the kernel agreed to deliver/accept GSO_UDP_L4 superpackets. -func NewOffloadQueueSet(usoEnabled bool) (QueueSet, error) { +func NewOffloadQueueSet(usoEnabled bool, l *slog.Logger) (QueueSet, error) { shutdownFd, err := unix.Eventfd(0, unix.EFD_NONBLOCK|unix.EFD_CLOEXEC) if err != nil { return nil, fmt.Errorf("failed to create eventfd: %w", err) @@ -36,6 +39,7 @@ func NewOffloadQueueSet(usoEnabled bool) (QueueSet, error) { pqi: []Queue{}, shutdownFd: shutdownFd, usoEnabled: usoEnabled, + l: l, } return out, nil @@ -46,7 +50,7 @@ func (c *offloadQueueSet) Queues() []Queue { } func (c *offloadQueueSet) Add(fd int) error { - x, err := newOffload(fd, c.shutdownFd, c.usoEnabled) + x, err := newOffload(fd, c.shutdownFd, c.usoEnabled, c.l) if err != nil { return err } diff --git a/overlay/tio/tio_gso_linux.go b/overlay/tio/tio_gso_linux.go index cdfa5d15..1f0e9707 100644 --- a/overlay/tio/tio_gso_linux.go +++ b/overlay/tio/tio_gso_linux.go @@ -4,8 +4,10 @@ package tio import ( + "context" "fmt" "io" + "log/slog" "os" "sync/atomic" "syscall" @@ -87,9 +89,13 @@ type Offload struct { // iovec[0] points at readVnetScratch // iovec[1].Base/Len is updated per read to address the current rxBuf slot. readIovs [2]unix.Iovec + + // l is only consulted on the rare bad-vnet-header drop path; it lives + // after the hot state on purpose. May be nil (tests); drops go unlogged then. + l *slog.Logger } -func newOffload(fd int, shutdownFd int, usoEnabled bool) (*Offload, error) { +func newOffload(fd int, shutdownFd int, usoEnabled bool, l *slog.Logger) (*Offload, error) { if err := unix.SetNonblock(fd, true); err != nil { return nil, fmt.Errorf("failed to set tun fd non-blocking: %w", err) } @@ -99,6 +105,7 @@ func newOffload(fd int, shutdownFd int, usoEnabled bool) (*Offload, error) { shutdownFd: shutdownFd, usoEnabled: usoEnabled, closed: atomic.Bool{}, + l: l, rxBuf: make([]byte, tunRxBufCap), gsoIovs: make([]unix.Iovec, 2, gsoMaxIovs), @@ -178,7 +185,9 @@ func (r *Offload) Read() ([]Packet, error) { return nil, err } if err := r.decodeRead(n); err != nil { - // Drop and read again. A bad packet should not kill the reader. + // Drop and read again. A bad packet should not kill the reader, + // but a systematic decode failure must not be invisible either. + r.logDroppedRead(err) continue } break @@ -200,6 +209,7 @@ func (r *Offload) Read() ([]Packet, error) { if err := r.decodeRead(n); err != nil { // Drop this packet and stop the drain; we'd rather hand off // what we have than keep spinning here. + r.logDroppedRead(err) break } } @@ -207,6 +217,14 @@ func (r *Offload) Read() ([]Packet, error) { return r.pending, nil } +// logDroppedRead reports a tun packet dropped for a bad/unsupported virtio +// header. Debug-gated so the happy path never pays for attribute assembly. +func (r *Offload) logDroppedRead(err error) { + if r.l != nil && r.l.Enabled(context.Background(), slog.LevelDebug) { + r.l.Debug("dropping tun packet with bad virtio header", "error", err) + } +} + // decodeRead processes the packet sitting in rxBuf at rxOff (length pktLen). // The bytes stay in rxBuf: // - for GSO_NONE we slice them as a regular IP datagram (running finishChecksum if NEEDS_CSUM is set); diff --git a/overlay/tio/tun_file_linux_test.go b/overlay/tio/tun_file_linux_test.go index 7d8b5c4e..670da94f 100644 --- a/overlay/tio/tun_file_linux_test.go +++ b/overlay/tio/tun_file_linux_test.go @@ -5,6 +5,7 @@ package tio import ( "errors" + "log/slog" "os" "sync" "testing" @@ -210,7 +211,7 @@ func TestPollQueueSet_Close_ClosesShutdownFd(t *testing.T) { // TestOffloadQueueSet_Close_ClosesShutdownFd mirrors the poll regression test // for the GSO/offload queueset. func TestOffloadQueueSet_Close_ClosesShutdownFd(t *testing.T) { - qs, err := NewOffloadQueueSet(false) + qs, err := NewOffloadQueueSet(false, slog.New(slog.DiscardHandler)) require.NoError(t, err) c, ok := qs.(*offloadQueueSet) require.True(t, ok) diff --git a/overlay/tun_linux.go b/overlay/tun_linux.go index fbc05a4c..b3335c4a 100644 --- a/overlay/tun_linux.go +++ b/overlay/tun_linux.go @@ -231,7 +231,7 @@ func newTunGeneric(c *config.C, l *slog.Logger, fd int, vnetHdr bool, offloadFla var qs tio.QueueSet var err error if vnetHdr { - qs, err = tio.NewOffloadQueueSet(offloadUSOEnabled(offloadFlags)) + qs, err = tio.NewOffloadQueueSet(offloadUSOEnabled(offloadFlags), l) } else { qs, err = tio.NewPollQueueSet() }