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 support for per-connection labels in conntrack netlink data and the public API, with 16-byte label validation; omitting a label clears it.
  • Tests

    • Added end-to-end tests for creating, updating, listing, filtering, clearing, and persisting conntrack labels.

Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

Adds ConntrackFlow.Labels ([]byte) and emits a CTA_LABELS netlink attribute from ConntrackFlow.toNlData when Labels is non-nil; enforces 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 insert iptables/nft rules that set conntrack labels for kernel tests.

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 may be set)
    CF->>ToNL: toNlData()
    ToNL->>ToNL: emit CTA_ORIG/CTA_REPLY/CTA_MARK/CTA_TIMEOUT
    alt Labels non-nil
        ToNL->>ToNL: if len(Labels) != 16 -> return error
        ToNL->>NL: append CTA_LABELS (raw 16 bytes)
    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" directly and accurately describes the primary change in the changeset. The title is concise, uses clear technical terminology, and specifies both the feature being added (encoding conntrack labels) and the condition triggering it (when labels are set). The changes in both conntrack_linux.go and conntrack_test.go align precisely with this description—adding logic to emit the CTA_LABELS netlink attribute when the Labels field is non-nil. The title would enable a teammate scanning commit history to quickly understand the purpose and scope of the change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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]>
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.

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