Skip to content

feat(network): wire ingress policy gate into publisher.rs - #621

Merged
toksdotdev merged 3 commits into
mainfrom
spec/network-policy-ingress
May 16, 2026
Merged

feat(network): wire ingress policy gate into publisher.rs#621
toksdotdev merged 3 commits into
mainfrom
spec/network-policy-ingress

Conversation

@toksdotdev

@toksdotdev toksdotdev commented Apr 27, 2026

Copy link
Copy Markdown
Member

last of four stacked PRs replacing #615. targets #620.

what's in this pr

every accepted tcp connection on a published port now runs through policy.evaluate_ingress(peer, guest_port, Protocol::Tcp) before being queued for the smoltcp accept loop. Action::Deny triggers SO_LINGER(0) via socket2::SockRef so the kernel sends a tcp rst — the peer sees ECONNRESET rather than a graceful close.

  • PortPublisher::new grew two parameters (Arc<NetworkPolicy>, Arc<SharedState>)
  • tcp_listener_task calls evaluate_ingress on each accept, drops with rst on deny
  • existing rejection paths in accept_inbound (max-inbound-reached, smoltcp-connect-failure) tightened to use the same zero-linger drop for consistency
  • socket2 added as a network-crate dep

asymmetric default (default_ingress = Allow with no implicit rules) preserves today's unfiltered published-port behavior. workspace tests stay green.

test plan

  • cargo test --workspace --lib
  • cargo nextest run -p microsandbox --tests --run-ignored=only host_access (kvm-gated; verifies ingress traffic still flows when allowed)
  • manual: --port 8080:8080 --net-rule "deny:ingress@any" rejects connections with ECONNRESET

@toksdotdev
toksdotdev requested a review from appcypher as a code owner April 27, 2026 01:27
@toksdotdev
toksdotdev force-pushed the spec/network-policy-cli branch from 5de97f4 to 3a7d231 Compare April 27, 2026 01:45
@toksdotdev
toksdotdev force-pushed the spec/network-policy-ingress branch from 133c231 to b3b1c79 Compare April 27, 2026 01:45
@toksdotdev
toksdotdev force-pushed the spec/network-policy-cli branch from 3a7d231 to fa179bd Compare April 27, 2026 01:54
@toksdotdev
toksdotdev force-pushed the spec/network-policy-ingress branch from b3b1c79 to 81dacf8 Compare April 27, 2026 01:54
@toksdotdev
toksdotdev force-pushed the spec/network-policy-cli branch from fa179bd to 58ae900 Compare April 27, 2026 02:29
@toksdotdev
toksdotdev force-pushed the spec/network-policy-ingress branch from 81dacf8 to 37ab367 Compare April 27, 2026 02:29
@toksdotdev
toksdotdev force-pushed the spec/network-policy-cli branch from 58ae900 to 743542f Compare April 27, 2026 02:35
@toksdotdev
toksdotdev force-pushed the spec/network-policy-ingress branch 2 times, most recently from 416bfa2 to eb75fa8 Compare April 27, 2026 08:11
@toksdotdev
toksdotdev force-pushed the spec/network-policy-cli branch from 743542f to 17ebbb4 Compare April 27, 2026 12:02
@toksdotdev
toksdotdev force-pushed the spec/network-policy-ingress branch from eb75fa8 to f85aa7a Compare April 27, 2026 12:02
@toksdotdev
toksdotdev force-pushed the spec/network-policy-cli branch from 17ebbb4 to 68f4f8a Compare April 27, 2026 12:46
@toksdotdev
toksdotdev force-pushed the spec/network-policy-ingress branch from f85aa7a to 50188d2 Compare April 27, 2026 12:46
Base automatically changed from spec/network-policy-cli to main April 27, 2026 14:16
@toksdotdev
toksdotdev force-pushed the spec/network-policy-ingress branch from bfce6d2 to a28fdac Compare April 27, 2026 15:56
@toksdotdev toksdotdev changed the title feat(network): wire ingress policy gate into publisher.rs (4/4) feat(network): wire ingress policy gate into publisher.rs Apr 27, 2026
toksdotdev added 3 commits May 6, 2026 21:51
every accepted tcp connection on a published port is now evaluated
against `network_policy.evaluate_ingress(peer, guest_port,
Protocol::Tcp)` before the listener queues it for the smoltcp accept
loop. on `Action::Deny`, the listener sets `SO_LINGER(0)` and drops
the stream so the kernel sends a tcp rst instead of the default fin
close. peers see `ECONNRESET` rather than a graceful close that looks
like the server simply went away. denials are logged at debug with
the peer address and guest port.

`PortPublisher::new` grew two parameters (`Arc<NetworkPolicy>`,
`Arc<SharedState>`) so the spawned `tcp_listener_task` can call into
the evaluator. the policy is evaluated against the freshly-accepted
peer's `SocketAddr`, the guest's listening port, and `Protocol::Tcp`;
the shared state carries domain cache + gateway ips that
`evaluate_ingress` reads through.

the existing rejection paths in `accept_inbound` (max-inbound-reached
and smoltcp-connect-failure) are also tightened to use the same
zero-linger drop, so all deliberate connection-rejection signaling
is uniform. the helper `reject_with_rst(&TcpStream)` centralizes the
linger-then-drop pattern via `socket2::SockRef` (tokio's
`TcpStream::set_linger` is deprecated; socket2 is the supported path
and the cast is zero-cost — it borrows the fd).

ingress traffic from sources matching a `Direction::Ingress` or
`Direction::Any` rule fires with first-match-wins; everything else
falls through to `default_ingress` (which is `Allow` by default to
preserve today's unfiltered published-port behavior, per the
asymmetric-defaults rationale in §Defaults).
when a proxy task's `TcpStream::connect` to the host-side upstream fails (e.g.
the guest dialed a port where nothing is listening), the smoltcp socket
currently closes cleanly with FIN. the guest sees "connection accepted then
closed" instead of "connection refused", and happy-eyeballs clients that
picked the wrong family stall rather than fall back.

track a per-connection `upstream_connected: Arc<AtomicBool>` that the proxy
task flips to `true` after a successful upstream connect. when the tracker
detects a dead proxy task with the flag still `false`, it aborts the smoltcp
socket (RST) instead of closing it (FIN). wired through the plain tcp proxy,
the tls bypass relay, and the tls intercept relay. dns / dot tasks skip the
flag (they never produce a guest-visible unreachable upstream).
splits the longer #[error(..)] format strings and the --no-net conflict
bail string across multiple source lines using rust's `\<newline>`
escape. the rendered messages are unchanged; the source is just easier
to read at the typical 100-column wrap.
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