mirror of
https://github.com/tailscale/tailscale.git
synced 2026-10-23 03:37:19 +09:00
net/tstun,feature/conn25: make ErrInjectionQueueFull detectable as ENOBUFS
Export ErrInjectionQueueFull, returned by TryInjectOutbound when a full injection queue forced a packet drop, and define it to wrap syscall.ENOBUFS on platforms that define that errno. Callers can now test for buffer exhaustion generically with errors.Is(err, syscall.ENOBUFS) instead of knowing about tstun's error type. plan9 and tamago have no ENOBUFS in their syscall packages, so there the error is a plain sentinel; code that must build on those platforms (or on all platforms, like feature/conn25) can still match the exported sentinel. Also stop conn25 from logging queue-full TSMP rejections: those drops are expected under saturation and are already counted as reason="queue_full" in the outbound usermetric. Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
1 parent
3c0508fa28
commit
c606e83fb2
6 files changed
+70
-9
No files matched your search
@@ -166,7 +166,13 @@ func (dh *datapathHandler) HandlePacketFromWireGuard(p *packet.Parsed, tun *tstu
|
||||
Proto: p.IPProto,
|
||||
Reason: packet.RejectedDueToUnknownAppConnectorTransitIP,
|
||||
}
|
||||
if err := tun.TryInjectOutbound(packet.Generate(rj, nil)); err != nil && !errors.Is(err, tstun.ErrClosed) {
|
||||
// Queue-full drops are expected under saturation and are already
|
||||
// counted in the outbound usermetric; only log the rest.
|
||||
// (On platforms that define it, the error also matches
|
||||
// syscall.ENOBUFS; tstun.ErrInjectionQueueFull is the portable
|
||||
// form for code that builds where that errno is absent.)
|
||||
if err := tun.TryInjectOutbound(packet.Generate(rj, nil)); err != nil &&
|
||||
!errors.Is(err, tstun.ErrClosed) && !errors.Is(err, tstun.ErrInjectionQueueFull) {
|
||||
dh.debugLogf("error sending TSMP flow rejection packet: %v", err)
|
||||
}
|
||||
return filter.Drop
|
||||
|
||||
+4
-6
@@ -68,10 +68,6 @@ var (
|
||||
ErrClosed = errors.New("device closed")
|
||||
// ErrFiltered is returned when the acted-on packet is rejected by a filter.
|
||||
ErrFiltered = errors.New("packet dropped by filter")
|
||||
|
||||
// errInjectionQueueFull is returned by TryInjectOutbound when the packet
|
||||
// cannot be queued immediately.
|
||||
errInjectionQueueFull = errors.New("injection queue full")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -548,7 +544,7 @@ func (t *Wrapper) tryInjectOutbound(data []byte) error {
|
||||
default:
|
||||
}
|
||||
t.metrics.outboundDroppedPacketsTotal.Add(usermetric.DropLabels{Reason: usermetric.ReasonQueueFull}, 1)
|
||||
return errInjectionQueueFull
|
||||
return ErrInjectionQueueFull
|
||||
}
|
||||
|
||||
// snat does SNAT on p if the destination address requires a different source address.
|
||||
@@ -1529,7 +1525,9 @@ func (t *Wrapper) InjectOutbound(pkt []byte) error {
|
||||
// TryInjectOutbound is InjectOutbound, but drops pkt rather than waiting for
|
||||
// queue space. Use it for best-effort replies generated while processing
|
||||
// packets, which must not stall behind the injection queue's reader.
|
||||
// Dropped packets are counted as queue_full outbound drops.
|
||||
// Dropped packets are counted as queue_full outbound drops, and the returned
|
||||
// error matches syscall.ENOBUFS via errors.Is on platforms that define it,
|
||||
// so callers can test for buffer exhaustion generically.
|
||||
func (t *Wrapper) TryInjectOutbound(pkt []byte) error {
|
||||
if len(pkt) > MaxPacketSize {
|
||||
return errPacketTooBig
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !plan9 && !tamago
|
||||
|
||||
package tstun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ErrInjectionQueueFull is returned when a full injection queue forced a
|
||||
// best-effort packet to be dropped rather than queued. It wraps
|
||||
// syscall.ENOBUFS, the platform's "no buffer space available" errno, so
|
||||
// callers can test for buffer exhaustion without knowing this package's
|
||||
// type:
|
||||
//
|
||||
// errors.Is(err, syscall.ENOBUFS)
|
||||
//
|
||||
// Dropped packets are already counted as reason="queue_full" in the
|
||||
// tailscaled_outbound_dropped_packets_total usermetric, so callers need not
|
||||
// log this error.
|
||||
var ErrInjectionQueueFull = fmt.Errorf("injection queue full: %w", syscall.ENOBUFS)
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build plan9 || tamago
|
||||
|
||||
package tstun
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrInjectionQueueFull is returned when a full injection queue forced a
|
||||
// best-effort packet to be dropped rather than queued. On platforms whose
|
||||
// syscall package has no ENOBUFS, it matches nothing but itself.
|
||||
var ErrInjectionQueueFull = errors.New("injection queue full")
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !plan9 && !tamago
|
||||
|
||||
package tstun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ErrInjectionQueueFull must match the platform ENOBUFS errno, so callers
|
||||
// can test for buffer exhaustion without knowing this package's type.
|
||||
func TestErrInjectionQueueFullMatchesENOBUFS(t *testing.T) {
|
||||
if !errors.Is(ErrInjectionQueueFull, syscall.ENOBUFS) {
|
||||
t.Errorf("errors.Is(ErrInjectionQueueFull, syscall.ENOBUFS) = false, want true")
|
||||
}
|
||||
}
|
||||
@@ -256,8 +256,8 @@ func TestTryInjectOutbound(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The queue is full and nothing reads it; this must drop, not block.
|
||||
if err := w.TryInjectOutbound(data); err != errInjectionQueueFull {
|
||||
t.Fatalf("TryInjectOutbound on full queue = %v, want errInjectionQueueFull", err)
|
||||
if err := w.TryInjectOutbound(data); !errors.Is(err, ErrInjectionQueueFull) {
|
||||
t.Fatalf("TryInjectOutbound on full queue = %v, want ErrInjectionQueueFull", err)
|
||||
}
|
||||
m, _ := w.metrics.outboundDroppedPacketsTotal.Get(usermetric.DropLabels{Reason: usermetric.ReasonQueueFull}).(*expvar.Int)
|
||||
if m == nil || m.Value() != 1 {
|
||||
|
||||
Reference in new issue
Block a user