feat(main): add connectivityCheck for diagnostics
This commit is contained in:
+127
-2
@@ -2,8 +2,17 @@ package core
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"tailscale.com/client/local"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/tsnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartTimeWatchDog(ctx context.Context, logger *slog.Logger) <-chan struct{} {
|
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{}{}
|
ch <- struct{}{}
|
||||||
logger.Debug("signal sent, watchdog exiting...")
|
logger.Debug("signal sent, watchdog exiting...")
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
lastUnix = nowUnix
|
|
||||||
}
|
}
|
||||||
|
lastUnix = nowUnix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return ch
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ func serviceLogic(configPath string, logger *slog.Logger) bool {
|
|||||||
|
|
||||||
core.RunLanDiscoverService(ctx, cfg.Connect, logger.With("from", "lan_service"))
|
core.RunLanDiscoverService(ctx, cfg.Connect, logger.With("from", "lan_service"))
|
||||||
|
|
||||||
|
core.StartPeerConnectivityDiagnostics(ctx, logger, srv, cfg.Connect)
|
||||||
|
|
||||||
sigHandler := make(chan os.Signal, 1)
|
sigHandler := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigHandler, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(sigHandler, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user