cmd/natc: separate perPeerState from connector

Make the perPeerState objects able to function independently without a
shared reference to the connector.

We don't currently change the values from connector that perPeerState
uses at runtime. Explicitly copying them at perPeerState creation allows
us to, for example, put the perPeerState into a consensus algorithm in
the future.

Updates #14667

Signed-off-by: Fran Bull <fran@tailscale.com>
This commit is contained in:
Fran Bull 2025-04-02 08:58:13 -07:00 committed by franbull
parent 7b29d39f45
commit e2eb6eb870
2 changed files with 14 additions and 6 deletions

View File

@ -359,7 +359,7 @@ var tsMBox = dnsmessage.MustNewName("support.tailscale.com.")
// generateDNSResponse generates a DNS response for the given request. The from // generateDNSResponse generates a DNS response for the given request. The from
// argument is the NodeID of the node that sent the request. // argument is the NodeID of the node that sent the request.
func (c *connector) generateDNSResponse(req *dnsmessage.Message, from tailcfg.NodeID) ([]byte, error) { func (c *connector) generateDNSResponse(req *dnsmessage.Message, from tailcfg.NodeID) ([]byte, error) {
pm, _ := c.perPeerMap.LoadOrStore(from, &perPeerState{c: c}) pm, _ := c.perPeerMap.LoadOrStore(from, newPerPeerState(c))
var addrs []netip.Addr var addrs []netip.Addr
if len(req.Questions) > 0 { if len(req.Questions) > 0 {
switch req.Questions[0].Type { switch req.Questions[0].Type {
@ -509,7 +509,8 @@ func proxyTCPConn(c net.Conn, dest string) {
// perPeerState holds the state for a single peer. // perPeerState holds the state for a single peer.
type perPeerState struct { type perPeerState struct {
c *connector v6ULA netip.Prefix
ipset *netipx.IPSet
mu sync.Mutex mu sync.Mutex
addrInUse *big.Int addrInUse *big.Int
@ -517,6 +518,13 @@ type perPeerState struct {
addrToDomain *bart.Table[string] addrToDomain *bart.Table[string]
} }
func newPerPeerState(c *connector) *perPeerState {
return &perPeerState{
ipset: c.ipset,
v6ULA: c.v6ULA,
}
}
// domainForIP returns the domain name assigned to the given IP address and // domainForIP returns the domain name assigned to the given IP address and
// whether it was found. // whether it was found.
func (ps *perPeerState) domainForIP(ip netip.Addr) (_ string, ok bool) { func (ps *perPeerState) domainForIP(ip netip.Addr) (_ string, ok bool) {
@ -555,7 +563,7 @@ func (ps *perPeerState) unusedIPv4Locked() netip.Addr {
if ps.addrInUse == nil { if ps.addrInUse == nil {
ps.addrInUse = big.NewInt(0) ps.addrInUse = big.NewInt(0)
} }
return allocAddr(ps.c.ipset, ps.addrInUse) return allocAddr(ps.ipset, ps.addrInUse)
} }
// assignAddrsLocked assigns a pair of unique IP addresses for the given domain // assignAddrsLocked assigns a pair of unique IP addresses for the given domain
@ -570,7 +578,7 @@ func (ps *perPeerState) assignAddrsLocked(domain string) []netip.Addr {
if !v4.IsValid() { if !v4.IsValid() {
return nil return nil
} }
as16 := ps.c.v6ULA.Addr().As16() as16 := ps.v6ULA.Addr().As16()
as4 := v4.As4() as4 := v4.As4()
copy(as16[12:], as4[:]) copy(as16[12:], as4[:])
v6 := netip.AddrFrom16(as16) v6 := netip.AddrFrom16(as16)

View File

@ -220,7 +220,7 @@ func TestPerPeerState(t *testing.T) {
} }
c.setPrefixes([]netip.Prefix{netip.MustParsePrefix("100.64.1.0/24")}) c.setPrefixes([]netip.Prefix{netip.MustParsePrefix("100.64.1.0/24")})
ps := &perPeerState{c: c} ps := newPerPeerState(c)
addrs, err := ps.ipForDomain("example.com") addrs, err := ps.ipForDomain("example.com")
if err != nil { if err != nil {
@ -360,7 +360,7 @@ func TestIPPoolExhaustion(t *testing.T) {
} }
c.setPrefixes([]netip.Prefix{smallPrefix}) c.setPrefixes([]netip.Prefix{smallPrefix})
ps := &perPeerState{c: c} ps := newPerPeerState(c)
assignedIPs := make(map[netip.Addr]string) assignedIPs := make(map[netip.Addr]string)