System UDP reply writer copies client ECN into reconstructed replies
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 237
- Forks
- 228
- PR merge metrics
- No merged PRs in 30d
Description
Summary
The system UDP reply writer copies the initiating client packet's ECN field into reconstructed server-to-client packets. ECN describes a packet in its own direction; a client packet's ECN marking is not a valid source for a reply's ECN field.
The writer saves the initiating IP header and reuses it when constructing replies, without resetting its ECN field.
Affected version
- sing-tun
v0.9.0-beta.4, commitf4c1f3ae265b930460774295b8fe5c73596c81cf. - Dependency of sing-box
1.14.0, revision0b8995879f29a9b98ee027bc17b75e101445b238. - Affected code path: system IPv4 UDP reply writer.
I have not tested the current development branch.
Expected behavior
The reply writer must not obtain reply ECN from the saved client header. If the outbound path supplies no reply ECN metadata and does not implement ECN propagation, reconstruct replies as Not-ECT rather than copying the opposite direction's marking.
ECT(0)/ECT(1) indicate ECN capability for the packet carrying those bits. CE indicates congestion experienced by that packet. None of these values can be inferred for a server-to-client reply from the initiating client-to-server packet. See RFC 3168, section 5.
Actual behavior
Saved client IPv4 header: ECN = ECT(0)
Reply input: UDP payload, no reply ECN metadata
Reconstructed IPv4 reply: ECN = ECT(0), copied from the client header
The reply inherits the saved client header's ECN instead of reflecting the absence of ECN metadata on the reply path.
Source path
At commit f4c1f3ae265b930460774295b8fe5c73596c81cf:
-
preparePacketConnection saves the initiating IP/UDP header:
copy(headerCopy, packet[:headerLen]) -
The IPv4 reply writer retains that header for the UDP connection.
-
systemUDPPacketWriter4.preparePacket prepends it to each reply payload:
copy(buffer.ExtendHeader(len(w.header)), w.header) -
The writer updates addresses, ports, lengths and checksums, but does not reset the IPv4 ECN bits. Single-packet and batch reply paths use this preparation method.
For comparison, the mixed/gVisor UDP writer passes TOS: 0 when constructing its reply.
Proposed local regression test
This is a test outline, not an implemented or executed reproducer:
- Construct a valid IPv4/UDP client packet with ECN ECT(0).
- Use its header to initialize the system UDP reply writer through the same preparation path used for a new UDP NAT entry.
- Supply an arbitrary reply payload without reply ECN metadata.
- Generate the reply using
preparePacketand inspect the reconstructed IPv4 header. - Assert that the reply has ECN Not-ECT. The current header-copy implementation is expected to retain ECT(0), causing the assertion to fail.
Cover the shared preparation method and its single/batch callers. Include Not-ECT, ECT(0), ECT(1), and CE as input cases. These are proposed test cases, not results from an executed test suite.
Scope and requested fix
Please stop inheriting ECN from the client header when constructing system UDP replies. For a payload-only reply interface without ECN support, clear the ECN bits before calculating the IPv4 header checksum. Define DSCP handling separately rather than changing the entire TOS byte as an incidental part of this fix.
This report focuses on the IPv4 writer. The IPv6 saved-header writer deserves a separate traffic-class review. I have not implemented the regression test or validated a patch.
AI assistance disclosure
I used OpenAI GPT-6 Astra to help identify and diagnose this issue while investigating connection problems in an iOS app.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in stack_system.go with preparePacketConnection and systemUDPPacketWriter4.preparePacket, then trace the shared single- and batch-reply callers. Review the proposed IPv4 regression cases for Not-ECT, ECT(0), ECT(1), and CE inputs. Done means replies without ECN metadata are reconstructed as Not-ECT while DSCP handling remains separate and checksums are valid.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100