fix: DoQ and HTTP/3 over proxy
This commit is contained in:
parent
71ab8298a2
commit
d9f848ec02
2 changed files with 158 additions and 149 deletions
169
dns/doh.go
169
dns/doh.go
|
@ -83,8 +83,9 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
|
||||||
}
|
}
|
||||||
|
|
||||||
doh := &dnsOverHTTPS{
|
doh := &dnsOverHTTPS{
|
||||||
url: u,
|
url: u,
|
||||||
r: r,
|
r: r,
|
||||||
|
proxyAdapter: proxyAdapter,
|
||||||
quicConfig: &quic.Config{
|
quicConfig: &quic.Config{
|
||||||
KeepAlivePeriod: QUICKeepAlivePeriod,
|
KeepAlivePeriod: QUICKeepAlivePeriod,
|
||||||
TokenStore: newQUICTokenStore(),
|
TokenStore: newQUICTokenStore(),
|
||||||
|
@ -98,8 +99,8 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address implements the Upstream interface for *dnsOverHTTPS.
|
// Address implements the Upstream interface for *dnsOverHTTPS.
|
||||||
func (p *dnsOverHTTPS) Address() string { return p.url.String() }
|
func (doh *dnsOverHTTPS) Address() string { return doh.url.String() }
|
||||||
func (p *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
// Quote from https://www.rfc-editor.org/rfc/rfc8484.html:
|
// Quote from https://www.rfc-editor.org/rfc/rfc8484.html:
|
||||||
// In order to maximize HTTP cache friendliness, DoH clients using media
|
// In order to maximize HTTP cache friendliness, DoH clients using media
|
||||||
// formats that include the ID field from the DNS message header, such
|
// formats that include the ID field from the DNS message header, such
|
||||||
|
@ -117,31 +118,31 @@ func (p *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Ms
|
||||||
|
|
||||||
// Check if there was already an active client before sending the request.
|
// Check if there was already an active client before sending the request.
|
||||||
// We'll only attempt to re-connect if there was one.
|
// We'll only attempt to re-connect if there was one.
|
||||||
client, isCached, err := p.getClient()
|
client, isCached, err := doh.getClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to init http client: %w", err)
|
return nil, fmt.Errorf("failed to init http client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the first attempt to send the DNS query.
|
// Make the first attempt to send the DNS query.
|
||||||
msg, err = p.exchangeHTTPS(ctx, client, m)
|
msg, err = doh.exchangeHTTPS(ctx, client, m)
|
||||||
|
|
||||||
// Make up to 2 attempts to re-create the HTTP client and send the request
|
// Make up to 2 attempts to re-create the HTTP client and send the request
|
||||||
// again. There are several cases (mostly, with QUIC) where this workaround
|
// again. There are several cases (mostly, with QUIC) where this workaround
|
||||||
// is necessary to make HTTP client usable. We need to make 2 attempts in
|
// is necessary to make HTTP client usable. We need to make 2 attempts in
|
||||||
// the case when the connection was closed (due to inactivity for example)
|
// the case when the connection was closed (due to inactivity for example)
|
||||||
// AND the server refuses to open a 0-RTT connection.
|
// AND the server refuses to open a 0-RTT connection.
|
||||||
for i := 0; isCached && p.shouldRetry(err) && i < 2; i++ {
|
for i := 0; isCached && doh.shouldRetry(err) && i < 2; i++ {
|
||||||
client, err = p.resetClient(err)
|
client, err = doh.resetClient(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to reset http client: %w", err)
|
return nil, fmt.Errorf("failed to reset http client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err = p.exchangeHTTPS(ctx, client, m)
|
msg, err = doh.exchangeHTTPS(ctx, client, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the request failed anyway, make sure we don't use this client.
|
// If the request failed anyway, make sure we don't use this client.
|
||||||
_, resErr := p.resetClient(err)
|
_, resErr := doh.resetClient(err)
|
||||||
|
|
||||||
return nil, fmt.Errorf("err:%v,resErr:%v", err, resErr)
|
return nil, fmt.Errorf("err:%v,resErr:%v", err, resErr)
|
||||||
}
|
}
|
||||||
|
@ -150,28 +151,28 @@ func (p *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Ms
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange implements the Upstream interface for *dnsOverHTTPS.
|
// Exchange implements the Upstream interface for *dnsOverHTTPS.
|
||||||
func (p *dnsOverHTTPS) Exchange(m *D.Msg) (*D.Msg, error) {
|
func (doh *dnsOverHTTPS) Exchange(m *D.Msg) (*D.Msg, error) {
|
||||||
return p.ExchangeContext(context.Background(), m)
|
return doh.ExchangeContext(context.Background(), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements the Upstream interface for *dnsOverHTTPS.
|
// Close implements the Upstream interface for *dnsOverHTTPS.
|
||||||
func (p *dnsOverHTTPS) Close() (err error) {
|
func (doh *dnsOverHTTPS) Close() (err error) {
|
||||||
p.clientMu.Lock()
|
doh.clientMu.Lock()
|
||||||
defer p.clientMu.Unlock()
|
defer doh.clientMu.Unlock()
|
||||||
|
|
||||||
runtime.SetFinalizer(p, nil)
|
runtime.SetFinalizer(doh, nil)
|
||||||
|
|
||||||
if p.client == nil {
|
if doh.client == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.closeClient(p.client)
|
return doh.closeClient(doh.client)
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeClient cleans up resources used by client if necessary. Note, that at
|
// closeClient cleans up resources used by client if necessary. Note, that at
|
||||||
// this point it should only be done for HTTP/3 as it may leak due to keep-alive
|
// this point it should only be done for HTTP/3 as it may leak due to keep-alive
|
||||||
// connections.
|
// connections.
|
||||||
func (p *dnsOverHTTPS) closeClient(client *http.Client) (err error) {
|
func (doh *dnsOverHTTPS) closeClient(client *http.Client) (err error) {
|
||||||
if isHTTP3(client) {
|
if isHTTP3(client) {
|
||||||
return client.Transport.(io.Closer).Close()
|
return client.Transport.(io.Closer).Close()
|
||||||
}
|
}
|
||||||
|
@ -180,15 +181,15 @@ func (p *dnsOverHTTPS) closeClient(client *http.Client) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// exchangeHTTPS logs the request and its result and calls exchangeHTTPSClient.
|
// exchangeHTTPS logs the request and its result and calls exchangeHTTPSClient.
|
||||||
func (p *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *D.Msg) (resp *D.Msg, err error) {
|
func (doh *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *D.Msg) (resp *D.Msg, err error) {
|
||||||
resp, err = p.exchangeHTTPSClient(ctx, client, req)
|
resp, err = doh.exchangeHTTPSClient(ctx, client, req)
|
||||||
|
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// exchangeHTTPSClient sends the DNS query to a DoH resolver using the specified
|
// exchangeHTTPSClient sends the DNS query to a DoH resolver using the specified
|
||||||
// http.Client instance.
|
// http.Client instance.
|
||||||
func (p *dnsOverHTTPS) exchangeHTTPSClient(
|
func (doh *dnsOverHTTPS) exchangeHTTPSClient(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
client *http.Client,
|
client *http.Client,
|
||||||
req *D.Msg,
|
req *D.Msg,
|
||||||
|
@ -206,10 +207,10 @@ func (p *dnsOverHTTPS) exchangeHTTPSClient(
|
||||||
method = http3.MethodGet0RTT
|
method = http3.MethodGet0RTT
|
||||||
}
|
}
|
||||||
|
|
||||||
p.url.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf))
|
doh.url.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf))
|
||||||
httpReq, err := http.NewRequest(method, p.url.String(), nil)
|
httpReq, err := http.NewRequest(method, doh.url.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("creating http request to %s: %w", p.url, err)
|
return nil, fmt.Errorf("creating http request to %s: %w", doh.url, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
httpReq.Header.Set("Accept", "application/dns-message")
|
httpReq.Header.Set("Accept", "application/dns-message")
|
||||||
|
@ -217,13 +218,13 @@ func (p *dnsOverHTTPS) exchangeHTTPSClient(
|
||||||
_ = httpReq.WithContext(ctx)
|
_ = httpReq.WithContext(ctx)
|
||||||
httpResp, err := client.Do(httpReq)
|
httpResp, err := client.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("requesting %s: %w", p.url, err)
|
return nil, fmt.Errorf("requesting %s: %w", doh.url, err)
|
||||||
}
|
}
|
||||||
defer httpResp.Body.Close()
|
defer httpResp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(httpResp.Body)
|
body, err := io.ReadAll(httpResp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("reading %s: %w", p.url, err)
|
return nil, fmt.Errorf("reading %s: %w", doh.url, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if httpResp.StatusCode != http.StatusOK {
|
if httpResp.StatusCode != http.StatusOK {
|
||||||
|
@ -232,7 +233,7 @@ func (p *dnsOverHTTPS) exchangeHTTPSClient(
|
||||||
"expected status %d, got %d from %s",
|
"expected status %d, got %d from %s",
|
||||||
http.StatusOK,
|
http.StatusOK,
|
||||||
httpResp.StatusCode,
|
httpResp.StatusCode,
|
||||||
p.url,
|
doh.url,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +242,7 @@ func (p *dnsOverHTTPS) exchangeHTTPSClient(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"unpacking response from %s: body is %s: %w",
|
"unpacking response from %s: body is %s: %w",
|
||||||
p.url,
|
doh.url,
|
||||||
body,
|
body,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
@ -256,7 +257,7 @@ func (p *dnsOverHTTPS) exchangeHTTPSClient(
|
||||||
|
|
||||||
// shouldRetry checks what error we have received and returns true if we should
|
// shouldRetry checks what error we have received and returns true if we should
|
||||||
// re-create the HTTP client and retry the request.
|
// re-create the HTTP client and retry the request.
|
||||||
func (p *dnsOverHTTPS) shouldRetry(err error) (ok bool) {
|
func (doh *dnsOverHTTPS) shouldRetry(err error) (ok bool) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -281,57 +282,57 @@ func (p *dnsOverHTTPS) shouldRetry(err error) (ok bool) {
|
||||||
// resetClient triggers re-creation of the *http.Client that is used by this
|
// resetClient triggers re-creation of the *http.Client that is used by this
|
||||||
// upstream. This method accepts the error that caused resetting client as
|
// upstream. This method accepts the error that caused resetting client as
|
||||||
// depending on the error we may also reset the QUIC config.
|
// depending on the error we may also reset the QUIC config.
|
||||||
func (p *dnsOverHTTPS) resetClient(resetErr error) (client *http.Client, err error) {
|
func (doh *dnsOverHTTPS) resetClient(resetErr error) (client *http.Client, err error) {
|
||||||
p.clientMu.Lock()
|
doh.clientMu.Lock()
|
||||||
defer p.clientMu.Unlock()
|
defer doh.clientMu.Unlock()
|
||||||
|
|
||||||
if errors.Is(resetErr, quic.Err0RTTRejected) {
|
if errors.Is(resetErr, quic.Err0RTTRejected) {
|
||||||
// Reset the TokenStore only if 0-RTT was rejected.
|
// Reset the TokenStore only if 0-RTT was rejected.
|
||||||
p.resetQUICConfig()
|
doh.resetQUICConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
oldClient := p.client
|
oldClient := doh.client
|
||||||
if oldClient != nil {
|
if oldClient != nil {
|
||||||
closeErr := p.closeClient(oldClient)
|
closeErr := doh.closeClient(oldClient)
|
||||||
if closeErr != nil {
|
if closeErr != nil {
|
||||||
log.Warnln("warning: failed to close the old http client: %v", closeErr)
|
log.Warnln("warning: failed to close the old http client: %v", closeErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugln("re-creating the http client due to %v", resetErr)
|
log.Debugln("re-creating the http client due to %v", resetErr)
|
||||||
p.client, err = p.createClient()
|
doh.client, err = doh.createClient()
|
||||||
|
|
||||||
return p.client, err
|
return doh.client, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// getQUICConfig returns the QUIC config in a thread-safe manner. Note, that
|
// getQUICConfig returns the QUIC config in a thread-safe manner. Note, that
|
||||||
// this method returns a pointer, it is forbidden to change its properties.
|
// this method returns a pointer, it is forbidden to change its properties.
|
||||||
func (p *dnsOverHTTPS) getQUICConfig() (c *quic.Config) {
|
func (doh *dnsOverHTTPS) getQUICConfig() (c *quic.Config) {
|
||||||
p.quicConfigGuard.Lock()
|
doh.quicConfigGuard.Lock()
|
||||||
defer p.quicConfigGuard.Unlock()
|
defer doh.quicConfigGuard.Unlock()
|
||||||
|
|
||||||
return p.quicConfig
|
return doh.quicConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetQUICConfig Re-create the token store to make sure we're not trying to
|
// resetQUICConfig Re-create the token store to make sure we're not trying to
|
||||||
// use invalid for 0-RTT.
|
// use invalid for 0-RTT.
|
||||||
func (p *dnsOverHTTPS) resetQUICConfig() {
|
func (doh *dnsOverHTTPS) resetQUICConfig() {
|
||||||
p.quicConfigGuard.Lock()
|
doh.quicConfigGuard.Lock()
|
||||||
defer p.quicConfigGuard.Unlock()
|
defer doh.quicConfigGuard.Unlock()
|
||||||
|
|
||||||
p.quicConfig = p.quicConfig.Clone()
|
doh.quicConfig = doh.quicConfig.Clone()
|
||||||
p.quicConfig.TokenStore = newQUICTokenStore()
|
doh.quicConfig.TokenStore = newQUICTokenStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClient gets or lazily initializes an HTTP client (and transport) that will
|
// getClient gets or lazily initializes an HTTP client (and transport) that will
|
||||||
// be used for this DoH resolver.
|
// be used for this DoH resolver.
|
||||||
func (p *dnsOverHTTPS) getClient() (c *http.Client, isCached bool, err error) {
|
func (doh *dnsOverHTTPS) getClient() (c *http.Client, isCached bool, err error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
p.clientMu.Lock()
|
doh.clientMu.Lock()
|
||||||
defer p.clientMu.Unlock()
|
defer doh.clientMu.Unlock()
|
||||||
if p.client != nil {
|
if doh.client != nil {
|
||||||
return p.client, true, nil
|
return doh.client, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout can be exceeded while waiting for the lock. This happens quite
|
// Timeout can be exceeded while waiting for the lock. This happens quite
|
||||||
|
@ -342,17 +343,17 @@ func (p *dnsOverHTTPS) getClient() (c *http.Client, isCached bool, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugln("creating a new http client")
|
log.Debugln("creating a new http client")
|
||||||
p.client, err = p.createClient()
|
doh.client, err = doh.createClient()
|
||||||
|
|
||||||
return p.client, false, err
|
return doh.client, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// createClient creates a new *http.Client instance. The HTTP protocol version
|
// createClient creates a new *http.Client instance. The HTTP protocol version
|
||||||
// will depend on whether HTTP3 is allowed and provided by this upstream. Note,
|
// will depend on whether HTTP3 is allowed and provided by this upstream. Note,
|
||||||
// that we'll attempt to establish a QUIC connection when creating the client in
|
// that we'll attempt to establish a QUIC connection when creating the client in
|
||||||
// order to check whether HTTP3 is supported.
|
// order to check whether HTTP3 is supported.
|
||||||
func (p *dnsOverHTTPS) createClient() (*http.Client, error) {
|
func (doh *dnsOverHTTPS) createClient() (*http.Client, error) {
|
||||||
transport, err := p.createTransport()
|
transport, err := doh.createTransport()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("initializing http transport: %w", err)
|
return nil, fmt.Errorf("initializing http transport: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -363,9 +364,9 @@ func (p *dnsOverHTTPS) createClient() (*http.Client, error) {
|
||||||
Jar: nil,
|
Jar: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.client = client
|
doh.client = client
|
||||||
|
|
||||||
return p.client, nil
|
return doh.client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createTransport initializes an HTTP transport that will be used specifically
|
// createTransport initializes an HTTP transport that will be used specifically
|
||||||
|
@ -374,7 +375,7 @@ func (p *dnsOverHTTPS) createClient() (*http.Client, error) {
|
||||||
// that this function will first attempt to establish a QUIC connection (if
|
// that this function will first attempt to establish a QUIC connection (if
|
||||||
// HTTP3 is enabled in the upstream options). If this attempt is successful,
|
// HTTP3 is enabled in the upstream options). If this attempt is successful,
|
||||||
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
|
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
|
||||||
func (p *dnsOverHTTPS) createTransport() (t http.RoundTripper, err error) {
|
func (doh *dnsOverHTTPS) createTransport() (t http.RoundTripper, err error) {
|
||||||
tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
|
tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
|
||||||
&tls.Config{
|
&tls.Config{
|
||||||
InsecureSkipVerify: false,
|
InsecureSkipVerify: false,
|
||||||
|
@ -382,15 +383,15 @@ func (p *dnsOverHTTPS) createTransport() (t http.RoundTripper, err error) {
|
||||||
SessionTicketsDisabled: false,
|
SessionTicketsDisabled: false,
|
||||||
})
|
})
|
||||||
var nextProtos []string
|
var nextProtos []string
|
||||||
for _, v := range p.httpVersions {
|
for _, v := range doh.httpVersions {
|
||||||
nextProtos = append(nextProtos, string(v))
|
nextProtos = append(nextProtos, string(v))
|
||||||
}
|
}
|
||||||
tlsConfig.NextProtos = nextProtos
|
tlsConfig.NextProtos = nextProtos
|
||||||
dialContext := getDialHandler(p.r, p.proxyAdapter)
|
dialContext := getDialHandler(doh.r, doh.proxyAdapter)
|
||||||
// First, we attempt to create an HTTP3 transport. If the probe QUIC
|
// First, we attempt to create an HTTP3 transport. If the probe QUIC
|
||||||
// connection is established successfully, we'll be using HTTP3 for this
|
// connection is established successfully, we'll be using HTTP3 for this
|
||||||
// upstream.
|
// upstream.
|
||||||
transportH3, err := p.createTransportH3(tlsConfig, dialContext)
|
transportH3, err := doh.createTransportH3(tlsConfig, dialContext)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
log.Debugln("using HTTP/3 for this upstream: QUIC was faster")
|
log.Debugln("using HTTP/3 for this upstream: QUIC was faster")
|
||||||
return transportH3, nil
|
return transportH3, nil
|
||||||
|
@ -398,7 +399,7 @@ func (p *dnsOverHTTPS) createTransport() (t http.RoundTripper, err error) {
|
||||||
|
|
||||||
log.Debugln("using HTTP/2 for this upstream: %v", err)
|
log.Debugln("using HTTP/2 for this upstream: %v", err)
|
||||||
|
|
||||||
if !p.supportsHTTP() {
|
if !doh.supportsHTTP() {
|
||||||
return nil, errors.New("HTTP1/1 and HTTP2 are not supported by this upstream")
|
return nil, errors.New("HTTP1/1 and HTTP2 are not supported by this upstream")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,14 +552,14 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
|
||||||
// probeH3 runs a test to check whether QUIC is faster than TLS for this
|
// probeH3 runs a test to check whether QUIC is faster than TLS for this
|
||||||
// upstream. If the test is successful it will return the address that we
|
// upstream. If the test is successful it will return the address that we
|
||||||
// should use to establish the QUIC connections.
|
// should use to establish the QUIC connections.
|
||||||
func (p *dnsOverHTTPS) probeH3(
|
func (doh *dnsOverHTTPS) probeH3(
|
||||||
tlsConfig *tls.Config,
|
tlsConfig *tls.Config,
|
||||||
dialContext dialHandler,
|
dialContext dialHandler,
|
||||||
) (addr string, err error) {
|
) (addr string, err error) {
|
||||||
// We're using bootstrapped address instead of what's passed to the function
|
// We're using bootstrapped address instead of what's passed to the function
|
||||||
// it does not create an actual connection, but it helps us determine
|
// it does not create an actual connection, but it helps us determine
|
||||||
// what IP is actually reachable (when there are v4/v6 addresses).
|
// what IP is actually reachable (when there are v4/v6 addresses).
|
||||||
rawConn, err := dialContext(context.Background(), "udp", p.url.Host)
|
rawConn, err := dialContext(context.Background(), "udp", doh.url.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to dial: %w", err)
|
return "", fmt.Errorf("failed to dial: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -567,13 +568,17 @@ func (p *dnsOverHTTPS) probeH3(
|
||||||
|
|
||||||
udpConn, ok := rawConn.(*net.UDPConn)
|
udpConn, ok := rawConn.(*net.UDPConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf("not a UDP connection to %s", p.Address())
|
if packetConn, ok := rawConn.(*wrapPacketConn); !ok {
|
||||||
|
return "", fmt.Errorf("not a UDP connection to %s", doh.Address())
|
||||||
|
} else {
|
||||||
|
addr = packetConn.RemoteAddr().String()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr = udpConn.RemoteAddr().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
addr = udpConn.RemoteAddr().String()
|
|
||||||
|
|
||||||
// Avoid spending time on probing if this upstream only supports HTTP/3.
|
// Avoid spending time on probing if this upstream only supports HTTP/3.
|
||||||
if p.supportsH3() && !p.supportsHTTP() {
|
if doh.supportsH3() && !doh.supportsHTTP() {
|
||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,8 +598,8 @@ func (p *dnsOverHTTPS) probeH3(
|
||||||
// Run probeQUIC and probeTLS in parallel and see which one is faster.
|
// Run probeQUIC and probeTLS in parallel and see which one is faster.
|
||||||
chQuic := make(chan error, 1)
|
chQuic := make(chan error, 1)
|
||||||
chTLS := make(chan error, 1)
|
chTLS := make(chan error, 1)
|
||||||
go p.probeQUIC(addr, probeTLSCfg, chQuic)
|
go doh.probeQUIC(addr, probeTLSCfg, chQuic)
|
||||||
go p.probeTLS(dialContext, probeTLSCfg, chTLS)
|
go doh.probeTLS(dialContext, probeTLSCfg, chTLS)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case quicErr := <-chQuic:
|
case quicErr := <-chQuic:
|
||||||
|
@ -618,16 +623,16 @@ func (p *dnsOverHTTPS) probeH3(
|
||||||
|
|
||||||
// probeQUIC attempts to establish a QUIC connection to the specified address.
|
// probeQUIC attempts to establish a QUIC connection to the specified address.
|
||||||
// We run probeQUIC and probeTLS in parallel and see which one is faster.
|
// We run probeQUIC and probeTLS in parallel and see which one is faster.
|
||||||
func (p *dnsOverHTTPS) probeQUIC(addr string, tlsConfig *tls.Config, ch chan error) {
|
func (doh *dnsOverHTTPS) probeQUIC(addr string, tlsConfig *tls.Config, ch chan error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
timeout := DefaultTimeout
|
timeout := DefaultTimeout
|
||||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout))
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
conn, err := p.dialQuic(ctx, addr, tlsConfig, p.getQUICConfig())
|
conn, err := doh.dialQuic(ctx, addr, tlsConfig, doh.getQUICConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- fmt.Errorf("opening QUIC connection to %s: %w", p.Address(), err)
|
ch <- fmt.Errorf("opening QUIC connection to %s: %w", doh.Address(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -642,10 +647,10 @@ func (p *dnsOverHTTPS) probeQUIC(addr string, tlsConfig *tls.Config, ch chan err
|
||||||
|
|
||||||
// probeTLS attempts to establish a TLS connection to the specified address. We
|
// probeTLS attempts to establish a TLS connection to the specified address. We
|
||||||
// run probeQUIC and probeTLS in parallel and see which one is faster.
|
// run probeQUIC and probeTLS in parallel and see which one is faster.
|
||||||
func (p *dnsOverHTTPS) probeTLS(dialContext dialHandler, tlsConfig *tls.Config, ch chan error) {
|
func (doh *dnsOverHTTPS) probeTLS(dialContext dialHandler, tlsConfig *tls.Config, ch chan error) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
conn, err := p.tlsDial(dialContext, "tcp", tlsConfig)
|
conn, err := doh.tlsDial(dialContext, "tcp", tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- fmt.Errorf("opening TLS connection: %w", err)
|
ch <- fmt.Errorf("opening TLS connection: %w", err)
|
||||||
return
|
return
|
||||||
|
@ -661,8 +666,8 @@ func (p *dnsOverHTTPS) probeTLS(dialContext dialHandler, tlsConfig *tls.Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
// supportsH3 returns true if HTTP/3 is supported by this upstream.
|
// supportsH3 returns true if HTTP/3 is supported by this upstream.
|
||||||
func (p *dnsOverHTTPS) supportsH3() (ok bool) {
|
func (doh *dnsOverHTTPS) supportsH3() (ok bool) {
|
||||||
for _, v := range p.supportedHTTPVersions() {
|
for _, v := range doh.supportedHTTPVersions() {
|
||||||
if v == C.HTTPVersion3 {
|
if v == C.HTTPVersion3 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -672,8 +677,8 @@ func (p *dnsOverHTTPS) supportsH3() (ok bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// supportsHTTP returns true if HTTP/1.1 or HTTP2 is supported by this upstream.
|
// supportsHTTP returns true if HTTP/1.1 or HTTP2 is supported by this upstream.
|
||||||
func (p *dnsOverHTTPS) supportsHTTP() (ok bool) {
|
func (doh *dnsOverHTTPS) supportsHTTP() (ok bool) {
|
||||||
for _, v := range p.supportedHTTPVersions() {
|
for _, v := range doh.supportedHTTPVersions() {
|
||||||
if v == C.HTTPVersion11 || v == C.HTTPVersion2 {
|
if v == C.HTTPVersion11 || v == C.HTTPVersion2 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -683,8 +688,8 @@ func (p *dnsOverHTTPS) supportsHTTP() (ok bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// supportedHTTPVersions returns the list of supported HTTP versions.
|
// supportedHTTPVersions returns the list of supported HTTP versions.
|
||||||
func (p *dnsOverHTTPS) supportedHTTPVersions() (v []C.HTTPVersion) {
|
func (doh *dnsOverHTTPS) supportedHTTPVersions() (v []C.HTTPVersion) {
|
||||||
v = p.httpVersions
|
v = doh.httpVersions
|
||||||
if v == nil {
|
if v == nil {
|
||||||
v = DefaultHTTPVersions
|
v = DefaultHTTPVersions
|
||||||
}
|
}
|
||||||
|
|
138
dns/doq.go
138
dns/doq.go
|
@ -88,9 +88,9 @@ func newDoQ(resolver *Resolver, addr string, adapter string) (dnsClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address implements the Upstream interface for *dnsOverQUIC.
|
// Address implements the Upstream interface for *dnsOverQUIC.
|
||||||
func (p *dnsOverQUIC) Address() string { return p.addr }
|
func (doq *dnsOverQUIC) Address() string { return doq.addr }
|
||||||
|
|
||||||
func (p *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
// When sending queries over a QUIC connection, the DNS Message ID MUST be
|
// When sending queries over a QUIC connection, the DNS Message ID MUST be
|
||||||
// set to zero.
|
// set to zero.
|
||||||
id := m.Id
|
id := m.Id
|
||||||
|
@ -105,49 +105,49 @@ func (p *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg
|
||||||
|
|
||||||
// Check if there was already an active conn before sending the request.
|
// Check if there was already an active conn before sending the request.
|
||||||
// We'll only attempt to re-connect if there was one.
|
// We'll only attempt to re-connect if there was one.
|
||||||
hasConnection := p.hasConnection()
|
hasConnection := doq.hasConnection()
|
||||||
|
|
||||||
// Make the first attempt to send the DNS query.
|
// Make the first attempt to send the DNS query.
|
||||||
msg, err = p.exchangeQUIC(ctx, m)
|
msg, err = doq.exchangeQUIC(ctx, m)
|
||||||
|
|
||||||
// Make up to 2 attempts to re-open the QUIC connection and send the request
|
// Make up to 2 attempts to re-open the QUIC connection and send the request
|
||||||
// again. There are several cases where this workaround is necessary to
|
// again. There are several cases where this workaround is necessary to
|
||||||
// make DoQ usable. We need to make 2 attempts in the case when the
|
// make DoQ usable. We need to make 2 attempts in the case when the
|
||||||
// connection was closed (due to inactivity for example) AND the server
|
// connection was closed (due to inactivity for example) AND the server
|
||||||
// refuses to open a 0-RTT connection.
|
// refuses to open a 0-RTT connection.
|
||||||
for i := 0; hasConnection && p.shouldRetry(err) && i < 2; i++ {
|
for i := 0; hasConnection && doq.shouldRetry(err) && i < 2; i++ {
|
||||||
log.Debugln("re-creating the QUIC connection and retrying due to %v", err)
|
log.Debugln("re-creating the QUIC connection and retrying due to %v", err)
|
||||||
|
|
||||||
// Close the active connection to make sure we'll try to re-connect.
|
// Close the active connection to make sure we'll try to re-connect.
|
||||||
p.closeConnWithError(err)
|
doq.closeConnWithError(err)
|
||||||
|
|
||||||
// Retry sending the request.
|
// Retry sending the request.
|
||||||
msg, err = p.exchangeQUIC(ctx, m)
|
msg, err = doq.exchangeQUIC(ctx, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we're unable to exchange messages, make sure the connection is
|
// If we're unable to exchange messages, make sure the connection is
|
||||||
// closed and signal about an internal error.
|
// closed and signal about an internal error.
|
||||||
p.closeConnWithError(err)
|
doq.closeConnWithError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange implements the Upstream interface for *dnsOverQUIC.
|
// Exchange implements the Upstream interface for *dnsOverQUIC.
|
||||||
func (p *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (doq *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
return p.ExchangeContext(context.Background(), m)
|
return doq.ExchangeContext(context.Background(), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements the Upstream interface for *dnsOverQUIC.
|
// Close implements the Upstream interface for *dnsOverQUIC.
|
||||||
func (p *dnsOverQUIC) Close() (err error) {
|
func (doq *dnsOverQUIC) Close() (err error) {
|
||||||
p.connMu.Lock()
|
doq.connMu.Lock()
|
||||||
defer p.connMu.Unlock()
|
defer doq.connMu.Unlock()
|
||||||
|
|
||||||
runtime.SetFinalizer(p, nil)
|
runtime.SetFinalizer(doq, nil)
|
||||||
|
|
||||||
if p.conn != nil {
|
if doq.conn != nil {
|
||||||
err = p.conn.CloseWithError(QUICCodeNoError, "")
|
err = doq.conn.CloseWithError(QUICCodeNoError, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -155,9 +155,9 @@ func (p *dnsOverQUIC) Close() (err error) {
|
||||||
|
|
||||||
// exchangeQUIC attempts to open a QUIC connection, send the DNS message
|
// exchangeQUIC attempts to open a QUIC connection, send the DNS message
|
||||||
// through it and return the response it got from the server.
|
// through it and return the response it got from the server.
|
||||||
func (p *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg, err error) {
|
func (doq *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg, err error) {
|
||||||
var conn quic.Connection
|
var conn quic.Connection
|
||||||
conn, err = p.getConnection(true)
|
conn, err = doq.getConnection(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ func (p *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg
|
||||||
}
|
}
|
||||||
|
|
||||||
var stream quic.Stream
|
var stream quic.Stream
|
||||||
stream, err = p.openStream(ctx, conn)
|
stream, err = doq.openStream(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,7 @@ func (p *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg
|
||||||
// write-direction of the stream, but does not prevent reading from it.
|
// write-direction of the stream, but does not prevent reading from it.
|
||||||
_ = stream.Close()
|
_ = stream.Close()
|
||||||
|
|
||||||
return p.readMsg(stream)
|
return doq.readMsg(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPrefix adds a 2-byte prefix with the DNS message length.
|
// AddPrefix adds a 2-byte prefix with the DNS message length.
|
||||||
|
@ -199,17 +199,17 @@ func AddPrefix(b []byte) (m []byte) {
|
||||||
|
|
||||||
// shouldRetry checks what error we received and decides whether it is required
|
// shouldRetry checks what error we received and decides whether it is required
|
||||||
// to re-open the connection and retry sending the request.
|
// to re-open the connection and retry sending the request.
|
||||||
func (p *dnsOverQUIC) shouldRetry(err error) (ok bool) {
|
func (doq *dnsOverQUIC) shouldRetry(err error) (ok bool) {
|
||||||
return isQUICRetryError(err)
|
return isQUICRetryError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getBytesPool returns (creates if needed) a pool we store byte buffers in.
|
// getBytesPool returns (creates if needed) a pool we store byte buffers in.
|
||||||
func (p *dnsOverQUIC) getBytesPool() (pool *sync.Pool) {
|
func (doq *dnsOverQUIC) getBytesPool() (pool *sync.Pool) {
|
||||||
p.bytesPoolGuard.Lock()
|
doq.bytesPoolGuard.Lock()
|
||||||
defer p.bytesPoolGuard.Unlock()
|
defer doq.bytesPoolGuard.Unlock()
|
||||||
|
|
||||||
if p.bytesPool == nil {
|
if doq.bytesPool == nil {
|
||||||
p.bytesPool = &sync.Pool{
|
doq.bytesPool = &sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
b := make([]byte, MaxMsgSize)
|
b := make([]byte, MaxMsgSize)
|
||||||
|
|
||||||
|
@ -218,19 +218,19 @@ func (p *dnsOverQUIC) getBytesPool() (pool *sync.Pool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.bytesPool
|
return doq.bytesPool
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConnection opens or returns an existing quic.Connection. useCached
|
// getConnection opens or returns an existing quic.Connection. useCached
|
||||||
// argument controls whether we should try to use the existing cached
|
// argument controls whether we should try to use the existing cached
|
||||||
// connection. If it is false, we will forcibly create a new connection and
|
// connection. If it is false, we will forcibly create a new connection and
|
||||||
// close the existing one if needed.
|
// close the existing one if needed.
|
||||||
func (p *dnsOverQUIC) getConnection(useCached bool) (quic.Connection, error) {
|
func (doq *dnsOverQUIC) getConnection(useCached bool) (quic.Connection, error) {
|
||||||
var conn quic.Connection
|
var conn quic.Connection
|
||||||
p.connMu.RLock()
|
doq.connMu.RLock()
|
||||||
conn = p.conn
|
conn = doq.conn
|
||||||
if conn != nil && useCached {
|
if conn != nil && useCached {
|
||||||
p.connMu.RUnlock()
|
doq.connMu.RUnlock()
|
||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
@ -238,50 +238,50 @@ func (p *dnsOverQUIC) getConnection(useCached bool) (quic.Connection, error) {
|
||||||
// we're recreating the connection, let's create a new one.
|
// we're recreating the connection, let's create a new one.
|
||||||
_ = conn.CloseWithError(QUICCodeNoError, "")
|
_ = conn.CloseWithError(QUICCodeNoError, "")
|
||||||
}
|
}
|
||||||
p.connMu.RUnlock()
|
doq.connMu.RUnlock()
|
||||||
|
|
||||||
p.connMu.Lock()
|
doq.connMu.Lock()
|
||||||
defer p.connMu.Unlock()
|
defer doq.connMu.Unlock()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
conn, err = p.openConnection()
|
conn, err = doq.openConnection()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p.conn = conn
|
doq.conn = conn
|
||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasConnection returns true if there's an active QUIC connection.
|
// hasConnection returns true if there's an active QUIC connection.
|
||||||
func (p *dnsOverQUIC) hasConnection() (ok bool) {
|
func (doq *dnsOverQUIC) hasConnection() (ok bool) {
|
||||||
p.connMu.Lock()
|
doq.connMu.Lock()
|
||||||
defer p.connMu.Unlock()
|
defer doq.connMu.Unlock()
|
||||||
|
|
||||||
return p.conn != nil
|
return doq.conn != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getQUICConfig returns the QUIC config in a thread-safe manner. Note, that
|
// getQUICConfig returns the QUIC config in a thread-safe manner. Note, that
|
||||||
// this method returns a pointer, it is forbidden to change its properties.
|
// this method returns a pointer, it is forbidden to change its properties.
|
||||||
func (p *dnsOverQUIC) getQUICConfig() (c *quic.Config) {
|
func (doq *dnsOverQUIC) getQUICConfig() (c *quic.Config) {
|
||||||
p.quicConfigGuard.Lock()
|
doq.quicConfigGuard.Lock()
|
||||||
defer p.quicConfigGuard.Unlock()
|
defer doq.quicConfigGuard.Unlock()
|
||||||
|
|
||||||
return p.quicConfig
|
return doq.quicConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// resetQUICConfig re-creates the tokens store as we may need to use a new one
|
// resetQUICConfig re-creates the tokens store as we may need to use a new one
|
||||||
// if we failed to connect.
|
// if we failed to connect.
|
||||||
func (p *dnsOverQUIC) resetQUICConfig() {
|
func (doq *dnsOverQUIC) resetQUICConfig() {
|
||||||
p.quicConfigGuard.Lock()
|
doq.quicConfigGuard.Lock()
|
||||||
defer p.quicConfigGuard.Unlock()
|
defer doq.quicConfigGuard.Unlock()
|
||||||
|
|
||||||
p.quicConfig = p.quicConfig.Clone()
|
doq.quicConfig = doq.quicConfig.Clone()
|
||||||
p.quicConfig.TokenStore = newQUICTokenStore()
|
doq.quicConfig.TokenStore = newQUICTokenStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
// openStream opens a new QUIC stream for the specified connection.
|
// openStream opens a new QUIC stream for the specified connection.
|
||||||
func (p *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (quic.Stream, error) {
|
func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (quic.Stream, error) {
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ func (p *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (qui
|
||||||
|
|
||||||
// We can get here if the old QUIC connection is not valid anymore. We
|
// We can get here if the old QUIC connection is not valid anymore. We
|
||||||
// should try to re-create the connection again in this case.
|
// should try to re-create the connection again in this case.
|
||||||
newConn, err := p.getConnection(false)
|
newConn, err := doq.getConnection(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -321,14 +321,18 @@ func (doq *dnsOverQUIC) openConnection() (conn quic.Connection, err error) {
|
||||||
// It's never actually used
|
// It's never actually used
|
||||||
_ = rawConn.Close()
|
_ = rawConn.Close()
|
||||||
cancel()
|
cancel()
|
||||||
|
var addr string
|
||||||
udpConn, ok := rawConn.(*net.UDPConn)
|
udpConn, ok := rawConn.(*net.UDPConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("failed to open connection to %s", doq.addr)
|
if packetConn, ok := rawConn.(*wrapPacketConn); !ok {
|
||||||
|
return nil, fmt.Errorf("failed to open connection to %s", doq.addr)
|
||||||
|
} else {
|
||||||
|
addr = packetConn.RemoteAddr().String()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr = udpConn.RemoteAddr().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := udpConn.RemoteAddr().String()
|
|
||||||
|
|
||||||
ip, port, err := net.SplitHostPort(addr)
|
ip, port, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -379,11 +383,11 @@ func (doq *dnsOverQUIC) openConnection() (conn quic.Connection, err error) {
|
||||||
// closeConnWithError closes the active connection with error to make sure that
|
// closeConnWithError closes the active connection with error to make sure that
|
||||||
// new queries were processed in another connection. We can do that in the case
|
// new queries were processed in another connection. We can do that in the case
|
||||||
// of a fatal error.
|
// of a fatal error.
|
||||||
func (p *dnsOverQUIC) closeConnWithError(err error) {
|
func (doq *dnsOverQUIC) closeConnWithError(err error) {
|
||||||
p.connMu.Lock()
|
doq.connMu.Lock()
|
||||||
defer p.connMu.Unlock()
|
defer doq.connMu.Unlock()
|
||||||
|
|
||||||
if p.conn == nil {
|
if doq.conn == nil {
|
||||||
// Do nothing, there's no active conn anyways.
|
// Do nothing, there's no active conn anyways.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -395,19 +399,19 @@ func (p *dnsOverQUIC) closeConnWithError(err error) {
|
||||||
|
|
||||||
if errors.Is(err, quic.Err0RTTRejected) {
|
if errors.Is(err, quic.Err0RTTRejected) {
|
||||||
// Reset the TokenStore only if 0-RTT was rejected.
|
// Reset the TokenStore only if 0-RTT was rejected.
|
||||||
p.resetQUICConfig()
|
doq.resetQUICConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.conn.CloseWithError(code, "")
|
err = doq.conn.CloseWithError(code, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("failed to close the conn: %v", err)
|
log.Errorln("failed to close the conn: %v", err)
|
||||||
}
|
}
|
||||||
p.conn = nil
|
doq.conn = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readMsg reads the incoming DNS message from the QUIC stream.
|
// readMsg reads the incoming DNS message from the QUIC stream.
|
||||||
func (p *dnsOverQUIC) readMsg(stream quic.Stream) (m *D.Msg, err error) {
|
func (doq *dnsOverQUIC) readMsg(stream quic.Stream) (m *D.Msg, err error) {
|
||||||
pool := p.getBytesPool()
|
pool := doq.getBytesPool()
|
||||||
bufPtr := pool.Get().(*[]byte)
|
bufPtr := pool.Get().(*[]byte)
|
||||||
|
|
||||||
defer pool.Put(bufPtr)
|
defer pool.Put(bufPtr)
|
||||||
|
@ -415,7 +419,7 @@ func (p *dnsOverQUIC) readMsg(stream quic.Stream) (m *D.Msg, err error) {
|
||||||
respBuf := *bufPtr
|
respBuf := *bufPtr
|
||||||
n, err := stream.Read(respBuf)
|
n, err := stream.Read(respBuf)
|
||||||
if err != nil && n == 0 {
|
if err != nil && n == 0 {
|
||||||
return nil, fmt.Errorf("reading response from %s: %w", p.Address(), err)
|
return nil, fmt.Errorf("reading response from %s: %w", doq.Address(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// All DNS messages (queries and responses) sent over DoQ connections MUST
|
// All DNS messages (queries and responses) sent over DoQ connections MUST
|
||||||
|
@ -426,7 +430,7 @@ func (p *dnsOverQUIC) readMsg(stream quic.Stream) (m *D.Msg, err error) {
|
||||||
m = new(D.Msg)
|
m = new(D.Msg)
|
||||||
err = m.Unpack(respBuf[2:])
|
err = m.Unpack(respBuf[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unpacking response from %s: %w", p.Address(), err)
|
return nil, fmt.Errorf("unpacking response from %s: %w", doq.Address(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return m, nil
|
return m, nil
|
||||||
|
@ -512,7 +516,7 @@ func getDialHandler(r *Resolver, proxyAdapter string) dialHandler {
|
||||||
if len(proxyAdapter) == 0 {
|
if len(proxyAdapter) == 0 {
|
||||||
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port), dialer.WithDirect())
|
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port), dialer.WithDirect())
|
||||||
} else {
|
} else {
|
||||||
return dialContextExtra(ctx, proxyAdapter, network, ip.Unmap(), port, dialer.WithDirect())
|
return dialContextExtra(ctx, proxyAdapter, network, ip.Unmap(), port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue