8000 fix(classifier): decode masked ct-mark writes in the install's conflict scan by agent-137 · Pull Request #3004 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

fix(classifier): decode masked ct-mark writes in the install's conflict scan - #3004

Merged
mitodrummer merged 6 commits into
mainfrom
fix/1201-tailscale-ct-mark
Nov 5, 2026
Merged

mitodrummer merged 6 commits into
mainfrom
fix/1201-tailscale-ct-mark

Conversation

@agent-137
@agent-137 agent-137 commented Nov 4, 2026 •
Copy link
Copy Markdown
Member

Refs gominimal/inbox#1201

Root cause (proven on the host)

On a host where tailscaled runs in iptables-nft mode, min finalize-install refused deny-all egress. The ct-mark conflict scan in scripts/install-host-classifier.sh read Tailscale's OUTPUT rule as a write of every bit of the connection mark. The kernel does not run that rule.

  • What the kernel runs. nft --debug=netlink list chain ip mangle OUTPUT on the affected host (posted on the issue) shows the rule ends in [ target name CONNMARK rev 2 ]. That is an xt compat target, not a native ct set mark. The ip6 rule is identical.
  • Its masks. iptables-save on the same host gives them: -j CONNMARK --save-mark --nfmask 0xff0000 --ctmask 0xff0000. xt_CONNMARK computes ctmark = (ctmark & ~ctmask) ^ (nfmark & nfmask). Every bit outside 0x00ff0000 is kept, including the default bits 0x30000000.
  • Why nft's text says otherwise. nft list prints an xt target through libxtables' xlate callback. In libxt_CONNMARK.c (iptables 1.8.11), connmark_tg_xlate_v2 prints a save with nfmask == ctmask as ct mark set mark and 0x%x. That leaves out the ct mark & ~ctmask term that keeps the other bits. For masks libxtables cannot translate (revision 1 with masks, or nfmask != ctmask), it prints no bits at all.
  • What the scan did. It read nft list ruleset text as if it were the kernel rule. It treated ct mark set mark and 0xff0000 as a full overwrite, so it refused under every --ct-mark-mask.

The fix

  1. Find xt compat targets in the netlink form. The scan now also reads nft -a --debug=netlink list ruleset (xt_target_rules_in).
    • The step already runs as root and already ran nft list ruleset, so this needs nothing new.
    • Each netlink rule header (<family> <table> <chain> <handle>) is matched to the -a text line that has the same # handle N.
    • A rule whose kernel form carries a [ target name … ] has its nft text skipped. The rule is read instead from its family's nf_tables iptables-save listing, which spells every mask.
    • nft's text is trusted only for native expressions.
  2. Read the nf_tables save listings. For each family, the scan reads iptables-nft-save / ip6tables-nft-save, or iptables-save / ip6tables-save when --version reports nf_tables (nft_backed_save).
    • A table counts as covered only if that listing prints a *table header for it. iptables-nft prints a table it cannot read as a comment instead.
  3. IPv6. ip6tables-save is now read the way iptables-save is, for both the nf_tables and the legacy backend (legacy_save).
  4. Fail closed. An xt CONNMARK rule whose table no save listing covers is refused, whatever its nft text says. The step cannot see which bits it keeps, so it treats the rule as if it might write every bit.
    • If nft -a --debug=netlink list ruleset fails, the scan falls back to the plain listing. It then finds an untranslated CONNMARK by its xt target CONNMARK text and refuses it when no save listing covers its table. A translated CONNMARK is decoded from its libxtables text, which is exact because libxtables prints a masked save only when the two masks agree.
  5. Native ct mark set is decoded for the bits it can change (ct_set_writes, from the first commits):
    • and K changes the bits outside K.
    • or / xor V changes the bits of V.
    • The libxtables save spelling mark and 0xM changes M.
    • Any other value replaces the whole mark.
    • Every ct mark set on a line is decoded.
    • This also catches ct mark set ct mark & 0xcfffffff, which clears the default bits and used to pass.

Step 3 of the issue: running the QUIC helper on its own (not done, and why)

The QUIC block helper depends on the classifier:

  • The helper only runs nft add|delete element inet <classifier table> quic443_blocked_leaves … (render_quic443_helper).
  • That set, and the UDP 443 reject rules that read it, are in the classifier's own table (render_ruleset).
  • The script installs the helper only after that table has loaded (comment above install_quic443_helper).

Installed alone, the helper would fail at runtime. The other finalize items run before the classifier block, so nothing independent was skipped.

Messages

New message text, all printed by scan_ct_mark_conflicts in scripts/install-host-classifier.sh:

  • The conflict line for an xt CONNMARK that no save listing covers (printed by the ct_mark_conflicts_in awk):
    table <family> <table>, chain <chain>: <rule as nft prints it> (an iptables rule whose masks iptables-save did not list)
  • The refusal when any such line is present:
    error: refusing to install over an iptables CONNMARK rule whose masks this step cannot read, so it may write the bits this install uses. Install iptables with its nf_tables backend, so that iptables-save and ip6tables-save list the rule, then run min finalize-install again.
    • Why the remedy works: once iptables-nft-save / ip6tables-nft-save (or an nf_tables iptables-save) lists the table, nft_backed_save covers it and the rule is decoded from its masks.

The new IPv6 save listing names its rules ip6tables table <table>, chain <chain>: …, in the same form as the existing iptables table ….

The new line and the new refusal are pinned by install_reads_xt_connmark_masks_from_iptables_save. The existing refusal strings are unchanged.

Tests

  • New install_reads_xt_connmark_masks_from_iptables_save. Its fixture is the netlink dump from the issue (OUTPUT, plus PREROUTING built the same way) for ip and ip6, the -a text, and the four iptables-save lines. It checks that:
    • Tailscale's rules, v4 and v6, install when the nf_tables save listings are there.
    • With no save listing, both families' rules are refused with the new line and message.
    • An untranslatable CONNMARK (xt target "CONNMARK" in nft, --nfmask 0xff0000 --ctmask 0x30ff0000 saved) is refused in ip and in ip6.
    • The same rule with --ctmask 0x00ffff00 installs.
    • With an nft that cannot print the netlink form, Tailscale's rules still install, and an untranslated CONNMARK with no listing is still refused.
    • An IPv6-only CONNMARK with only an IPv4 listing is refused.
  • Updated stand-ins. host_tools stands in for nft (plain and annotated), iptables-save, ip6tables-save and the -nft-save pair, each with a --version answer, so no real tool on the test host is read. The existing host_ruleset_tools keeps its old behaviour on top of it.
  • Earlier tests. install_decodes_masked_ct_mark_writes and install_refuses_a_whole_mark_ct_writer still pass.
  • Runs:
    • just test-one install_ -p minimald: 6 passed, also with awk resolved to gawk --posix.
    • just test-one classifier -p minimald: 67 passed.
    • just test-one host_ip -p minimald: 26 passed.
    • just test-one classifier -p minimal: 8 passed (first round).
    • cargo clippy -p minimald --all-targets -D warnings, just clippy-strict and just lint-shell: clean.
    • Not run: just test-root-integration (needs root) and the full just ci.

Open

  • Untested nft behaviour. nft -a --debug=netlink list ruleset interleaving: nft writes the netlink lines during the cache fetch, before the text, and the scan reads both streams together. The pairing relies on the rule handle, not on order. A real nft run of list ruleset (not only list chain) with this flag has not been checked on a host with iptables-nft rules.

🤖 Generated with Claude Code

https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

Summary by CodeRabbit

  • Bug Fixes
    • Improved installer checks for connection-mark writes across nftables and iptables backends, including writes that overlap classifier bits.
    • Installation is refused when a connection-mark target’s mark mask cannot be determined from the available rules listings.
    • Added fallback to plain rules listings when annotated netlink listings are unavailable or incomplete.
  • Tests
    • Expanded coverage for masked and whole-mark writes, IPv4 and IPv6 listings, and iptables using the nftables backend.

…ct scan

nft prints an iptables-nft CONNMARK --save-mark --nfmask M --ctmask M
through libxtables as `ct mark set mark and 0xM`, which drops the
--ctmask. The scan read it as a write of every bit and refused to
install over Tailscale's rp_filter workaround rules. The kernel keeps
every connection-mark bit outside --ctmask, so those rules change the
bits M alone.

The scan now decodes each `ct mark set` value for the bits it can
change: the libxtables save spelling changes M, a value read from the
ct mark changes the bits an `and` clears and an `or`/`xor` names, and
any other value replaces the whole mark. An `and` that clears the
install's bits is now a conflict too.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3
@coderabbitai
coderabbitai Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor

Explain Change →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

The installer scans nft rules and IPv4 and IPv6 iptables-save listings for connection-mark writes that overlap selected mark bits. Tests cover masked writes, compatibility targets, missing listings, and fallback to plain ruleset output.

Changes: Connection-mark conflict scanning

Layer / File(s) Summary
Decode nft connection-mark writes
scripts/install-host-classifier.sh, crates/minimald/src/net/classifier.rs
The scanner determines which selected mark bits ct mark set expressions can write. Unsupported expressions and unmasked whole-mark writes count as conflicts. Tests cover overlapping and disjoint masks, whole-mark writes, and untranslated CONNMARK rules.
Resolve xt compatibility target masks
scripts/install-host-classifier.sh, crates/minimald/src/net/classifier.rs, scripts/install_test.sh
The installer correlates annotated nft output with family-specific iptables-save listings and scans legacy listings separately. It refuses installation when a CONNMARK mask is unavailable. Tests cover family-specific listings and fallback to plain ruleset output when annotated output fails or omits tables.

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

Sequence Diagram(s)

sequenceDiagram
  participant Installer
  participant NftRuleset
  participant IptablesSave
  Installer->>NftRuleset: Read plain and annotated rulesets
  Installer->>IptablesSave: Read IPv4 and IPv6 save listings
  IptablesSave-->>Installer: Return compatibility-target rules
  Installer->>Installer: Check selected mark bits
Loading
Merge Risk: Minimal · up to c3805

The installer now decodes masked mark writes. No concrete merge-blocking defect was established.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title is concise, uses the required Conventional Commit format, and accurately identifies the main change: decoding masked ct-mark writes in the install conflict scan.
Description check Passed The description clearly explains the root cause, implementation, fallback and fail-closed behavior, tests, test results, issue reference, and known limitation. It does not use the template headings or…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
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.
Full details: Description check

Explanation

The description clearly explains the root cause, implementation, fallback and fail-closed behavior, tests, test results, issue reference, and known limitation. It does not use the template headings or include an explicit checklist, but it is substantially complete.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 7 functions across 3 files.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit checks each mark with care,
For bits that overlap lurking there.
The masked writes pass or meet a stop,
While plain rules help when netlink drops.
IPv4 and IPv6 join the scan,
The rabbit hops, content with the plan.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • < 8000 li>Plan: Essentials
  • Run ID: 21f157a2-eb31-47b5-a2dd-501b20435a8e
Commits

Reviewing files that changed from the base of the PR and between 58e3ecc and c380510.

Files selected for processing: 2
  • scripts/install-host-classifier.sh
  • scripts/install_test.sh

Included review availability: This review used your included allowance. 1 included review remains after this review. Your included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.

@agent-137
Copy link
Copy Markdown
Member Author

@coderabbitai review

@agent-137
Copy link
Copy Markdown
Member Author

/review

@coderabbitai
coderabbitai Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@minimal-foundry
minimal-foundry Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor

Approved: no findings at c3805101f891.

Nothing to do.

Clobber: none seen.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimald/src/net/classifier.rs Resolved
… saves

The mask 0x00030000 lies in Tailscale's well-known range, so the install
refused it before the conflict scan ran, and the assertion held whatever
the decoder did. The table now runs `ct mark set mark and 0xM` against
the default bits, over them and beside them, and each refusal must be
the conflict scan's.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3
@agent-137
Copy link
Copy Markdown
Member Author

/review

@agent-137
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
coderabbitai Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread scripts/install-host-classifier.sh Resolved
…ict scan

The scan decoded only the first `ct mark set` on a line and read later
ones for 0x values alone, so a whole-mark write after a masked one
(`ct mark set mark and 0xff0000 ct mark set meta mark`) passed. Each
statement is now decoded from where the previous value ended.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3
@agent-137
Copy link
Copy Markdown
Member Author

/review

@agent-137
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
coderabbitai Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@agent-137
Copy link
Copy Markdown
Member Author

/review

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minimal-review — approved

Verdict APPROVE — No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Nov 4, 2026
@mitodrummer
mitodrummer marked this pull request as ready for review November 4, 2026 21:13
@mitodrummer
mitodrummer requested a review from a team as a code owner November 4, 2026 21:13
A rule iptables-nft wrote is an xt compat target in the kernel
(`[ target name CONNMARK rev 2 ]`), and `nft list` prints it through
libxtables, which drops a --ctmask or names no bits at all. The scan
now reads `nft -a --debug=netlink list ruleset`, finds each rule whose
kernel form carries an xt target by family, table, chain and handle,
and decodes it from the nf_tables iptables-save and ip6tables-save
listings instead of its nft text. A CONNMARK no listing covers is
refused, because the step cannot see which bits it keeps.

ip6tables-save is now read the way iptables-save is, for both
backends.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3
@agent-137
Copy link
Copy Markdown
Member Author

@coderabbitai review

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread scripts/install-host-classifier.sh Resolved
@minimal-foundry minimal-foundry Bot removed the review:approved minimal-review approved the current head label Nov 4, 2026
@coderabbitai
coderabbitai Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor
Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…tlink form

An nft that cannot run `nft -a --debug=netlink list ruleset` blocked
every install, though the plain listing had just been read. The scan
now uses the plain listing there, and finds an untranslated CONNMARK
by its `xt target CONNMARK` text, so one no iptables-save listing
covers is still refused.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3
@agent-137
Copy link
Copy Markdown
Member Author

@coderabbitai review

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minimal-review — approved

Verdict APPROVE — No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Nov 4, 2026
@coderabbitai
coderabbitai Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor
Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

The conflict scan now also runs `nft -a --debug=netlink list ruleset`,
and the installer harness's stand-in nft answered it with nothing, so
the staged conflict was never read. The stand-in serves the same
fixture for it, and the scan falls back to the plain listing when the
annotated one carries no table the plain one shows.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3
@agent-137
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
coderabbitai Bot commented Nov 4, 2026 •
Copy link
Copy Markdown
Contributor
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

minimal-review — approved

Verdict APPROVE — No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@mitodrummer
mitodrummer added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit fce331f Nov 5, 2026
37 checks passed
@mitodrummer
mitodrummer deleted the fix/1201-tailscale-ct-mark branch November 5, 2026 00:18
norrietaylor pushed a commit that referenced this pull request Nov 5, 2026
…ct scan (#3004)

* fix(classifier): decode masked ct-mark writes in the install's conflict scan

nft prints an iptables-nft CONNMARK --save-mark --nfmask M --ctmask M
through libxtables as `ct mark set mark and 0xM`, which drops the
--ctmask. The scan read it as a write of every bit and refused to
install over Tailscale's rp_filter workaround rules. The kernel keeps
every connection-mark bit outside --ctmask, so those rules change the
bits M alone.

The scan now decodes each `ct mark set` value for the bits it can
change: the libxtables save spelling changes M, a value read from the
ct mark changes the bits an `and` clears and an `or`/`xor` names, and
any other value replaces the whole mark. An `and` that clears the
install's bits is now a conflict too.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* test(minimald): pin the conflict scan's verdict on libxtables ct-mark saves

The mask 0x00030000 lies in Tailscale's well-known range, so the install
refused it before the conflict scan ran, and the assertion held whatever
the decoder did. The table now runs `ct mark set mark and 0xM` against
the default bits, over them and beside them, and each refusal must be
the conflict scan's.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* fix(classifier): decode every ct mark set on a rule line in the conflict scan

The scan decoded only the first `ct mark set` on a line and read later
ones for 0x values alone, so a whole-mark write after a masked one
(`ct mark set mark and 0xff0000 ct mark set meta mark`) passed. Each
statement is now decoded from where the previous value ended.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* fix(classifier): read xt compat CONNMARK masks from iptables-save

A rule iptables-nft wrote is an xt compat target in the kernel
(`[ target name CONNMARK rev 2 ]`), and `nft list` prints it through
libxtables, which drops a --ctmask or names no bits at all. The scan
now reads `nft -a --debug=netlink list ruleset`, finds each rule whose
kernel form carries an xt target by family, table, chain and handle,
and decodes it from the nf_tables iptables-save and ip6tables-save
listings instead of its nft text. A CONNMARK no listing covers is
refused, because the step cannot see which bits it keeps.

ip6tables-save is now read the way iptables-save is, for both
backends.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* fix(classifier): fall back to the plain listing when nft prints no netlink form

An nft that cannot run `nft -a --debug=netlink list ruleset` blocked
every install, though the plain listing had just been read. The scan
now uses the plain listing there, and finds an untranslated CONNMARK
by its `xt target CONNMARK` text, so one no iptables-save listing
covers is still refused.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

* test(install): serve the annotated ruleset listing from the stand-in nft

The conflict scan now also runs `nft -a --debug=netlink list ruleset`,
and the installer harness's stand-in nft answered it with nothing, so
the staged conflict was never read. The stand-in serves the same
fixture for it, and the scan falls back to the plain listing when the
annotated one carries no table the plain one shows.

Refs gominimal/inbox#1201

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3

---------

Co-authored-by: Karl Godard <karl@minimal.dev>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fce331f)

Ported to the 0.7 line without main's QUIC443 work, which the line doesn't
have: host_ruleset for kept_quic443_leaves, the nft -j list set stub branch
and the BEP-018 tests next to the new test helper.
agent-137 pushed a commit that referenced this pull request Nov 5, 2026
…sifier-race

Brings in #3004 (the Tailscale ct-mark decode in
install-host-classifier.sh), so the classifier step no longer refuses
on every Tailscale host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:approved minimal-review approved the current head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0