mirror of
https://github.com/coredns/coredns.git
synced 2026-07-26 01:20:11 -04:00
plugin/forward: Fix UDP forwarding so a malformed upstream datagram wont block valid ones later (#8287)
* plugin/plugin: Fix UDP forwarding so a malformed upstream datagram wont block valid ones later This PR fixes UDP forwarding so a malformed upstream datagram does not prevent CoreDNS from accepting a subsequent valid response before the existing read deadline. Signed-off-by: Yong Tang <yong.tang.github@outlook.com> * Address review and add additonal tests Signed-off-by: Yong Tang <yong.tang.github@outlook.com> * Cover branch Signed-off-by: Yong Tang <yong.tang.github@outlook.com> --------- Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
@@ -186,6 +186,12 @@ func (p *Proxy) lookupDNS(_ctx context.Context, state request.Request, opts Opti
|
|||||||
for {
|
for {
|
||||||
ret, err = pc.c.ReadMsg()
|
ret, err = pc.c.ReadMsg()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if p.transport.transportTypeFromConn(pc) == typeUDP &&
|
||||||
|
((ret == nil && errors.Is(err, dns.ErrShortRead)) ||
|
||||||
|
(ret != nil && ret.Id != state.Req.Id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if ret != nil && (state.Req.Id == ret.Id) && p.transport.transportTypeFromConn(pc) == typeUDP && shouldTruncateResponse(err) {
|
if ret != nil && (state.Req.Id == ret.Id) && p.transport.transportTypeFromConn(pc) == typeUDP && shouldTruncateResponse(err) {
|
||||||
// For UDP, if the error is an overflow, we probably have an upstream misbehaving in some way.
|
// For UDP, if the error is an overflow, we probably have an upstream misbehaving in some way.
|
||||||
// (e.g. sending >512 byte responses without an eDNS0 OPT RR).
|
// (e.g. sending >512 byte responses without an eDNS0 OPT RR).
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -269,3 +270,124 @@ func TestShouldTruncateResponse(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProxyMalformedUDPThenValid(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
malformed func(*dns.Msg) []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "short packet without complete header",
|
||||||
|
malformed: func(r *dns.Msg) []byte {
|
||||||
|
badID := r.Id + 1
|
||||||
|
|
||||||
|
return []byte{
|
||||||
|
byte(badID >> 8),
|
||||||
|
byte(badID),
|
||||||
|
0x00,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partial malformed response with mismatched ID",
|
||||||
|
malformed: func(r *dns.Msg) []byte {
|
||||||
|
badID := r.Id + 1
|
||||||
|
|
||||||
|
// Complete DNS header claiming one question, followed by an
|
||||||
|
// incomplete one-byte label. ReadMsg returns a partial message
|
||||||
|
// containing badID together with an unpacking error.
|
||||||
|
return []byte{
|
||||||
|
byte(badID >> 8),
|
||||||
|
byte(badID),
|
||||||
|
0x81,
|
||||||
|
0x80,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x01,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
serverResult := make(chan error, 1)
|
||||||
|
|
||||||
|
s := dnstest.NewServer(func(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
if _, err := w.Write(tc.malformed(r)); err != nil {
|
||||||
|
serverResult <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediately send a completely valid response for the same query.
|
||||||
|
reply := new(dns.Msg)
|
||||||
|
reply.SetReply(r)
|
||||||
|
reply.Answer = append(
|
||||||
|
reply.Answer,
|
||||||
|
test.A("variant.example. IN A 192.0.2.55"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := w.WriteMsg(reply); err != nil {
|
||||||
|
serverResult <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serverResult <- nil
|
||||||
|
})
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
p := NewProxy(
|
||||||
|
"TestProxyMalformedUDPThenValid",
|
||||||
|
s.Addr,
|
||||||
|
transport.DNS,
|
||||||
|
)
|
||||||
|
p.readTimeout = time.Second
|
||||||
|
p.Start(5 * time.Second)
|
||||||
|
defer p.Stop()
|
||||||
|
|
||||||
|
q := new(dns.Msg)
|
||||||
|
q.SetQuestion("variant.example.", dns.TypeA)
|
||||||
|
|
||||||
|
req := request.Request{
|
||||||
|
Req: q,
|
||||||
|
W: &test.ResponseWriter{},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _, _, err := p.Connect(
|
||||||
|
context.Background(),
|
||||||
|
req,
|
||||||
|
Options{PreferUDP: true},
|
||||||
|
)
|
||||||
|
|
||||||
|
if serverErr := <-serverResult; serverErr != nil {
|
||||||
|
t.Fatalf("upstream failed to send responses: %v", serverErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expected: ignore the malformed UDP datagram and read the valid response.
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"valid response after malformed UDP datagram was not accepted: %v",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("expected valid response, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Answer) != 1 {
|
||||||
|
t.Fatalf("expected one answer, got %d", len(resp.Answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := resp.Answer[0].String(); !strings.Contains(got, "192.0.2.55") {
|
||||||
|
t.Fatalf("expected 192.0.2.55, got %q", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user