Environment
- WSL 2.9.3.0, kernel 6.18.35.2-1, WSLg 1.0.79
- wslc 2.9.3.0
- Windows 10.0.26200.8737 (Win 11)
- Tested with
python:3.11-slim and ubuntu:22.04, also seen with a real game server binary (HappinessMP)
What's happening
wslc run -p <port>:<port>/udp forwards inbound UDP fine. The container sees the client's real source port on the way in. The problem is on the way back: replies leave the container through wslc's proxy and come out on a different, random source port instead of the port that was published. A client using a connected UDP socket (connect() + send()/recv()) only accepts packets from the exact address:port it connected to, so it just times out waiting. Nothing looks wrong on the server side either. The container logs show the packet arriving and the reply being sent normally.
This breaks basically anything using UDP with a fixed reply port: ENet (used by HappinessMP, https://github.com/HappinessMP/enet), QUIC, DTLS, most game netcode. TCP publishing is fine, this is UDP-only.
Repro
udpecho.py, no deps:
import socket
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.bind(("::", 18888))
print("echo ready", flush=True)
while True:
d, a = s.recvfrom(2048)
print("recv from", a, flush=True)
s.sendto(b"ECHO:" + d, a)
Run it:
wslc pull python:3.11-slim
wslc run -d --name udpecho-test -p 18888:18888/udp -v "${PWD}\udpecho.py:/udpecho.py" python:3.11-slim python3 /udpecho.py
Client (connected socket, plus an unconnected one for comparison):
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(5)
s.connect(("127.0.0.1", 18888))
print("local port:", s.getsockname()[1])
s.send(b"hello")
try:
print("received:", s.recv(2048))
except socket.timeout:
print("TIMEOUT on connected socket")
s2 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s2.settimeout(5)
s2.bind(("0.0.0.0", 0))
s2.sendto(b"hello2", ("127.0.0.1", 18888))
try:
data, addr = s2.recvfrom(2048)
print("unconnected got reply from:", addr, data)
except socket.timeout:
print("TIMEOUT on unconnected socket too")
Result
Client:
local port: 53582
TIMEOUT on connected socket
unconnected got reply from: ('127.0.0.1', 53583) b'ECHO:hello2'
Container (wslc logs udpecho-test):
echo ready
recv from ('::ffff:169.254.73.254', 53582, 0, 0)
recv from ('::ffff:169.254.73.254', 49665, 0, 0)
So the container gets the right source port on the way in: 53582 and 49665, both match what the client actually used. But the reply to the first request comes back from port 53583. That's not the published port (18888) and not the port the client sent from either. The connected socket rejects it and the read times out.
Expected
Replies should appear to come from 127.0.0.1:<published port>, same as any normal port forward. I ran the identical repro against docker run -p 18888:18888/udp on the same machine (Docker engine 29.6.1) and there the connected socket got its reply correctly, from the published port.
Why it matters
If you're hosting a UDP game or voice server in a wslc container, connections from real clients just fail, silently, with nothing wrong showing up on either side. Server logs look clean the whole time. I ran into this trying to host a HappinessMP (GTA IV multiplayer mod) server, which uses ENet.
Workaround
Run the same image under Docker Desktop instead of wslc. UDP forwarding works correctly there.
Environment
python:3.11-slimandubuntu:22.04, also seen with a real game server binary (HappinessMP)What's happening
wslc run -p <port>:<port>/udpforwards inbound UDP fine. The container sees the client's real source port on the way in. The problem is on the way back: replies leave the container through wslc's proxy and come out on a different, random source port instead of the port that was published. A client using a connected UDP socket (connect()+send()/recv()) only accepts packets from the exact address:port it connected to, so it just times out waiting. Nothing looks wrong on the server side either. The container logs show the packet arriving and the reply being sent normally.This breaks basically anything using UDP with a fixed reply port: ENet (used by HappinessMP, https://github.com/HappinessMP/enet), QUIC, DTLS, most game netcode. TCP publishing is fine, this is UDP-only.
Repro
udpecho.py, no deps:Run it:
Client (connected socket, plus an unconnected one for comparison):
Result
Client:
Container (
wslc logs udpecho-test):So the container gets the right source port on the way in: 53582 and 49665, both match what the client actually used. But the reply to the first request comes back from port 53583. That's not the published port (18888) and not the port the client sent from either. The connected socket rejects it and the read times out.
Expected
Replies should appear to come from
127.0.0.1:<published port>, same as any normal port forward. I ran the identical repro againstdocker run -p 18888:18888/udpon the same machine (Docker engine 29.6.1) and there the connected socket got its reply correctly, from the published port.Why it matters
If you're hosting a UDP game or voice server in a wslc container, connections from real clients just fail, silently, with nothing wrong showing up on either side. Server logs look clean the whole time. I ran into this trying to host a HappinessMP (GTA IV multiplayer mod) server, which uses ENet.
Workaround
Run the same image under Docker Desktop instead of wslc. UDP forwarding works correctly there.