wslc: UDP port publishing rewrites reply source port, breaks connected UDP sockets (ENet/QUIC/games)
- Dominant language
- C++
- Stars
- 33.7k
- Forks
- 1.8k
- Avg merge
- 3d 17h
- Merged PRs (30d)
- 116
Description
## 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 :/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:
```python
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:
```powershell
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):
```python
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:`, 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.
Contributor guide
Research direction
Start by running the supplied udpecho.py reproduction with `wslc run -p 18888:18888/udp`, then compare the connected and unconnected client results. Trace the UDP publishing path responsible for reply source-port translation. Done means replies reach connected UDP sockets from 127.0.0.1:18888, matching the published port.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- networking, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100