cmd/natc: allow specifying the tsnet state dir

Which can make operating the service more convenient.
It makes sense to put the cluster state with this if specified, so
rearrange the logic to handle that.

Updates #14667

Signed-off-by: Fran Bull <fran@tailscale.com>
This commit is contained in:
Fran Bull 2025-06-06 09:38:34 -07:00 committed by franbull
parent 6a93b17c8c
commit 3b25e94352
2 changed files with 34 additions and 33 deletions

View File

@ -10,8 +10,6 @@ import (
"fmt" "fmt"
"log" "log"
"net/netip" "net/netip"
"os"
"path/filepath"
"time" "time"
"github.com/hashicorp/raft" "github.com/hashicorp/raft"
@ -155,11 +153,7 @@ func (ipp *ConsensusIPPool) domainLookup(from tailcfg.NodeID, addr netip.Addr) (
func (ipp *ConsensusIPPool) StartConsensus(ctx context.Context, ts *tsnet.Server, clusterTag string, clusterStateDir string) error { func (ipp *ConsensusIPPool) StartConsensus(ctx context.Context, ts *tsnet.Server, clusterTag string, clusterStateDir string) error {
cfg := tsconsensus.DefaultConfig() cfg := tsconsensus.DefaultConfig()
cfg.ServeDebugMonitor = true cfg.ServeDebugMonitor = true
var err error cfg.StateDirPath = clusterStateDir
cfg.StateDirPath, err = getStatePath(clusterStateDir)
if err != nil {
return err
}
cns, err := tsconsensus.Start(ctx, ts, ipp, clusterTag, cfg) cns, err := tsconsensus.Start(ctx, ts, ipp, clusterTag, cfg)
if err != nil { if err != nil {
return err return err
@ -211,30 +205,6 @@ func (ps *consensusPerPeerState) unusedIPV4(ipset *netipx.IPSet, reuseDeadline t
return netip.Addr{}, false, "", errors.New("ip pool exhausted") return netip.Addr{}, false, "", errors.New("ip pool exhausted")
} }
func getStatePath(pathFromFlag string) (string, error) {
var dirPath string
if pathFromFlag != "" {
dirPath = pathFromFlag
} else {
confDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
dirPath = filepath.Join(confDir, "nat-connector-cluster-state")
}
if err := os.MkdirAll(dirPath, 0700); err != nil {
return "", err
}
if fi, err := os.Stat(dirPath); err != nil {
return "", err
} else if !fi.IsDir() {
return "", fmt.Errorf("%v is not a directory", dirPath)
}
return dirPath, nil
}
// isCloseToExpiry returns true if the lastUsed and now times are more than // isCloseToExpiry returns true if the lastUsed and now times are more than
// half the lifetime apart // half the lifetime apart
func isCloseToExpiry(lastUsed, now time.Time, lifetime time.Duration) bool { func isCloseToExpiry(lastUsed, now time.Time, lifetime time.Duration) bool {

View File

@ -18,6 +18,7 @@ import (
"net/http" "net/http"
"net/netip" "net/netip"
"os" "os"
"path/filepath"
"strings" "strings"
"time" "time"
@ -59,7 +60,7 @@ func main() {
wgPort = fs.Uint("wg-port", 0, "udp port for wireguard and peer to peer traffic") wgPort = fs.Uint("wg-port", 0, "udp port for wireguard and peer to peer traffic")
clusterTag = fs.String("cluster-tag", "", "optionally run in a consensus cluster with other nodes with this tag") clusterTag = fs.String("cluster-tag", "", "optionally run in a consensus cluster with other nodes with this tag")
server = fs.String("login-server", ipn.DefaultControlURL, "the base URL of control server") server = fs.String("login-server", ipn.DefaultControlURL, "the base URL of control server")
clusterStateDir = fs.String("cluster-state-dir", "", "path to directory in which to store raft state") stateDir = fs.String("state-dir", "", "path to directory in which to store app state")
) )
ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("TS_NATC")) ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("TS_NATC"))
@ -96,6 +97,7 @@ func main() {
} }
ts := &tsnet.Server{ ts := &tsnet.Server{
Hostname: *hostname, Hostname: *hostname,
Dir: *stateDir,
} }
ts.ControlURL = *server ts.ControlURL = *server
if *wgPort != 0 { if *wgPort != 0 {
@ -156,7 +158,11 @@ func main() {
var ipp ippool.IPPool var ipp ippool.IPPool
if *clusterTag != "" { if *clusterTag != "" {
cipp := ippool.NewConsensusIPPool(addrPool) cipp := ippool.NewConsensusIPPool(addrPool)
err = cipp.StartConsensus(ctx, ts, *clusterTag, *clusterStateDir) clusterStateDir, err := getClusterStatePath(*stateDir)
if err != nil {
log.Fatalf("Creating cluster state dir failed: %v", err)
}
err = cipp.StartConsensus(ctx, ts, *clusterTag, clusterStateDir)
if err != nil { if err != nil {
log.Fatalf("StartConsensus: %v", err) log.Fatalf("StartConsensus: %v", err)
} }
@ -570,3 +576,28 @@ func proxyTCPConn(c net.Conn, dest string, ctor *connector) {
p.Start() p.Start()
} }
func getClusterStatePath(stateDirFlag string) (string, error) {
var dirPath string
if stateDirFlag != "" {
dirPath = stateDirFlag
} else {
confDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
dirPath = filepath.Join(confDir, "nat-connector-state")
}
dirPath = filepath.Join(dirPath, "cluster")
if err := os.MkdirAll(dirPath, 0700); err != nil {
return "", err
}
if fi, err := os.Stat(dirPath); err != nil {
return "", err
} else if !fi.IsDir() {
return "", fmt.Errorf("%v is not a directory", dirPath)
}
return dirPath, nil
}