feat(main): add connectivityCheck for diagnostics

This commit is contained in:
nullcat
2026-05-18 15:04:48 +08:00
parent b1b9a21eb2
commit 56f37faa84
2 changed files with 129 additions and 2 deletions
+127 -2
View File
@@ -2,8 +2,17 @@ package core
import (
"context"
"errors"
"fmt"
"log/slog"
"net"
"net/netip"
"strings"
"time"
"tailscale.com/client/local"
"tailscale.com/tailcfg"
"tailscale.com/tsnet"
)
func StartTimeWatchDog(ctx context.Context, logger *slog.Logger) <-chan struct{} {
@@ -27,11 +36,127 @@ func StartTimeWatchDog(ctx context.Context, logger *slog.Logger) <-chan struct{}
ch <- struct{}{}
logger.Debug("signal sent, watchdog exiting...")
return
} else {
lastUnix = nowUnix
}
lastUnix = nowUnix
}
}
}()
return ch
}
func resolveAddr(ctx context.Context, srv *tsnet.Server, addr string) (*netip.Addr, error) {
lc, err := srv.LocalClient()
if err != nil {
return nil, err
}
stat, err := lc.Status(ctx)
if err != nil {
return nil, err
}
if ip, err := netip.ParseAddr(addr); err == nil {
for _, peer := range stat.Peer {
for _, ipRange := range peer.AllowedIPs.All() {
if ipRange.Contains(ip) {
return &peer.TailscaleIPs[0], nil
}
}
}
} else {
// addr is domain, resolve it
for _, peer := range stat.Peer {
dnsName := strings.TrimSuffix(peer.DNSName, ".")
if dnsName == addr {
return &peer.TailscaleIPs[0], nil
}
}
}
return nil, errors.New(fmt.Sprintf("addr '%s' not found in tsnet", addr))
}
func getPeerFromRules(ctx context.Context, srv *tsnet.Server, rules map[string][]ConnectRule, logger *slog.Logger) ([]netip.Addr, error) {
peerSet := make(map[netip.Addr]struct{})
for tag, rrs := range rules {
for _, rule := range rrs {
rule := rule
tag := tag
ap, _, err := net.SplitHostPort(rule.DstAddr)
if err != nil {
logger.Debug("error parsing rule", "tag", tag, "dst", rule.DstAddr, "err", err)
// err log
continue
}
addr, err := resolveAddr(ctx, srv, ap)
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{}{}
}
}
var result []netip.Addr
for peer := range peerSet {
result = append(result, peer)
}
return result, nil
}
func peerConnectivityLogic(ctx context.Context, lc *local.Client, relativePeers []netip.Addr, logger *slog.Logger) {
for _, peer := range relativePeers {
ping, err := lc.Ping(ctx, peer, tailcfg.PingDisco)
if err != nil {
logger.Debug("connectivity: failed to ping peer", "peer", peer, "err", err)
return
}
var connect string
if ping.DERPRegionCode == "" {
connect = "direct"
} else {
connect = ping.DERPRegionCode
}
logger.Info("connectivity: peer pinged",
"peer", peer,
"latency", fmt.Sprintf("%.2fms", ping.LatencySeconds*1000),
"connect", connect,
)
}
}
func StartPeerConnectivityDiagnostics(ctx context.Context, logger *slog.Logger, srv *tsnet.Server, rules map[string][]ConnectRule) {
relativePeers, err := getPeerFromRules(ctx, srv, rules, logger)
if err != nil {
return
}
logger.Debug("Peers loaded", "count", len(relativePeers))
if len(relativePeers) == 0 {
return
}
go func() {
lc, err := srv.LocalClient()
if err != nil {
logger.Error("failed to get local client", "err", err)
return
}
ticker := time.NewTicker(120 * time.Second)
defer ticker.Stop()
peerConnectivityLogic(ctx, lc, relativePeers, logger) // execute now
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
peerConnectivityLogic(ctx, lc, relativePeers, logger)
}
}
}()
}