perf(doq): implement connection pooling for improved performance

Implement QUIC connection pooling for DoQ resolver to match DoH3
performance. Previously, DoQ created a new QUIC connection for every
DNS query, incurring significant handshake overhead. Now connections are
reused across queries, eliminating this overhead for subsequent requests.

The implementation follows the same pattern as DoH3, using parallel dialing
and connection pooling to achieve comparable performance characteristics.
This commit is contained in:
Cuong Manh Le
2026-01-06 14:46:00 +07:00
committed by Cuong Manh Le
parent aacba92698
commit e4e655414c
3 changed files with 286 additions and 31 deletions
+30 -2
View File
@@ -282,6 +282,9 @@ type UpstreamConfig struct {
http3RoundTripper http.RoundTripper
http3RoundTripper4 http.RoundTripper
http3RoundTripper6 http.RoundTripper
doqConnPool *doqConnPool
doqConnPool4 *doqConnPool
doqConnPool6 *doqConnPool
certPool *x509.CertPool
u *url.URL
fallbackOnce sync.Once
@@ -504,7 +507,7 @@ func (uc *UpstreamConfig) SetupBootstrapIP(ctx context.Context) {
// ReBootstrap re-setup the bootstrap IP and the transport.
func (uc *UpstreamConfig) ReBootstrap(ctx context.Context) {
switch uc.Type {
case ResolverTypeDOH, ResolverTypeDOH3:
case ResolverTypeDOH, ResolverTypeDOH3, ResolverTypeDOQ:
default:
return
}
@@ -525,6 +528,27 @@ func (uc *UpstreamConfig) SetupTransport(ctx context.Context) {
uc.setupDOHTransport(ctx)
case ResolverTypeDOH3:
uc.setupDOH3Transport(ctx)
case ResolverTypeDOQ:
uc.setupDOQTransport(ctx)
}
}
func (uc *UpstreamConfig) setupDOQTransport(ctx context.Context) {
switch uc.IPStack {
case IpStackBoth, "":
uc.doqConnPool = uc.newDOQConnPool(ctx, uc.bootstrapIPs)
case IpStackV4:
uc.doqConnPool = uc.newDOQConnPool(ctx, uc.bootstrapIPs4)
case IpStackV6:
uc.doqConnPool = uc.newDOQConnPool(ctx, uc.bootstrapIPs6)
case IpStackSplit:
uc.doqConnPool4 = uc.newDOQConnPool(ctx, uc.bootstrapIPs4)
if HasIPv6(ctx) {
uc.doqConnPool6 = uc.newDOQConnPool(ctx, uc.bootstrapIPs6)
} else {
uc.doqConnPool6 = uc.doqConnPool4
}
uc.doqConnPool = uc.newDOQConnPool(ctx, uc.bootstrapIPs)
}
}
@@ -612,7 +636,7 @@ func (uc *UpstreamConfig) ErrorPing(ctx context.Context) error {
func (uc *UpstreamConfig) ping(ctx context.Context) error {
switch uc.Type {
case ResolverTypeDOH, ResolverTypeDOH3:
case ResolverTypeDOH, ResolverTypeDOH3, ResolverTypeDOQ:
default:
return nil
}
@@ -646,6 +670,10 @@ func (uc *UpstreamConfig) ping(ctx context.Context) error {
if err := ping(uc.doh3Transport(ctx, typ)); err != nil {
return err
}
case ResolverTypeDOQ:
// For DoQ, we just ensure transport is set up by calling doqTransport
// DoQ doesn't use HTTP, so we can't ping it the same way
_ = uc.doqTransport(ctx, typ)
}
}