net/netutil: only check Linux sysctls w/ procfs, assume absent means false

Fixes #7217

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2023-02-08 20:11:25 +00:00 committed by Brad Fitzpatrick
parent 05adf22383
commit 2477fc4952
2 changed files with 27 additions and 13 deletions

View File

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"net/netip" "net/netip"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv" "strconv"
@ -194,27 +193,28 @@ const (
// given interface. // given interface.
// The iface param determines which interface to check against, "" means to check // The iface param determines which interface to check against, "" means to check
// global config. // global config.
// It tries to lookup the value directly from `/proc/sys`, and falls back to // This is Linux-specific: it only reads from /proc/sys and doesn't shell out to
// using `sysctl` on failure. // sysctl (which on Linux just reads from /proc/sys anyway).
func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) { func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
k := ipForwardSysctlKey(slashFormat, p, iface) k := ipForwardSysctlKey(slashFormat, p, iface)
bs, err := os.ReadFile(filepath.Join("/proc/sys", k)) bs, err := os.ReadFile(filepath.Join("/proc/sys", k))
if err != nil { if err != nil {
// Fallback to using sysctl. if os.IsNotExist(err) {
// Sysctl accepts `/` as separator. // If IPv6 is disabled, sysctl keys like "net.ipv6.conf.all.forwarding" just don't
bs, err = exec.Command("sysctl", "-n", k).Output() // exist on disk. But first diagnose whether procfs is even mounted before assuming
if err != nil { // absence means false.
// But in case it doesn't. if fi, err := os.Stat("/proc/sys"); err != nil {
k := ipForwardSysctlKey(dotFormat, p, iface) return false, fmt.Errorf("failed to check sysctl %v; no procfs? %w", k, err)
bs, err = exec.Command("sysctl", "-n", k).Output() } else if !fi.IsDir() {
if err != nil { return false, fmt.Errorf("failed to check sysctl %v; /proc/sys isn't a directory, is %v", k, fi.Mode())
return false, fmt.Errorf("couldn't check %s (%v)", k, err)
} }
return false, nil
} }
return false, err
} }
on, err := strconv.ParseBool(string(bytes.TrimSpace(bs))) on, err := strconv.ParseBool(string(bytes.TrimSpace(bs)))
if err != nil { if err != nil {
return false, fmt.Errorf("couldn't parse %s (%v)", k, err) return false, fmt.Errorf("couldn't parse %s: %w", k, err)
} }
return on, nil return on, nil
} }

View File

@ -6,6 +6,7 @@ package netutil
import ( import (
"io" "io"
"net" "net"
"runtime"
"testing" "testing"
) )
@ -51,3 +52,16 @@ func TestOneConnListener(t *testing.T) {
t.Errorf("nil Addr") t.Errorf("nil Addr")
} }
} }
func TestIPForwardingEnabledLinux(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skipf("skipping on %s", runtime.GOOS)
}
got, err := ipForwardingEnabledLinux(ipv4, "some-not-found-interface")
if err != nil {
t.Fatal(err)
}
if got {
t.Errorf("got true; want false")
}
}