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"
"log"
"net/netip"
"os"
"path/filepath"
"time"
"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 {
cfg := tsconsensus.DefaultConfig()
cfg.ServeDebugMonitor = true
var err error
cfg.StateDirPath, err = getStatePath(clusterStateDir)
if err != nil {
return err
}
cfg.StateDirPath = clusterStateDir
cns, err := tsconsensus.Start(ctx, ts, ipp, clusterTag, cfg)
if err != nil {
return err
@ -211,30 +205,6 @@ func (ps *consensusPerPeerState) unusedIPV4(ipset *netipx.IPSet, reuseDeadline t
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
// half the lifetime apart
func isCloseToExpiry(lastUsed, now time.Time, lifetime time.Duration) bool {

View File

@ -18,6 +18,7 @@ import (
"net/http"
"net/netip"
"os"
"path/filepath"
"strings"
"time"
@ -59,7 +60,7 @@ func main() {
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")
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"))
@ -96,6 +97,7 @@ func main() {
}
ts := &tsnet.Server{
Hostname: *hostname,
Dir: *stateDir,
}
ts.ControlURL = *server
if *wgPort != 0 {
@ -156,7 +158,11 @@ func main() {
var ipp ippool.IPPool
if *clusterTag != "" {
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 {
log.Fatalf("StartConsensus: %v", err)
}
@ -570,3 +576,28 @@ func proxyTCPConn(c net.Conn, dest string, ctor *connector) {
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
}