Understanding Session Timeout and Stream Lifecycle Management in MasterDnsVPN
TLDR: MasterDnsVPN implements a sophisticated session and stream model in internal/udpserver/session.go that guarantees reliable UDP-tunnel delivery while automatically expiring inactive resources using per-session activity timestamps and configurable idle thresholds.
The masterking32/MasterDnsVPN repository provides a high-performance UDP tunneling solution that requires precise resource management to handle thousands of concurrent virtual streams. The system couples a session store with automatic garbage collection to ensure that idle sessions and terminated streams do not exhaust server memory. This article examines the lifecycle management logic, timeout configurations, and runtime integration patterns that keep the VPN server responsive.
Session Lifecycle Management
The session lifecycle in MasterDnsVPN follows a strict state machine from initialization through cleanup. Each phase is designed to prevent resource leaks while allowing legitimate clients to reconnect during brief network interruptions.
Session Creation and Reuse Windows
When a client initiates a connection, the server invokes sessionStore.findOrCreate in internal/udpserver/session.go to validate the payload and generate a fresh cookie. This function constructs a sessionRecord that stores MTU limits, compression settings, and a reuse-until deadline (sessionInitTTL, default 10 minutes) as seen in lines 71-75.
During this reuse window, a new client can reclaim the same session ID if the original client disconnects temporarily, preventing premature session expiration during network handoffs.
// Example: creating a session store with custom time‑outs
store := newSessionStore(
orphanQueueCap = 16,
streamQueueCap = 64,
30*time.Minute, // sessionInitTTL – reuse window
15*time.Minute, // recentlyClosedTTL
)
Activity Tracking and Idle Detection
Every successful packet receipt updates the session's lastActivityUnixNano field via record.setLastActivity (lines 15-22). The server uses this nanosecond-precision timestamp to detect idle sessions through the periodic sessionStore.Cleanup method.
The cleanup routine, starting at line 90, iterates through active sessions and removes any record whose last activity exceeds the configured idle-timeout (default 15 seconds). Removed sessions transition to a recent-closed map, allowing the server to identify and properly reject "ghost" packets from recently expired sessions rather than treating them as new connection attempts.
Explicit Closure and Reuse Sweeping
When the server terminates a session explicitly—whether due to shutdown signals, protocol errors, or administrative actions—it executes the closure logic in lines 44-68. This marks the record as closed, removes it from active lookup tables, and optionally stores a closed-session entry if a retention period is requested.
Simultaneously, the expireReuseLocked function (lines 37-58) runs to clear expired entries from the reuse window, ensuring that session IDs cannot be reclaimed indefinitely after the initial 10-minute period expires.
Stream Lifecycle Management
Within each session, MasterDnsVPN multiplexes multiple virtual streams identified by 16-bit stream IDs. The stream manager integrates with the ARQ (Automatic Repeat Request) transport layer in internal/udpserver/stream_server.go to provide reliable delivery semantics.
Virtual Stream Creation and Scheduling
New streams materialize through sessionRecord.getOrCreateStream (starting at line 58). This method validates the 16-bit stream ID and enforces the per-session limit (MaxActiveStreamsPerSession, default 1000).
Created streams append to a sorted ActiveStreams slice (lines 83-100), enabling round-robin scheduling across concurrent flows. When a packet arrives for an unknown stream ID within an active session, the system automatically instantiates the corresponding Stream_server object and binds it to the ARQ state machine.
// Example: creating a new stream inside an existing session
func newDataStream(sess *sessionRecord, streamID uint16) *Stream_server {
cfg := arq.Config{IsVirtual: false, MaxPacketSize: 1400}
// `nil` means the stream talks directly to the client (no local net.Conn)
stream := sess.getOrCreateStream(streamID, cfg, nil, nil)
// The returned stream can now be used with ARQ methods:
stream.ARQ.Send([]byte("hello"))
return stream
}
Graceful Termination and TIME_WAIT States
Stream closure occurs through the onStreamClosed callback (line 43), which handles normal termination, errors, or STREAM_CLOSE control packets. This callback removes the stream from the active map and records the closure in a RecentlyClosed cache to prevent immediate ID reuse.
Streams entering the ARQ TIME_WAIT state remain in memory for a configurable retention period. The cleanupTerminalStreams function (lines 124-170) periodically purges these terminated streams, aborting underlying ARQ sessions and freeing associated buffers. This prevents memory exhaustion from accumulated dead streams while allowing final ACK packets to process correctly.
// Example: handling a stream‑close notification
func (s *sessionRecord) onStreamClosed(streamID uint16, now time.Time, reason string) {
// Clean up internal bookkeeping
s.removeStream(streamID, now, shouldSuppressServerOrphanForCloseReason(reason))
// Optional: propagate the close to higher layers
if s.streamCleanup != nil {
s.streamCleanup(s.ID, streamID)
}
}
Recently Closed Cache and Orphan Handling
To avoid protocol errors from delayed packets, closed streams enter a RecentlyClosed cache with a TTL of 10 minutes and capacity limit of 2000 entries (configured in newSessionStore at lines 27-28). The pruneRecentlyClosed logic (lines 90-110) evicts stale entries to maintain bounded memory usage.
When a stream closes, pending packets may still arrive from the client. These are queued in the per-session OrphanQueue (a multi-level priority queue). The enqueueOrphanReset helper (lines 126-146) injects high-priority reset packets that notify the client to clean up its local state without waiting for network timeouts.
Configurable Timeout Parameters
MasterDnsVPN exposes several timeout configurations that govern resource lifetimes. These values are hard-coded or passed through the initialization chain in internal/udpserver/session.go and server_runtime.go:
sessionInitTTL: 10 minutes (hard-coded innewSessionStore, lines 25-27) — Duration allowing session ID reuse after initial creation.idleTimeout: 15 seconds (fallback inserver_runtime.goif unset) — Threshold for cleaning inactive sessions, passed tosessionStore.Cleanup(lines 165-169 inserver_runtime.go).recentlyClosedTTL/recentlyClosedCap: 10 minutes / 2000 entries — Governs the stream ID reuse prevention cache.deferredConnectAttemptTimeout: 8 seconds (capped insocks5_upstream.go, lines 26-33) — SOCKS upstream connection timeout before stream creation.DNSUpstreamTimeout: 4 seconds (fallback indns_tunnel.go, lines 260-267) — Timeout for DNS resolver queries tunneled through the session.
Server Runtime Integration
The server entry point in cmd/server/main.go constructs the sessionStore and wires periodic maintenance through the serverRuntime loop. The runtime invokes three critical housekeeping methods on a configurable ticker interval:
// Periodic housekeeping (e.g. called from a ticker)
func housekeeping() {
now := time.Now()
idleTimeout := 20 * time.Second // expire idle sessions after 20 s
closedRetention := 5 * time.Minute // keep closed‑session entries for 5 min
// Remove dead sessions and streams
store.Cleanup(now, idleTimeout, closedRetention)
store.SweepTerminalStreams(now, 2*time.Minute)
store.SweepRecentlyClosedStreams(now)
}
These calls keep in-memory tables bounded and guarantee that stale resources reclaim without impacting active traffic. The serverRuntime passes context-aware timeouts down to the storage layer, allowing dynamic adjustment based on server load.
Summary
- Session lifecycle spans creation with 10-minute reuse windows, activity tracking via nanosecond timestamps, and automatic cleanup after 15 seconds of idle time.
- Stream management enforces a hard limit of 1000 active streams per session, utilizes round-robin scheduling, and retains terminated streams in TIME_WAIT states until explicitly purged.
- Memory safety relies on the
RecentlyClosedcache (10-minute TTL, 2000-entry cap) and orphan queues to handle delayed packets without protocol errors. - Runtime integration requires explicit invocation of
Cleanup,SweepTerminalStreams, andSweepRecentlyClosedStreamsfrom the server main loop to prevent resource exhaustion. - Timeout configurations are centralized in
session.goinitialization andserver_runtime.go, with specialized overrides for SOCKS and DNS upstream operations.
Frequently Asked Questions
How does MasterDnsVPN detect and clean up idle sessions?
The server tracks session activity through the lastActivityUnixNano field updated on every packet receipt via record.setLastActivity. The sessionStore.Cleanup method (line 90 in internal/udpserver/session.go) runs periodically to remove sessions idle longer than the configured threshold (default 15 seconds), moving them to a closed-session cache to handle late-arriving packets gracefully.
What prevents immediate reuse of a stream ID after closure?
Each closed stream enters a RecentlyClosed cache with a 10-minute TTL and 2000-entry capacity limit as implemented in newSessionStore (lines 27-28). The pruneRecentlyClosed function prevents the server from accepting new data on a recently terminated stream ID, avoiding confusion in the ARQ sequence number space.
How does the server handle packets that arrive after a stream closes?
Late packets are queued in the per-session OrphanQueue, a multi-level priority structure. The enqueueOrphanReset helper (lines 126-146) generates high-priority reset packets that inform the client to discard its local stream state, ensuring synchronization without requiring the client to wait for a network timeout.
Where are timeout values configured in the MasterDnsVPN codebase?
Session-level timeouts reside in internal/udpserver/session.go (initialization in newSessionStore), while runtime invocation parameters appear in internal/udpserver/server_runtime.go (lines 165-169). Specialized timeouts for SOCKS upstream connections and DNS queries are defined in socks5_upstream.go (lines 26-33) and dns_tunnel.go (lines 260-267), respectively.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →