From 21291afa33bf9efc411060bbfb34a50be468fd92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 20 May 2026 16:45:22 +0800 Subject: [PATCH] tailscale: Fix handle peer DNS query --- go.mod | 2 +- go.sum | 4 +- protocol/tailscale/endpoint.go | 111 +++++++++++++++++++++------------ 3 files changed, 74 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index b8b2830c5..ce1c75b21 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/sagernet/sing-tun v0.8.11-0.20260603045801-6e76db79f94a github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 github.com/sagernet/smux v1.5.50-sing-box-mod.1 - github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7 + github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260520083128-cbb28480e2d5 github.com/sagernet/wireguard-go v0.0.3 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index 3466575da..65c1463a3 100644 --- a/go.sum +++ b/go.sum @@ -262,8 +262,8 @@ github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkV github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY= github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478= github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7 h1:8zc1Aph1+ElqF9/7aSPkO0o4vTd+AfQC+CO324mLWGg= -github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260520083128-cbb28480e2d5 h1:QPLC5oJWzqHhxLobokFM7Utnqbr5OkLT/Kjc0od6qJs= +github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7.0.20260520083128-cbb28480e2d5/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc= github.com/sagernet/wireguard-go v0.0.3 h1:6ebmwj/SFQRnYv6/nRCnwUzf+KFepF8tIBd57IAq1jE= github.com/sagernet/wireguard-go v0.0.3/go.mod h1:hEqi4y5czEg6LYtX2Bpjg+lV0b/J1n+5rA885Z66Mx0= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= diff --git a/protocol/tailscale/endpoint.go b/protocol/tailscale/endpoint.go index 988315577..41e6ad50b 100644 --- a/protocol/tailscale/endpoint.go +++ b/protocol/tailscale/endpoint.go @@ -28,9 +28,10 @@ import ( "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/route/rule" + R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun/ping" "github.com/sagernet/sing/common" @@ -61,6 +62,7 @@ import ( "github.com/sagernet/tailscale/wgengine/router" "github.com/sagernet/tailscale/wgengine/wgcfg" + mDNS "github.com/miekg/dns" "go4.org/netipx" ) @@ -170,47 +172,47 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } + dialerQueryOptions := outboundDialer.(dialer.ResolveDialer).QueryOptions() dnsRouter := service.FromContext[adapter.DNSRouter](ctx) - server := &tsnet.Server{ - Dir: stateDirectory, - Hostname: hostname, - Logf: func(format string, args ...any) { - logger.Trace(fmt.Sprintf(format, args...)) - }, - UserLogf: func(format string, args ...any) { - logger.Debug(fmt.Sprintf(format, args...)) - }, - Ephemeral: options.Ephemeral, - AuthKey: options.AuthKey, - ControlURL: options.ControlURL, - AdvertiseTags: options.AdvertiseTags, - Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger}, - LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) { - return dnsRouter.Lookup(ctx, host, outboundDialer.(dialer.ResolveDialer).QueryOptions()) - }, - DNS: &dnsConfigurtor{}, - HTTPClient: &http.Client{ - Transport: &http.Transport{ - ForceAttemptHTTP2: true, - DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { - return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(address)) - }, - TLSClientConfig: &tls.Config{ - RootCAs: adapter.RootPoolFromContext(ctx), - Time: ntp.TimeFuncFromContext(ctx), + return &Endpoint{ + Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil), + ctx: ctx, + router: router, + logger: logger, + dnsRouter: dnsRouter, + network: service.FromContext[adapter.NetworkManager](ctx), + platformInterface: service.FromContext[adapter.PlatformInterface](ctx), + server: &tsnet.Server{ + Dir: stateDirectory, + Hostname: hostname, + Logf: func(format string, args ...any) { + logger.Trace(fmt.Sprintf(format, args...)) + }, + UserLogf: func(format string, args ...any) { + logger.Debug(fmt.Sprintf(format, args...)) + }, + Ephemeral: options.Ephemeral, + AuthKey: options.AuthKey, + ControlURL: options.ControlURL, + AdvertiseTags: options.AdvertiseTags, + Dialer: &endpointDialer{Dialer: outboundDialer, logger: logger}, + LookupHook: func(ctx context.Context, host string) ([]netip.Addr, error) { + return dnsRouter.Lookup(ctx, host, dialerQueryOptions) + }, + DNS: &dnsConfigurtor{}, + HTTPClient: &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + return outboundDialer.DialContext(ctx, network, M.ParseSocksaddr(address)) + }, + TLSClientConfig: &tls.Config{ + RootCAs: adapter.RootPoolFromContext(ctx), + Time: ntp.TimeFuncFromContext(ctx), + }, }, }, }, - } - return &Endpoint{ - Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil), - ctx: ctx, - router: router, - logger: logger, - dnsRouter: dnsRouter, - network: service.FromContext[adapter.NetworkManager](ctx), - platformInterface: service.FromContext[adapter.PlatformInterface](ctx), - server: server, acceptRoutes: options.AcceptRoutes, exitNode: options.ExitNode, exitNodeAllowLANAccess: options.ExitNodeAllowLANAccess, @@ -229,6 +231,8 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL func (t *Endpoint) Start(stage adapter.StartStage) error { switch stage { + case adapter.StartStateInitialize: + t.server.PeerDNSQueryHandler = (*peerDNSQueryHandler)(t) case adapter.StartStateStart: return t.start() case adapter.StartStatePostStart: @@ -681,9 +685,9 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina }, routeContext, timeout, false) if err != nil { switch { - case rule.IsBypassed(err): + case R.IsBypassed(err): err = nil - case rule.IsRejected(err): + case R.IsRejected(err): t.logger.Trace("reject ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()) default: if network == N.NetworkICMP { @@ -877,3 +881,30 @@ func (c *dnsConfigurtor) GetBaseConfig() (tsDNS.OSConfig, error) { func (c *dnsConfigurtor) Close() error { return nil } + +type peerDNSQueryHandler Endpoint + +func (t *peerDNSQueryHandler) HandlePeerDNSQuery(ctx context.Context, query []byte, sourceAddress netip.AddrPort, allowName func(name string) bool) ([]byte, error) { + var message mDNS.Msg + err := message.Unpack(query) + if err != nil { + return nil, err + } + for _, question := range message.Question { + if allowName != nil && !allowName(question.Name) { + return dns.FixedResponseStatus(&message, mDNS.RcodeRefused).Pack() + } + } + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = M.SocksaddrFromNetIP(sourceAddress) + response, err := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{}) + if err != nil { + if !R.IsRejected(err) && !E.IsClosedOrCanceled(err) { + t.logger.ErrorContext(ctx, E.Cause(err, "process peer DNS query")) + } + return nil, err + } + return response.Pack() +}