forwarder

package
v0.19.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Forwarder

type Forwarder struct {
	// contains filtered or unexported fields
}

Forwarder splices frames between a physical and a virtual interface using XDP sockets. It uses a handler to convert frames between the two interfaces.

func NewForwarder

func NewForwarder(handler Handler, opts ...ForwarderOption) (*Forwarder, error)

NewForwarder creates a new Forwarder with the given handler and options.

func (*Forwarder) Close

func (f *Forwarder) Close() (err error)

Close tears down the forwarder. It is idempotent and returns the join of all teardown errors (the previous implementation discarded them with a bare "return nil", making Start's closeErr check dead).

func (*Forwarder) Start

func (f *Forwarder) Start(ctx context.Context) error

type ForwarderOption

type ForwarderOption func(*forwarderOptions) error

func WithBusyPoll added in v0.18.0

func WithBusyPoll(usecs, budget int) ForwarderOption

WithBusyPoll enables AF_XDP socket busy polling on every datapath socket. usecs is the SO_BUSY_POLL timeout in microseconds (around 20 is typical); usecs <= 0 leaves busy poll disabled (the default). budget caps how many packets one busy-poll NAPI pass processes (SO_BUSY_POLL_BUDGET); budget <= 0 uses xsk.DefaultBusyPollBudget.

Busy polling makes the forwarder's own poll() drive the NIC's NAPI inline, on the datapath thread's core, instead of relying on the NIC IRQ's RX softirq running on a separate (IRQ-affined) core. Combined with the per-netdev napi_defer_hard_irqs/gro_flush_timeout knobs this also sets (and restores on Close), it is the AF_XDP equivalent of a DPDK poll-mode driver: the RX path no longer needs the IRQ core at all. That removes the contention which made a naively pinned datapath thread (WithCPUPinning) collapse ~10x when it landed on the NIC RX softirq core (APO-670). Like CPU pinning, busy poll burns the core at 100%, so enable it only on a dedicated datapath host. Requires Linux >= 5.11.

func WithCPUPinning added in v0.18.0

func WithCPUPinning(enabled bool) ForwarderOption

WithCPUPinning controls whether each per-queue datapath goroutine pins its (LockOSThread'd) OS thread to a distinct CPU drawn from the process's allowed affinity mask. Enabled by default: the per-queue loops are long-lived busy pollers, so keeping each one resident on one core cuts the cross-core cache and scheduler churn the unpinned default incurred (APO-670). Disable it on oversubscribed or shared hosts where the dedicated-core assumption does not hold.

func WithNumQueues added in v0.18.0

func WithNumQueues(n int) ForwarderOption

WithNumQueues overrides the number of per-queue datapath sockets to create, instead of deriving it from the NIC's reported channel count. A non-positive value (the default) keeps the auto-derived count. This is needed on devices whose ethtool channel count exceeds the number of AF_XDP-bindable queues — for example SR-IOV VFs, which advertise the PF's max channels but only expose a couple of real queues, so binding the surplus queues fails with EINVAL.

func WithPcapWriter

func WithPcapWriter(writer *pcapgo.Writer) ForwarderOption

WithPcapWriter sets a pcap writer to log all frames sent and received on both interfaces. If nil, no pcap logging is performed.

func WithPhyFilter

func WithPhyFilter(prog *filter.Program) ForwarderOption

WithPhyFilter sets a custom XDP filter program to use on the physical interface. If nil, a default filter is created that accepts all Geneve packets addressed to the default port (6081).

func WithPhyName

func WithPhyName(name string) ForwarderOption

WithPhyName sets the name of the physical interface to use. Defaults to "eth0".

func WithVirtName

func WithVirtName(name string) ForwarderOption

WithVirtName sets the name of the virtual interface to use. Defaults to "tun0".

type Handler

type Handler interface {
	// PhyToVirtInPlace converts a physical frame to a virtual frame (typically
	// decapsulation), in place within buf. The input physical frame is
	// buf[off:off+length]; it returns the (offset, length) window of the
	// resulting virtual frame within buf, or length 0 to drop.
	PhyToVirtInPlace(buf []byte, off, length int) (outOff, outLen int)
	// VirtToPhyInPlace converts a virtual frame to a physical frame (typically
	// encapsulation), in place within buf. It returns the (offset, length) window
	// of the resulting physical frame within buf, or length 0 to drop. handled
	// reports an immediate local reply (ARP/ND) that must be transmitted back on
	// the virtual interface rather than forwarded to the physical one.
	VirtToPhyInPlace(buf []byte, off, length int) (outOff, outLen int, handled bool)
	// ToPhyInPlace is called periodically to let the handler emit scheduled
	// frames (e.g. keep-alives) to the physical interface, built in place within
	// buf starting at off. It returns the (offset, length) window of the frame,
	// or length 0 when there is nothing to send.
	ToPhyInPlace(buf []byte, off int) (outOff, outLen int)
}

Handler decapsulates and encapsulates frames between the physical and virtual interfaces, operating IN PLACE on a single shared-UMEM frame: it is handed the frame buffer and the (offset, length) window of the input packet within it, and returns the (offset, length) window of the output packet within the SAME buffer. The output overlaps the input — no copy between separate buffers — so a frame received on one socket is transformed and transmitted on the other without ever leaving the UMEM. *icx.Handler implements this; its in-place transforms are byte-for-byte equivalent to the cross-buffer ones (kept for non-zero-copy callers).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL