fix: use correct tailscale dns behaviour
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
- **双向转发**:`forward`(Tailscale → 本地)与 `connect`(本地 → Tailscale)两种模式
|
- **双向转发**:`forward`(Tailscale → 本地)与 `connect`(本地 → Tailscale)两种模式
|
||||||
- **TCP / UDP 全支持**:透明转发 TCP 流与 UDP 数据包
|
- **TCP / UDP 全支持**:透明转发 TCP 流与 UDP 数据包
|
||||||
- **Minecraft 专用模式**:支持局域网广播发现(MOTD),让本地设备发现 Tailnet 上的 Minecraft 服务器
|
- **Minecraft 专用模式**:支持局域网广播发现(MOTD),让本地设备发现 Tailnet 上的 Minecraft 服务器
|
||||||
- **MagicDNS 主机名补全**:`dst_addr` 支持短主机名(如 `home:8080`),启动时自动补全为 `home.<suffix>:8080`
|
- **MagicDNS 主机名补全**:`dst_addr` 支持按照 Tailscale 规则正确解析 Split DNS 和 Magic DNS
|
||||||
- **连接类型识别**:区分 `direct` 直连与 `derp` 中继,便于排查延迟问题
|
- **连接类型识别**:区分 `direct` 直连与 `derp` 中继,便于排查延迟问题
|
||||||
- **对端连通性诊断**:定期 ping 目标节点并报告延迟与连接路径(direct/DERP)
|
- **对端连通性诊断**:定期 ping 目标节点并报告延迟与连接路径(direct/DERP)
|
||||||
- **Web 管理**:内置 Tailscale Web Client(端口 `5252`),可在线管理节点配置
|
- **Web 管理**:内置 Tailscale Web Client(端口 `5252`),可在线管理节点配置
|
||||||
|
|||||||
@@ -57,14 +57,6 @@ func InitTsNet(ctx context.Context, cfg *Core, logger *slog.Logger, withDebugLog
|
|||||||
logger.With(slog.String("ip", ip.String())).Info("ip got from tsnet")
|
logger.With(slog.String("ip", ip.String())).Info("ip got from tsnet")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawSuffix, err := GetMagicDNSSuffixFromStatus(status)
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("failed to extract MagicDNS suffix", slog.String("error", err.Error()))
|
|
||||||
} else {
|
|
||||||
SetMagicDNSSuffix(rawSuffix)
|
|
||||||
logger.Info("MagicDNS suffix extracted", slog.String("suffix", rawSuffix))
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.AcceptRoutes {
|
if cfg.AcceptRoutes {
|
||||||
lc, err := srv.LocalClient()
|
lc, err := srv.LocalClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+78
-57
@@ -8,11 +8,12 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
"tailscale.com/client/local"
|
"tailscale.com/client/local"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
|
"tailscale.com/net/dns"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tsnet"
|
"tailscale.com/tsnet"
|
||||||
)
|
)
|
||||||
@@ -80,24 +81,14 @@ func resolveAddr(ctx context.Context, srv *tsnet.Server, addr string) (*netip.Ad
|
|||||||
func getPeerFromRules(ctx context.Context, srv *tsnet.Server, rules map[string][]ConnectRule, logger *slog.Logger) ([]netip.Addr, error) {
|
func getPeerFromRules(ctx context.Context, srv *tsnet.Server, rules map[string][]ConnectRule, logger *slog.Logger) ([]netip.Addr, error) {
|
||||||
peerSet := make(map[netip.Addr]struct{})
|
peerSet := make(map[netip.Addr]struct{})
|
||||||
|
|
||||||
for tag, rrs := range rules {
|
for _, rrs := range rules {
|
||||||
for _, rule := range rrs {
|
for _, rule := range rrs {
|
||||||
rule := rule
|
rule := rule
|
||||||
tag := tag
|
ap, err := netip.ParseAddrPort(rule.DstAddr)
|
||||||
|
|
||||||
ap, _, err := net.SplitHostPort(rule.DstAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug("error parsing rule", "tag", tag, "dst", rule.DstAddr, "err", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
addr, err := resolveAddr(ctx, srv, ap)
|
peerSet[ap.Addr()] = struct{}{}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("failed to resolve address", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
logger.Debug("address found", "dst_addr", rule.DstAddr, "tag", tag, "address", addr)
|
|
||||||
peerSet[*addr] = struct{}{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,36 +182,7 @@ func getSelfTsnetAddr(srv *tsnet.Server) netip.Addr {
|
|||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
func PresolveDstAddrWithSuffix(dst string, srv *tsnet.Server) (string, bool, error) {
|
||||||
magicDNSSuffixMu sync.RWMutex
|
|
||||||
magicDNSSuffix string
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetMagicDNSSuffix(raw string) {
|
|
||||||
magicDNSSuffixMu.Lock()
|
|
||||||
defer magicDNSSuffixMu.Unlock()
|
|
||||||
magicDNSSuffix = strings.Trim(raw, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMagicDNSSuffix() (string, bool) {
|
|
||||||
magicDNSSuffixMu.RLock()
|
|
||||||
defer magicDNSSuffixMu.RUnlock()
|
|
||||||
if magicDNSSuffix == "" {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return magicDNSSuffix, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMagicDNSSuffixFromStatus(st *ipnstate.Status) (string, error) {
|
|
||||||
suffix := st.CurrentTailnet.MagicDNSSuffix
|
|
||||||
suffix = strings.Trim(suffix, ".")
|
|
||||||
if suffix == "" {
|
|
||||||
return "", errors.New("magic dns suffix not found in status")
|
|
||||||
}
|
|
||||||
return suffix, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NormalizeDstAddrWithSuffix(dst string) (string, bool, error) {
|
|
||||||
host, port, err := net.SplitHostPort(dst)
|
host, port, err := net.SplitHostPort(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dst, false, err
|
return dst, false, err
|
||||||
@@ -230,26 +192,85 @@ func NormalizeDstAddrWithSuffix(dst string) (string, bool, error) {
|
|||||||
return dst, false, nil
|
return dst, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(host, ".") {
|
dnsMgr, ok := srv.Sys().DNSManager.GetOK()
|
||||||
return dst, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
suffix, ok := GetMagicDNSSuffix()
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return dst, false, nil
|
return dst, false, errors.New("DNS manager not available")
|
||||||
}
|
}
|
||||||
|
addr, err := resolveHostViaResolver(dnsMgr, host)
|
||||||
normalized := net.JoinHostPort(host+"."+suffix, port)
|
if err != nil {
|
||||||
return normalized, true, nil
|
return dst, false, err
|
||||||
|
}
|
||||||
|
return net.JoinHostPort(addr.String(), port), true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NormalizeConnectRulesDstAddr(rules map[string][]ConnectRule, logger *slog.Logger) {
|
// resolveHostViaResolver resolves a hostname to a netip.Addr using the
|
||||||
|
// Tailscale DNS resolver. It queries A and AAAA records in a single
|
||||||
|
// message and follows CNAME chains (up to 8 levels deep).
|
||||||
|
func resolveHostViaResolver(resolver *dns.Manager, host string) (netip.Addr, error) {
|
||||||
|
return resolveHostWithDepth(resolver, host, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveHostWithDepth(r *dns.Manager, host string, depth int) (netip.Addr, error) {
|
||||||
|
const maxCNAMEChase = 8
|
||||||
|
if depth > maxCNAMEChase {
|
||||||
|
return netip.Addr{}, fmt.Errorf("CNAME chain too deep for %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := dnsmessage.NewName(host + ".")
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, fmt.Errorf("invalid hostname %s: %w", host, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := dnsmessage.Message{
|
||||||
|
Header: dnsmessage.Header{RecursionDesired: true},
|
||||||
|
Questions: []dnsmessage.Question{
|
||||||
|
{Name: name, Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
queryBytes, err := msg.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, fmt.Errorf("failed to pack DNS query: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
respBytes, err := r.Query(ctx, queryBytes, "udp", netip.AddrPort{})
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, fmt.Errorf("DNS resolution failed for %s: %w", host, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp dnsmessage.Message
|
||||||
|
if err := resp.Unpack(respBytes); err != nil {
|
||||||
|
return netip.Addr{}, fmt.Errorf("failed to unpack DNS response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cnameTarget string
|
||||||
|
for _, ans := range resp.Answers {
|
||||||
|
switch r := ans.Body.(type) {
|
||||||
|
case *dnsmessage.AResource:
|
||||||
|
if ip := netip.AddrFrom4(r.A); ip.IsValid() {
|
||||||
|
return ip, nil
|
||||||
|
}
|
||||||
|
case *dnsmessage.CNAMEResource:
|
||||||
|
cnameTarget = strings.TrimSuffix(r.CNAME.String(), ".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow CNAME if no direct A found
|
||||||
|
if cnameTarget != "" {
|
||||||
|
return resolveHostWithDepth(r, cnameTarget, depth+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netip.Addr{}, fmt.Errorf("no A/AAAA record found for %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PresolveConnectRulesDstAddr(rules map[string][]ConnectRule, logger *slog.Logger, srv *tsnet.Server) {
|
||||||
for tag, rrs := range rules {
|
for tag, rrs := range rules {
|
||||||
for i := range rrs {
|
for i := range rrs {
|
||||||
rule := &rrs[i]
|
rule := &rrs[i]
|
||||||
normalized, changed, err := NormalizeDstAddrWithSuffix(rule.DstAddr)
|
normalized, changed, err := PresolveDstAddrWithSuffix(rule.DstAddr, srv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug("failed to normalize dst_addr",
|
logger.Warn("failed to resolve dst_addr",
|
||||||
slog.String("tag", tag),
|
slog.String("tag", tag),
|
||||||
slog.String("dst", rule.DstAddr),
|
slog.String("dst", rule.DstAddr),
|
||||||
slog.String("error", err.Error()),
|
slog.String("error", err.Error()),
|
||||||
@@ -257,7 +278,7 @@ func NormalizeConnectRulesDstAddr(rules map[string][]ConnectRule, logger *slog.L
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if changed {
|
if changed {
|
||||||
logger.Debug("dst_addr normalized with MagicDNS suffix",
|
logger.Debug("dst_addr resolved",
|
||||||
slog.String("tag", tag),
|
slog.String("tag", tag),
|
||||||
slog.String("original", rule.DstAddr),
|
slog.String("original", rule.DstAddr),
|
||||||
slog.String("normalized", normalized),
|
slog.String("normalized", normalized),
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func serviceLogic(configPath string, isTsnetDebug bool, configURL string, logger
|
|||||||
}
|
}
|
||||||
logger.Info("tsnet server initialized")
|
logger.Info("tsnet server initialized")
|
||||||
|
|
||||||
core.NormalizeConnectRulesDstAddr(cfg.Connect, logger)
|
core.PresolveConnectRulesDstAddr(cfg.Connect, logger, srv)
|
||||||
|
|
||||||
core.StartForwarders(ctx, srv, cfg.Forward)
|
core.StartForwarders(ctx, srv, cfg.Forward)
|
||||||
core.StartConnectors(ctx, srv, cfg.Connect)
|
core.StartConnectors(ctx, srv, cfg.Connect)
|
||||||
|
|||||||
Reference in New Issue
Block a user