mirror of
https://github.com/coredns/coredns.git
synced 2026-07-10 17:50:12 -04:00
plugin/dnstap: close the previous connection before reconnecting (#8224)
This commit is contained in:
committed by
GitHub
parent
b1a0d2f287
commit
19ce0e173a
@@ -64,7 +64,17 @@ func newIO(proto, endpoint string, multipleQueue int, multipleTcpWriteBuf int) *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dial opens a new connection to the dnstap endpoint and installs the new
|
||||||
|
// encoder on d.enc. Any previous encoder (and the socket beneath it) is
|
||||||
|
// flushed and closed first so a reconnect after a write error does not leak
|
||||||
|
// the old connection.
|
||||||
func (d *dio) dial() error {
|
func (d *dio) dial() error {
|
||||||
|
if d.enc != nil {
|
||||||
|
d.enc.flush()
|
||||||
|
d.enc.close()
|
||||||
|
d.enc = nil
|
||||||
|
}
|
||||||
|
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|||||||
@@ -284,3 +284,82 @@ func TestFullQueueWriteFail(t *testing.T) {
|
|||||||
require.NotEqual(t, 0, logger.WarnCount())
|
require.NotEqual(t, 0, logger.WarnCount())
|
||||||
require.Contains(t, logger.WarnLog(), "Dropped dnstap messages")
|
require.Contains(t, logger.WarnLog(), "Dropped dnstap messages")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReconnectClosesPreviousConnection(t *testing.T) {
|
||||||
|
// A reconnect (a second dial after a write error) must flush and close the
|
||||||
|
// previous encoder and its connection before installing the new one.
|
||||||
|
// Otherwise the old socket leaks on every redial.
|
||||||
|
l, err := reuseport.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot start listener: %s", err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
accepted := make(chan struct{}, 2)
|
||||||
|
firstClosed := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
n := 0
|
||||||
|
for {
|
||||||
|
server, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
go func(server net.Conn, n int) {
|
||||||
|
dec, err := fs.NewDecoder(server, &fs.DecoderOptions{
|
||||||
|
ContentType: []byte("protobuf:dnstap.Dnstap"),
|
||||||
|
Bidirectional: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
server.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accepted <- struct{}{}
|
||||||
|
if n == 1 {
|
||||||
|
// Block until the client closes this connection during the
|
||||||
|
// second dial; Decode then returns an error (framestream FINISH/EOF).
|
||||||
|
for {
|
||||||
|
if _, err := dec.Decode(); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.Close()
|
||||||
|
close(firstClosed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// n >= 2: leave the connection open for the rest of the test.
|
||||||
|
}(server, n)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
dio := newIO("tcp", l.Addr().String(), 1, 1)
|
||||||
|
dio.tcpTimeout = time.Second
|
||||||
|
|
||||||
|
// First dial establishes the initial connection.
|
||||||
|
if err := dio.dial(); err != nil {
|
||||||
|
t.Fatalf("first dial failed: %s", err)
|
||||||
|
}
|
||||||
|
<-accepted
|
||||||
|
first := dio.enc
|
||||||
|
if first == nil {
|
||||||
|
t.Fatal("first dial did not set an encoder")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second dial simulates a reconnect: it must replace the encoder.
|
||||||
|
if err := dio.dial(); err != nil {
|
||||||
|
t.Fatalf("second dial failed: %s", err)
|
||||||
|
}
|
||||||
|
<-accepted
|
||||||
|
if dio.enc == first {
|
||||||
|
t.Fatal("second dial did not replace the previous encoder")
|
||||||
|
}
|
||||||
|
defer dio.enc.close()
|
||||||
|
|
||||||
|
// The previous connection must have been closed by the second dial.
|
||||||
|
select {
|
||||||
|
case <-firstClosed:
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("previous connection was not closed on reconnect (fd/goroutine leak)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user