|
|
@@ -5,7 +5,6 @@ package netutil
|
|
|
|
|
|
import (
|
|
|
"bytes"
|
|
|
- "errors"
|
|
|
"fmt"
|
|
|
"net/netip"
|
|
|
"os"
|
|
|
@@ -146,64 +145,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 +176,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 (
|
|
|
@@ -305,32 +227,6 @@ func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
|
|
|
return on, nil
|
|
|
}
|
|
|
|
|
|
-// reversePathFilterValueLinux reports the reverse path filter setting on Linux
|
|
|
-// for the given interface.
|
|
|
-//
|
|
|
-// 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()
|
|
|
- if err != nil {
|
|
|
- return -1, fmt.Errorf("couldn't check %s (%v)", k, err)
|
|
|
- }
|
|
|
- }
|
|
|
- v, err := strconv.Atoi(string(bytes.TrimSpace(bs)))
|
|
|
- if err != nil {
|
|
|
- return -1, fmt.Errorf("couldn't parse %s (%v)", k, err)
|
|
|
- }
|
|
|
- return v, nil
|
|
|
-}
|
|
|
-
|
|
|
func ipForwardingEnabledSunOS(p protocol, iface string) (bool, error) {
|
|
|
var proto string
|
|
|
if p == ipv4 {
|