tap

package
v0.7.21 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package tap manages the per-host pool of (TAP-device, host-IP, guest-IP, /30-CIDR, vsock-CID) slots that Firecracker microVMs claim at create time. The state lives in SQLite (firecracker_tap_pool table); this package owns the seed + allocation policy and exposes a typed API over the raw store CRUD.

What lives here vs. internal/store:

  • internal/store: schema, raw CRUD, partial unique index, idempotency primitives. The load-bearing transactional substrate. Touching this triggers pr-review.md §5 (TCP-host-port-pool & L4-bootstrap fragility class) — the indexes are the contract.

  • this package: policy on top of those primitives. Computing the next /30 from a base CIDR, picking the next vsock CID, naming TAP devices ("fctapN"), validating SeedConfig. No SQL, no transactions of its own — the store is the boundary.

What does NOT live here yet: actual host-side TAP device manipulation (`ip link add fctap0 type tap`, `ip addr add 172.16.0.1/30 dev fctap0`, `ip link set fctap0 up`). The first cut wires only the *allocation bookkeeping* — the act of creating the slot row and reserving the IP. Real `ip` invocations are the runtime driver's job and land in a follow-up file (`tap/host.go`) once Driver.Create needs them. Keeping allocation separate from realization mirrors the host-port pool (allocation in store; bind() in caddy-l4) and lets each step be tested in isolation.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Host

type Host struct {
	// IPCmd is the absolute path to the `ip` binary. Empty defaults to "ip"
	// (resolved from $PATH). Operators with a hardened iproute2 install
	// (e.g. /usr/sbin/ip not in the daemon's PATH) point this at it.
	IPCmd string
	// contains filtered or unexported fields
}

Host is the production HostManager. It shells out to /sbin/ip (or whichever IPCmd points at on this host). All methods are concurrency- safe — `ip` does its own kernel-side serialization, and the methods hold no per-host state.

func NewHost

func NewHost(ipCmd string) *Host

NewHost constructs a Host with the given ip-binary path. Empty path means "use `ip` from $PATH". Most callers leave it at "" because the daemon's launchd/systemd unit already has /usr/sbin on PATH.

func (*Host) Ensure

func (h *Host) Ensure(ctx context.Context, slot Slot) error

Ensure creates the TAP device, assigns the host-side /30 IP, brings the link up, and pins the guest's L2 neighbor entry. Each step is idempotent on its own; the composition is idempotent overall because each step recognizes the "already in desired state" output and treats it as success.

Order matters:

  1. `ip tuntap add` first — we need the device to exist before `ip addr add` can target it.
  2. `ip addr add` next — putting the IP on first means the link is usable the moment step 3 makes it up. Reverse order would race against the guest's DHCP/static-IP probe.
  3. `ip link set ... up` — flipping IFF_UP is what the guest's virtio-net device sees.
  4. `ip neigh replace` last — host-to-guest connects should not depend on ARP discovery during Firecracker's early boot window.

Rollback on partial failure: if step 2 or 3 fails we DO NOT call Remove. The caller is expected to call Remove from its error-cleanup path (the firecracker driver's deferred-release runs it), which keeps the "single owner of cleanup" rule pr-review.md §4 describes. Mixing rollback in here would mean a Destroy after a failed Ensure double-Removes — harmless on idempotent Remove, but makes the failure log noisier.

func (*Host) Remove

func (h *Host) Remove(ctx context.Context, tapName string) error

Remove deletes the TAP device. Idempotent: a missing device returns nil (the operator's restart path doesn't need to know whether teardown already happened).

type HostManager

type HostManager interface {
	// Ensure brings the slot's TAP device up. Idempotent: a call against a
	// device that already exists with the right address is a no-op (no
	// error). A call against a device that exists with the WRONG address
	// surfaces an error — that's a state-mismatch the operator needs to
	// see, not silently fix, because the wrong address may be in use by
	// another sandbox.
	Ensure(ctx context.Context, slot Slot) error
	// Remove tears the slot's TAP device down. Idempotent: missing device
	// returns nil. Used by Destroy and by the rollback-on-Create-failure
	// path; both must be safe to retry.
	Remove(ctx context.Context, tapName string) error
}

HostManager is the operations the firecracker runtime driver depends on for putting a Slot onto the host's network surface. Declared here as an exported interface so the driver can hold this type without importing internal/network/tap (driver tests would otherwise drag in the SQLite-backed pool just to satisfy the type).

Production implementation is *Host below. Tests inject a fake that records calls.

type Pool

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

Pool is the policy layer. Holds a *store.Store reference; the zero value is not usable.

func New

func New(st *store.Store) *Pool

New returns a Pool. The store must already be open. The Pool does not take ownership; the caller is responsible for the store's lifetime.

func (*Pool) Allocate

func (p *Pool) Allocate(ctx context.Context, sandboxID string, now time.Time) (*Slot, error)

Allocate claims a slot for sandboxID. Idempotent (re-Allocate for the same sandbox returns the existing slot, not a new one). Returns store.ErrNoFreeFirecrackerTapSlot when the pool is exhausted — the runtime driver translates that into a 503-ish admission error so the operator sees "pool exhausted" rather than a confusing timeout.

func (*Pool) Get

func (p *Pool) Get(ctx context.Context, sandboxID string) (*Slot, error)

Get returns the slot owned by sandboxID, or nil. Used by Inspect / Destroy paths. Errors from the underlying store bubble up untouched.

func (*Pool) Release

func (p *Pool) Release(ctx context.Context, sandboxID string) error

Release returns a sandbox's slot to the pool. Idempotent.

func (*Pool) Seed

func (p *Pool) Seed(ctx context.Context, cfg SeedConfig, now time.Time) error

Seed lays out PoolSize slots from BaseCIDR. Idempotent: a re-Seed with the same config is a no-op (the underlying ON CONFLICT(tap_name) DO NOTHING in the store path handles this). A re-Seed with a DIFFERENT BaseCIDR or PoolSize will silently keep the original layout for any pre-existing tap_name rows and add the new ones — operators must migrate manually (drop + reseed) if they need to shrink or relocate the pool. This is by design: rewriting an in-use slot would orphan running sandboxes.

func (*Pool) Stats

func (p *Pool) Stats(ctx context.Context) (Stats, error)

func (*Pool) Transfer

func (p *Pool) Transfer(ctx context.Context, fromID, toID string, now time.Time) (*Slot, error)

Transfer re-keys a claimed slot from fromID to toID. Used by the Firecracker warm-VMM pool: warm slots allocate TAPs under their pool slot id, then a create transfers that same TAP to the sandbox id on warm hit.

type SeedConfig

type SeedConfig struct {
	BaseCIDR string
	PoolSize int
}

SeedConfig describes the network plan the pool is laid out from. BaseCIDR is carved into /30 subnets, one per slot; PoolSize is the number of /30s to produce (and therefore the maximum number of concurrent Firecracker sandboxes per host). Both fields are required.

Why /30 specifically: a /30 holds 4 addresses — network, host-side TAP IP, guest IP, broadcast. That's the minimum that lets the host route to the guest. /31 (RFC 3021) would also work but is unsupported by some older `ip` tooling; /30 is the safe default.

type Slot

type Slot struct {
	TapName  string
	CIDR     string
	HostIP   string
	GuestIP  string
	VsockCID uint32
	GuestMAC string
}

Slot is the Pool-level view of an allocated network slot. Mirrors store.FirecrackerTapSlot but lives in this package so callers (the Firecracker driver) depend on the policy layer's type, not the store's. SandboxID is always non-empty here — a Slot returned by the pool is always claimed. GuestMAC is an optional host-neighbor override used by snapshot restores whose guest NIC MAC is frozen in snapshot state.

type Stats

Stats reports current occupancy. Used by /healthz and the admission controller — a near-empty pool blocks new Firecracker creates upstream of the failing Allocate call.

Jump to

Keyboard shortcuts

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