Skip to content

netem: add Gilbert-Elliot (gemodel) loss support - #1210

Open
checcoalt wants to merge 2 commits into
vishvananda:mainfrom
checcoalt:netem-gemodel-loss
Open

netem: add Gilbert-Elliot (gemodel) loss support#1210
checcoalt wants to merge 2 commits into
vishvananda:mainfrom
checcoalt:netem-gemodel-loss

Conversation

@checcoalt

@checcoalt checcoalt commented Aug 9, 2026

Copy link
Copy Markdown

Summary

The kernel's netem qdisc supports a Gilbert-Elliot two-state loss model (TCA_NETEM_LOSS / NETEM_LOSS_GE, struct tc_netem_gemodel), used to model bursty/correlated packet loss instead of the independent/iid loss the existing Loss/LossCorr fields provide. The TCA_NETEM_LOSS constant already existed in this package but had no encode/decode support at all.

  • Adds TcNetemGemodel to the nl package, a direct mirror of the kernel's struct tc_netem_gemodel (include/uapi/linux/pkt_sched.h), following the exact Serialize/Deserialize/Len pattern already used by TcNetemCorrupt/TcNetemReorder.
  • Adds GELossP, GELossR, GELossH, GELossK1 to NetemQdiscAttrs (percent, human-facing) and Netem (raw kernel-scaled values), wired through NewNetem.
  • Encoding adds the nested TCA_NETEM_LOSS | NLA_F_NESTED attribute containing NETEM_LOSS_GE, matching how iproute2's tc/q_netem.c builds the same attribute (addattr_nest(n, ..., TCA_NETEM_LOSS | NLA_F_NESTED)).
  • Decoding parses the same nested attribute back into Netem.
  • GELossH/GELossK1 map directly onto the kernel's h/k1 fields, not the tc CLI's "1-h"/"1-k" convention (the CLI performs that complement itself before calling into the kernel — see q_netem.c); this is documented on the struct and on NetemQdiscAttrs.
  • Verified in net/sched/sch_netem.c that the GE model and the basic Loss/LossCorr model are mutually exclusive at the kernel level (selected by q->loss_model); documented on the new fields.
  • NETEM_LOSS_GI (4-state) is out of scope for this PR; only the enum value is added since it lives in the same kernel attribute.

Test plan

  • go build ./...
  • go test ./nl/... — new TestTcNetemGemodelDeserializeSerialize (random-byte round-trip, no root required) passes
  • go test . — extended TestClassAddDel with GELossP/R/H/K1 values and added assertions comparing the qdisc as added vs. as read back from QdiscList; this test is root-gated like the rest of the suite (skipUnlessRoot) and was confirmed to skip cleanly in an unprivileged sandbox — a maintainer/CI with root will exercise the actual netlink round-trip against a live vcan/ifb interface

Summary by CodeRabbit

  • New Features

    • Added support for configuring Gilbert-Elliot burst-loss behavior in network emulation.
    • Added four percentage-based loss parameters that can be configured and retrieved through netem settings.
    • Netem configuration now preserves Gilbert-Elliot parameters when serialized, applied, and listed.
  • Tests

    • Added coverage for serialization, deserialization, validation, and round-trip preservation of Gilbert-Elliot loss settings.

The kernel's netem qdisc supports a Gilbert-Elliot two-state loss
model (TCA_NETEM_LOSS / NETEM_LOSS_GE, struct tc_netem_gemodel) as an
alternative to the basic Loss/LossCorr correlated-loss model, letting
callers model bursty packet loss instead of independent/iid loss.
This attribute was previously unimplemented in this package even
though the TCA_NETEM_LOSS constant already existed.

Adds TcNetemGemodel to the nl package mirroring the kernel ABI
directly (net/sched/sch_netem.c, include/uapi/linux/pkt_sched.h), and
GELossP/R/H/K1 fields on NetemQdiscAttrs/Netem, wired through
NewNetem and the netem qdisc encode/decode path the same way
Corrupt/Reorder already are.
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 63571523-df66-4e2d-8ecf-f9eacab937cd

📥 Commits

Reviewing files that changed from the base of the PR and between bcf3a14 and f3ef24b.

📒 Files selected for processing (1)
  • qdisc_linux.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • qdisc_linux.go

📝 Walkthrough

Walkthrough

Netem now supports Gilbert-Elliot burst-loss parameters. The change adds kernel-compatible encoding, nested qdisc serialization and deserialization, public configuration fields, and round-trip tests.

Changes

Netem Gilbert-Elliot Loss

Layer / File(s) Summary
Kernel loss-model contract
nl/tc_linux.go, nl/tc_linux_test.go
Adds kernel ABI constants, nested loss-model identifiers, TcNetemGemodel, serialization, safe deserialization, and randomized round-trip tests.
Qdisc loss integration
qdisc.go, qdisc_linux.go
Adds Gilbert-Elliot fields to Netem structures. Converts percentage values to kernel values and handles nested attribute serialization and deserialization.
Qdisc round-trip validation
class_test.go
Configures all four Gilbert-Elliot values and verifies them after qdisc listing.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant NewNetem
  participant NetemSerializer
  participant KernelQdisc
  Client->>NewNetem: Configure GELoss percentages
  NewNetem->>NewNetem: Convert percentages to kernel values
  NewNetem->>NetemSerializer: Provide Netem with GELoss fields
  NetemSerializer->>KernelQdisc: Send nested TCA_NETEM_LOSS attributes
  KernelQdisc-->>NetemSerializer: Return nested NETEM_LOSS_GE attributes
  NetemSerializer-->>Client: Restore GELoss fields
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding Gilbert-Elliot loss support to the netem qdisc.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@qdisc_linux.go`:
- Around line 669-682: Update the netem loss parsing switch in QdiscList to
handle the unflagged nl.TCA_NETEM_LOSS attribute as well as the existing nested
form, reusing the same ParseRouteAttr and GELoss* assignment logic so dumped
Gilbert-Elliot configurations populate their fields.
- Around line 253-263: Update the GE model condition in the qdisc serialization
path to emit the nested NETEM_LOSS_GE attributes whenever any GE parameter is
configured, including when GELossP is zero. Use the existing GELossP, GELossR,
GELossH, and GELossK1 fields to detect configuration, while preserving the
current gemodel serialization and legacy-model exclusivity.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e53dd92e-ac0c-4f82-846c-98183cd291c2

📥 Commits

Reviewing files that changed from the base of the PR and between 156a440 and bcf3a14.

📒 Files selected for processing (5)
  • class_test.go
  • nl/tc_linux.go
  • nl/tc_linux_test.go
  • qdisc.go
  • qdisc_linux.go

Comment thread qdisc_linux.go
Comment on lines +253 to +263
// Gilbert-Elliot loss model. Mutually exclusive with the basic
// Loss/LossCorr model: the kernel selects whichever was supplied.
if qdisc.GELossP > 0 {
gemodel := nl.TcNetemGemodel{}
gemodel.P = qdisc.GELossP
gemodel.R = qdisc.GELossR
gemodel.H = qdisc.GELossH
gemodel.K1 = qdisc.GELossK1
loss := options.AddRtAttr(nl.TCA_NETEM_LOSS|unix.NLA_F_NESTED, nil)
loss.AddRtAttr(nl.NETEM_LOSS_GE, gemodel.Serialize())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate qdisc_linux.go =="
fd -a 'qdisc_linux\.go$' . || true

echo "== relevant symbol excerpts =="
file="$(fd 'qdisc_linux\.go$' . | head -n1)"
if [ -n "${file:-}" ]; then
  wc -l "$file"
  sed -n '55,95p' "$file" | cat -n
  sed -n '235,270p' "$file" | cat -n
  sed -n '655,690p' "$file" | cat -n
fi

echo "== git diff stat/name =="
git diff --stat || true
git diff --name-only || true

Repository: vishvananda/netlink

Length of output: 4410


🌐 Web query:

Linux kernel netem NETEM_LOSS_GE nla nested p = 0 accepted

💡 Result:

In the Linux kernel's netem (Network Emulator) implementation, NETEM_LOSS_GE refers to the Gilbert-Elliot packet loss model [1]. This model uses a nested Netlink attribute structure to pass parameters between user space (typically the tc tool) and the kernel [2][3]. The p = 0 value in your query refers to the transition probability parameter within the Gilbert-Elliot model configuration [4][3]. When configuring netem loss gemodel via tc, the parameters correspond to the structure struct tc_netem_gemodel [3][1]: - p: Probability of transitioning to the bad (lossy) state [4][5]. - r: Probability of exiting the bad state [4][5]. - h: Loss probability in the bad state (often represented as 1-h in documentation) [4][5]. - k1: Loss probability in the good state (often represented as 1-k in documentation) [4][3]. The kernel's get_loss_clg function handles the parsing of these nested attributes [2][3]. It expects the data size to match sizeof(struct tc_netem_gemodel) [3][6]. If the parameters are provided as p = 0, the kernel accepts this value as a valid probability (specifically, 0% probability of transitioning to the bad state in this context) [4][3]. The input is mapped directly to the clg.a1 field in the kernel's internal netem_sched_data structure [3][7]. There is no specific mechanism that rejects p=0 as an invalid configuration; rather, it is processed as a standard integer value for the Gilbert-Elliot model [3]. Documentation confirms that the Bernoulli model is a special case of the Gilbert-Elliot model, where setting specific parameters (like p) allows the model to function as a simpler random loss generator [4][8].

Citations:


🌐 Web query:

site:codebrowser.dev sch_netem.c get_loss_clg TCA_NETEM_LOSS NLA_F_NESTED NETEM_LOSS_GE

💡 Result:

In the Linux kernel, specifically within the network traffic control (tc) subsystem, the terms identified relate to the configuration and handling of netem (Network Emulator) attributes via Netlink messages. 1. TCA_NETEM_LOSS: This is an enumeration value in the Linux kernel (defined in include/uapi/linux/pkt_sched.h) used as a Netlink attribute type to configure packet loss for the netem qdisc [1]. 2. NETEM_LOSS_GE: This represents the "Gilbert-Elliot" packet loss model, also defined in include/uapi/linux/pkt_sched.h within the NETEM_LOSS enumeration [1]. It is used to specify advanced loss patterns beyond simple random loss [1]. 3. NLA_F_NESTED: This is a Netlink flag (defined in include/uapi/linux/netlink.h) used to indicate that an attribute contains a nested list of further attributes [2]. When configuring complex netem attributes (like specific loss models), the kernel often expects them to be nested within a parent Netlink attribute, requiring this flag to be set to ensure proper parsing and validation by the netlink attribute validation logic [3][4]. 4. sch_netem.c and get_loss_clg: While get_loss_clg is not a standard, globally exposed symbol, it refers to the pattern of internal helper functions found in sch_netem.c (and other qdisc implementations) that parse and "get" configuration data—such as loss model parameters (clg likely standing for "Gilbert-Elliot" or a similar configuration model)—from Netlink attributes [1][4][5]. In the context of traffic control, code paths like these utilize the NLA_F_NESTED flag to safely unpack nested structures describing complex loss behaviors, such as the parameters for the NETEM_LOSS_GE model, into the kernel's internal qdisc structures [4][5].

Citations:


🌐 Web query:

sch_netem.c nla_attr_size TCA_NETEM_LOSS NETEM_LOSS_GE nla_f_nested

💡 Result:

In the Linux kernel's Network Emulator (netem) implementation (sch_netem.c), these constants and identifiers are used to manage loss models via Netlink attributes [1][2]. TCA_NETEM_LOSS is a Netlink attribute type used to group nested loss model parameters [1][3]. It is defined with the NLA_NESTED policy [3][4], allowing multiple specific loss model parameters to be included within the same nested structure [1][5]. NETEM_LOSS_GE refers to the Gilbert-Elliot loss model, which is one of the supported loss models in netem [1][2]. When userspace configures this model, it passes a struct tc_netem_gemodel containing parameters (p, r, h, k1) [1][3]. NLA_F_NESTED is a flag used by userspace tools (such as iproute2's tc command) when creating nested Netlink attributes [6][7]. When the tc command adds the TCA_NETEM_LOSS attribute, it performs bitwise OR with NLA_F_NESTED (TCA_NETEM_LOSS | NLA_F_NESTED) to indicate to the kernel that the attribute contains further nested attributes [6][8]. The kernel's netem implementation in sch_netem.c includes a get_loss_clg function that uses nla_for_each_nested to iterate through the attributes inside the TCA_NETEM_LOSS nest, identifies them by type (e.g., NETEM_LOSS_GE), and parses the data accordingly [1][5]. The use of nla_attr_size is typically used in the kernel to validate the size of incoming Netlink attributes, including nested ones [3][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('/home/jailuser/git/qdisc_linux.go')
text = p.read_text()

checks = {
    "serialize_condition": "if qdisc.GELossP > 0" in text,
    "gemodel_assignments_present": all(s in text for s in [
        "gemodel := nl.TcNetemGemodel{}",
        "gemodel.P = qdisc.GELossP",
        "gemodel.R = qdisc.GELossR",
        "gemodel.H = qdisc.GELossH",
        "gemodel.K1 = qdisc.GELossK1",
    ]),
    "nested_loss_attribute_present": "nl.TCA_NETEM_LOSS|unix.NLA_F_NESTED" in text,
    "ge_add_attribute_present": "loss.AddRtAttr(nl.NETEM_LOSS_GE" in text,
    "ge_fields_retained_from_nattrs": all(s in text for s in [
        "geLossP := Percentage2u32(nattrs.GELossP)",
        "geLossR := Percentage2u32(nattrs.GELossR)",
        "geLossH := Percentage2u32(nattrs.GELossH)",
        "geLossK1 := Percentage2u32(nattrs.GELossK1)",
    ]),
    "dump_case_exact": "case nl.TCA_NETEM_LOSS | unix.NLA_F_NESTED:" in text,
}
for name, ok in checks.items():
    print(f"{name}: {ok}")

# Simulate the relevant branch invariants for representative configured inputs.
class Qdisc:
    GELossP = 0
    GELossR = 0
    GELossH = 0
    GELossK1 = 0

inputs = [
    {"labels": ["GELossP=0,GELossK1=1"], "q": (0, 0, 0, 1)},
    {"labels": ["GELossR=1"], "q": (0, 1, 0, 0)},
    {"labels": ["GELossH=1"], "q": (0, 0, 1, 0)},
    {"labels": ["GELossP=1"], "q": (1, 0, 0, 0)},
]
for case in inputs:
    qdisc = Qdisc()
    qdisc.GELossP = case["q"][0]
    qdisc.GELossR = case["q"][1]
    qdisc.GELossH = case["q"][2]
    qdisc.GELossK1 = case["q"][3]
    legacy_branch = not (qdisc.GELossP > 0 or qdisc.GELossR > 0 or qdisc.GELossH > 0 or qdisc.GELossK1 > 0)
    print(f"configured input {case['labels']}: GELoss config branch selected = {not legacy_branch}")
PY

Repository: vishvananda/netlink

Length of output: 630


Serialize the GE model when any GE parameter is configured.

p = 0 is valid for the GE ABI, so GELossP = 0 with any nonzero GE parameter still requires TCA_NETEM_LOSS to select the GE loss model. The current GELossP > 0 check can fall back to the legacy loss model.

Proposed fix
-		if qdisc_GELossP > 0 {
+		if qdisc.GELossP > 0 || qdisc.GELossR > 0 ||
+			qdisc.GELossH > 0 || qdisc.GELossK1 > 0 {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@qdisc_linux.go` around lines 253 - 263, Update the GE model condition in the
qdisc serialization path to emit the nested NETEM_LOSS_GE attributes whenever
any GE parameter is configured, including when GELossP is zero. Use the existing
GELossP, GELossR, GELossH, and GELossK1 fields to detect configuration, while
preserving the current gemodel serialization and legacy-model exclusivity.

Comment thread qdisc_linux.go Outdated
CI caught this: TestClassAddDel round-tripped GELossP as 0 instead of
the value that was set. The parse-side switch case required
TCA_NETEM_LOSS | NLA_F_NESTED, matching how iproute2 builds the
attribute on the way in (tc/q_netem.c). But net/sched/sch_netem.c's
dump_loss_model() builds its reply with nla_nest_start_noflag(),
which deliberately omits the flag bit on the way out, so the case
never matched and the GE fields stayed zeroed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant