mirror of
https://github.com/tailscale/tailscale.git
synced 2026-02-09 17:52:57 +01:00
This file was never truly necessary and has never actually been used in the history of Tailscale's open source releases. A Brief History of AUTHORS files --- The AUTHORS file was a pattern developed at Google, originally for Chromium, then adopted by Go and a bunch of other projects. The problem was that Chromium originally had a copyright line only recognizing Google as the copyright holder. Because Google (and most open source projects) do not require copyright assignemnt for contributions, each contributor maintains their copyright. Some large corporate contributors then tried to add their own name to the copyright line in the LICENSE file or in file headers. This quickly becomes unwieldy, and puts a tremendous burden on anyone building on top of Chromium, since the license requires that they keep all copyright lines intact. The compromise was to create an AUTHORS file that would list all of the copyright holders. The LICENSE file and source file headers would then include that list by reference, listing the copyright holder as "The Chromium Authors". This also become cumbersome to simply keep the file up to date with a high rate of new contributors. Plus it's not always obvious who the copyright holder is. Sometimes it is the individual making the contribution, but many times it may be their employer. There is no way for the proejct maintainer to know. Eventually, Google changed their policy to no longer recommend trying to keep the AUTHORS file up to date proactively, and instead to only add to it when requested: https://opensource.google/docs/releasing/authors. They are also clear that: > Adding contributors to the AUTHORS file is entirely within the > project's discretion and has no implications for copyright ownership. It was primarily added to appease a small number of large contributors that insisted that they be recognized as copyright holders (which was entirely their right to do). But it's not truly necessary, and not even the most accurate way of identifying contributors and/or copyright holders. In practice, we've never added anyone to our AUTHORS file. It only lists Tailscale, so it's not really serving any purpose. It also causes confusion because Tailscalars put the "Tailscale Inc & AUTHORS" header in other open source repos which don't actually have an AUTHORS file, so it's ambiguous what that means. Instead, we just acknowledge that the contributors to Tailscale (whoever they are) are copyright holders for their individual contributions. We also have the benefit of using the DCO (developercertificate.org) which provides some additional certification of their right to make the contribution. The source file changes were purely mechanical with: git ls-files | xargs sed -i -e 's/\(Tailscale Inc &\) AUTHORS/\1 contributors/g' Updates #cleanup Change-Id: Ia101a4a3005adb9118051b3416f5a64a4a45987d Signed-off-by: Will Norris <will@tailscale.com>
320 lines
9.2 KiB
Go
320 lines
9.2 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// 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
|
|
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 IsTailscaleIPv4(ip)
|
|
}
|
|
return TailscaleULARange().Contains(ip)
|
|
}
|
|
|
|
// IsTailscaleIPv4 reports whether an IPv4 IP is an IP address that
|
|
// Tailscale assigns from.
|
|
func IsTailscaleIPv4(ip netip.Addr) bool {
|
|
return CGNATRange().Contains(ip) && !ChromeOSVMRange().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
|
|
}
|
|
|
|
// 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 _, r := range rr.All() {
|
|
if r == allIPv4 {
|
|
v4 = true
|
|
} else if r == allIPv6 {
|
|
v6 = true
|
|
}
|
|
}
|
|
return v4 && v6
|
|
}
|
|
|
|
// ContainsExitRoute reports whether rr contains at least one of IPv4 or
|
|
// IPv6 /0 (exit) routes.
|
|
func ContainsExitRoute(rr views.Slice[netip.Prefix]) bool {
|
|
for _, r := range rr.All() {
|
|
if r.Bits() == 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ContainsNonExitSubnetRoutes reports whether v contains Subnet
|
|
// Routes other than ExitNode Routes.
|
|
func ContainsNonExitSubnetRoutes(rr views.Slice[netip.Prefix]) bool {
|
|
for _, r := range rr.All() {
|
|
if r.Bits() != 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// WithoutExitRoutes returns rr unchanged if it has only 1 or 0 /0
|
|
// routes. If it has both IPv4 and IPv6 /0 routes, then it returns
|
|
// a copy with all /0 routes removed.
|
|
func WithoutExitRoutes(rr views.Slice[netip.Prefix]) views.Slice[netip.Prefix] {
|
|
if !ContainsExitRoutes(rr) {
|
|
return rr
|
|
}
|
|
var out []netip.Prefix
|
|
for _, r := range rr.All() {
|
|
if r.Bits() > 0 {
|
|
out = append(out, r)
|
|
}
|
|
}
|
|
return views.SliceOf(out)
|
|
}
|
|
|
|
// WithoutExitRoute returns rr unchanged if it has 0 /0
|
|
// routes. If it has a IPv4 or IPv6 /0 routes, then it returns
|
|
// a copy with all /0 routes removed.
|
|
func WithoutExitRoute(rr views.Slice[netip.Prefix]) views.Slice[netip.Prefix] {
|
|
if !ContainsExitRoute(rr) {
|
|
return rr
|
|
}
|
|
var out []netip.Prefix
|
|
for _, r := range rr.All() {
|
|
if r.Bits() > 0 {
|
|
out = append(out, r)
|
|
}
|
|
}
|
|
return views.SliceOf(out)
|
|
}
|
|
|
|
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} }
|
|
|
|
// IsExitRoute reports whether p is an exit node route.
|
|
func IsExitRoute(p netip.Prefix) bool {
|
|
return p == allIPv4 || p == 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.Len() {
|
|
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
|
|
}
|