feat(everything): fix doh and increase min TLS to 1.3

This commit is contained in:
2026-07-08 08:42:39 +01:00
parent 0c03bcbcfe
commit cc5e0ab00a
13 changed files with 691 additions and 530 deletions
+58 -18
View File
@@ -27,7 +27,6 @@ type Config struct {
Path string
DNSSEC bool
HTTP3 bool
HTTP2 bool
KeepAlive bool
}
@@ -62,6 +61,7 @@ func New(config Config) (*Client, error) {
tlsConfig := &tls.Config{
ServerName: config.Host,
MinVersion: tls.VersionTLS13,
ClientSessionCache: tls.NewLRUClientSessionCache(100),
}
@@ -88,14 +88,7 @@ func New(config Config) (*Client, error) {
}
var transportType string
if config.HTTP2 {
http2Transport := &http2.Transport{
TLSClientConfig: tlsConfig,
AllowHTTP: true,
}
httpClient.Transport = http2Transport
transportType = "HTTP/2"
} else if config.HTTP3 {
if config.HTTP3 {
quicTlsConfig := http3.ConfigureTLSConfig(tlsConfig)
http3Transport := &http3.Transport{
TLSClientConfig: quicTlsConfig,
@@ -104,7 +97,12 @@ func New(config Config) (*Client, error) {
httpClient.Transport = http3Transport
transportType = "HTTP/3"
} else {
transportType = "HTTP/1.1"
http2Transport := &http2.Transport{
TLSClientConfig: tlsConfig,
AllowHTTP: true,
}
httpClient.Transport = http2Transport
transportType = "HTTP/2"
}
logger.Debug("DoH client created: %s (%s, DNSSEC: %v, KeepAlive: %v)", rawURL, transportType, config.DNSSEC, config.KeepAlive)
@@ -116,6 +114,43 @@ func New(config Config) (*Client, error) {
}, nil
}
// newSingleUseClient builds a throwaway *http.Client whose transport is torn
// down by the returned closeFn. Used for non-persistent (non-keep-alive) mode
// so that each query establishes and tears down its own connection, and thus
// pays its own TCP+TLS (or QUIC) handshake. Without this, the pooled
// HTTP/2 transport silently reuses a single connection across all queries
// (HTTP/2 multiplexes and ignores the Connection: close header), which would
// make the non-persistent measurement indistinguishable from persistent.
func (c *Client) newSingleUseClient() (*http.Client, func()) {
tlsConfig := &tls.Config{
ServerName: c.config.Host,
MinVersion: tls.VersionTLS13,
ClientSessionCache: nil, // no resumption: force a full handshake each query
}
if c.config.HTTP3 {
quicConf := &quic.Config{
MaxIdleTimeout: 30 * time.Second,
DisablePathMTUDiscovery: true,
}
quicTLS := http3.ConfigureTLSConfig(tlsConfig)
tr := &http3.Transport{
TLSClientConfig: quicTLS,
QUICConfig: quicConf,
}
client := &http.Client{Transport: tr, Timeout: 30 * time.Second}
return client, func() { tr.Close() }
}
tr := &http2.Transport{
TLSClientConfig: tlsConfig,
AllowHTTP: true,
DisableCompression: true,
}
client := &http.Client{Transport: tr, Timeout: 30 * time.Second}
return client, func() { tr.CloseIdleConnections() }
}
func (c *Client) Close() {
logger.Debug("Closing DoH client")
if t, ok := c.httpClient.Transport.(*http.Transport); ok {
@@ -143,6 +178,18 @@ func (c *Client) Query(msg *dns.Msg) (*dns.Msg, error) {
return nil, fmt.Errorf("doh: failed to pack DNS message: %w", err)
}
// Select the HTTP client. In persistent mode, reuse the pooled client built
// in New(). In non-persistent mode, build a fresh client (and transport)
// per query and tear it down afterwards, so every query pays its own
// handshake — otherwise HTTP/2 would reuse one connection and the
// non-persistent measurement would be wrong.
httpClient := c.httpClient
if !c.config.KeepAlive {
freshClient, closeFn := c.newSingleUseClient()
defer closeFn()
httpClient = freshClient
}
httpReq, err := http.NewRequest(http.MethodPost, c.upstreamURL.String(), bytes.NewReader(packedMsg))
if err != nil {
logger.Error("DoH failed to create HTTP request: %v", err)
@@ -153,14 +200,7 @@ func (c *Client) Query(msg *dns.Msg) (*dns.Msg, error) {
httpReq.Header.Set("Content-Type", dnsMessageContentType)
httpReq.Header.Set("Accept", dnsMessageContentType)
// Set Connection header based on KeepAlive setting
if c.config.KeepAlive {
httpReq.Header.Set("Connection", "keep-alive")
} else {
httpReq.Header.Set("Connection", "close")
}
httpResp, err := c.httpClient.Do(httpReq)
httpResp, err := httpClient.Do(httpReq)
if err != nil {
logger.Error("DoH request failed to %s: %v", c.upstreamURL.Host, err)
return nil, fmt.Errorf("doh: failed executing HTTP request to %s: %w", c.upstreamURL.Host, err)
+2 -2
View File
@@ -26,7 +26,7 @@ type Client struct {
targetAddr *net.UDPAddr
tlsConfig *tls.Config
udpConn *net.UDPConn
quicConn quic.Connection
quicConn *quic.Conn
quicTransport *quic.Transport
quicConfig *quic.Config
config Config
@@ -119,7 +119,7 @@ func (c *Client) Query(msg *dns.Msg) (*dns.Msg, error) {
return nil, fmt.Errorf("doq: failed to pack message: %w", err)
}
var quicStream quic.Stream
var quicStream *quic.Stream
quicStream, err = c.quicConn.OpenStream()
if err != nil {
logger.Debug("DoQ stream failed, reconnecting: %v", err)
+1
View File
@@ -49,6 +49,7 @@ func New(config Config) (*Client, error) {
tlsConfig := &tls.Config{
ServerName: config.Host,
MinVersion: tls.VersionTLS13,
}
client := &Client{