Skip to content

Conversation

@aojea
Copy link
Contributor

@aojea aojea commented Oct 21, 2025

conntrack: update conntrack labels

The logic for updating conntrack labels was missing.
The conntrack labels is an slice of bytes, if is not nil we send
its current value.
The all zeros slice has a special meaning , that wipes out the existing
labels.

There is also some unexpected behavior, the conntrack table does not
reserve space for the labels if there is no label set in any rule,
causing the netlink calls to fail with ENOSPC

Signed-off-by: Antonio Ojea [email protected]

Summary by CodeRabbit

  • New Features

    • Added per-connection labels for conntrack entries exposed via netlink and the public API; labels are 16 bytes and a missing label clears existing labels.
  • Tests

    • Added end-to-end tests covering creation, update, listing, filtering, clearing, and persistence of conntrack labels.

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

Adds a Labels field to ConntrackFlow and emits a CTA_LABELS netlink attribute from ConntrackFlow.toNlData when Labels is non-nil; requires Labels length == 16 (error otherwise). Tests and kernel hook setup were extended to set and verify per-conntrack labels.

Changes

Cohort / File(s) Summary
Conntrack netlink serialization
conntrack_linux.go
When ConntrackFlow.Labels is non-nil, validate len(Labels) == 16 (error otherwise) and append a CTA_LABELS attribute to the netlink payload after timeout; parsing path unchanged.
Tests and kernel hook setup for labels
conntrack_test.go
Add Labels []byte to ConntrackFlow; extend checkFlowsEqual to compare labels; add TestConntrackLabels; initialize Labels in TestConntrackFlowToNlData; modify ensureCtHooksInThisNS to install iptables/nft rules that set conntrack labels for test flows; add bytes import.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test code
    participant CF as ConntrackFlow
    participant ToNL as toNlData()
    participant NL as Netlink payload

    Test->>CF: create ConntrackFlow (Labels maybe set)
    CF->>ToNL: toNlData()
    ToNL->>ToNL: emit CTA_* fields (mark, timeout, ...)
    alt Labels non-nil
        ToNL->>ToNL: validate len(Labels) == 16
        opt len==16
            ToNL->>NL: append CTA_LABELS (raw 16 bytes)
        end
        opt len!=16
            ToNL-->>Test: return error
        end
    else Labels nil
        ToNL->>NL: no CTA_LABELS appended
    end
    ToNL-->>Test: return payload or error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
I nibble code and mark each trail,
Sixteen bytes — a tiny tale.
Labels tucked in nets so neat,
Conntrack hops with steady feet.
Hop—serialization complete!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "conntrack: encode conntrack labels if set" accurately reflects the main change in the changeset. The core modification adds support for emitting the CTA_LABELS netlink attribute in ConntrackFlow.toNlData when Labels is non-nil, which is precisely what "encode conntrack labels if set" conveys. The title is concise, clear, and specific enough that a teammate scanning the repository history would immediately understand the primary purpose: adding label encoding functionality to the conntrack implementation. The title avoids vague terms and directly describes the functional change without unnecessary noise.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5dad30c and 628845b.

📒 Files selected for processing (2)
  • conntrack_linux.go (2 hunks)
  • conntrack_test.go (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • conntrack_linux.go
🧰 Additional context used
🧬 Code graph analysis (1)
conntrack_test.go (5)
netlink_test.go (1)
  • KernelVersion (290-308)
conntrack_linux.go (9)
  • ConntrackFlow (298-309)
  • IPTuple (256-264)
  • ProtoInfo (219-221)
  • ConntrackUpdate (70-72)
  • ConntrackTable (22-22)
  • ConntrackCreate (64-66)
  • ConntrackTableList (51-53)
  • ConntrackFilter (754-760)
  • ConntrackFilterType (731-731)
nl/nl_linux.go (1)
  • FAMILY_V4 (25-25)
nl/conntrack_linux.go (2)
  • TCP_CONNTRACK_SYN_SENT2 (45-45)
  • TCP_CONNTRACK_ESTABLISHED (38-38)
netlink.go (1)
  • NewIPNet (35-40)
🔇 Additional comments (6)
conntrack_test.go (6)

7-7: LGTM!

The bytes import is correctly added to support label comparison at line 1747.


76-86: LGTM!

The iptables rules correctly allocate conntrack label space by setting a label in both INPUT and OUTPUT chains. This addresses the ENOSPC issue described in the PR where the conntrack table doesn't reserve space for labels without an existing rule.


108-113: LGTM!

The nftables rules correctly set conntrack labels for both input and output chains, providing parity with the iptables implementation and ensuring label space allocation. The previous issues (stray brace and missing input rule) have been addressed.


1514-1644: LGTM!

The test comprehensively validates conntrack label functionality through a complete create-update-verify cycle. The test correctly handles the semantic where sending an all-zero 16-byte slice clears labels, while the kernel returns nil for cleared labels. The implementation follows established patterns and includes proper error handling and cleanup.


1666-1666: LGTM!

Adding the Labels field to both IPv4 and IPv6 test flows ensures the serialization/deserialization logic is properly exercised and validated in the round-trip conversion test.

Also applies to: 1689-1689


1746-1750: LGTM!

The label comparison correctly uses bytes.Equal, which properly handles nil slices and byte-by-byte comparison. The implementation follows the existing pattern in checkFlowsEqual with appropriate error logging.


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 and usage tips.

@aojea
Copy link
Contributor Author

aojea commented Oct 21, 2025

ping @aboch , appreciate if you can prioritize this review , it solves a gap in network policies in kubernetes kubernetes-sigs/kube-network-policies#268

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
conntrack_test.go (2)

76-79: Add cleanup for connlabel rules and make rule insertion more resilient.

  • Clean up the inserted connlabel rules to keep the test netns tidy.
  • Optionally, fall back to nft “ct label set 1” if xt_connlabel isn’t available.

Add to the cleanup closure:

   return func() {
     if addedInput {
       ipt(false, "-D", "INPUT", "-m", "conntrack", "--ctstate", "NEW,ESTABLISHED", "-j", "ACCEPT")
     }
     if addedOutput {
       ipt(false, "-D", "OUTPUT", "-m", "conntrack", "--ctstate", "ESTABLISHED", "-j", "ACCEPT")
     }
+    // Best-effort removal of label rules.
+    ipt(false, "-D", "INPUT", "-m", "connlabel", "--set", "--label", "1")
+    ipt(false, "-D", "OUTPUT", "-m", "connlabel", "--set", "--label", "1")
   }

Also applies to: 83-86


1607-1615: Clarify clearing semantics in comment.

You send a 16-byte all-zero slice to clear, not an empty slice.

Apply this diff:

- // Change the conntrack and update the kernel entry.
+ // Change the conntrack and update the kernel entry.
   flow.Mark = 10
-  flow.Labels = make([]byte, 16) // zero labels
+  flow.Labels = make([]byte, 16) // 16-byte all-zero slice clears labels
@@
- // To clear the labels we send an empty slice, but when reading back
- // from the kernel we get a nil slice.
+ // After clearing, reading back yields a nil slice from the kernel.

Also applies to: 1636-1639

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19840db and a82971e.

📒 Files selected for processing (2)
  • conntrack_linux.go (2 hunks)
  • conntrack_test.go (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
conntrack_test.go (3)
netlink_test.go (1)
  • KernelVersion (290-308)
conntrack_linux.go (9)
  • ConntrackFlow (298-309)
  • IPTuple (256-264)
  • ProtoInfo (219-221)
  • ConntrackUpdate (70-72)
  • ConntrackTable (22-22)
  • ConntrackCreate (64-66)
  • ConntrackTableList (51-53)
  • ConntrackFilter (750-756)
  • ConntrackFilterType (727-727)
netlink.go (1)
  • NewIPNet (35-40)
conntrack_linux.go (2)
nl/nl_linux.go (1)
  • NewRtAttr (402-410)
nl/conntrack_linux.go (1)
  • CTA_LABELS (126-126)
🔇 Additional comments (5)
conntrack_linux.go (1)

369-371: Doc addition looks good.

CTA_LABELS placement documented clearly.

conntrack_test.go (4)

7-7: Import is correct.

bytes is needed for bytes.Equal in label comparisons.


1512-1590: End-to-end label create/read assertions look solid.

Good coverage of label propagation through create/list paths.


1665-1666: Round-trip NL data now includes Labels – LGTM.

The added Labels fixtures match parser expectations.

Also applies to: 1688-1689


1746-1749: Label comparison via bytes.Equal – LGTM.

Handles nil vs empty equivalence correctly for test purposes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
conntrack_linux.go (1)

397-404: CTA_LABELS emission semantics are correct.

Nil skips sending; exact 16 bytes enforced; zeroed 16-byte slice clears. This matches kernel expectations. Good change.

conntrack_test.go (1)

108-112: Fix nft rule: remove stray brace and add input-chain rule.

Current command is invalid due to trailing "}" and only sets label on output; add input for parity with iptables.

-        // Add a rule to set conntrack label to allocate the label space
-        // https://lore.kernel.org/netfilter-devel/[email protected]/
-        _ = exec.Command("nft", "add", "rule", "inet", "ct_test", "output",
-            "ct", "label", "set", "1", "}").Run()
+        // Add rules to set a conntrack label and allocate label space
+        // https://lore.kernel.org/netfilter-devel/[email protected]/
+        _ = exec.Command("nft", "add", "rule", "inet", "ct_test", "output",
+            "ct", "label", "set", "1").Run()
+        _ = exec.Command("nft", "add", "rule", "inet", "ct_test", "input",
+            "ct", "label", "set", "1").Run()
🧹 Nitpick comments (2)
conntrack_linux.go (1)

334-420: Add a negative test for invalid label length.

To lock the contract, add a test that sets flow.Labels to a non-16 length (e.g., 8 or 17) and asserts toNlData() returns an error, and that ConntrackCreate/Update propagate it. Keeps behavior from regressing.

conntrack_test.go (1)

71-87: Also delete iptables connlabel rules in cleanup for symmetry.

We insert connlabel rules but never remove them. Track and delete them to keep the test namespace tidy.

-        var addedInput, addedOutput bool
+        var addedInput, addedOutput bool
+        var addedLabelInput, addedLabelOutput bool
         if ipt(false, "-C", "INPUT", "-m", "conntrack", "--ctstate", "NEW,ESTABLISHED", "-j", "ACCEPT") != nil {
             ipt(true, "-I", "INPUT", "-m", "conntrack", "--ctstate", "NEW,ESTABLISHED", "-j", "ACCEPT")
             // Add a rule to set conntrack label to allocate the label space
             // https://lore.kernel.org/netfilter-devel/[email protected]/
-            ipt(true, "-I", "INPUT", "-m", "connlabel", "--set", "--label", "1")
+            if ipt(true, "-I", "INPUT", "-m", "connlabel", "--set", "--label", "1") == nil {
+                addedLabelInput = true
+            }
             addedInput = true
         }
         if ipt(false, "-C", "OUTPUT", "-m", "conntrack", "--ctstate", "ESTABLISHED", "-j", "ACCEPT") != nil {
             ipt(true, "-I", "OUTPUT", "-m", "conntrack", "--ctstate", "ESTABLISHED", "-j", "ACCEPT")
             // Add a rule to set conntrack label to allocate the label space
             // https://lore.kernel.org/netfilter-devel/[email protected]/
-            ipt(true, "-I", "OUTPUT", "-m", "connlabel", "--set", "--label", "1")
+            if ipt(true, "-I", "OUTPUT", "-m", "connlabel", "--set", "--label", "1") == nil {
+                addedLabelOutput = true
+            }
             addedOutput = true
         }
         return func() {
             if addedInput {
                 ipt(false, "-D", "INPUT", "-m", "conntrack", "--ctstate", "NEW,ESTABLISHED", "-j", "ACCEPT")
+                if addedLabelInput {
+                    ipt(false, "-D", "INPUT", "-m", "connlabel", "--set", "--label", "1")
+                }
             }
             if addedOutput {
                 ipt(false, "-D", "OUTPUT", "-m", "conntrack", "--ctstate", "ESTABLISHED", "-j", "ACCEPT")
+                if addedLabelOutput {
+                    ipt(false, "-D", "OUTPUT", "-m", "connlabel", "--set", "--label", "1")
+                }
             }
         }

Also applies to: 88-96

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a82971e and 7804471.

📒 Files selected for processing (2)
  • conntrack_linux.go (2 hunks)
  • conntrack_test.go (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
conntrack_test.go (5)
netlink_test.go (1)
  • KernelVersion (290-308)
conntrack_linux.go (15)
  • ConntrackFlow (298-309)
  • IPTuple (256-264)
  • ProtoInfo (219-221)
  • ConntrackUpdate (70-72)
  • ConntrackTable (22-22)
  • ConntrackCreate (64-66)
  • ConntrackTableList (51-53)
  • ConntrackFilter (754-760)
  • ConntrackFilterType (731-731)
  • ConntrackOrigSrcIP (734-734)
  • ConntrackOrigDstIP (735-735)
  • ConntrackReplySrcIP (736-736)
  • ConntrackReplyDstIP (737-737)
  • ConntrackOrigSrcPort (739-739)
  • ConntrackOrigDstPort (740-740)
nl/nl_linux.go (1)
  • FAMILY_V4 (25-25)
nl/conntrack_linux.go (2)
  • TCP_CONNTRACK_SYN_SENT2 (45-45)
  • TCP_CONNTRACK_ESTABLISHED (38-38)
netlink.go (1)
  • NewIPNet (35-40)
conntrack_linux.go (2)
nl/nl_linux.go (1)
  • NewRtAttr (402-410)
nl/conntrack_linux.go (1)
  • CTA_LABELS (126-126)
🔇 Additional comments (3)
conntrack_test.go (3)

1746-1749: Good choice using bytes.Equal for Labels comparison.

Handles nil vs empty slices correctly; avoids false negatives when clearing labels.


1648-1693: Including Labels in FlowToNlData round-trip tests is valuable.

Ensures CTA_LABELS is serialized and parsed consistently for both v4 and v6.


1512-1643: TestConntrackLabels exercises set/clear semantics well.

Create with labels, update with zeroed 16-byte slice to clear, then compare against nil on readback. Matches kernel behavior.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
conntrack_test.go (1)

76-95: Cleanup function doesn't remove connlabel rules.

The iptables connlabel rules added on lines 78 and 85 are not removed in the cleanup function (lines 88-94). While test namespaces are typically destroyed, explicitly cleaning up all added rules would be more consistent.

Consider updating the cleanup function:

 		return func() {
 			if addedInput {
 				ipt(false, "-D", "INPUT", "-m", "conntrack", "--ctstate", "NEW,ESTABLISHED", "-j", "ACCEPT")
+				ipt(false, "-D", "INPUT", "-m", "connlabel", "--set", "--label", "1")
 			}
 			if addedOutput {
 				ipt(false, "-D", "OUTPUT", "-m", "conntrack", "--ctstate", "ESTABLISHED", "-j", "ACCEPT")
+				ipt(false, "-D", "OUTPUT", "-m", "connlabel", "--set", "--label", "1")
 			}
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7804471 and 5dad30c.

📒 Files selected for processing (2)
  • conntrack_linux.go (2 hunks)
  • conntrack_test.go (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • conntrack_linux.go
🧰 Additional context used
🧬 Code graph analysis (1)
conntrack_test.go (5)
netlink_test.go (1)
  • KernelVersion (290-308)
conntrack_linux.go (11)
  • ConntrackFlow (298-309)
  • IPTuple (256-264)
  • ProtoInfo (219-221)
  • ProtoInfoTCP (225-227)
  • ProtoInfoTCP (230-230)
  • ConntrackFilter (754-760)
  • ConntrackFilterType (731-731)
  • ConntrackOrigSrcIP (734-734)
  • ConntrackOrigDstIP (735-735)
  • ConntrackReplySrcIP (736-736)
  • ConntrackReplyDstIP (737-737)
nl/nl_linux.go (1)
  • FAMILY_V4 (25-25)
nl/conntrack_linux.go (2)
  • TCP_CONNTRACK_SYN_SENT2 (45-45)
  • TCP_CONNTRACK_ESTABLISHED (38-38)
netlink.go (1)
  • NewIPNet (35-40)
🔇 Additional comments (4)
conntrack_test.go (4)

7-7: LGTM: Import added for label comparison.

The bytes import is correctly added to support the bytes.Equal comparison for conntrack labels in checkFlowsEqual.


108-113: LGTM: nft rules correctly set labels for both chains.

The nft rules now correctly set conntrack labels for both input and output chains without syntax errors. The cleanup properly removes these rules by deleting the entire table.


1537-1645: LGTM: Test correctly exercises label lifecycle.

The test appropriately verifies creating conntrack entries with labels, updating labels to clear them (using an all-zero slice), and confirming the cleared state (which reads back as nil). The documented semantic difference between sending an empty slice and receiving nil is intentional per the PR objectives.


1747-1751: LGTM: Labels comparison correctly implemented.

Using bytes.Equal is the appropriate way to compare byte slices and handles nil values correctly for the label clearing semantics.

Comment on lines 1514 to 1529
// TestConntrackLabels test the conntrack table labels
// Creates some flows and then checks the labels associated
func TestConntrackLabels(t *testing.T) {
skipUnlessRoot(t)
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack"))
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink"))
k, m, err := KernelVersion()
if err != nil {
t.Fatal(err)
}
// conntrack l3proto was unified since 4.19
// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
if k < 4 || k == 4 && m < 19 {
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4"))
}
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack"))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicate module setup.

Line 1529 duplicates the nf_conntrack module setup from line 1518, which is unnecessary.

Apply this diff:

 	skipUnlessRoot(t)
 	t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack"))
 	t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink"))
 	k, m, err := KernelVersion()
 	if err != nil {
 		t.Fatal(err)
 	}
 	// conntrack l3proto was unified since 4.19
 	// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
 	if k < 4 || k == 4 && m < 19 {
 		t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4"))
 	}
-	t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack"))
 	// Creates a new namespace and bring up the loopback interface
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TestConntrackLabels test the conntrack table labels
// Creates some flows and then checks the labels associated
func TestConntrackLabels(t *testing.T) {
skipUnlessRoot(t)
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack"))
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink"))
k, m, err := KernelVersion()
if err != nil {
t.Fatal(err)
}
// conntrack l3proto was unified since 4.19
// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
if k < 4 || k == 4 && m < 19 {
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4"))
}
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack"))
// TestConntrackLabels test the conntrack table labels
// Creates some flows and then checks the labels associated
func TestConntrackLabels(t *testing.T) {
skipUnlessRoot(t)
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack"))
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack_netlink"))
k, m, err := KernelVersion()
if err != nil {
t.Fatal(err)
}
// conntrack l3proto was unified since 4.19
// https://github.com/torvalds/linux/commit/a0ae2562c6c4b2721d9fddba63b7286c13517d9f
if k < 4 || k == 4 && m < 19 {
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack_ipv4"))
}
🤖 Prompt for AI Agents
In conntrack_test.go around lines 1514 to 1529, the final
t.Cleanup(setUpNetlinkTestWithKModule(t, "nf_conntrack")) is a duplicate of the
earlier module setup; remove that duplicate line (the one at line 1529) so each
kernel module is registered for cleanup only once.

The logic for updating conntrack labels was missing.
The conntrack labels is an slice of bytes, if is not nil we send
its current value.
The all zeros slice has a special meaning , that wipes out the existing
labels.

There is also some unexpected behavior, the conntrack table does not
reserve space for the labels if there is no label set in any rule,
causing the netlink calls to fail with ENOSPC

Signed-off-by: Antonio Ojea <[email protected]>
@aboch
Copy link
Collaborator

aboch commented Oct 22, 2025

LGTM

@aboch aboch merged commit 03b8f90 into vishvananda:main Oct 22, 2025
3 checks passed
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.

2 participants