Linux Deterministic Packet Launch Playbook: SO_TXTIME + ETF + TAPRIO (2026)

2026-03-31 · software

Linux Deterministic Packet Launch Playbook: SO_TXTIME + ETF + TAPRIO (2026)

TL;DR


1) What this is for

This stack is useful when you care about time-of-transmission determinism, such as:

It is not a universal replacement for rate pacing. If your target is just smooth bandwidth, fq/fq_codel pacing is usually simpler.


2) Building blocks

A. SO_TXTIME + SCM_TXTIME (application layer)

Application sets socket policy via SO_TXTIME and per-packet launch timestamps via SCM_TXTIME in sendmsg().

Typical sock_txtime fields:

SOF_TXTIME_REPORT_ERRORS is important in production because missed/invalid launch times are surfaced via error queue.

B. ETF qdisc (kernel scheduler)

ETF (Earliest TxTime First):

Core knobs:

C. mqprio (class/queue mapping)

mqprio maps skb priorities to traffic classes and queue ranges. ETF is commonly attached under mqprio class handles.

D. TAPRIO (time-aware gates, optional)

TAPRIO adds cyclic gate schedules per traffic class.

Important: in txtime-assist mode, txtime-delay should be greater than ETF delta.


3) Deployment patterns

Pattern 1 — ETF only (quickest path)

Use when you need deterministic launch for one class without gate scheduling.

  1. Configure mqprio with a dedicated traffic class.
  2. Attach ETF under that class.
  3. App sets SO_TXTIME and per-packet SCM_TXTIME.
  4. Monitor error queue + tx timestamps.

Pattern 2 — TAPRIO txtime-assist + ETF

Use when you need class windows and software-assisted launch timing.

  1. Configure TAPRIO with flags 0x1 and schedule entries.
  2. Attach ETF on exposed class queue.
  3. Keep txtime-delay > etf delta.
  4. Validate actual queueing slack under load.

Pattern 3 — TAPRIO full offload

Use only when NIC support and clock discipline are mature.


4) Practical configuration skeleton

Step 1: map traffic classes (mqprio)

tc qdisc add dev eth0 root handle 100: mqprio \
  num_tc 3 \
  map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 \
  queues 1@0 1@1 2@2 \
  hw 0

Step 2: attach ETF for deterministic class

tc qdisc replace dev eth0 parent 100:1 etf \
  clockid CLOCK_TAI \
  delta 300000 \
  offload

(Use offload only if driver/NIC supports launch-time offload.)

Step 3: app socket setup (conceptual)

struct sock_txtime cfg = {
  .clockid = CLOCK_TAI,
  .flags = SOF_TXTIME_REPORT_ERRORS,
};
setsockopt(fd, SOL_SOCKET, SO_TXTIME, &cfg, sizeof(cfg));

Per packet:


5) Clock discipline: the hidden make-or-break

Deterministic launch is impossible with sloppy clocking.

Minimum standards:

Symptoms of clock hygiene issues:


6) Sizing delta and txtime-delay

delta too small → late wakeups and avoidable drops. delta too large → extra queue residency and latency inflation.

A practical tuning loop:

  1. start with conservative delta (hundreds of microseconds class)
  2. measure scheduling miss rate + end-to-end latency
  3. reduce in steps until miss rate inflects
  4. set operating point with margin for burst/GC/IRQ noise

With TAPRIO txtime-assist:


7) Observability and SLOs

Track these from day one:

If you do only one thing: drain and parse error queue continuously.


8) Common failure modes

  1. Clockid mismatch between app and ETF

    • ETF expects same reference clock and may drop non-compliant packets.
  2. Past txtime under burst/backpressure

    • app computes tx times too aggressively near “now”.
  3. No error-queue consumer

    • latent misses stay invisible until user-visible jitter incidents.
  4. Blind skip_sock_check usage

    • can hide validation assumptions; use only when kernel path sets txtime.
  5. Assuming offload == automatically better

    • offload helps determinism only with compatible driver/NIC and disciplined clocks.

9) Rollout plan (operator-safe)

Phase 0: lab

Phase 1: canary class

Phase 2: scale-out


10) Decision guide

Choose SO_TXTIME + ETF when:

Add TAPRIO txtime-assist when:

Use TAPRIO full offload when:


References