Files
netbird/client/internal/routemanager/fakeip/fakeip.go

146 lines
3.7 KiB
Go

package fakeip
import (
"errors"
"fmt"
"net/netip"
"sync"
)
var (
// 240.0.0.1 - 240.255.255.254, block 240.0.0.0/8 (reserved, RFC 1112)
v4Base = netip.AddrFrom4([4]byte{240, 0, 0, 1})
v4Max = netip.AddrFrom4([4]byte{240, 255, 255, 254})
v4Block = netip.PrefixFrom(netip.AddrFrom4([4]byte{240, 0, 0, 0}), 8)
// 0100::1 - 0100::ffff:ffff:ffff:fffe, block 0100::/64 (discard, RFC 6666)
v6Base = netip.AddrFrom16([16]byte{0x01, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01})
v6Max = netip.AddrFrom16([16]byte{0x01, 0x00, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe})
v6Block = netip.PrefixFrom(netip.AddrFrom16([16]byte{0x01, 0x00}), 64)
)
// fakeIPPool holds the allocation state for a single address family.
type fakeIPPool struct {
nextIP netip.Addr
baseIP netip.Addr
maxIP netip.Addr
block netip.Prefix
allocated map[netip.Addr]netip.Addr // real IP -> fake IP
fakeToReal map[netip.Addr]netip.Addr // fake IP -> real IP
}
func newPool(base, maxAddr netip.Addr, block netip.Prefix) *fakeIPPool {
return &fakeIPPool{
nextIP: base,
baseIP: base,
maxIP: maxAddr,
block: block,
allocated: make(map[netip.Addr]netip.Addr),
fakeToReal: make(map[netip.Addr]netip.Addr),
}
}
// allocate allocates a fake IP for the given real IP.
// Returns the existing fake IP if already allocated.
func (p *fakeIPPool) allocate(realIP netip.Addr) (netip.Addr, error) {
if fakeIP, exists := p.allocated[realIP]; exists {
return fakeIP, nil
}
startIP := p.nextIP
for {
currentIP := p.nextIP
// Advance to next IP, wrapping at boundary
if p.nextIP.Compare(p.maxIP) >= 0 {
p.nextIP = p.baseIP
} else {
p.nextIP = p.nextIP.Next()
}
if _, inUse := p.fakeToReal[currentIP]; !inUse {
p.allocated[realIP] = currentIP
p.fakeToReal[currentIP] = realIP
return currentIP, nil
}
if p.nextIP.Compare(startIP) == 0 {
return netip.Addr{}, fmt.Errorf("no more fake IPs available in %s block", p.block)
}
}
}
// Manager manages allocation of fake IPs for dynamic DNS routes.
// IPv4 uses 240.0.0.0/8 (reserved), IPv6 uses 0100::/64 (discard, RFC 6666).
type Manager struct {
mu sync.Mutex
v4 *fakeIPPool
v6 *fakeIPPool
}
// NewManager creates a new fake IP manager.
func NewManager() *Manager {
return &Manager{
v4: newPool(v4Base, v4Max, v4Block),
v6: newPool(v6Base, v6Max, v6Block),
}
}
func (m *Manager) pool(ip netip.Addr) *fakeIPPool {
if ip.Is6() {
return m.v6
}
return m.v4
}
// AllocateFakeIP allocates a fake IP for the given real IP.
func (m *Manager) AllocateFakeIP(realIP netip.Addr) (netip.Addr, error) {
realIP = realIP.Unmap()
if !realIP.IsValid() {
return netip.Addr{}, errors.New("invalid IP address")
}
m.mu.Lock()
defer m.mu.Unlock()
return m.pool(realIP).allocate(realIP)
}
// GetFakeIP returns the fake IP for a real IP if it exists.
func (m *Manager) GetFakeIP(realIP netip.Addr) (netip.Addr, bool) {
realIP = realIP.Unmap()
if !realIP.IsValid() {
return netip.Addr{}, false
}
m.mu.Lock()
defer m.mu.Unlock()
fakeIP, ok := m.pool(realIP).allocated[realIP]
return fakeIP, ok
}
// GetRealIP returns the real IP for a fake IP if it exists.
func (m *Manager) GetRealIP(fakeIP netip.Addr) (netip.Addr, bool) {
fakeIP = fakeIP.Unmap()
if !fakeIP.IsValid() {
return netip.Addr{}, false
}
m.mu.Lock()
defer m.mu.Unlock()
realIP, ok := m.pool(fakeIP).fakeToReal[fakeIP]
return realIP, ok
}
// GetFakeIPBlock returns the v4 fake IP block used by this manager.
func (m *Manager) GetFakeIPBlock() netip.Prefix {
return m.v4.block
}
// GetFakeIPv6Block returns the v6 fake IP block used by this manager.
func (m *Manager) GetFakeIPv6Block() netip.Prefix {
return m.v6.block
}