Files
ginuerzh 988c9afe28 fix(masque): reap idle CONNECT-UDP associations
udp.Relay.Run ignored ctx cancellation, so an idle MASQUE association (or a
disconnecting client) leaked its target UDP socket and both copy goroutines,
and service shutdown blocked on the stuck handlers. quic-go's
ReceiveDatagram only unblocks on ctx cancellation, never on stream close, so
closing the datagram conn could not release the read either.

Make Relay.Run cancellation-aware and close both conns on exit, add
WithReadTimeout for an idle read deadline, give DatagramConn a cancelable
context that Close cancels, and apply the parsed idleTimeout to the UDP
relay.

Ref: https://github.com/go-gost/x/issues/128
2026-09-11 21:40:12 +08:00

76 lines
2.8 KiB
Go

package masque
import (
"time"
mdata "github.com/go-gost/core/metadata"
mdutil "github.com/go-gost/x/metadata/util"
)
const (
// defaultBufferSize is the default UDP relay buffer size in bytes.
defaultBufferSize = 4096
// defaultRealm is the default HTTP Basic authentication realm for proxy auth.
defaultRealm = "gost"
)
// metadata holds parsed configuration values for the MASQUE handler.
// Values are extracted from the generic metadata map via parseMetadata.
type metadata struct {
// hash defines the hash source for load-balancing selector.
// When set to "host", the target address is used as the hash input
// for consistent routing through chain hops.
hash string
// bufferSize is the UDP relay buffer size in bytes for datagram forwarding.
bufferSize int
// authBasicRealm is the realm string sent in Proxy-Authenticate challenges.
authBasicRealm string
// idleTimeout is the read timeout for both TCP and UDP (CONNECT-UDP)
// relays. After this duration with no traffic in a direction, that relay
// is terminated and its sockets released.
idleTimeout time.Duration
// observerPeriod is the interval between observer stats collection cycles.
observerPeriod time.Duration
// observerResetTraffic controls whether per-client traffic counters are
// reset to zero after each observer report.
observerResetTraffic bool
// limiterRefreshInterval controls how often the cached traffic limiter
// refreshes its limits from the global limiter source.
limiterRefreshInterval time.Duration
// limiterCleanupInterval controls how often the cached traffic limiter
// removes stale cached entries for disconnected clients.
limiterCleanupInterval time.Duration
}
// parseMetadata extracts typed configuration values from the generic metadata map.
// Metadata keys support multiple fallback names for compatibility (e.g., "bufferSize"
// and "udp.bufferSize"). Duration values accept both integer (seconds) and string
// (Go duration format like "5s") inputs.
func (h *masqueHandler) parseMetadata(md mdata.Metadata) (err error) {
h.md.hash = mdutil.GetString(md, "hash")
h.md.bufferSize = mdutil.GetInt(md, "bufferSize", "udp.bufferSize")
if h.md.bufferSize <= 0 {
h.md.bufferSize = defaultBufferSize
}
h.md.authBasicRealm = mdutil.GetString(md, "authBasicRealm")
h.md.observerPeriod = mdutil.GetDuration(md, "observePeriod", "observer.period", "observer.observePeriod")
if h.md.observerPeriod == 0 {
h.md.observerPeriod = 5 * time.Second
}
if h.md.observerPeriod < time.Second {
h.md.observerPeriod = time.Second
}
h.md.observerResetTraffic = mdutil.GetBool(md, "observer.resetTraffic")
h.md.idleTimeout = mdutil.GetDuration(md, "readTimeout", "read.timeout", "idleTimeout")
h.md.limiterRefreshInterval = mdutil.GetDuration(md, "limiter.refreshInterval")
h.md.limiterCleanupInterval = mdutil.GetDuration(md, "limiter.cleanupInterval")
return nil
}