mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
Add detailed logging throughout DNS proxy operations to improve visibility into query processing, cache operations, and upstream resolver performance. Key improvements: - DNS server setup and listener management logging - Complete query processing pipeline visibility - Cache hit/miss and stale response handling logs - Upstream resolver iteration and failure tracking - Resolver-specific logging (OS, DoH, DoT, DoQ, Legacy) - All log messages capitalized for better readability This provides comprehensive debugging capabilities for DNS proxy operations and helps identify performance bottlenecks and failure points in the resolution chain.
119 lines
3.0 KiB
Go
119 lines
3.0 KiB
Go
//go:build !qf
|
|
|
|
package ctrld
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"io"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/quic-go/quic-go"
|
|
)
|
|
|
|
type doqResolver struct {
|
|
uc *UpstreamConfig
|
|
}
|
|
|
|
func (r *doqResolver) Resolve(ctx context.Context, msg *dns.Msg) (*dns.Msg, error) {
|
|
logger := LoggerFromCtx(ctx)
|
|
Log(ctx, logger.Debug(), "DoQ resolver query started")
|
|
|
|
endpoint := r.uc.Endpoint
|
|
tlsConfig := &tls.Config{NextProtos: []string{"doq"}}
|
|
ip := r.uc.BootstrapIP
|
|
if ip == "" {
|
|
dnsTyp := uint16(0)
|
|
if msg != nil && len(msg.Question) > 0 {
|
|
dnsTyp = msg.Question[0].Qtype
|
|
}
|
|
ip = r.uc.bootstrapIPForDNSType(ctx, dnsTyp)
|
|
}
|
|
tlsConfig.ServerName = r.uc.Domain
|
|
_, port, _ := net.SplitHostPort(endpoint)
|
|
endpoint = net.JoinHostPort(ip, port)
|
|
|
|
Log(ctx, logger.Debug(), "Sending DoQ request to: %s", endpoint)
|
|
answer, err := resolve(ctx, msg, endpoint, tlsConfig)
|
|
if err != nil {
|
|
Log(ctx, logger.Error().Err(err), "DoQ request failed")
|
|
} else {
|
|
Log(ctx, logger.Debug(), "DoQ resolver query successful")
|
|
}
|
|
return answer, err
|
|
}
|
|
|
|
func resolve(ctx context.Context, msg *dns.Msg, endpoint string, tlsConfig *tls.Config) (*dns.Msg, error) {
|
|
// DoQ quic-go server returns io.EOF error after running for a long time,
|
|
// even for a good stream. So retrying the query for 5 times before giving up.
|
|
for i := 0; i < 5; i++ {
|
|
answer, err := doResolve(ctx, msg, endpoint, tlsConfig)
|
|
if err == io.EOF {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, wrapCertificateVerificationError(err)
|
|
}
|
|
return answer, nil
|
|
}
|
|
return nil, &quic.ApplicationError{ErrorCode: quic.ApplicationErrorCode(quic.InternalError), ErrorMessage: quic.InternalError.Message()}
|
|
}
|
|
|
|
func doResolve(ctx context.Context, msg *dns.Msg, endpoint string, tlsConfig *tls.Config) (*dns.Msg, error) {
|
|
session, err := quic.DialAddr(ctx, endpoint, tlsConfig, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer session.CloseWithError(quic.ApplicationErrorCode(quic.NoError), "")
|
|
|
|
msgBytes, err := msg.Pack()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stream, err := session.OpenStream()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
deadline, ok := ctx.Deadline()
|
|
if !ok {
|
|
deadline = time.Now().Add(5 * time.Second)
|
|
}
|
|
_ = stream.SetDeadline(deadline)
|
|
|
|
var msgLen = uint16(len(msgBytes))
|
|
var msgLenBytes = []byte{byte(msgLen >> 8), byte(msgLen & 0xFF)}
|
|
if _, err := stream.Write(msgLenBytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if _, err := stream.Write(msgBytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buf, err := io.ReadAll(stream)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_ = stream.Close()
|
|
|
|
// io.ReadAll hide the io.EOF error returned by quic-go server.
|
|
// Once we figure out why quic-go server sends io.EOF after running
|
|
// for a long time, we can have a better way to handle this. For now,
|
|
// make sure io.EOF error returned, so the caller can handle it cleanly.
|
|
if len(buf) == 0 {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
answer := new(dns.Msg)
|
|
if err := answer.Unpack(buf[2:]); err != nil {
|
|
return nil, err
|
|
}
|
|
answer.SetReply(msg)
|
|
return answer, nil
|
|
}
|