feat(net): add network allowlist with DNS sinkhole filtering#410
Merged
Conversation
5 tasks
5a9a3ea to
847a458
Compare
Rename NetworkSpec: Isolated → Enabled{allow_net}/Disabled.
Add DNS sinkhole: blocked hosts resolve to 0.0.0.0 via gvproxy DNS zones.
- NetworkSpec::Enabled{allow_net: []} — full access (default)
- NetworkSpec::Enabled{allow_net: ["host"]} — DNS sinkhole for unlisted hosts
- NetworkSpec::Disabled — no network (no gvproxy)
Changes:
- Rust: NetworkSpec enum, allow_net plumbing through config chain
- Go: dns_filter.go with AllowNetMatcher + buildAllowNetDNSZones
- SDKs: Python/Node accept "enabled"/"disabled" strings
- Server/OpenAPI: updated test + spec
No proxy crate in this PR (TCP-level filtering in follow-up).
847a458 to
7e66c22
Compare
Three fixes for the network allowlist feature: 1. DNS sinkhole: per-TLD zones now include DefaultIP 0.0.0.0 so blocked hosts resolve to 0.0.0.0 instead of NXDOMAIN (industry standard per Pi-hole/AdGuard — prevents DNS fallback bypass in browsers). 2. NetworkSpec::Disabled: use dead socket trick to prevent libkrun's TSI from auto-enabling. Creates a non-functional virtio-net interface via UnixStream::pair() so TSI sees non-empty net.list and stays disabled. 3. Guest init: skip network config when NetworkSpec::Disabled (send network: None to guest). Use constants instead of hardcoded IPs. Also exposes allow_net parameter in Python SDK.
DorianZheng
added a commit
that referenced
this pull request
Mar 28, 2026
Complements the DNS sinkhole from #410 with transport-layer filtering. Guests can no longer bypass the allowlist by connecting to raw IPs. For port 443: extracts hostname from TLS ClientHello SNI extension. For port 80: extracts hostname from HTTP Host header. Other ports: filters by IP/CIDR rules only. Zero overhead when no allowlist is configured (filter=nil). Uses bufio.Reader.Peek() + crypto/tls.Server.GetConfigForClient for non-consuming SNI extraction (same technique as inet.af/tcpproxy). Overrides gvproxy's TCP handler via reflect to inject the filter after virtualnetwork.New() — guarded by structural test. Go: 25 unit tests (filter, SNI peek, structural guard) Rust: 2 new ignored VM integration tests Python: 21 end-to-end integration tests across 7 categories Also fixes pre-existing boxlite-server test that used wrong serde format for network field (JSON object instead of string).
5 tasks
DorianZheng
added a commit
that referenced
this pull request
Mar 28, 2026
…#411) Complements the DNS sinkhole from #410 with transport-layer filtering. Guests can no longer bypass the allowlist by connecting to raw IPs. For port 443: extracts hostname from TLS ClientHello SNI extension. For port 80: extracts hostname from HTTP Host header. Other ports: filters by IP/CIDR rules only. Zero overhead when no allowlist is configured (filter=nil). Uses bufio.Reader.Peek() + crypto/tls.Server.GetConfigForClient for non-consuming SNI extraction (same technique as inet.af/tcpproxy). Overrides gvproxy's TCP handler via reflect to inject the filter after virtualnetwork.New() — guarded by structural test. Go: 25 unit tests (filter, SNI peek, structural guard) Rust: 2 new ignored VM integration tests Python: 21 end-to-end integration tests across 7 categories Also fixes pre-existing boxlite-server test that used wrong serde format for network field (JSON object instead of string).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rename
NetworkSpecenum and add DNS sinkhole for outbound network filtering:NetworkSpec::Enabled { allow_net: [] }— full internet access (default)NetworkSpec::Enabled { allow_net: ["api.openai.com"] }— only listed hosts reachable via DNSNetworkSpec::Disabled— no network (gvproxy not started)DNS sinkhole: blocked hostnames resolve to
0.0.0.0via gvproxy DNS zones. Direct IP connections are not filtered (TCP-level filtering in follow-up PR).Changes
NetworkSpecenum withEnabled{allow_net}/Disabled, plumbing through config chaindns_filter.gowithAllowNetMatcher+buildAllowNetDNSZones(11 Go tests)"enabled"/"disabled"stringsNot included (follow-up PRs)
Test plan
Enabled { allow_net: [] }= full access)