From 4056231e5cf2ccecbf89401420c1b27797a5e20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 21 May 2026 21:38:29 +0800 Subject: [PATCH] Fix tailscale dns --- dns/transport/https.go | 5 +++- dns/transport/https_transport.go | 46 ++++++++++++++++++----------- protocol/tailscale/dns_transport.go | 34 +++++++++++---------- protocol/tailscale/endpoint.go | 2 ++ 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/dns/transport/https.go b/dns/transport/https.go index b508e6ea..206e6fb8 100644 --- a/dns/transport/https.go +++ b/dns/transport/https.go @@ -120,13 +120,16 @@ func NewHTTPSRaw( serverAddr M.Socksaddr, tlsConfig tls.Config, ) *HTTPSTransport { + if tlsConfig != nil { + dialer = tls.NewDialer(dialer, tlsConfig) + } return &HTTPSTransport{ TransportAdapter: adapter, logger: logger, dialer: dialer, destination: destination, headers: headers, - transport: NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr), + transport: NewHTTPSTransportWrapper(dialer, serverAddr, destination), } } diff --git a/dns/transport/https_transport.go b/dns/transport/https_transport.go index 84cfa17c..c823718a 100644 --- a/dns/transport/https_transport.go +++ b/dns/transport/https_transport.go @@ -5,11 +5,13 @@ import ( "errors" "net" "net/http" + "net/url" "sync/atomic" "github.com/sagernet/sing-box/common/tls" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" "golang.org/x/net/http2" ) @@ -22,27 +24,36 @@ type HTTPSTransportWrapper struct { fallback *atomic.Bool } -func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPSTransportWrapper { +func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, destination *url.URL) *HTTPSTransportWrapper { var fallback atomic.Bool + if destination.Scheme == "http" { + // plain HTTP DoH used by Tailscale + fallback.Store(true) + } return &HTTPSTransportWrapper{ http2Transport: &http2.Transport{ DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) { - tlsConn, err := dialer.DialTLSContext(ctx, serverAddr) + resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr) if err != nil { return nil, err } - state := tlsConn.ConnectionState() - if state.NegotiatedProtocol == http2.NextProtoTLS { - return tlsConn, nil + if tlsConn, isTLSConn := resultConn.(tls.Conn); isTLSConn { + state := tlsConn.ConnectionState() + if state.NegotiatedProtocol != http2.NextProtoTLS { + tlsConn.Close() + fallback.Store(true) + return nil, errFallback + } } - tlsConn.Close() - fallback.Store(true) - return nil, errFallback + return resultConn, nil }, }, httpTransport: &http.Transport{ + DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, N.NetworkTCP, serverAddr) + }, DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) { - return dialer.DialTLSContext(ctx, serverAddr) + return dialer.DialContext(ctx, N.NetworkTCP, serverAddr) }, }, fallback: &fallback, @@ -52,16 +63,15 @@ func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPST func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) { if h.fallback.Load() { return h.httpTransport.RoundTrip(request) - } else { - response, err := h.http2Transport.RoundTrip(request) - if err != nil { - if errors.Is(err, errFallback) { - return h.httpTransport.RoundTrip(request) - } - return nil, err - } - return response, nil } + response, err := h.http2Transport.RoundTrip(request) + if err != nil { + if errors.Is(err, errFallback) { + return h.httpTransport.RoundTrip(request) + } + return nil, err + } + return response, nil } func (h *HTTPSTransportWrapper) CloseIdleConnections() { diff --git a/protocol/tailscale/dns_transport.go b/protocol/tailscale/dns_transport.go index 4195235c..6c4b551f 100644 --- a/protocol/tailscale/dns_transport.go +++ b/protocol/tailscale/dns_transport.go @@ -161,8 +161,9 @@ func (t *DNSTransport) updateDNSServers(routeConfig *router.Config, dnsConfig *n func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dnstype.Resolver) (adapter.DNSTransport, error) { serverURL, parseURLErr := url.Parse(resolver.Addr) + isHTTPScheme := parseURLErr == nil && (serverURL.Scheme == "http" || serverURL.Scheme == "https") var myDialer N.Dialer - if parseURLErr == nil && serverURL.Scheme == "http" { + if isHTTPScheme && serverURL.Scheme == "http" { myDialer = t.endpoint } else { myDialer = directDialer() @@ -170,36 +171,39 @@ func (t *DNSTransport) createResolver(directDialer func() N.Dialer, resolver *dn if len(resolver.BootstrapResolution) > 0 { bootstrapTransport := transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, M.SocksaddrFrom(resolver.BootstrapResolution[0], 53)) myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", adapter.DNSQueryOptions{Transport: bootstrapTransport}, 0) - } - if serverAddr := M.ParseSocksaddr(resolver.Addr); serverAddr.IsValid() { - if serverAddr.Port == 0 { - serverAddr.Port = 53 - } - return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil - } else if parseURLErr != nil { - return nil, E.Cause(parseURLErr, "parse resolver address") } else { + myDialer = dialer.NewResolveDialer(t.ctx, myDialer, false, "", t.endpoint.queryOptions, 0) + } + if isHTTPScheme { + serverAddr := M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port()) switch serverURL.Scheme { case "https": - serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port()) if serverAddr.Port == 0 { serverAddr.Port = 443 } tlsConfig := common.Must1(tls.NewClient(t.ctx, t.logger, serverAddr.AddrString(), option.OutboundTLSOptions{ - ALPN: []string{http2.NextProtoTLS, "http/1.1"}, + Enabled: true, + ALPN: []string{http2.NextProtoTLS, "http/1.1"}, })) return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, tlsConfig), nil case "http": - serverAddr = M.ParseSocksaddrHostPortStr(serverURL.Hostname(), serverURL.Port()) if serverAddr.Port == 0 { serverAddr.Port = 80 } return transport.NewHTTPSRaw(t.TransportAdapter, t.logger, myDialer, serverURL, http.Header{}, serverAddr, nil), nil - // case "tls": - default: - return nil, E.New("unknown resolver scheme: ", serverURL.Scheme) } } + serverAddr := M.ParseSocksaddr(resolver.Addr) + if !serverAddr.IsValid() { + if parseURLErr != nil { + return nil, E.Cause(parseURLErr, "parse resolver address") + } + return nil, E.New("invalid resolver address: ", resolver.Addr) + } + if serverAddr.Port == 0 { + serverAddr.Port = 53 + } + return transport.NewUDPRaw(t.logger, t.TransportAdapter, myDialer, serverAddr), nil } func buildRoutePrefixes(routeConfig *router.Config) []netip.Prefix { diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index e122b494..255f075e 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -83,6 +83,7 @@ type Endpoint struct { ctx context.Context router adapter.Router logger logger.ContextLogger + queryOptions adapter.DNSQueryOptions dnsRouter adapter.DNSRouter network adapter.NetworkManager platformInterface adapter.PlatformInterface @@ -246,6 +247,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL ctx: ctx, router: router, logger: logger, + queryOptions: outboundDialer.(dialer.ResolveDialer).QueryOptions(), dnsRouter: dnsRouter, network: service.FromContext[adapter.NetworkManager](ctx), platformInterface: service.FromContext[adapter.PlatformInterface](ctx),