net/dns: don't send on closed channel when message too large

Previously, if a DNS-over-TCP message was received while there were
existing queries in-flight, and it was over the size limit, we'd close
the 'responses' channel. This would cause those in-flight queries to
send on the closed channel and panic.

Instead, don't close the channel at all and rely on s.ctx being
canceled, which will ensure that in-flight queries don't hang.

Fixes #6725

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I8267728ac37ed7ae38ddd09ce2633a5824320097
This commit is contained in:
Andrew Dunham
2022-12-13 14:49:08 -05:00
parent 44be59c15a
commit 3f4d51c588
2 changed files with 114 additions and 5 deletions

View File

@@ -447,9 +447,17 @@ type dnsTCPSession struct {
func (s *dnsTCPSession) handleWrites() {
defer s.conn.Close()
defer close(s.responses)
defer s.closeCtx()
// NOTE(andrew): we explicitly do not close the 'responses' channel
// when this function exits. If we hit an error and return, we could
// still have outstanding 'handleQuery' goroutines running, and if we
// closed this channel they'd end up trying to send on a closed channel
// when they finish.
//
// Because we call closeCtx, those goroutines will not hang since they
// select on <-s.ctx.Done() as well as s.responses.
for {
select {
case <-s.readClosing:
@@ -476,6 +484,7 @@ func (s *dnsTCPSession) handleQuery(q []byte) {
return
}
// See note in handleWrites (above) regarding this select{}
select {
case <-s.ctx.Done():
case s.responses <- resp:
@@ -483,6 +492,7 @@ func (s *dnsTCPSession) handleQuery(q []byte) {
}
func (s *dnsTCPSession) handleReads() {
defer s.conn.Close()
defer close(s.readClosing)
for {
@@ -515,6 +525,11 @@ func (s *dnsTCPSession) handleReads() {
case <-s.ctx.Done():
return
default:
// NOTE: by kicking off the query handling in a
// new goroutine, it is possible that we'll
// deliver responses out-of-order. This is
// explicitly allowed by RFC7766, Section
// 6.2.1.1 ("Query Pipelining").
go s.handleQuery(buf)
}
}