Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netutil
|
||||
|
||||
+65
-101
@@ -1,16 +1,15 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -61,7 +60,12 @@ func protocolsRequiredForForwarding(routes []netip.Prefix, state *netmon.State)
|
||||
func CheckIPForwarding(routes []netip.Prefix, state *netmon.State) (warn, err error) {
|
||||
if runtime.GOOS != "linux" {
|
||||
switch runtime.GOOS {
|
||||
case "dragonfly", "freebsd", "netbsd", "openbsd":
|
||||
case "freebsd":
|
||||
if state == nil {
|
||||
return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration; no link state")
|
||||
}
|
||||
return checkIPForwardingFreeBSD(routes, state)
|
||||
case "dragonfly", "netbsd", "openbsd":
|
||||
return fmt.Errorf("Subnet routing and exit nodes only work with additional manual configuration on %v, and is not currently officially supported.", runtime.GOOS), nil
|
||||
case "illumos", "solaris":
|
||||
_, err := ipForwardingEnabledSunOS(ipv4, "")
|
||||
@@ -146,64 +150,6 @@ func CheckIPForwarding(routes []netip.Prefix, state *netmon.State) (warn, err er
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CheckReversePathFiltering reports whether reverse path filtering is either
|
||||
// disabled or set to 'loose' mode for exit node functionality on any
|
||||
// interface.
|
||||
//
|
||||
// The routes should only be advertised routes, and should not contain the
|
||||
// node's Tailscale IPs.
|
||||
//
|
||||
// This function returns an error if it is unable to determine whether reverse
|
||||
// path filtering is enabled, or a warning describing configuration issues if
|
||||
// reverse path fitering is non-functional or partly functional.
|
||||
func CheckReversePathFiltering(state *netmon.State) (warn []string, err error) {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if state == nil {
|
||||
return nil, errors.New("no link state")
|
||||
}
|
||||
|
||||
// The kernel uses the maximum value for rp_filter between the 'all'
|
||||
// setting and each per-interface config, so we need to fetch both.
|
||||
allSetting, err := reversePathFilterValueLinux("all")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading global rp_filter value: %w", err)
|
||||
}
|
||||
|
||||
const (
|
||||
filtOff = 0
|
||||
filtStrict = 1
|
||||
filtLoose = 2
|
||||
)
|
||||
|
||||
// Because the kernel use the max rp_filter value, each interface will use 'loose', so we
|
||||
// can abort early.
|
||||
if allSetting == filtLoose {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
for _, iface := range state.Interface {
|
||||
if iface.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
|
||||
iSetting, err := reversePathFilterValueLinux(iface.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading interface rp_filter value for %q: %w", iface.Name, err)
|
||||
}
|
||||
// Perform the same max() that the kernel does
|
||||
if allSetting > iSetting {
|
||||
iSetting = allSetting
|
||||
}
|
||||
if iSetting == filtStrict {
|
||||
warn = append(warn, fmt.Sprintf("interface %q has strict reverse-path filtering enabled", iface.Name))
|
||||
}
|
||||
}
|
||||
return warn, nil
|
||||
}
|
||||
|
||||
// ipForwardSysctlKey returns the sysctl key for the given protocol and iface.
|
||||
// When the dotFormat parameter is true the output is formatted as `net.ipv4.ip_forward`,
|
||||
// else it is `net/ipv4/ip_forward`
|
||||
@@ -235,25 +181,6 @@ func ipForwardSysctlKey(format sysctlFormat, p protocol, iface string) string {
|
||||
return fmt.Sprintf(k, iface)
|
||||
}
|
||||
|
||||
// rpFilterSysctlKey returns the sysctl key for the given iface.
|
||||
//
|
||||
// Format controls whether the output is formatted as
|
||||
// `net.ipv4.conf.iface.rp_filter` or `net/ipv4/conf/iface/rp_filter`.
|
||||
func rpFilterSysctlKey(format sysctlFormat, iface string) string {
|
||||
// No iface means all interfaces
|
||||
if iface == "" {
|
||||
iface = "all"
|
||||
}
|
||||
|
||||
k := "net/ipv4/conf/%s/rp_filter"
|
||||
if format == dotFormat {
|
||||
// Swap the delimiters.
|
||||
iface = strings.ReplaceAll(iface, ".", "/")
|
||||
k = strings.ReplaceAll(k, "/", ".")
|
||||
}
|
||||
return fmt.Sprintf(k, iface)
|
||||
}
|
||||
|
||||
type sysctlFormat int
|
||||
|
||||
const (
|
||||
@@ -276,7 +203,10 @@ const (
|
||||
// sysctl (which on Linux just reads from /proc/sys anyway).
|
||||
func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
|
||||
k := ipForwardSysctlKey(slashFormat, p, iface)
|
||||
bs, err := os.ReadFile(filepath.Join("/proc/sys", k))
|
||||
// Open k under /proc/sys with os.OpenInRoot so a ".." or symlinked
|
||||
// component in the interface name can't read outside /proc/sys. It's
|
||||
// openat-based, so it's also TOCTOU-resistant. See https://go.dev/blog/osroot.
|
||||
f, err := os.OpenInRoot("/proc/sys", k)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// If IPv6 is disabled, sysctl keys like "net.ipv6.conf.all.forwarding" just don't
|
||||
@@ -291,6 +221,11 @@ func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
bs, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
val, err := strconv.ParseInt(string(bytes.TrimSpace(bs)), 10, 32)
|
||||
if err != nil {
|
||||
@@ -305,30 +240,59 @@ func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
|
||||
return on, nil
|
||||
}
|
||||
|
||||
// reversePathFilterValueLinux reports the reverse path filter setting on Linux
|
||||
// for the given interface.
|
||||
// checkIPForwardingFreeBSD reports whether IP forwarding is enabled for the
|
||||
// protocols required by routes.
|
||||
//
|
||||
// The iface param determines which interface to check against; the empty
|
||||
// string means to check the global config.
|
||||
//
|
||||
// This function tries to look up the value directly from `/proc/sys`, and
|
||||
// falls back to using the `sysctl` command on failure.
|
||||
func reversePathFilterValueLinux(iface string) (int, error) {
|
||||
k := rpFilterSysctlKey(slashFormat, iface)
|
||||
bs, err := os.ReadFile(filepath.Join("/proc/sys", k))
|
||||
if err != nil {
|
||||
// Fall back to the sysctl command
|
||||
k := rpFilterSysctlKey(dotFormat, iface)
|
||||
bs, err = exec.Command("sysctl", "-n", k).Output()
|
||||
// Unlike Linux, FreeBSD has no per-interface forwarding knob: net.inet.ip.forwarding
|
||||
// and net.inet6.ip6.forwarding are global, so we only check those.
|
||||
func checkIPForwardingFreeBSD(routes []netip.Prefix, state *netmon.State) (warn, err error) {
|
||||
const kbLink = "\nSee https://tailscale.com/s/ip-forwarding"
|
||||
wantV4, wantV6 := protocolsRequiredForForwarding(routes, state)
|
||||
if !wantV4 && !wantV6 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var warnings []string
|
||||
if wantV4 {
|
||||
on, err := ipForwardingEnabledFreeBSD(ipv4)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("couldn't check %s (%v)", k, err)
|
||||
return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
|
||||
}
|
||||
if !on {
|
||||
warnings = append(warnings, "IPv4 forwarding is disabled.")
|
||||
}
|
||||
}
|
||||
v, err := strconv.Atoi(string(bytes.TrimSpace(bs)))
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("couldn't parse %s (%v)", k, err)
|
||||
if wantV6 {
|
||||
on, err := ipForwardingEnabledFreeBSD(ipv6)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
|
||||
}
|
||||
if !on {
|
||||
warnings = append(warnings, "IPv6 forwarding is disabled.")
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
if len(warnings) > 0 {
|
||||
return fmt.Errorf("%s\nSubnet routes and exit nodes may not work correctly.%s", strings.Join(warnings, "\n"), kbLink), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ipForwardingEnabledFreeBSD reports whether IP forwarding is enabled globally
|
||||
// for the given protocol.
|
||||
func ipForwardingEnabledFreeBSD(p protocol) (bool, error) {
|
||||
k := "net.inet.ip.forwarding"
|
||||
if p == ipv6 {
|
||||
k = "net.inet6.ip6.forwarding"
|
||||
}
|
||||
bs, err := exec.Command("sysctl", "-n", k).Output()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't check sysctl %s: %w", k, err)
|
||||
}
|
||||
val, err := strconv.ParseInt(string(bytes.TrimSpace(bs)), 10, 32)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't parse %s: %w", k, err)
|
||||
}
|
||||
return val == 1, nil
|
||||
}
|
||||
|
||||
func ipForwardingEnabledSunOS(p protocol, iface string) (bool, error) {
|
||||
|
||||
+31
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package netutil contains misc shared networking code & types.
|
||||
@@ -8,10 +8,40 @@ import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"tailscale.com/syncs"
|
||||
)
|
||||
|
||||
// NewDefaultTransport returns a new *http.Transport configured identically to
|
||||
// the Go standard library's http.DefaultTransport.
|
||||
//
|
||||
// Unlike http.DefaultTransport.(*http.Transport).Clone(), it does not panic
|
||||
// when a program has replaced http.DefaultTransport with a RoundTripper that
|
||||
// is not a *http.Transport. In the common case (the global is still the
|
||||
// standard *http.Transport) it returns a clone of it, preserving the existing
|
||||
// behavior exactly; otherwise it returns a fresh transport mirroring the
|
||||
// stdlib defaults.
|
||||
func NewDefaultTransport() *http.Transport {
|
||||
if tr, ok := http.DefaultTransport.(*http.Transport); ok {
|
||||
return tr.Clone()
|
||||
}
|
||||
// Values copied verbatim from net/http's DefaultTransport.
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).DialContext,
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// NewOneConnListener returns a net.Listener that returns c on its
|
||||
// first Accept and EOF thereafter.
|
||||
//
|
||||
|
||||
+9
-5
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netutil
|
||||
@@ -13,7 +13,11 @@ import (
|
||||
"tailscale.com/net/tsaddr"
|
||||
)
|
||||
|
||||
func validateViaPrefix(ipp netip.Prefix) error {
|
||||
// ValidateViaPrefix checks that the IP prefix is a valid 4via6 route.
|
||||
// It verifies that the prefix is in the Tailscale via range, has a prefix
|
||||
// length between /96 and /128, and that the embedded site ID is in the
|
||||
// range 0–65535.
|
||||
func ValidateViaPrefix(ipp netip.Prefix) error {
|
||||
if !tsaddr.IsViaPrefix(ipp) {
|
||||
return fmt.Errorf("%v is not a 4-in-6 prefix", ipp)
|
||||
}
|
||||
@@ -41,8 +45,8 @@ func CalcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
|
||||
routeMap := map[netip.Prefix]bool{}
|
||||
if advertiseRoutes != "" {
|
||||
var default4, default6 bool
|
||||
advroutes := strings.Split(advertiseRoutes, ",")
|
||||
for _, s := range advroutes {
|
||||
advroutes := strings.SplitSeq(advertiseRoutes, ",")
|
||||
for s := range advroutes {
|
||||
ipp, err := netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%q is not a valid IP address or CIDR prefix", s)
|
||||
@@ -51,7 +55,7 @@ func CalcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
|
||||
return nil, fmt.Errorf("%s has non-address bits set; expected %s", ipp, ipp.Masked())
|
||||
}
|
||||
if tsaddr.IsViaPrefix(ipp) {
|
||||
if err := validateViaPrefix(ipp); err != nil {
|
||||
if err := ValidateViaPrefix(ipp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user