@@ -48,6 +48,14 @@ const quicAddrValidatorCacheSize = 1000
4848// TODO(ameshkov): make it configurable.
4949const quicAddrValidatorCacheTTL = 30 * time .Minute
5050
51+ const (
52+ // DefaultDoQReadTimeout is the default timeout for DoQ reading operations.
53+ DefaultDoQReadTimeout = 2 * time .Second
54+
55+ // DefaultDoQWriteTimeout is the default timeout for DoQ writing operations.
56+ DefaultDoQWriteTimeout = 2 * time .Second
57+ )
58+
5159const (
5260 // DoQCodeNoError is used when the connection or stream needs to be closed,
5361 // but there is no error to signal.
@@ -129,7 +137,7 @@ func (p *Proxy) quicPacketLoop(
129137 p .logger .InfoContext (ctx , "entering dns-over-quic listener loop" , "addr" , l .Addr ())
130138
131139 for {
132- conn , err := l . Accept (ctx )
140+ conn , err := p . acceptQUICConn (ctx , l )
133141 if err != nil {
134142 logQUICError (ctx , "accepting quic conn" , err , p .logger )
135143
@@ -155,6 +163,17 @@ func (p *Proxy) quicPacketLoop(
155163 }
156164}
157165
166+ // acceptQUICConn reads and accepts a single QUIC connection.
167+ func (p * Proxy ) acceptQUICConn (
168+ ctx context.Context ,
169+ l * quic.EarlyListener ,
170+ ) (conn * quic.Conn , err error ) {
171+ acceptCtx , cancel := context .WithDeadline (ctx , time .Now ().Add (DefaultDoQReadTimeout ))
172+ defer cancel ()
173+
174+ return l .Accept (acceptCtx )
175+ }
176+
158177// logQUICError writes suitable log message for the given err.
159178func logQUICError (ctx context.Context , prefix string , err error , l * slog.Logger ) {
160179 if isQUICErrorForDebugLog (err ) {
@@ -184,7 +203,7 @@ func (p *Proxy) handleQUICConnection(
184203 // design specifies that for each subsequent query on a QUIC connection
185204 // the client MUST select the next available client-initiated
186205 // bidirectional stream.
187- stream , err := conn . AcceptStream (ctx )
206+ stream , err := acceptStream (ctx , conn )
188207 if err != nil {
189208 logQUICError (ctx , "accepting quic stream" , err , p .logger )
190209
@@ -216,6 +235,48 @@ func (p *Proxy) handleQUICConnection(
216235 }
217236}
218237
238+ // acceptStream accepts and starts processing a single QUIC stream. All
239+ // arguments must not be nil.
240+ //
241+ // NOTE: Any error returned from this method stops handling on conn.
242+ func acceptStream (parent context.Context , conn * quic.Conn ) (stream * quic.Stream , err error ) {
243+ // The stub to resolver DNS traffic follows a simple pattern in which the
244+ // client sends a query, and the server provides a response. This design
245+ // specifies that for each subsequent query on a QUIC connection the client
246+ // MUST select the next available client-initiated bidirectional stream.
247+ ctx , cancel := context .WithDeadline (parent , time .Now ().Add (maxQUICIdleTimeout ))
248+ defer cancel ()
249+
250+ // For some reason AcceptStream below seems to get stuck even when ctx is
251+ // canceled. As a mitigation, check the context manually right before
252+ // feeding it into AcceptStream.
253+ //
254+ // TODO(a.garipov): Try to reproduce and report.
255+ select {
256+ case <- ctx .Done ():
257+ return nil , fmt .Errorf ("checking accept ctx: %w" , ctx .Err ())
258+ default :
259+ // Go on.
260+ }
261+
262+ stream , err = conn .AcceptStream (ctx )
263+ if err != nil {
264+ return nil , fmt .Errorf ("accepting quic stream: %w" , err )
265+ }
266+
267+ err = stream .SetReadDeadline (time .Now ().Add (DefaultDoQReadTimeout ))
268+ if err != nil {
269+ return nil , fmt .Errorf ("setting read deadline: %w" , err )
270+ }
271+
272+ err = stream .SetWriteDeadline (time .Now ().Add (DefaultDoQWriteTimeout ))
273+ if err != nil {
274+ return nil , fmt .Errorf ("setting write deadline: %w" , err )
275+ }
276+
277+ return stream , nil
278+ }
279+
219280// handleQUICStream reads DNS queries from the stream, processes them,
220281// and writes back the response.
221282func (p * Proxy ) handleQUICStream (ctx context.Context , stream * quic.Stream , conn * quic.Conn ) {
0 commit comments