Skip to content

feat(egress): use native nftables for DNS and transparent redirects (gVisor support) [DO NOT MERGE YET]#1374

Draft
ferponse wants to merge 4 commits into
opensandbox-group:mainfrom
ferponse:feat/egress-native-nft-redirect
Draft

feat(egress): use native nftables for DNS and transparent redirects (gVisor support) [DO NOT MERGE YET]#1374
ferponse wants to merge 4 commits into
opensandbox-group:mainfrom
ferponse:feat/egress-native-nft-redirect

Conversation

@ferponse

Copy link
Copy Markdown
Contributor

⚠️ DO NOT MERGE YET. This PR depends on four upstream gVisor PRs that add the nftables features the new rulesets rely on. Until those land in gVisor and a runsc release ships with them, the native-nft redirect will not install under gVisor (GKE Sandbox). On runc and Kata the change works today. Please hold until the gVisor side is merged and released — see Dependency below.

Why

The egress sidecar programs its dataplane with nftables, but the two NAT redirects — the DNS redirect (→ local DNS proxy) and the transparent-HTTP MITM redirect — were still shelling out to iptables/iptables-nft. Under gVisor (runsc) the iptables nat table is unavailable (gvisor#170), so the egress sidecar could not run under GKE Sandbox at all. These two redirects were the last part of the egress that was not native nftables.

What

Rewrite both redirects to emit native nftables (a nat/output chain with redirect to :port), consolidated into pkg/nftables alongside the existing filter-policy manager. The egress dataplane is now 100% native nftables — no iptables/xt-compat dependency.

  • pkg/nftables/redirect.go — DNS redirect: OUTPUT :53 (udp/tcp) → :15353, with a meta mark 0x1 bypass for the proxy's own SO_MARK'd upstream queries and per-destination RETURN for the exempt nameserver list (ip + ip6).
  • pkg/nftables/transparent.go — MITM transparent redirect: loopback RETURN, then meta skuid != <mitm uid> tcp dport 80/443 redirect to :<port> (IPv4, mirroring the previous behavior).
  • pkg/nftables/nat_common.go — idempotent per-table cleanup helper.
  • Delete pkg/iptables/ (redirect + transparent + tests).
  • Dockerfile — drop the iptables package (keep nftables).
  • scripts/cleanup.sh — crash recovery now deletes the native-nft redirect tables (opensandbox_dns_redirect, opensandbox_transparent) instead of iptables rules; leaves the inet opensandbox filter table in place so filtering stays fail-closed across restarts.
  • Update call sites (main.go, shutdown.go, mitmproxy_transparent.go), comments and docs.

No behavior change on runc/Kata: the rendered rules are equivalent to the previous iptables REDIRECT rules.

Dependency: gVisor nftables support

The native-nft rulesets use nftables features runsc did not implement. These upstream gVisor PRs add them (tracking issue google/gvisor#13796, which also documents this OpenSandbox use case):

gVisor PR Feature the egress needs
google/gvisor#13797 meta mark load (SO_MARK bypass)
google/gvisor#13799 interval sets (CIDR allow/deny)
google/gvisor#13800 timeout sets (expiring dynamic allow)
google/gvisor#13801 redir / REDIRECT (this PR's DNS + MITM redirect)

This PR must not be merged until those are merged and a runsc release includes them.

Testing

  • go build ./..., go vet, gofmt — clean.
  • Full egress module test suite (go test ./...) — pass. New unit tests in pkg/nftables cover both redirect script builders, the missing-table cleanup path, and the apply-error path.
  • End-to-end under a patched runsc (built from the four gVisor PRs above, --TESTONLY-nftables): the exact rulesets this code emits install cleanly and nft list ruleset renders them correctly — redirect to :port, meta mark, meta skuid !=, and exact tcp dport all evaluate under gVisor's netstack.
rendered ruleset under runsc
table ip opensandbox_dns_redirect {
	chain output {
		type nat hook output priority -100; policy accept;
		udp dport 53 ip daddr 10.179.156.2 return
		tcp dport 53 ip daddr 10.179.156.2 return
		meta mark 0x00000001 udp dport 53 return
		meta mark 0x00000001 tcp dport 53 return
		udp dport 53 redirect to :15353
		tcp dport 53 redirect to :15353
	}
}
table ip opensandbox_transparent {
	chain output {
		type nat hook output priority -100; policy accept;
		ip daddr 127.0.0.0/8 return
		meta skuid != 1000 tcp dport 80 redirect to :8080
		meta skuid != 1000 tcp dport 443 redirect to :8080
	}
}
table ip6 opensandbox_dns_redirect { ... }

Replace the iptables/iptables-nft shell-outs for the DNS redirect and the
transparent-HTTP (MITM) redirect with native nftables rulesets, matching the
style of the existing filter-policy manager. The egress sidecar's dataplane is
now 100% native nftables, with no dependency on iptables/xt-compat.

- pkg/nftables/redirect.go: DNS redirect (nat/output, meta mark bypass,
  exempt-dst RETURN, redirect to :15353) in ip/ip6 tables.
- pkg/nftables/transparent.go: MITM redirect (loopback RETURN,
  meta skuid != <mitm uid>, tcp dport 80/443, redirect to :port).
- pkg/nftables/nat_common.go: idempotent table cleanup helper.
- Remove pkg/iptables (redirect.go, transparent.go and tests).
- Dockerfile: drop the iptables package (nftables only).
- scripts/cleanup.sh: tear down the native-nft redirect tables on crash
  recovery instead of iptables rules; leave the inet filter table in place.
- Update call sites, comments and docs.

This makes the egress sidecar compatible with gVisor (runsc), whose netstack
implements native nftables but not the iptables nat table. It depends on the
upstream gVisor nftables PRs (see the PR description) being merged first.
@github-actions github-actions Bot added component/egress size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jul 22, 2026
@ferponse
ferponse marked this pull request as draft July 22, 2026 17:04

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d7bcf72f92

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread components/egress/main.go
Comment thread components/egress/docs/mitmproxy-transparent.md
The egress sidecar no longer uses the iptables nat table (it programs its
dataplane with native nftables), so gVisor is no longer incompatible with
networkPolicy. Remove the now-dead runtime gate and its startup warning
instead of repurposing them:

- validators.py: remove ensure_egress_runtime_compatible and the
  _GVISOR_NAT_INCOMPATIBLE_RUNTIMES set (and its now-unused import).
- runtime_resolver.py: remove the gVisor egress-incompatibility warning.
- docker/k8s providers: drop the call sites and imports.
- tests: remove the runtime-compatibility test class.

Docs: update the canonical egress / secure-container / network-isolation
pages to describe the native-nftables dataplane and gVisor support on a
runsc build that includes native nftables (google/gvisor#13796).

Addresses the review on opensandbox-group#1374 (P1: server validator, P2: canonical docs).
@github-actions github-actions Bot added component/server documentation Improvements or additions to documentation labels Jul 22, 2026
@ferponse

Copy link
Copy Markdown
Contributor Author

Addressed the review:

P1 (validator) — The gVisor–egress incompatibility no longer exists: the egress sidecar's dataplane is now native nftables (no iptables nat table), which gVisor supports on a runsc build that includes the nftables features (google/gvisor#13796). Rather than repurpose the now-dead gate, I removed it:

  • ensure_egress_runtime_compatible and _GVISOR_NAT_INCOMPATIBLE_RUNTIMES removed from validators.py, along with its call sites in the Docker/K8s providers and the _warn_gvisor_egress_incompatibility startup warning in runtime_resolver.py.
  • Removed the corresponding TestEgressRuntimeCompatibility test class; test_validators.py still passes (76 tests), ruff clean.

P2 (docs) — Updated the canonical docs: docs/components/egress.md, docs/guides/secure-container.md (§"Egress Sidecar Incompatible with gVisor" + compatibility matrix), and docs/architecture/network-isolation.md. They now describe the native-nftables dataplane, drop the pkg/iptables reference, and state that gVisor support requires a runsc build that includes native nftables (google/gvisor#13796).

Still draft / DO NOT MERGE until the gVisor PRs land and a runsc release ships them.

@ferponse

Copy link
Copy Markdown
Contributor Author

@Pangjiping — would you mind sharing your opinion on this PR when you have a moment?

Quick summary of what it does:

  • Rewrites the egress sidecar's DNS redirect and transparent-HTTP (MITM) redirect to emit native nftables (a nat/output chain with redirect to :port) instead of shelling out to iptables/iptables-nft, so the egress dataplane is 100% native nftables and no longer depends on the iptables nat table.
  • This makes the egress sidecar compatible with gVisor (its netstack implements native nftables but not the iptables nat table), while remaining unchanged on runc and Kata.
  • It depends on four upstream gVisor PRs (tracking issue nftables: support an nftables-based egress-firewall sidecar under runsc (OpenSandbox) — use case + tracking PRs google/gvisor#13796) that add the nftables features the rulesets use, so it's kept as a draft / DO NOT MERGE until those land and a runsc release ships them.
  • The automated Codex review has been addressed (removed the now-obsolete server-side gVisor rejection and updated the canonical docs).

I'd especially value your view on two points:

  1. Whether consolidating the redirects into pkg/nftables (deleting pkg/iptables) is the direction you'd want.
  2. How you'd prefer to gate gVisor + networkPolicy at the API layer — allow it unconditionally (trusting the operator's runsc), or require an explicit opt-in flag (e.g. secure_runtime.gvisor_nftables_egress) so it fails closed on a runsc without native nftables support.

Thanks!

@Pangjiping Pangjiping self-assigned this Jul 23, 2026
@Pangjiping

Copy link
Copy Markdown
Collaborator

@Pangjiping — would you mind sharing your opinion on this PR when you have a moment?

Quick summary of what it does:

  • Rewrites the egress sidecar's DNS redirect and transparent-HTTP (MITM) redirect to emit native nftables (a nat/output chain with redirect to :port) instead of shelling out to iptables/iptables-nft, so the egress dataplane is 100% native nftables and no longer depends on the iptables nat table.
  • This makes the egress sidecar compatible with gVisor (its netstack implements native nftables but not the iptables nat table), while remaining unchanged on runc and Kata.
  • It depends on four upstream gVisor PRs (tracking issue nftables: support an nftables-based egress-firewall sidecar under runsc (OpenSandbox) — use case + tracking PRs google/gvisor#13796) that add the nftables features the rulesets use, so it's kept as a draft / DO NOT MERGE until those land and a runsc release ships them.
  • The automated Codex review has been addressed (removed the now-obsolete server-side gVisor rejection and updated the canonical docs).

I'd especially value your view on two points:

  1. Whether consolidating the redirects into pkg/nftables (deleting pkg/iptables) is the direction you'd want.
  2. How you'd prefer to gate gVisor + networkPolicy at the API layer — allow it unconditionally (trusting the operator's runsc), or require an explicit opt-in flag (e.g. secure_runtime.gvisor_nftables_egress) so it fails closed on a runsc without native nftables support.

Thanks!

Thanks, great jobs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/egress component/server documentation Improvements or additions to documentation size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants