Add fallback handling for non-DNS UDP traffic - #199
Open
Mygod wants to merge 2 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an optional UDP “fallback” path to the server’s UDP/53 listener so that structurally complete DNS datagrams continue through the normal DNS tunnel handling, while non-DNS datagrams can be forwarded to a configured upstream HOST:PORT with per-source sticky routing and bounded async forwarding.
Changes:
- Introduces
FALLBACK = "HOST:PORT"server config + validation, and updates docs across READMEs and the sample TOML. - Adds a new UDP fallback session manager with idle expiry, sticky routing, epoch-based DNS reply gating, and bounded upstream send queue.
- Adds strict DNS datagram parsing (
ParseDNSDatagramLite) used for routing decisions, plus extensive unit/integration tests for fallback behavior.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| server_config.toml.simple | Documents the optional FALLBACK setting and behavior notes. |
| README.MD | Adds FALLBACK documentation and clarifies packet sizing requirements with fallback. |
| README_ZH.MD | Adds Chinese documentation for FALLBACK. |
| README_RU.MD | Adds Russian documentation for FALLBACK. |
| README_IT.MD | Adds Italian documentation for FALLBACK. |
| README_FA.MD | Adds Persian documentation for FALLBACK. |
| README_ES.MD | Adds Spanish documentation for FALLBACK. |
| internal/udpserver/udp_fallback.go | Implements fallback routing state, per-peer sessions, bounded queues, expiry, and write barriers/epochs. |
| internal/udpserver/udp_fallback_test.go | Unit tests for fallback manager routing, epochs, cleanup, and backpressure/shutdown behavior. |
| internal/udpserver/server.go | Wires fallback into server startup/shutdown, forces single reader for ordered classification, and adds loop-prevention check. |
| internal/udpserver/server_runtime.go | Integrates fallback classification into ingress, adds epoch-gated DNS reply writes, and refactors parsing/handling paths. |
| internal/udpserver/server_ingress.go | Refactors ingress handling to accept a parsed packet + parse error and drops non-request datagrams. |
| internal/udpserver/server_ingress_test.go | Adds coverage ensuring non-request/structurally incomplete DNS-shaped datagrams are dropped. |
| internal/udpserver/server_fallback_test.go | Adds integration-style tests validating end-to-end demux behavior and loop-prevention logic. |
| internal/dnsparser/response.go | Splits message-vs-request header plausibility checks and tightens header validation. |
| internal/dnsparser/parser.go | Adds ParseDNSDatagramLite and strengthens name parsing (pointer validation, wire-length limits). |
| internal/dnsparser/parser_lite_test.go | Adds extensive tests for complete-message requirements and rejection cases. |
| internal/config/server.go | Adds FALLBACK to config schema, defaulting, and validation (HOST:PORT, port bounds, no unspecified IPs). |
| internal/config/server_test.go | Adds tests for CLI flag binding, config loading, and validation for FALLBACK. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+285
to
+291
| func (s *Server) safeHandlePacket(packet []byte) []byte { | ||
| parsed, parseErr, ok := s.safeParseDNSDatagram(packet) | ||
| if !ok { | ||
| return nil | ||
| } | ||
| return s.safeHandleParsedPacket(packet, parsed, parseErr) | ||
| } |
Comment on lines
+434
to
+437
| interfaceAddrs, err := net.InterfaceAddrs() | ||
| if err != nil { | ||
| return false | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
MasterDnsVPN normally handles DNS tunnel traffic on UDP port 53. Some deployments also need to expose other UDP protocols through that same public port, where binding a separate listener or opening another port may not be possible.
Fallback handling lets the existing listener demultiplex traffic: structurally valid DNS remains with MasterDnsVPN, while other UDP datagrams are forwarded to a configured service. This allows multiple protocols to share UDP port 53.
Summary
FALLBACK = "HOST:PORT"server setting for forwarding raw non-DNS UDP datagrams.Routing behavior
When fallback is enabled, a previously unknown non-DNS source is forwarded immediately and remains assigned to fallback. A source already classified as DNS switches after 16 consecutive non-DNS datagrams. A valid DNS datagram resets that streak.
Peer classification and fallback sessions expire after 180 seconds without activity.
Queued DNS replies are protected by per-peer routing epochs and a write barrier, preventing stale DNS responses from overtaking a transition into fallback mode.
When
FALLBACKis unset, existing server behavior is preserved.Validation
go test ./...go vet ./...go test -race ./internal/config ./internal/dnsparsergo test -race ./internal/udpserver -run 'Test(DNSWorkerFallback|DNSWorkerDropsQueuedReplyAfterFallbackTransition|DNSWorkerWithoutFallback|UDPFallback|FallbackTargetsListener)' -count=20