doc comments

This commit is contained in:
Fran Bull
2025-04-24 14:28:16 -07:00
parent 33d0c245b5
commit 7ce80691f2

View File

@@ -21,43 +21,27 @@ import (
"tailscale.com/util/mak" "tailscale.com/util/mak"
) )
/* // A ConsensusIPPool is an IPSet from which individual IPV4 addresses can be checked out.
TODO(fran) //
// The pool is distributed across servers in a cluster, to provide high availability.
An ConsensusIPPool is a group of one or more IPV4 ranges from which individual IPV4 addresses can be //
checked out. // Each tailcfg.NodeID has the full range available. The same IPV4 address will be provided to different nodes.
//
natc-consensus provides per domain router functionality for a tailnet. // ConsensusIPPool will maintain the node-ip-domain mapping until it expires, and won't hand out the IP address to that node
- when a node does a dns lookup for a domain the natc-consensus handles, natc-consensus asks ConsensusIPPool for an IP address // again while it maintains the mapping.
for that node and domain. When ConsensusIPPool //
- when a node sends traffic to the IP address it has for a domain, natc-consensus asks ConsensusIPPool which domain that traffic // Reading from the pool is fast, writing to the pool is slow. Because reads can be done in memory on the server that got
is for. // the traffic, but writes must be sent to the consensus peers.
- when an IP address hasn't been used for a while ConsensusIPPool forgets about that node-ip-domain mapping and may provide //
that IP address to that node in response to a subsequent DNS request. // To handle expiry we write on reads, to update the last-used-date, but we do that after we've returned a response.
The pool is distributed across servers in a cluster, to provide high availability.
Each tailcfg.NodeID has the full range available. The same IPV4 address will be provided to different nodes.
ConsensusIPPool will maintain the node-ip-domain mapping until it expires, and won't hand out the IP address to that node
again while it maintains the mapping.
Reading from the pool is fast, writing to the pool is slow. Because reads can be done in memory on the server that got
the traffic, but writes must be sent to the consensus peers.
To handle expiry we write on reads, to update the last-used-date, but we do that after we've returned a response.
# ConsensusIPPool.DomainForIP gets the domain associated with a previous IP checkout for a node
ConsensusIPPool.IPForDomain gets an IP address for the node+domain. It will return an IP address from any existing mapping,
or it may create a mapping with a new unused IP address.
*/
type ConsensusIPPool struct { type ConsensusIPPool struct {
IPSet *netipx.IPSet IPSet *netipx.IPSet
perPeerMap syncs.Map[tailcfg.NodeID, *consensusPerPeerState] perPeerMap syncs.Map[tailcfg.NodeID, *consensusPerPeerState]
consensus commandExecutor consensus commandExecutor
} }
// DomainForIP is part of the IPPool interface. It returns a domain for a given IP address, if we have
// previously assigned the IP address to a domain for the node that is asking. Otherwise it logs and returns the empty string.
func (ipp *ConsensusIPPool) DomainForIP(from tailcfg.NodeID, addr netip.Addr, updatedAt time.Time) (string, bool) { func (ipp *ConsensusIPPool) DomainForIP(from tailcfg.NodeID, addr netip.Addr, updatedAt time.Time) (string, bool) {
pm, ok := ipp.perPeerMap.Load(from) pm, ok := ipp.perPeerMap.Load(from)
if !ok { if !ok {
@@ -87,6 +71,7 @@ type markLastUsedArgs struct {
UpdatedAt time.Time UpdatedAt time.Time
} }
// executeMarkLastUsed parses a markLastUsed log entry and applies it.
func (ipp *ConsensusIPPool) executeMarkLastUsed(bs []byte) tsconsensus.CommandResult { func (ipp *ConsensusIPPool) executeMarkLastUsed(bs []byte) tsconsensus.CommandResult {
var args markLastUsedArgs var args markLastUsedArgs
err := json.Unmarshal(bs, &args) err := json.Unmarshal(bs, &args)
@@ -100,6 +85,8 @@ func (ipp *ConsensusIPPool) executeMarkLastUsed(bs []byte) tsconsensus.CommandRe
return tsconsensus.CommandResult{} return tsconsensus.CommandResult{}
} }
// applyMarkLastUsed applies the arguments from the log entry to the state. It updates an entry in the AddrToDomain
// map with a new LastUsed timestamp.
// applyMarkLastUsed is not safe for concurrent access. It's only called from raft which will // applyMarkLastUsed is not safe for concurrent access. It's only called from raft which will
// not call it concurrently. // not call it concurrently.
func (ipp *ConsensusIPPool) applyMarkLastUsed(from tailcfg.NodeID, addr netip.Addr, domain string, updatedAt time.Time) error { func (ipp *ConsensusIPPool) applyMarkLastUsed(from tailcfg.NodeID, addr netip.Addr, domain string, updatedAt time.Time) error {
@@ -127,6 +114,7 @@ func (ipp *ConsensusIPPool) applyMarkLastUsed(from tailcfg.NodeID, addr netip.Ad
return nil return nil
} }
// StartConsensus is part of the IPPool interface. It starts the raft background routines that handle consensus.
func (ipp *ConsensusIPPool) StartConsensus(ctx context.Context, ts *tsnet.Server, clusterTag string) error { func (ipp *ConsensusIPPool) StartConsensus(ctx context.Context, ts *tsnet.Server, clusterTag string) error {
cfg := tsconsensus.DefaultConfig() cfg := tsconsensus.DefaultConfig()
cfg.ServeDebugMonitor = true cfg.ServeDebugMonitor = true
@@ -149,6 +137,7 @@ type consensusPerPeerState struct {
mu sync.Mutex mu sync.Mutex
} }
// StopConsensus is part of the IPPool interface. It stops the raft background routines that handle consensus.
func (ipp *ConsensusIPPool) StopConsensus(ctx context.Context) error { func (ipp *ConsensusIPPool) StopConsensus(ctx context.Context) error {
return (ipp.consensus).(*tsconsensus.Consensus).Stop(ctx) return (ipp.consensus).(*tsconsensus.Consensus).Stop(ctx)
} }
@@ -182,6 +171,8 @@ 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")
} }
// IPForDomain is part of the IPPool interface. It returns an IP address for the given domain for the given node
// allocating an IP address from the pool if we haven't already.
func (ipp *ConsensusIPPool) IPForDomain(nid tailcfg.NodeID, domain string) (netip.Addr, error) { func (ipp *ConsensusIPPool) IPForDomain(nid tailcfg.NodeID, domain string) (netip.Addr, error) {
now := time.Now() now := time.Now()
args := checkoutAddrArgs{ args := checkoutAddrArgs{
@@ -212,6 +203,7 @@ func (ipp *ConsensusIPPool) IPForDomain(nid tailcfg.NodeID, domain string) (neti
return addr, err return addr, err
} }
// markLastUsed executes a markLastUsed command on the leader with raft.
func (ipp *ConsensusIPPool) markLastUsed(nid tailcfg.NodeID, addr netip.Addr, domain string, lastUsed time.Time) error { func (ipp *ConsensusIPPool) markLastUsed(nid tailcfg.NodeID, addr netip.Addr, domain string, lastUsed time.Time) error {
args := markLastUsedArgs{ args := markLastUsedArgs{
NodeID: nid, NodeID: nid,
@@ -246,6 +238,7 @@ type checkoutAddrArgs struct {
UpdatedAt time.Time UpdatedAt time.Time
} }
// executeCheckoutAddr parses a checkoutAddr raft log entry and applies it.
func (ipp *ConsensusIPPool) executeCheckoutAddr(bs []byte) tsconsensus.CommandResult { func (ipp *ConsensusIPPool) executeCheckoutAddr(bs []byte) tsconsensus.CommandResult {
var args checkoutAddrArgs var args checkoutAddrArgs
err := json.Unmarshal(bs, &args) err := json.Unmarshal(bs, &args)
@@ -294,7 +287,7 @@ func (ipp *ConsensusIPPool) applyCheckoutAddr(nid tailcfg.NodeID, domain string,
return addr, nil return addr, nil
} }
// fulfil the raft lib functional state machine interface // Apply is part of the raft.FSM interface. It takes an incoming log entry and applies it to the state.
func (ipp *ConsensusIPPool) Apply(l *raft.Log) interface{} { func (ipp *ConsensusIPPool) Apply(l *raft.Log) interface{} {
var c tsconsensus.Command var c tsconsensus.Command
if err := json.Unmarshal(l.Data, &c); err != nil { if err := json.Unmarshal(l.Data, &c); err != nil {
@@ -311,10 +304,12 @@ func (ipp *ConsensusIPPool) Apply(l *raft.Log) interface{} {
} }
// TODO(fran) what exactly would we gain by implementing Snapshot and Restore? // TODO(fran) what exactly would we gain by implementing Snapshot and Restore?
// Snapshot is part of the raft.FSM interface.
func (ipp *ConsensusIPPool) Snapshot() (raft.FSMSnapshot, error) { func (ipp *ConsensusIPPool) Snapshot() (raft.FSMSnapshot, error) {
return nil, nil return nil, nil
} }
// Restore is part of the raft.FSM interface.
func (ipp *ConsensusIPPool) Restore(rc io.ReadCloser) error { func (ipp *ConsensusIPPool) Restore(rc io.ReadCloser) error {
return nil return nil
} }