mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 21:27:31 +00:00
cmd/natc: only store v4 addresses
Because we derive v6 addresses from v4 addresses we only need to store the v4 address, not both. Updates #14667 Signed-off-by: Fran Bull <fran@tailscale.com>
This commit is contained in:
@@ -41,7 +41,7 @@ func (ipp *IPPool) DomainForIP(from tailcfg.NodeID, addr netip.Addr) (string, bo
|
||||
return domain, ok
|
||||
}
|
||||
|
||||
func (ipp *IPPool) IPForDomain(from tailcfg.NodeID, domain string) ([]netip.Addr, error) {
|
||||
func (ipp *IPPool) IPForDomain(from tailcfg.NodeID, domain string) (netip.Addr, error) {
|
||||
npps := &perPeerState{
|
||||
ipset: ipp.IPSet,
|
||||
v6ULA: ipp.V6ULA,
|
||||
@@ -57,7 +57,7 @@ type perPeerState struct {
|
||||
|
||||
mu sync.Mutex
|
||||
addrInUse *big.Int
|
||||
domainToAddr map[string][]netip.Addr
|
||||
domainToAddr map[string]netip.Addr
|
||||
addrToDomain *bart.Table[string]
|
||||
}
|
||||
|
||||
@@ -75,23 +75,23 @@ func (ps *perPeerState) domainForIP(ip netip.Addr) (_ string, ok bool) {
|
||||
// ipForDomain assigns a pair of unique IP addresses for the given domain and
|
||||
// returns them. The first address is an IPv4 address and the second is an IPv6
|
||||
// address. If the domain already has assigned addresses, it returns them.
|
||||
func (ps *perPeerState) ipForDomain(domain string) ([]netip.Addr, error) {
|
||||
func (ps *perPeerState) ipForDomain(domain string) (netip.Addr, error) {
|
||||
fqdn, err := dnsname.ToFQDN(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
domain = fqdn.WithoutTrailingDot()
|
||||
|
||||
ps.mu.Lock()
|
||||
defer ps.mu.Unlock()
|
||||
if addrs, ok := ps.domainToAddr[domain]; ok {
|
||||
return addrs, nil
|
||||
if addr, ok := ps.domainToAddr[domain]; ok {
|
||||
return addr, nil
|
||||
}
|
||||
addrs := ps.assignAddrsLocked(domain)
|
||||
if addrs == nil {
|
||||
return nil, ErrNoIPsAvailable
|
||||
addr := ps.assignAddrsLocked(domain)
|
||||
if !addr.IsValid() {
|
||||
return netip.Addr{}, ErrNoIPsAvailable
|
||||
}
|
||||
return addrs, nil
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// unusedIPv4Locked returns an unused IPv4 address from the available ranges.
|
||||
@@ -106,22 +106,16 @@ func (ps *perPeerState) unusedIPv4Locked() netip.Addr {
|
||||
// and returns them. The first address is an IPv4 address and the second is an
|
||||
// IPv6 address. It does not check if the domain already has assigned addresses.
|
||||
// ps.mu must be held.
|
||||
func (ps *perPeerState) assignAddrsLocked(domain string) []netip.Addr {
|
||||
func (ps *perPeerState) assignAddrsLocked(domain string) netip.Addr {
|
||||
if ps.addrToDomain == nil {
|
||||
ps.addrToDomain = &bart.Table[string]{}
|
||||
}
|
||||
v4 := ps.unusedIPv4Locked()
|
||||
if !v4.IsValid() {
|
||||
return nil
|
||||
return netip.Addr{}
|
||||
}
|
||||
as16 := ps.v6ULA.Addr().As16()
|
||||
as4 := v4.As4()
|
||||
copy(as16[12:], as4[:])
|
||||
v6 := netip.AddrFrom16(as16)
|
||||
addrs := []netip.Addr{v4, v6}
|
||||
mak.Set(&ps.domainToAddr, domain, addrs)
|
||||
for _, a := range addrs {
|
||||
ps.addrToDomain.Insert(netip.PrefixFrom(a, a.BitLen()), domain)
|
||||
}
|
||||
return addrs
|
||||
addr := v4
|
||||
mak.Set(&ps.domainToAddr, domain, addr)
|
||||
ps.addrToDomain.Insert(netip.PrefixFrom(addr, addr.BitLen()), domain)
|
||||
return addr
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"go4.org/netipx"
|
||||
@@ -33,20 +32,18 @@ func TestIPPoolExhaustion(t *testing.T) {
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
for _, domain := range domains {
|
||||
addrs, err := pool.IPForDomain(from, domain)
|
||||
addr, err := pool.IPForDomain(from, domain)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to get IP for domain %q: %w", domain, err))
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
if d, ok := assignedIPs[addr]; ok {
|
||||
if d != domain {
|
||||
t.Errorf("IP %s reused for domain %q, previously assigned to %q", addr, domain, d)
|
||||
}
|
||||
} else {
|
||||
assignedIPs[addr] = domain
|
||||
if d, ok := assignedIPs[addr]; ok {
|
||||
if d != domain {
|
||||
t.Errorf("IP %s reused for domain %q, previously assigned to %q", addr, domain, d)
|
||||
}
|
||||
} else {
|
||||
assignedIPs[addr] = domain
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,50 +77,36 @@ func TestIPPool(t *testing.T) {
|
||||
IPSet: addrPool,
|
||||
}
|
||||
from := tailcfg.NodeID(12345)
|
||||
addrs, err := pool.IPForDomain(from, "example.com")
|
||||
addr, err := pool.IPForDomain(from, "example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("ipForDomain() error = %v", err)
|
||||
}
|
||||
|
||||
if len(addrs) != 2 {
|
||||
t.Fatalf("ipForDomain() returned %d addresses, want 2", len(addrs))
|
||||
if !addr.IsValid() {
|
||||
t.Fatal("ipForDomain() returned an invalid address")
|
||||
}
|
||||
|
||||
v4 := addrs[0]
|
||||
v6 := addrs[1]
|
||||
|
||||
if !v4.Is4() {
|
||||
t.Errorf("First address is not IPv4: %s", v4)
|
||||
if !addr.Is4() {
|
||||
t.Errorf("Address is not IPv4: %s", addr)
|
||||
}
|
||||
|
||||
if !v6.Is6() {
|
||||
t.Errorf("Second address is not IPv6: %s", v6)
|
||||
if !addrPool.Contains(addr) {
|
||||
t.Errorf("IPv4 address %s not in range %s", addr, addrPool)
|
||||
}
|
||||
|
||||
if !addrPool.Contains(v4) {
|
||||
t.Errorf("IPv4 address %s not in range %s", v4, addrPool)
|
||||
}
|
||||
|
||||
domain, ok := pool.DomainForIP(from, v4)
|
||||
domain, ok := pool.DomainForIP(from, addr)
|
||||
if !ok {
|
||||
t.Errorf("domainForIP(%s) not found", v4)
|
||||
t.Errorf("domainForIP(%s) not found", addr)
|
||||
} else if domain != "example.com" {
|
||||
t.Errorf("domainForIP(%s) = %s, want %s", v4, domain, "example.com")
|
||||
t.Errorf("domainForIP(%s) = %s, want %s", addr, domain, "example.com")
|
||||
}
|
||||
|
||||
domain, ok = pool.DomainForIP(from, v6)
|
||||
if !ok {
|
||||
t.Errorf("domainForIP(%s) not found", v6)
|
||||
} else if domain != "example.com" {
|
||||
t.Errorf("domainForIP(%s) = %s, want %s", v6, domain, "example.com")
|
||||
}
|
||||
|
||||
addrs2, err := pool.IPForDomain(from, "example.com")
|
||||
addr2, err := pool.IPForDomain(from, "example.com")
|
||||
if err != nil {
|
||||
t.Fatalf("ipForDomain() second call error = %v", err)
|
||||
}
|
||||
|
||||
if !slices.Equal(addrs, addrs2) {
|
||||
t.Errorf("ipForDomain() second call = %v, want %v", addrs2, addrs)
|
||||
if addr.Compare(addr2) != 0 {
|
||||
t.Errorf("ipForDomain() second call = %v, want %v", addr2, addr)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user