317 lines
9.4 KiB
Go
317 lines
9.4 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package tsaddr handles Tailscale-specific IPs and ranges.
|
|
package tsaddr
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"net/netip"
|
|
"slices"
|
|
"sync"
|
|
|
|
"go4.org/netipx"
|
|
"tailscale.com/net/netaddr"
|
|
"tailscale.com/types/views"
|
|
)
|
|
|
|
// ChromeOSVMRange returns the subset of the CGNAT IPv4 range used by
|
|
// ChromeOS to interconnect the host OS to containers and VMs. We
|
|
// avoid allocating Tailscale IPs from it, to avoid conflicts.
|
|
func ChromeOSVMRange() netip.Prefix {
|
|
chromeOSRange.Do(func() { mustPrefix(&chromeOSRange.v, "100.115.92.0/23") })
|
|
return chromeOSRange.v
|
|
}
|
|
|
|
var chromeOSRange oncePrefix
|
|
|
|
// CGNATRange returns the Carrier Grade NAT address range that
|
|
// is the superset range that Tailscale assigns out of.
|
|
// See https://tailscale.com/s/cgnat
|
|
// Note that Tailscale does not assign out of the ChromeOSVMRange.
|
|
func CGNATRange() netip.Prefix {
|
|
cgnatRange.Do(func() { mustPrefix(&cgnatRange.v, "100.64.0.0/10") })
|
|
return cgnatRange.v
|
|
}
|
|
|
|
var (
|
|
cgnatRange oncePrefix
|
|
ulaRange oncePrefix
|
|
tsUlaRange oncePrefix
|
|
tsViaRange oncePrefix
|
|
ula4To6Range oncePrefix
|
|
ulaEph6Range oncePrefix
|
|
serviceIPv6 oncePrefix
|
|
)
|
|
|
|
// TailscaleServiceIP returns the IPv4 listen address of services
|
|
// provided by Tailscale itself such as the MagicDNS proxy.
|
|
//
|
|
// For IPv6, use TailscaleServiceIPv6.
|
|
func TailscaleServiceIP() netip.Addr {
|
|
return netaddr.IPv4(100, 100, 100, 100) // "100.100.100.100" for those grepping
|
|
}
|
|
|
|
// TailscaleServiceIPv6 returns the IPv6 listen address of the services
|
|
// provided by Tailscale itself such as the MagicDNS proxy.
|
|
//
|
|
// For IPv4, use TailscaleServiceIP.
|
|
func TailscaleServiceIPv6() netip.Addr {
|
|
serviceIPv6.Do(func() { mustPrefix(&serviceIPv6.v, TailscaleServiceIPv6String+"/128") })
|
|
return serviceIPv6.v.Addr()
|
|
}
|
|
|
|
const (
|
|
TailscaleServiceIPString = "100.100.100.100"
|
|
TailscaleServiceIPv6String = "fd7a:115c:a1e0::53"
|
|
)
|
|
|
|
// IsTailscaleIP reports whether ip is an IP address in a range that
|
|
// Tailscale assigns from.
|
|
func IsTailscaleIP(ip netip.Addr) bool {
|
|
if ip.Is4() {
|
|
return CGNATRange().Contains(ip) && !ChromeOSVMRange().Contains(ip)
|
|
}
|
|
return TailscaleULARange().Contains(ip)
|
|
}
|
|
|
|
// TailscaleULARange returns the IPv6 Unique Local Address range that
|
|
// is the superset range that Tailscale assigns out of.
|
|
func TailscaleULARange() netip.Prefix {
|
|
tsUlaRange.Do(func() { mustPrefix(&tsUlaRange.v, "fd7a:115c:a1e0::/48") })
|
|
return tsUlaRange.v
|
|
}
|
|
|
|
// TailscaleViaRange returns the IPv6 Unique Local Address subset range
|
|
// TailscaleULARange that's used for IPv4 tunneling via IPv6.
|
|
func TailscaleViaRange() netip.Prefix {
|
|
// Mnemonic: "b1a" sounds like "via".
|
|
tsViaRange.Do(func() { mustPrefix(&tsViaRange.v, "fd7a:115c:a1e0:b1a::/64") })
|
|
return tsViaRange.v
|
|
}
|
|
|
|
// Tailscale4To6Range returns the subset of TailscaleULARange used for
|
|
// auto-translated Tailscale ipv4 addresses.
|
|
func Tailscale4To6Range() netip.Prefix {
|
|
// This IP range has no significance, beyond being a subset of
|
|
// TailscaleULARange. The bits from /48 to /104 were picked at
|
|
// random.
|
|
ula4To6Range.Do(func() { mustPrefix(&ula4To6Range.v, "fd7a:115c:a1e0:ab12:4843:cd96:6200::/104") })
|
|
return ula4To6Range.v
|
|
}
|
|
|
|
// TailscaleEphemeral6Range returns the subset of TailscaleULARange
|
|
// used for ephemeral IPv6-only Tailscale nodes.
|
|
func TailscaleEphemeral6Range() netip.Prefix {
|
|
// This IP range has no significance, beyond being a subset of
|
|
// TailscaleULARange. The bits from /48 to /64 were picked at
|
|
// random, with the only criterion being to not be the conflict
|
|
// with the Tailscale4To6Range above.
|
|
ulaEph6Range.Do(func() { mustPrefix(&ulaEph6Range.v, "fd7a:115c:a1e0:efe3::/64") })
|
|
return ulaEph6Range.v
|
|
}
|
|
|
|
// Tailscale4To6Placeholder returns an IP address that can be used as
|
|
// a source IP when one is required, but a netmap didn't provide
|
|
// any. This address never gets allocated by the 4-to-6 algorithm in
|
|
// control.
|
|
//
|
|
// Currently used to work around a Windows limitation when programming
|
|
// IPv6 routes in corner cases.
|
|
func Tailscale4To6Placeholder() netip.Addr {
|
|
return Tailscale4To6Range().Addr()
|
|
}
|
|
|
|
// Tailscale4To6 returns a Tailscale IPv6 address that maps 1:1 to the
|
|
// given Tailscale IPv4 address. Returns a zero IP if ipv4 isn't a
|
|
// Tailscale IPv4 address.
|
|
func Tailscale4To6(ipv4 netip.Addr) netip.Addr {
|
|
if !ipv4.Is4() || !IsTailscaleIP(ipv4) {
|
|
return netip.Addr{}
|
|
}
|
|
ret := Tailscale4To6Range().Addr().As16()
|
|
v4 := ipv4.As4()
|
|
copy(ret[13:], v4[1:])
|
|
return netip.AddrFrom16(ret)
|
|
}
|
|
|
|
// Tailscale6to4 returns the IPv4 address corresponding to the given
|
|
// tailscale IPv6 address within the 4To6 range. The IPv4 address
|
|
// and true are returned if the given address was in the correct range,
|
|
// false if not.
|
|
func Tailscale6to4(ipv6 netip.Addr) (netip.Addr, bool) {
|
|
if !ipv6.Is6() || !Tailscale4To6Range().Contains(ipv6) {
|
|
return netip.Addr{}, false
|
|
}
|
|
v6 := ipv6.As16()
|
|
return netip.AddrFrom4([4]byte{100, v6[13], v6[14], v6[15]}), true
|
|
}
|
|
|
|
func mustPrefix(v *netip.Prefix, prefix string) {
|
|
var err error
|
|
*v, err = netip.ParsePrefix(prefix)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
type oncePrefix struct {
|
|
sync.Once
|
|
v netip.Prefix
|
|
}
|
|
|
|
// FalseContainsIPFunc is shorthand for NewContainsIPFunc(views.Slice[netip.Prefix]{}).
|
|
func FalseContainsIPFunc() func(ip netip.Addr) bool {
|
|
return func(ip netip.Addr) bool { return false }
|
|
}
|
|
|
|
// NewContainsIPFunc returns a func that reports whether ip is in addrs.
|
|
//
|
|
// It's optimized for the cases of addrs being empty and addrs
|
|
// containing 1 or 2 single-IP prefixes (such as one IPv4 address and
|
|
// one IPv6 address).
|
|
//
|
|
// Otherwise the implementation is somewhat slow.
|
|
func NewContainsIPFunc(addrs views.Slice[netip.Prefix]) func(ip netip.Addr) bool {
|
|
// Specialize the three common cases: no address, just IPv4
|
|
// (or just IPv6), and both IPv4 and IPv6.
|
|
if addrs.Len() == 0 {
|
|
return func(netip.Addr) bool { return false }
|
|
}
|
|
// If any addr is more than a single IP, then just do the slow
|
|
// linear thing until
|
|
// https://github.com/inetaf/netaddr/issues/139 is done.
|
|
if views.SliceContainsFunc(addrs, func(p netip.Prefix) bool { return !p.IsSingleIP() }) {
|
|
acopy := addrs.AsSlice()
|
|
return func(ip netip.Addr) bool {
|
|
for _, a := range acopy {
|
|
if a.Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
// Fast paths for 1 and 2 IPs:
|
|
if addrs.Len() == 1 {
|
|
a := addrs.At(0)
|
|
return func(ip netip.Addr) bool { return ip == a.Addr() }
|
|
}
|
|
if addrs.Len() == 2 {
|
|
a, b := addrs.At(0), addrs.At(1)
|
|
return func(ip netip.Addr) bool { return ip == a.Addr() || ip == b.Addr() }
|
|
}
|
|
// General case:
|
|
m := map[netip.Addr]bool{}
|
|
for i := range addrs.LenIter() {
|
|
m[addrs.At(i).Addr()] = true
|
|
}
|
|
return func(ip netip.Addr) bool { return m[ip] }
|
|
}
|
|
|
|
// PrefixesContainsIP reports whether any prefix in ipp contains ip.
|
|
func PrefixesContainsIP(ipp []netip.Prefix, ip netip.Addr) bool {
|
|
for _, r := range ipp {
|
|
if r.Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// PrefixIs4 reports whether p is an IPv4 prefix.
|
|
func PrefixIs4(p netip.Prefix) bool { return p.Addr().Is4() }
|
|
|
|
// PrefixIs6 reports whether p is an IPv6 prefix.
|
|
func PrefixIs6(p netip.Prefix) bool { return p.Addr().Is6() }
|
|
|
|
// ContainsExitRoutes reports whether rr contains both the IPv4 and
|
|
// IPv6 /0 route.
|
|
func ContainsExitRoutes(rr views.Slice[netip.Prefix]) bool {
|
|
var v4, v6 bool
|
|
for i := range rr.LenIter() {
|
|
r := rr.At(i)
|
|
if r == allIPv4 {
|
|
v4 = true
|
|
} else if r == allIPv6 {
|
|
v6 = true
|
|
}
|
|
}
|
|
return v4 && v6
|
|
}
|
|
|
|
// ContainsNonExitSubnetRoutes reports whether v contains Subnet
|
|
// Routes other than ExitNode Routes.
|
|
func ContainsNonExitSubnetRoutes(rr views.Slice[netip.Prefix]) bool {
|
|
for i := range rr.LenIter() {
|
|
if rr.At(i).Bits() != 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var (
|
|
allIPv4 = netip.MustParsePrefix("0.0.0.0/0")
|
|
allIPv6 = netip.MustParsePrefix("::/0")
|
|
)
|
|
|
|
// AllIPv4 returns 0.0.0.0/0.
|
|
func AllIPv4() netip.Prefix { return allIPv4 }
|
|
|
|
// AllIPv6 returns ::/0.
|
|
func AllIPv6() netip.Prefix { return allIPv6 }
|
|
|
|
// ExitRoutes returns a slice containing AllIPv4 and AllIPv6.
|
|
func ExitRoutes() []netip.Prefix { return []netip.Prefix{allIPv4, allIPv6} }
|
|
|
|
// SortPrefixes sorts the prefixes in place.
|
|
func SortPrefixes(p []netip.Prefix) {
|
|
slices.SortFunc(p, netipx.ComparePrefix)
|
|
}
|
|
|
|
// FilterPrefixes returns a new slice, not aliasing in, containing elements of
|
|
// in that match f.
|
|
func FilterPrefixesCopy(in views.Slice[netip.Prefix], f func(netip.Prefix) bool) []netip.Prefix {
|
|
var out []netip.Prefix
|
|
for i := range in.LenIter() {
|
|
if v := in.At(i); f(v) {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// IsViaPrefix reports whether p is a CIDR in the Tailscale "via" range.
|
|
// See TailscaleViaRange.
|
|
func IsViaPrefix(p netip.Prefix) bool {
|
|
return TailscaleViaRange().Contains(p.Addr())
|
|
}
|
|
|
|
// UnmapVia returns the IPv4 address that corresponds to the provided Tailscale
|
|
// "via" IPv4-in-IPv6 address.
|
|
//
|
|
// If ip is not a via address, it returns ip unchanged.
|
|
func UnmapVia(ip netip.Addr) netip.Addr {
|
|
if TailscaleViaRange().Contains(ip) {
|
|
a := ip.As16()
|
|
return netip.AddrFrom4(*(*[4]byte)(a[12:16]))
|
|
}
|
|
return ip
|
|
}
|
|
|
|
// MapVia returns an IPv6 "via" route for an IPv4 CIDR in a given siteID.
|
|
func MapVia(siteID uint32, v4 netip.Prefix) (via netip.Prefix, err error) {
|
|
if !v4.Addr().Is4() {
|
|
return via, errors.New("want IPv4 CIDR with a site ID")
|
|
}
|
|
viaRange16 := TailscaleViaRange().Addr().As16()
|
|
var a [16]byte
|
|
copy(a[:], viaRange16[:8])
|
|
binary.BigEndian.PutUint32(a[8:], siteID)
|
|
ip4a := v4.Addr().As4()
|
|
copy(a[12:], ip4a[:])
|
|
return netip.PrefixFrom(netip.AddrFrom16(a), v4.Bits()+64+32), nil
|
|
}
|