Skip to content

Mirrored: 0x8007054f traced to HcnReserveGuestNetworkServicePortRange(4095) failing when WinNAT exclusions fragment the dynamic port range #41812

Open

Description

@sl3456

Windows Version

Windows 11 (retail channel), OS Build 10.0.26300.9457, fse.sys 10.0.26100.9278

WSL Version

WSL 3.0.2.0 (also reproduced with 3.0.1.0), kernel 6.18.40.1-1, WSL 2

Are you using WSL 1 or WSL 2?

WSL 2

Summary

networkingMode=Mirrored fails with CreateInstance/CreateVm/ConfigureNetworking/0x8007054f and falls back to networkingMode=None (no network at all in the guest). We traced the failure to a single host-side call: HcnReserveGuestNetworkServicePortRange cannot satisfy the hardcoded 4095-port contiguous reservation because WinNAT's runtime port exclusions fragment the host's dynamic port range. The failure is deterministic within a boot and varies across boots ("boot lottery"), which explains the intermittent reports on this error code.

Root cause analysis

1. Event log timeline (Microsoft-Windows-Host-Network-Service-Operational, events 1053/1054/1055) — the GuestNetworkService is created and torn down within ~23 ms, and HNS logs no error at all:

19:29:53.171 Create  GuestNetworkService (MirrorHostNetworking:true, SchemaVersion 2.0)
19:29:53.173 Modify  GuestNetworkService -> Bootstrapping                (succeeds)
19:29:53.174 Query   GuestNetworkService -> State:2, IsFseSelfManaged:true
19:29:53.197 Delete  GuestNetworkService                                <- teardown

2. Source path: In src/windows/service/exe/MirroredNetworking.cpp, Initialize() calls CreateGuestNetworkService() (the Create/Modify/Query triple above), then immediately:

m_ephemeralPortRange = m_guestNetworkService.AllocateEphemeralPortRange();

which in src/windows/service/exe/WslCoreGuestNetworkService.cpp does:

static constexpr auto c_ephemeralPortRangeSize = 4095;
THROW_IF_FAILED(m_allocatePortRange.value()(m_service.get(), c_ephemeralPortRangeSize, &m_reservedPortRange, &port));

The exception propagates, GuestNetworkService::Stop() deletes the GNS, and the whole mirrored configuration surfaces as an opaque 0x8007054f.

3. Direct API repro (PowerShell P/Invoke against computenetwork.dll, no WSL involved):

Add-Type @'
using System;
using System.Runtime.InteropServices;
public static class HcnTest {
    [DllImport("computenetwork.dll", CharSet = CharSet.Unicode)]
    public static extern int HcnCreateGuestNetworkService(ref Guid Id, string Settings, out IntPtr Service, out IntPtr Error);
    [DllImport("computenetwork.dll")]
    public static extern int HcnReserveGuestNetworkServicePortRange(IntPtr Service, uint PortCount, out uint StartPort, out uint EndPort, out IntPtr PortReservation);
    [DllImport("computenetwork.dll", CharSet = CharSet.Unicode)]
    public static extern int HcnDeleteGuestNetworkService(ref Guid Id, out IntPtr Error);
}
'@
$id = [Guid]::NewGuid()
$settings = '{"VirtualMachineId":"' + $id + '","MirrorHostNetworking":true,"SchemaVersion":{"Major":2,"Minor":0},"Flags":3}'
$svc = [IntPtr]::Zero; $err = [IntPtr]::Zero
[HcnTest]::HcnCreateGuestNetworkService([ref]$id, $settings, [ref]$svc, [ref]$err)   # -> 0x0
$s=[uint32]0;$e=[uint32]0;$h=[IntPtr]::Zero
[HcnTest]::HcnReserveGuestNetworkServicePortRange($svc, 4095, [ref]$s, [ref]$e, [ref]$h)  # -> 0x8007054F  <-- the exact error
[HcnTest]::HcnReserveGuestNetworkServicePortRange($svc, 2047, [ref]$s, [ref]$e, [ref]$h)  # -> 0x0 (succeeds)
[HcnTest]::HcnDeleteGuestNetworkService([ref]$id, [ref]$err)

On the affected host, 4095 ports always fails with 0x8007054f, smaller sizes succeed.

4. Why the port space is fragmented: the host's TCP dynamic port range had been reconfigured to 1024-60000 (traced to the legacy HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort = 60000 value) while UDP remained at the default 49152-65535. WinNAT reserves multiple random ~100-port blocks at each boot. With the TCP∩UDP intersection reduced to 49152-60000 (~10.8k ports), the exclusions left no contiguous 4095-port window:

$ netsh int ipv4 show excludedportrange protocol=udp   (fragmented state)
50000-50059, 51912-52411 (5 blocks), 53935-54034, 56206-56305
-> largest free contiguous window: 3695 < 4095

Since WinNAT re-randomizes its blocks every boot, mirrored networking works on some boots and fails on others — matching the "sometimes a reboot fixes it" pattern seen in #13454 / #13587 / #12470 / #41228.

Third-party drivers were ruled out on this host (stopping Sangfor/VMware/Hikvision network filter drivers changed nothing).

Fix / workaround (verified)

netsh int ipv4 set dynamicport tcp start=49152 num=16384
netsh int ipv6 set dynamicport tcp start=49152 num=16384
reg delete "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v MaxUserPort /f
net stop winnat && net start winnat
wsl --shutdown

After this, HcnReserveGuestNetworkServicePortRange(4095) returns S_OK and mirrored networking comes up (mirrored eth0, default route, DNS, outbound 200s).

Suggestions

  1. Surface an actionable error. 0x8007054f (ERROR_INTERNAL_ERROR) from a port-range allocation tells the user nothing, and HNS logs no failure event. A specific error/warning (e.g. "could not reserve 4095 contiguous ports in dynamic port range") would have saved days of forensics.
  2. Degrade gracefully. Retrying with a smaller range, or composing the reservation out of multiple smaller ranges, would keep mirrored networking functional instead of dropping to None.
  3. Consider a .wslconfig knob for the ephemeral port-range size.

Related issues

Same error-code cluster: #41228 (26300, closed unresolved), #41251 (26200, HypervisorPlatform case), #13454, #13587, #12470. #41398 is a different failure stage (VmSwitch veto 0xc0010022) but same FSE/GNS subsystem.

Activity

  1. github-actions commented on Oct 6, 2026

    @github-actions
    Contributor

    Logs are required for review from WSL team

    If this a feature request, please reply with '/feature'. If this is a question, reply with '/question'.
    Otherwise, please attach logs by following the instructions below, your issue will not be reviewed unless they are added. These logs will help us understand what is going on in your machine.

    How to collect WSL logs

    Download and execute collect-wsl-logs.ps1 in an administrative powershell prompt:

    Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/microsoft/WSL/master/diagnostics/collect-wsl-logs.ps1" -OutFile collect-wsl-logs.ps1
    Set-ExecutionPolicy Bypass -Scope Process -Force
    .\collect-wsl-logs.ps1
    

    The script will output the path of the log file once done.

    If this is a networking issue, please use .\collect-wsl-logs.ps1 -LogProfile networking instead, following the instructions in Collect WSL logs for networking issues

    Once completed please upload the output files to this GitHub issue.

    See Collect WSL logs (recommended method).

    If you choose to email these logs instead of attaching them to the bug, please send them to [email protected] with the GitHub issue number in the subject, and include a link to your GitHub issue comment in the message body, and reply with '/emailed-logs'.

  2. annis-souames commented on Oct 16, 2026

    @annis-souames

    Thanks a lot for this detailed report! I was struggling with this issue for MONTHS and restarting HNS solved the issues only 40-50% of the time.

    Thanks to this report, I tried resetting my TCP port range because it was abnormally large and the issue seems to have disappeared for now. Several issues have been raised about this, WSL seriously needs a patch for this.

  3. LegaSage commented on Oct 20, 2026

    @LegaSage

    Related data point from the other side of the same reservation: when it succeeds, the window it picks swallows ports host apps rely on, and nothing on the host can steer it.

    WSL 2.7.3.0, kernel 6.6.114.1-1, Windows 10.0.26300.9711, networkingMode=mirrored. Measured by bind-scanning 30000–49599 from the host and reading ip_local_port_range in the guest:

    Condition at WSL start Window reserved (guest ip_local_port_range)
    nothing held 44620–48715
    host listener on 48477 43255–47350
    host listener on 44620 37795–41890 (39160–43255 skipped; a host listener sat on 42050)
    WSL stopped none

    So it tries 44620–48715 first and steps down by 1365 past any window containing an in-use port. Inside the window a host bind() fails with EADDRINUSE. Nothing appears in netstat/Get-NetTCPConnection, and the range is not listed in netsh int ipv4 show excludedportrange.

    Two things an app developer can't work around:

    1. An administered exclusion does not keep a port out of the window. netsh int ipv4 add excludedportrange protocol=tcp startport=48477 numberofports=1 was in place, and 48477 was still reserved after wsl --shutdown and a restart.
    2. Setting net.ipv4.ip_local_port_range in the guest (/etc/sysctl.d) doesn't move the host-side reservation. The guest used the new range while the host still withheld 44620–48715, which also leaves the guest's ephemeral ports un-reserved on the host.

    Apps with a default port in 44620–48715 fail on every mirrored-mode host. We hit it on 48477; another project hit 48666 (vinzdg/codenotch#443). A .wslconfig setting for the reserved range (start, or start + size), or honouring administered exclusions, would fix both this and the fragmentation failure above.

  4. bkataru commented on Oct 29, 2026

    @bkataru

    Confirming the mechanism on a second host, with a test that controls for whether the dynamic-port setting is actually live.

    Environment: Windows 10.0.26200.9168, WSL 2.7.13.0, kernel 6.18.33.2-2, networkingMode=mirrored, single Wi-Fi uplink.

    Building on LegaSage's finding above (44620–48715 → 1365 step-down), I can reproduce all four of my observations on the same lattice: start ≡ 940 (mod 1365), 1365 = 4095/3, anchored at 56905 → 43255 (k=10), 29605 (k=20), 28240 (k=21), 21415 (k=26). Credit for the stride goes to that comment; the extra point here is that it holds across many boots on this host too.

    Controlled test (the part I think adds something). Earlier in my debugging I convinced myself the dynamic range was irrelevant, because mirrored succeeded with dynamicport tcp start=50000 num=1000. That conclusion was wrong — I had not verified the setting was live. Changing netsh int ipv4 set dynamicport does take effect immediately on this build (no reboot), which can be verified by opening outbound connections and reading the local ports:

    set dynamicport tcp start=20000 num=1000
    -> 12/12 new ephemeral local ports landed inside 20000-20999
    

    With the setting proven live, the results are unambiguous:

    Dynamic TCP range (verified live) Outcome
    1024 + 64511 (original) mirrored starts; reserves 43255–47350
    30000 + 21000 mirrored starts; reserves 35065–39160
    20000 + 1000 mirrored fails, no 4096-block reserved at all

    So the 4095-port reservation must fit inside the dynamic TCP range, exactly as the OP describes. My "contradiction" was measurement error, not a different mechanism — worth stating explicitly in case anyone else runs a similarly sloppy version of this test.

    Practical consequence: the failure is predictable before booting. If no 56905 - 1365k window (k = 0..40, size 4096) fits entirely inside [dynamicport start, dynamicport start + num), the next start will fail with 0x8007054f and fall back to networkingMode=None.

    One negative result, consistent with LegaSage's item 1: administering excludedportrange over the exact window WSL had just chosen did not move it. I could not separate "exclusions are ignored" from "exclusion needs a reboot to take effect", so I am not claiming the stronger version. Documenting whether administered exclusions are consulted would settle it.

    Finally, one observation that may be useful for triage: on this host no 0x8007054f appears in any Windows event log. HNS Admin logs every guest-network-service create/remove as Result code = '0x0' right through the failure; the HRESULT only surfaces on WSL's stderr. That matches the request in the Suggestions section for an actionable error.

  5. DBinK commented on Nov 9, 2026

    @DBinK

    Additional reproduction on WSL 3.0.3: a temporary UDP bind triggers the failure

    This is additional evidence for this issue, not a report of a regression in #41880. I have not tested a version containing that fix.

    Environment

    • Windows build: 10.0.26100.9550 (x64)
    • WSL: 3.0.3.0
    • Kernel: 6.18.54.1-1
    • Distribution: Ubuntu 26.04, WSL 2
    • Configuration: [wsl2] networkingMode=mirrored, dnsTunneling=true

    Observed failure

    Starting WSL with the relevant host port occupied produced:

    CreateInstance/CreateVm/ConfigureNetworking/0x8007054f
    Failed to configure network (networkingMode Mirrored), falling back to networkingMode None.
    

    wslinfo --networking-mode returned none, and Linux had only its loopback interface.

    Controlled reproduction and recovery

    On this host, binding UDP port 41641 on both IPv4 and IPv6 is sufficient to reproduce the failure. No traffic needs to be sent, and no adapter or security software needs to be disabled.

    1. Begin with working mirrored networking.
    2. Run the temporary UDP holder below in a Windows terminal.
    3. While it is holding the sockets, run wsl --shutdown, then start the distribution and query wslinfo --networking-mode.
    4. Observe error 0x8007054f and mode none.
    5. Let the holder exit, releasing the sockets.
    6. Run wsl --shutdown and start the distribution again.
    7. Observe mode mirrored; an outbound HTTPS request returns HTTP 200.
    # Windows Python; no dependencies. Can also run with uv run --no-project holder.py.
    import socket
    import time
    
    sockets = []
    try:
        for family, address in [
            (socket.AF_INET6, ("::", 41641)),
            (socket.AF_INET, ("0.0.0.0", 41641)),
        ]:
            sock = socket.socket(family, socket.SOCK_DGRAM)
            sockets.append(sock)
            if family == socket.AF_INET6:
                sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
            sock.bind(address)
        print("UDP 41641 bound on IPv4/IPv6 for 45 seconds", flush=True)
        time.sleep(45)
    finally:
        for sock in sockets:
            sock.close()

    This is host-state-dependent: port 41641 is not claimed to be a universal trigger. Other existing allocations on this machine determine whether this extra bind prevents the larger allocation. Keeping Tailscale running without this particular binding did not, by itself, reproduce the failure.

    Direct HCN API checks

    Separate probes using a temporary guest network service returned:

    Condition PortCount HRESULT Returned range
    Before releasing the original conflicting binding 4095 0x8007054F None
    Same condition 2047 0x00000000 Allocation succeeded
    After stopping Tailscale and confirming UDP 41641 was released 4095 0x00000000 40525–44620

    Reservation handles and temporary guest network services were released/deleted after the probes. One important limitation: a separate 4095-port probe also failed while an already-working mirrored setup could restart successfully, so an independent probe is not treated as a complete predictor of WSL startup.

    Relationship to the merged fix

    #41880, commit 48aefbb2b6bba28dba32187fd228c1e788207261, adds the smaller-range fallback and warnings. I verified that the 3.0.4 prerelease source contains it, but this machine remains on 3.0.3; the fix has not been validated here.

    The reversible UDP-bind reproduction above may be useful as additional evidence or a regression-test scenario. It also shows why this symptom should not automatically be attributed to a Tailscale driver defect: an ordinary Python UDP bind reproduced the same startup failure, and releasing it restored mirrored networking.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions