fix(masque): default enableDatagrams to true on HTTP/3 listener and fix CONNECT-UDP error handling
- listener/http3: default enableDatagrams to true when not explicitly set, so CONNECT-UDP (RFC 9298) works out of the box without requiring ?enableDatagrams=true in the config URL - handler/masque: dial target before sending 200 OK in handleConnectUDP, matching the handleConnectTCP order; if target dial fails, the handler can still return an error status instead of having already committed 200 - handler/masque: add missing WriteHeader(BadRequest) when ResolveUDPAddr fails, so the client gets a proper HTTP error instead of an opaque H3_REQUEST_CANCELLED stream reset Fixes #103
This commit is contained in:
+38
-37
@@ -355,6 +355,7 @@ func (h *masqueHandler) handleConnectUDP(ctx context.Context, w http.ResponseWri
|
|||||||
// Resolve target address
|
// Resolve target address
|
||||||
raddr, err := net.ResolveUDPAddr("udp", targetAddr)
|
raddr, err := net.ResolveUDPAddr("udp", targetAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
log.Error("masque: failed to resolve target address: ", err)
|
log.Error("masque: failed to resolve target address: ", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -370,43 +371,6 @@ func (h *masqueHandler) handleConnectUDP(ctx context.Context, w http.ResponseWri
|
|||||||
// Get the underlying HTTP/3 stream
|
// Get the underlying HTTP/3 stream
|
||||||
stream := streamer.HTTPStream()
|
stream := streamer.HTTPStream()
|
||||||
|
|
||||||
// Send success response with capsule-protocol header
|
|
||||||
w.Header().Set("Capsule-Protocol", "?1")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
// Flush the response headers
|
|
||||||
if flusher, ok := w.(http.Flusher); ok {
|
|
||||||
flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create datagram connection wrapping the HTTP/3 stream (client side)
|
|
||||||
datagramConn := masque_util.NewDatagramConn(stream, laddr, raddr)
|
|
||||||
defer datagramConn.Close()
|
|
||||||
|
|
||||||
// Wrap with recorder stats and traffic limiter
|
|
||||||
var clientPC net.PacketConn = datagramConn
|
|
||||||
clientPC = stats_wrapper.WrapPacketConn(clientPC, pStats)
|
|
||||||
clientPC = traffic_wrapper.WrapPacketConn(
|
|
||||||
clientPC,
|
|
||||||
h.limiter,
|
|
||||||
clientID,
|
|
||||||
limiter.ServiceOption(h.options.Service),
|
|
||||||
limiter.ScopeOption(limiter.ScopeClient),
|
|
||||||
limiter.NetworkOption("udp"),
|
|
||||||
limiter.AddrOption(targetAddr),
|
|
||||||
limiter.ClientOption(clientID),
|
|
||||||
limiter.SrcOption(ro.RemoteAddr),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Track per-client connection stats
|
|
||||||
if h.options.Observer != nil {
|
|
||||||
pstats := h.stats.Stats(clientID)
|
|
||||||
pstats.Add(stats.KindTotalConns, 1)
|
|
||||||
pstats.Add(stats.KindCurrentConns, 1)
|
|
||||||
defer pstats.Add(stats.KindCurrentConns, -1)
|
|
||||||
clientPC = stats_wrapper.WrapPacketConn(clientPC, pstats)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get target connection - either through router/chain or direct
|
// Get target connection - either through router/chain or direct
|
||||||
var targetPC net.PacketConn
|
var targetPC net.PacketConn
|
||||||
|
|
||||||
@@ -460,6 +424,43 @@ func (h *masqueHandler) handleConnectUDP(ctx context.Context, w http.ResponseWri
|
|||||||
// Wrap target with metrics
|
// Wrap target with metrics
|
||||||
targetPC = metrics.WrapPacketConn(h.options.Service, targetPC)
|
targetPC = metrics.WrapPacketConn(h.options.Service, targetPC)
|
||||||
|
|
||||||
|
// Send success response with capsule-protocol header
|
||||||
|
w.Header().Set("Capsule-Protocol", "?1")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
// Flush the response headers
|
||||||
|
if flusher, ok := w.(http.Flusher); ok {
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create datagram connection wrapping the HTTP/3 stream (client side)
|
||||||
|
datagramConn := masque_util.NewDatagramConn(stream, laddr, raddr)
|
||||||
|
defer datagramConn.Close()
|
||||||
|
|
||||||
|
// Wrap with recorder stats and traffic limiter
|
||||||
|
var clientPC net.PacketConn = datagramConn
|
||||||
|
clientPC = stats_wrapper.WrapPacketConn(clientPC, pStats)
|
||||||
|
clientPC = traffic_wrapper.WrapPacketConn(
|
||||||
|
clientPC,
|
||||||
|
h.limiter,
|
||||||
|
clientID,
|
||||||
|
limiter.ServiceOption(h.options.Service),
|
||||||
|
limiter.ScopeOption(limiter.ScopeClient),
|
||||||
|
limiter.NetworkOption("udp"),
|
||||||
|
limiter.AddrOption(targetAddr),
|
||||||
|
limiter.ClientOption(clientID),
|
||||||
|
limiter.SrcOption(ro.RemoteAddr),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Track per-client connection stats
|
||||||
|
if h.options.Observer != nil {
|
||||||
|
pstats := h.stats.Stats(clientID)
|
||||||
|
pstats.Add(stats.KindTotalConns, 1)
|
||||||
|
pstats.Add(stats.KindCurrentConns, 1)
|
||||||
|
defer pstats.Add(stats.KindCurrentConns, -1)
|
||||||
|
clientPC = stats_wrapper.WrapPacketConn(clientPC, pstats)
|
||||||
|
}
|
||||||
|
|
||||||
// Relay UDP packets between client and target
|
// Relay UDP packets between client and target
|
||||||
relay := udp.NewRelay(clientPC, targetPC).
|
relay := udp.NewRelay(clientPC, targetPC).
|
||||||
WithService(h.options.Service).
|
WithService(h.options.Service).
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ func (l *http3Listener) parseMetadata(md mdata.Metadata) (err error) {
|
|||||||
l.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
|
l.md.handshakeTimeout = mdutil.GetDuration(md, handshakeTimeout)
|
||||||
l.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
|
l.md.maxIdleTimeout = mdutil.GetDuration(md, maxIdleTimeout)
|
||||||
l.md.maxStreams = mdutil.GetInt(md, maxStreams)
|
l.md.maxStreams = mdutil.GetInt(md, maxStreams)
|
||||||
|
if mdutil.IsExists(md, "enableDatagrams") {
|
||||||
l.md.enableDatagrams = mdutil.GetBool(md, "enableDatagrams")
|
l.md.enableDatagrams = mdutil.GetBool(md, "enableDatagrams")
|
||||||
|
} else {
|
||||||
|
l.md.enableDatagrams = true
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user