Files
tslink/core/utils.go
T

214 lines
4.9 KiB
Go

package core
import (
"context"
"errors"
"fmt"
"log/slog"
"net"
"net/netip"
"strings"
"time"
"tailscale.com/client/local"
"tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg"
"tailscale.com/tsnet"
)
func StartTimeWatchDog(ctx context.Context, logger *slog.Logger) <-chan struct{} {
logger.Info("starting watchdog")
ch := make(chan struct{}, 1)
go func() {
lastUnix := time.Now().Unix()
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
nowUnix := time.Now().Unix()
diff := nowUnix - lastUnix
if diff > 300 {
logger.Warn("system time jump detected(wake up from sleep?)",
slog.Int64("jump_seconds", diff),
)
ch <- struct{}{}
logger.Debug("signal sent, watchdog exiting...")
return
}
lastUnix = nowUnix
}
}
}()
return ch
}
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)
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 {
loLog := logger.With("peer", peer)
ping, err := func() (*ipnstate.PingResult, error) {
cnclCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
ping, err := lc.Ping(cnclCtx, peer, tailcfg.PingDisco)
return ping, err
}()
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
loLog.Warn("connectivity: peer ping timeout")
} else {
loLog.Warn("connectivity: failed to ping peer", "err", err)
}
continue
}
peerInfo, err := lc.WhoIs(ctx, peer.String())
if err != nil {
loLog.Warn("failed to get peer info", "err", err)
} else {
loLog = loLog.With("name", peerInfo.Node.ComputedName)
}
var connect string
if ping.DERPRegionCode == "" {
connect = "direct"
} else {
connect = ping.DERPRegionCode
}
loLog.Info("connectivity: peer pinged",
"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)
}
}
}()
}
func getSelfTsnetAddr(srv *tsnet.Server) netip.Addr {
ip4, ip6 := srv.TailscaleIPs()
ip := ip4
if !ip.IsValid() {
ip = ip6
}
return ip
}
func NormalizeDstAddrWithSuffix(ctx context.Context, srv *tsnet.Server, dst string) (string, bool, error) {
host, port, err := net.SplitHostPort(dst)
if err != nil {
return dst, false, err
}
if _, err := netip.ParseAddr(host); err == nil {
return dst, false, nil
}
suffix, ok := GetMagicDNSSuffix()
if !ok {
return dst, false, nil
}
normalized := net.JoinHostPort(host+"."+suffix, port)
// check domain exists before use
if strings.Contains(host, ".") {
_, err = resolveAddr(ctx, srv, normalized)
if err != nil {
return dst, false, nil
}
}
return normalized, true, nil
}
func NormalizeConnectRulesDstAddr(ctx context.Context, srv *tsnet.Server, rules map[string][]ConnectRule, logger *slog.Logger) {
for tag, rrs := range rules {
for i := range rrs {
rule := &rrs[i]
normalized, changed, err := NormalizeDstAddrWithSuffix(ctx, srv, rule.DstAddr)
if err != nil {
logger.Debug("failed to normalize dst_addr",
slog.String("tag", tag),
slog.String("dst", rule.DstAddr),
slog.String("error", err.Error()),
)
continue
}
if changed {
logger.Debug("dst_addr normalized with MagicDNS suffix",
slog.String("tag", tag),
slog.String("original", rule.DstAddr),
slog.String("normalized", normalized),
)
rule.DstAddr = normalized
}
}
}
}