net

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jun 9, 2026 License: BSD-3-Clause Imports: 2 Imported by: 0

README

go-virtio/net

Pure-Go virtio-net driver targeting the go-virtio/common transport interfaces. Implements the modern-transport (Virtio 1.0+) init sequence and split-virtqueue TX/RX path for the standard PCI-bound virtio-net device (VID 0x1AF4, DID 0x1041).

This package owns the spec-level driver (the per-frame header layout, the feature-acceptance mask, the init sequence per Virtio 1.1 §3.1.1, the rxq/txq state machine) and routes every transport-level operation through go-virtio/common's Transport interface. Drop in any implementation of that interface (UEFI's EFI_PCI_IO_PROTOCOL, bare-metal MMIO, virtio-mmio adapter) and the same driver code drives the device.

Quick start

import (
    virtionet "github.com/go-virtio/net"
)

// transport is any value that implements go-virtio/common.Transport.
vn, err := virtionet.OpenVirtioNet(transport)
if err != nil {
    return err
}
if err := vn.TransmitFrame(ethFrame); err != nil {
    return err
}
frame, err := vn.ReceiveFrame(10000) // poll budget

Sibling packages

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package net is a pure-Go virtio-net (network device) driver. It drives a modern (Virtio 1.0+) PCI virtio-net device through the transport interfaces defined in github.com/go-virtio/common; the same code drives a UEFI-backed device, a bare-metal device, or a virtio-mmio device depending on which `common.Transport` implementation the caller supplies.

Scope:

  • Modern transport (VIRTIO_F_VERSION_1 mandatory). Legacy / I/O-port transitional devices are NOT supported — the modern transport init sequence rejects them via the underlying common package.
  • Split-virtqueue layout. The packed-ring variant (VIRTIO_F_RING_PACKED) is NOT supported; the driver negotiates it OUT.
  • One queue pair (rxq + txq). Multi-queue (VIRTIO_NET_F_MQ) is NOT negotiated.
  • No GSO, no checksum offload (driver always emits an all-zero virtio_net_hdr; on RX we ignore the device-set flags).

References:

  • Virtio 1.1 §5.1 "Network Device" — device-type 1 binding.
  • Virtio 1.1 §5.1.3 "Feature bits" — VIRTIO_NET_F_*.
  • Virtio 1.1 §5.1.4 "Device configuration layout" — MAC, status, max_virtqueue_pairs, MTU.
  • Virtio 1.1 §5.1.6 "Device Operation" — the per-frame struct virtio_net_hdr.
  • Virtio 1.1 §3.1.1 "Driver Requirements: Device Initialization" — the status-bit choreography in OpenVirtioNet.
  • Linux drivers/net/virtio_net.c — canonical Go-translatable reference for the init sequence and rxq pre-post pattern.

Index

Constants

View Source
const (
	HdrFNeedsCsum uint8 = 0x1
	HdrFDataValid uint8 = 0x2

	GSONone  uint8 = 0
	GSOTCPv4 uint8 = 1
	GSOUDP   uint8 = 3
	GSOTCPv6 uint8 = 4
	GSOECN   uint8 = 0x80
)

VIRTIO_NET_HDR_F_* and VIRTIO_NET_HDR_GSO_* values (Virtio 1.1 §5.1.6.2). The driver emits all-zero headers (no GSO, no checksum offload).

View Source
const (
	// FeatureCSUM (bit 0): driver checksum-offload support.
	// Informational; not accepted.
	FeatureCSUM uint64 = 1 << 0

	// FeatureGuestCSUM (bit 1): device may set "needs checksum" on
	// RX. Informational; not accepted.
	FeatureGuestCSUM uint64 = 1 << 1

	// FeatureMTU (bit 3): device-provided MTU.
	//
	// Required by Apple VZ — without ack'ing it, VZ clears FEATURES_OK
	// and the init aborts. Informational/no-op on QEMU+EDK2 (the bit is
	// always offered, no semantic change in the driver path).
	//
	// We don't read the device's `mtu` field; we use the default-MTU
	// 1518-byte rxq buffer per VirtioNetMaxFrameSize.
	FeatureMTU uint64 = 1 << 3

	// FeatureMAC (bit 5): device-provided MAC at DeviceCfg offset 0.
	// REQUIRED by this driver — the probe needs the device-published
	// MAC for the source field of outbound frames.
	FeatureMAC uint64 = 1 << 5

	// FeatureGuestTSO4 (bit 7), FeatureGuestTSO6 (bit 8),
	// FeatureHostTSO4 (bit 11), FeatureHostTSO6 (bit 12): TCP
	// segmentation offload. Informational; not accepted (the driver
	// emits all-zero headers).
	FeatureGuestTSO4 uint64 = 1 << 7
	FeatureGuestTSO6 uint64 = 1 << 8
	FeatureHostTSO4  uint64 = 1 << 11
	FeatureHostTSO6  uint64 = 1 << 12

	// FeatureMrgRxbuf (bit 15): merged-receive-buffer mode.
	// NOT ACCEPTED — the driver assumes one packet per buffer (no
	// chained descriptors on RX).
	FeatureMrgRxbuf uint64 = 1 << 15

	// FeatureStatus (bit 16): device publishes link-up bit in
	// DeviceCfg.status. Informational; accepted so the device
	// publishes the field.
	FeatureStatus uint64 = 1 << 16

	// FeatureMQ (bit 22): multi-queue. NOT ACCEPTED — driver uses one
	// queue pair.
	FeatureMQ uint64 = 1 << 22
)

VIRTIO_NET_F_* feature bits (Virtio 1.1 §5.1.3). Only the ones we either accept or explicitly reject are listed; the full set lives in the spec.

View Source
const (
	CfgOffsetMAC               uint32 = 0
	CfgOffsetStatus            uint32 = 6
	CfgOffsetMaxVirtqueuePairs uint32 = 8
	CfgOffsetMTU               uint32 = 10
)

VirtioNetCfgOffset* — the device-specific config field offsets for virtio-net (Virtio 1.1 §5.1.4):

struct virtio_net_config {
    u8 mac[6];                 // offset 0
    le16 status;               // offset 6
    le16 max_virtqueue_pairs;  // offset 8
    le16 mtu;                  // offset 10
    // ... 1.1 additions
};
View Source
const (
	RxQueueIdx uint16 = 0
	TxQueueIdx uint16 = 1
)

RxQueueIdx / TxQueueIdx are the canonical queue indices for the single virtio-net queue pair (Virtio 1.1 §5.1.2 — "queue 0 = receive queue, queue 1 = transmit queue").

AcceptedFeatures is the feature mask the driver negotiates ON:

VIRTIO_NET_F_MTU      (3)   device-provided MTU (REQUIRED by Apple VZ;
                            informational/no-op on QEMU+EDK2)
VIRTIO_NET_F_MAC      (5)   device MAC published in DeviceCfg
VIRTIO_NET_F_STATUS   (16)  link-up bit (informational)
VIRTIO_F_VERSION_1    (32)  modern transport (non-negotiable)

Apple VZ live empirical narrow (2026-06-07) established that VZ clears FEATURES_OK and aborts the init unless VIRTIO_NET_F_MTU is acknowledged — ack'ing the bit costs us nothing on the driver side (we don't read the device-published `mtu` field) and unblocks the VZ cell. QEMU+EDK2 always offers this bit too, so accepting it is a no-op there.

All other bits are masked OUT. If the device REQUIRES a bit we didn't ack, FEATURES_OK will fail to stick after we write it; the init sequence catches that and surfaces ErrFeaturesNotOK.

NOTE: VIRTIO_NET_F_MRG_RXBUF (15) is NOT accepted. Without it the device places one packet per buffer (no chained descriptors on receive), which simplifies the RX path significantly.

View Source
const MACLen = 6

MACLen is the byte length of the virtio-net MAC field (Virtio 1.1 §5.1.4 — 6 bytes, IEEE 802.3 EUI-48).

View Source
const MaxFrameSize = 1518

MaxFrameSize is the Ethernet MTU (1500) + Ethernet header (14) + 4 bytes for VLAN headroom. We pre-post 1518-byte buffers on the rxq; on the txq the same size is enough for ARP (42 bytes) plus the virtio header.

View Source
const RxRingSize uint16 = 16

RxRingSize is the number of buffers pre-posted on the rxq. Sized for "a few simultaneous in-flight frames". MUST be a power of two.

View Source
const TxPollIterations = 200000

TxPollIterations is the default polling budget for TransmitFrame. Empirically large enough on QEMU+EDK2 and Apple VZ; far from the firmware's actual round-trip but a sensible upper bound for the busy-poll model the driver uses.

View Source
const TxRingSize uint16 = 8

TxRingSize is the txq depth. The driver issues one frame at a time so 8 is plenty.

View Source
const VirtioNetHdrSize = 12

VirtioNetHdrSize is the on-the-wire byte length of `struct virtio_net_hdr` (Virtio 1.1 §5.1.6.1). With VIRTIO_F_VERSION_1 negotiated, the header is ALWAYS 12 bytes regardless of VIRTIO_NET_F_MRG_RXBUF — the `num_buffers` field is unconditional on modern devices. We negotiate VERSION_1 ON and MRG_RXBUF OFF, so 12 bytes is what every TX/RX buffer must reserve.

Field offsets (little-endian within the 12-byte block):

0      u8     flags
1      u8     gso_type
2..3   le16   hdr_len
4..5   le16   gso_size
6..7   le16   csum_start
8..9   le16   csum_offset
10..11 le16   num_buffers   (driver writes 0 on TX; device fills on RX)

Variables

View Source
var (
	ErrFrameTooShort     = commonNetError("go-virtio/net: RX buffer shorter than virtio_net_hdr (12 bytes)")
	ErrNotModernDevice   = commonNetError("go-virtio/net: device doesn't offer VIRTIO_F_VERSION_1 (legacy-only)")
	ErrNoMACFeature      = commonNetError("go-virtio/net: device doesn't offer VIRTIO_NET_F_MAC")
	ErrFeaturesNotOK     = commonNetError("go-virtio/net: FEATURES_OK status bit didn't stick after DriverFeature write")
	ErrMACReadFailed     = commonNetError("go-virtio/net: MAC read returned all-zero (bounds-check failure or unsupported device)")
	ErrInitWrongDeviceID = commonNetError("go-virtio/net: PCI device ID is not 0x1041 (modern net device)")
	ErrQueueNotAvailable = commonNetError("go-virtio/net: device reports QueueSize=0 for required queue")
	ErrTransmitTimeout   = commonNetError("go-virtio/net: TX poll timeout (device did not return descriptor)")
	ErrReceiveTimeout    = commonNetError("go-virtio/net: RX poll timeout (no frame received within budget)")
	ErrBufferTooSmall    = commonNetError("go-virtio/net: PageAllocator returned a chunk smaller than VirtioNetHdrSize + MaxFrameSize")
)

Sentinel errors for the virtio-net path. All exported so callers can branch + format them.

Functions

func AcceptFeatures

func AcceptFeatures(deviceFeatures uint64) (uint64, error)

AcceptFeatures returns the negotiated feature mask: the intersection of what the device offers and what we accept. The caller writes this back via DriverFeature.

We require VIRTIO_F_VERSION_1 — if the device doesn't offer it, we return ErrNotModernDevice and the init aborts. We require VIRTIO_NET_F_MAC because the probe needs the device-published MAC.

func PrependVirtioNetHdr

func PrependVirtioNetHdr(frame []byte) []byte

PrependVirtioNetHdr builds a 12-byte all-zero `virtio_net_hdr` followed by the Ethernet frame in `frame`. Returns the full per-descriptor buffer the driver passes to AddBuffer.

func StripVirtioNetHdr

func StripVirtioNetHdr(buf []byte) ([]byte, error)

StripVirtioNetHdr returns the Ethernet frame embedded in a device-RX buffer of the given length. Returns nil + ErrFrameTooShort if the buffer is shorter than the header (a malformed RX).

Types

type MAC6

type MAC6 [6]byte

MAC6 is the 6-byte EUI-48 MAC address of one virtio-net device.

func (MAC6) IsZero

func (m MAC6) IsZero() bool

IsZero reports whether the MAC is all-zero (used to detect a failed MAC read).

func (MAC6) String

func (m MAC6) String() string

String formats the MAC in standard "XX:XX:XX:XX:XX:XX" hex notation. Avoids fmt for a dep-light footprint.

type VirtioNet

type VirtioNet struct {
	// Cfg is the modern-transport handle (BARs + offsets + the
	// BARMemoryAccessor used for every register access).
	Cfg *common.ModernConfig

	// MAC is the device-published MAC address (Virtio 1.1 §5.1.4).
	// Read after FEATURES_OK at OpenVirtioNet completion.
	MAC MAC6

	// NegotiatedFeatures records what the driver-feature handshake
	// settled on. Exposed for diagnostic prints.
	NegotiatedFeatures uint64
	// contains filtered or unexported fields
}

VirtioNet wraps one initialised virtio-net device. The caller holds this for the lifetime of the probe; the underlying virtqueue pages live as long as the supplied PageAllocator's lifetime contract.

func OpenVirtioNet

func OpenVirtioNet(t common.Transport) (*VirtioNet, error)

OpenVirtioNet drives the full bring-up of one virtio-net device:

  1. Verify the PCI VID:DID is 1AF4:1041 (modern net).
  2. InitModernConfig walks PCI caps + populates the BAR locators.
  3. Reset → ACK → DRIVER status progression.
  4. Read DeviceFeature, mask, write DriverFeature.
  5. Set FEATURES_OK, verify it stuck.
  6. Allocate + publish rxq (queue 0) + txq (queue 1).
  7. DRIVER_OK status.
  8. Read MAC from DeviceCfg.
  9. Pre-post RxRingSize receive buffers + notify the device.

On success the device is in DRIVER_OK state, the rxq is pre-posted with RxRingSize buffers, the txq is empty + ready, and the device MAC is set.

func OpenVirtioNetWithFeatures

func OpenVirtioNetWithFeatures(t common.Transport, acceptedFeatures uint64) (*VirtioNet, error)

OpenVirtioNetWithFeatures is the parameterised variant that takes a caller-supplied accepted-features mask. The override is applied AFTER the device's offered bitmap is read, so the negotiated mask is `deviceFeats & overrideAcceptedFeatures` (with FeatureVersion1 and FeatureMAC still enforced).

Used by diagnostic narrows to test widening the accepted set (e.g. acknowledging Apple's private bits 28/29) without committing that to the production mask.

func (*VirtioNet) ReceiveFrame

func (v *VirtioNet) ReceiveFrame(pollIterations int) ([]byte, error)

ReceiveFrame polls the rxq for one new frame. Returns the Ethernet payload (header stripped) on success, or ErrReceiveTimeout if no frame arrives within `pollIterations` busy-spin cycles.

The returned slice is a fresh copy of the descriptor's DMA buffer — safe to retain after this call returns (and after the descriptor is reclaimed and refilled).

func (*VirtioNet) RxQueue

func (v *VirtioNet) RxQueue() *common.Virtqueue

RxQueue / TxQueue expose the per-direction *common.Virtqueue handles. Read-only accessors so callers can inspect ring state for diagnostic dumps; the fields themselves stay unexported.

func (*VirtioNet) TransmitFrame

func (v *VirtioNet) TransmitFrame(frame []byte) error

TransmitFrame copies a virtio_net_hdr + the Ethernet `frame` into a fresh DMA-visible buffer, enqueues it on the txq, notifies the device, and polls the used ring for completion.

Polls for up to `TxPollIterations` iterations. On QEMU the round-trip is usually < 1 ms; on VZ similar. Returns ErrTransmitTimeout if the budget exhausts.

func (*VirtioNet) TxQueue

func (v *VirtioNet) TxQueue() *common.Virtqueue

TxQueue returns the TX virtqueue handle.

type VirtioNetHdr

type VirtioNetHdr struct {
	Flags      uint8
	GSOType    uint8
	HdrLen     uint16
	GSOSize    uint16
	CsumStart  uint16
	CsumOffset uint16
	NumBuffers uint16
}

VirtioNetHdr is the Go view of `struct virtio_net_hdr` (Virtio 1.1 §5.1.6.1). The driver always emits an all-zero header and ignores the received one.

Jump to

Keyboard shortcuts

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