Skip to content

[WSLC] Proposal: Built-in proxy for networking - #1625

Open
Elliot (theelliotm) wants to merge 9 commits into
mainfrom
user/emichlin/investigate-wslc-builtin-proxy
Open

Elliot (theelliotm) wants to merge 9 commits into
mainfrom
user/emichlin/investigate-wslc-builtin-proxy

Conversation

@theelliotm

@theelliotm Elliot (theelliotm) commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

WSLC networking today supports only all-allow (BRIDGED) or all-deny (NONE). This design adds a per-container controller container that enforces TCP CIDR and port rules, and lets egress, ingress and hostLoopback defaults be set independently. It uses the existing network fields.

  • README: docs/development/architecture/designs/1462-wslc-builtin-proxy/README.md
  • API Doc: docs/development/architecture/designs/1462-wslc-builtin-proxy/api.md
  • Related Issue: [WSLC] In-built proxy for networking #1462
Microsoft Reviewers: Open in CodeFlow

@theelliotm Elliot (theelliotm) self-assigned this Oct 30, 2026
Copilot AI balanced review requested due to automatic review settings October 30, 2026 22:04
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@theelliotm
Elliot (theelliotm) marked this pull request as ready for review October 30, 2026 22:04
@theelliotm
Elliot (theelliotm) requested a review from a team October 30, 2026 22:04
@theelliotm
Elliot (theelliotm) requested a review from a team as a code owner October 30, 2026 22:04

Copilot AI 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.

🟡 Changes recommended

The design leaves unresolved DNS policy enforcement gaps, Windows loopback port collisions, and crash recovery that cannot reclaim networks while orphan workloads remain attached.

3 open issues
What changed in this PR

This documentation-only PR proposes a per-container WSLC networking controller for #1462, extending today’s all-or-nothing networking with TCP CIDR and port filtering through existing network fields.

Changes:

  • Defines proxy selection, host-loopback access, port relaying, and custom-proxy behavior.
  • Describes controller lifecycle, cleanup, prerequisites, staged delivery, and planned validation.
  • Provides Rust, .NET, and Node usage examples, compatibility notes, and open API questions.
File Description
docs/​development/​architecture/​designs/​1462-wslc-builtin-proxy/​README.md Proposes controller architecture, networking behavior, lifecycle management, and validation.
docs/​development/​architecture/​designs/​1462-wslc-builtin-proxy/​api.md Documents SDK usage, request mapping, compatibility, and planned SDK tests.

Review effort: Balanced

@bbonaby Branden Bonaby (bbonaby) added the Feature-Spec-In-Review Label for feature spec PRs that are currently in review label Oct 30, 2026
Copilot AI balanced review requested due to automatic review settings October 31, 2026 00:57

Copilot AI 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.

🟡 Changes recommended

The design leaves HTTPS upstream TLS handling and controller proxy-variable injection for subsequent execs unresolved, risking failures for otherwise permitted HTTP(S) connections.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Low severity Inject retained proxy settings into every proxied exec

docs/​development/​architecture/​designs/​1462-wslc-builtin-proxy/​README.md:236

Lifecycle exec cannot remain unchanged: state_aware.rs::exec_config injects proxy variables only from the current exec request, and the daemon passes that environment to the new process. After provisioning a CIDR-filtered container, an exec without networkProxy therefore lacks the controller proxy variables, so even permitted HTTP(S) connections fail on the internal-only network. Specify controller endpoint retention and proxy-variable injection for every proxied exec, including replacement environments, and validate an exec without an explicit proxy setting.

3 resolved since last review

Review effort: Balanced

- **Implementation PRs:** Not started.

WSLC networking today supports only all-allow (`BRIDGED`) or all-deny (`NONE`).
This design adds a per-container controller container that enforces TCP CIDR

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.

Per-container controller container or a per vm controller container that enforces traffic rules per-container across all containers in the vm?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Per-container controller container. Adding networks to a live controller container isn't currently possible - it can only be done during creation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

to confirm, is the controller container a seperate wsl container we spin up? or is it another executable the we'll add to mxc? just want to make sure I understand what controller means here is all.


- With this feature, configs with CIDR rules, mixed defaults, egress deny plus `networkProxy`, or
all-deny plus port mappings automatically use a built-in controller. __Configs
that are valid today don't change.__

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.

we discussed that the udp/icmp behavior will change?

@theelliotm Elliot (theelliotm) Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

UDP/ICMP behavior will only change for configs that modify themselves in such a way that traffic gets proxied. allow-all or deny-all configs remain unaffected.

@theelliotm Elliot (theelliotm) Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The only significant change for unmodified configs is that hostLoopback will actually work now (as a result of the SDK update).

Copilot AI balanced review requested due to automatic review settings November 2, 2026 21:57

Copilot AI 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.

🟡 Changes recommended

Unresolved proxy rules would break supported BRIDGED SDK callers, risk exposing inherited credentials in replacement environments, and leave mixed-default all-TCP access inconsistent.

2 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Reject proxied mode with default-allow egress and denied ingress

docs/​development/​architecture/​designs/​1462-wslc-builtin-proxy/​README.md:210

[P2] With egress.default: allow, denied ingress/host loopback, and networkProxy, the mode table selects Proxied, but the custom-proxy path permits only the proxy endpoint rather than all TCP. Reject this combination in proxied mode, as the linked Bubblewrap guide does, or define how all-TCP access is preserved. Keep the existing all-allow BRIDGED cooperative-proxy case unchanged and add the mixed-default combination to the validation plan.

1 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 2, 2026 22:34

Copilot AI 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.

🔵 Needs a closer look

Human assessment is needed to evaluate the design’s attribution of host-loopback access to the SDK upgrade rather than MXC’s deliberate per-session enablement.

No open issues 🎉

2 resolved since last review

Review effort: Balanced


- **Example 2: Windows loopback service plus an inbound port.** All egress
denied, `hostLoopback: allow`, and `wslc.portMappings`
`[{ "windowsPort": 18080, "containerPort": 8080, "protocol": "tcp" }]`.

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.

please include the full schema for readability.

@theelliotm

Copy link
Copy Markdown
Contributor Author

Tracking microsoft/WSL#42161, which adds the ability to connect/disconnect networks to running containers. Would allow one container to manage the proxy for everything.

Comment on lines +144 to +148
- **SDK differences:** one existing difference matters here. Rust and .NET
provision requests carry `NetworkPolicy.runtime_config` / `RuntimeConfig`;
Node's provision `DirectionalNetworkConfig` has none. WSLC rejects a proxy
at provision today. The resolution is design spec
[open question 1](README.md#open-questions).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: doesn't node have it right here?

export interface NetworkPolicy extends DirectionalNetworkConfig {

Comment on lines +192 to +197
1. **Discovery capability:** should `BackendCapability` gain a value for WSLC
egress rules or host loopback, so callers can detect support before
running? Without one, unsupported hosts fail at run time with
`backend_unavailable`. Adding a value is non-breaking: the enum is
`#[non_exhaustive]` in Rust and has an `Unknown` value in .NET. Owners:
`@theelliotm` and SDK maintainers.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: Any idea what you want to call it? wslcProxyAvailable? or reusng an existing name but for wslc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'd have to take a look at the full list, but I imagine we can re-use existing names. The built-in proxy functionality should feel plug-and-play.

Comment on lines +192 to +198
- **Proxy URL with a hostname:** the workload's proxy URL is passed through
unchanged. MXC resolves the name on Windows before creating the controller
and passes the first IPv4 answer in the controller's policy. The
controller's DNS answers only that name, with the controller's own address,
and the controller relays the proxy's port byte-for-byte to the resolved
IP. It never resolves the name itself. TLS to an `https://` proxy therefore
runs end to end and the certificate is checked against the proxy's name.

@bbonaby Branden Bonaby (bbonaby) Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: Not sure if you can get around this but when you start accepting host names you have to start thinking about DNS changes. e.g When we resolve the name on windows, the address can change right after. I assume we don't do any refreshing or anything like that right? Just a one time thing?

Comment on lines +200 to +203
- **Proxy URL with an IP or loopback address:** the workload can't reach
that address directly, so MXC replaces the URL's host with the controller's
address. A certificate can't match the replaced host, so these URLs must
use `http://`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: probably want to reject random ip addresses, but can it not get to localhost at all?

Comment on lines +652 to +661
7. **`egress.default: allow` plus `networkProxy` in proxied mode:** a denied
`ingress` or `hostLoopback` selects proxied mode, where the controller
forwards only to the custom proxy. `allow` and `deny` then behave the same.
Potential options:
- Reject it, like Bubblewrap. `deny` plus `networkProxy` gives the same
result.
- Accept it and document that `allow` has no effect with a custom proxy.

`BRIDGED` with everything allowed plus the cooperative proxy is unchanged
either way.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: probably just reject it for now if it has no effect and then document it

Comment on lines +639 to +648
- **Ownership record:** before creating them, MXC writes the names to a
file under `%LOCALAPPDATA%\mxc` and cleanup deletes only those names. A
lost record leaves leftovers in place but never deletes another tool's
resources.
- **Exact names:** derive every name from one MXC ID (`mxc-<id>` for the
workload via `WslcSetContainerSettingsName`, plus `-ctl`, `-int` and
`-out`), list networks with `WslcListSessionNetworks`, and delete only
full-pattern matches. Unlikely to collide, but not ruled out.
- **Default stores only:** skip cleanup when `wslc.storagePath` is set.
- Ask WSLC for SDK support for listing containers.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thought: we should ask them for a listing API for sure. But in the meantime, we probably can't do the owership thing since multiple apps could be using mxc at the same time. For the exact name, I suppose we could do that and add our own guid at the end? Assuming WslcSetContainerSettingsName can allow duplicates.

If the app crashes though, then I don't think we can do anything since all our internal state will be lost. Probably will need to document.

Comment on lines +625 to +633
4. **DNS and deny rules:** a deny rule covering UDP 53 is bypassed: the
controller's DNS lookups go through Docker's resolver (`127.0.0.11`), WSL
and Windows, which picks the server per name.
Potential options:
- Query Windows' DNS servers directly, checking each IP against the rules
first (matches LXC). Stale after network changes; VPN DNS reachability
from WSLC is unverified.
- Turn DNS off when any deny rule covers UDP 53 (fail closed; over-blocks).
- Document DNS as outside egress rules.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thought: I don't think we can do anything about this until we get a general solution from the Networking team tbh. Process container has the same issue with dns bypass.

Comment on lines +606 to +614
1. **Provision `networkProxy`:** the controller is created at provision, so
it needs the proxy then. Today WSLC accepts the proxy only at exec
(`validate_provision_policy` rejects it), and the MXC request JSON
provision contract has no `runtimeConfig`. Rust and .NET typed provision
requests carry it; Node's doesn't. Potential options:
- Add `runtimeConfig` to the development provision contract and Node's
provision request.
- Support custom proxies only for create-and-run, and reject them for
lifecycle containers.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: see comment above, I thought we already had this in place already

Comment on lines +622 to +623
3. **Controller image distribution:** a bundled tar or a registry, and how is
it signed? Owners: build maintainers.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thought: users should definitely bring their own imo that we use. Or it at least shouldn't be offered by us.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The main question isn't if someone should bring their own, the main question is, do we load the filtering script onto the container at runtime (e.g. using stock alpine), or do we bake it in on top of the base image and sign it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Could be a feature request to add the option for a "custom proxy image".

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

when you say "filtering script" what's that? Checked the docs in this PR but I might be missing where it's talked about

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The controller container will have the filtering script inside of it. The networking policy is converted into environment variables that are set on the controller container. The filtering script reads those env vars and creates network rules to follow the policy.

Comment on lines +618 to +620
2. **Host loopback on older runtimes:** Currently valid `BRIDGED` contracts running
on WSL 3.0.3 will NOT get host loopback while those contracts running on 3.0.5 will.
Is this acceptable?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thought: to me it is. We have a remediation field in our MxcError object that we can say "proxy support is only available on wsl 3.0.3 or higher"

- **Implementation PRs:** Not started.

WSLC networking today supports only all-allow (`BRIDGED`) or all-deny (`NONE`).
This design adds a per-container controller container that enforces TCP CIDR

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

to confirm, is the controller container a seperate wsl container we spin up? or is it another executable the we'll add to mxc? just want to make sure I understand what controller means here is all.

- **Cost:** one extra container and two networks per container, plus startup
time.

## Open questions

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

note: lets see if we can update the doc to remove any answered questions before check in. I still had a question about what the controller is and whether it's a wslc concept or we're creating it ourselves.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The controller is an image we build from a Dockerfile during CI and ship with the binaries. The image will include the filtering script and the packages it needs. At build time, the hash of the image will be embedded into wxc-exec and the daemon. At runtime, MXC will only use the controller image with that hash.

Copilot AI 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.

🟡 Changes recommended

Expanding Node v1’s closed BackendCapability union breaks compilation for existing consumers that rely on exhaustive mappings or switches.

1 open issue

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 7, 2026 00:59

Copilot AI 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.

🟡 Changes recommended

Adding a member to Node V1’s closed BackendCapability union breaks source compatibility for existing callers using exhaustive switches or complete capability-keyed records.

2 open issues

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings November 9, 2026 21:58

Copilot AI 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.

🔵 Needs a closer look

Human assessment is needed because crash recovery could mistake a pending reservation for confirmed ownership and force-delete another tool’s resource in shared storage.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

This branch has not been deployed

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

Labels

Feature-Spec-In-Review Label for feature spec PRs that are currently in review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants