mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
net/netns: support !CAP_NET_ADMIN
netns_linux checked whether "ip rule" could run to determine whether to use SO_MARK for network namespacing. However in Linux environments which lack CAP_NET_ADMIN, such as various container runtimes, the "ip rule" command succeeds but SO_MARK fails due to lack of permission. SO_BINDTODEVICE would work in these environments, but isn't tried. In addition to running "ip rule" check directly whether SO_MARK works or not. Among others, this allows Microsoft Azure App Service and AWS App Runner to work. Signed-off-by: Denton Gentry <dgentry@tailscale.com>
This commit is contained in:
parent
1896bf99d9
commit
d2480fd508
@ -22,7 +22,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/derp from tailscale.com/derp/derphttp
|
||||
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
|
||||
tailscale.com/disco from tailscale.com/derp
|
||||
tailscale.com/hostinfo from tailscale.com/net/interfaces
|
||||
tailscale.com/hostinfo from tailscale.com/net/interfaces+
|
||||
tailscale.com/ipn from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/metrics from tailscale.com/derp
|
||||
|
@ -7,14 +7,15 @@
|
||||
package netns
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/net/interfaces"
|
||||
)
|
||||
|
||||
@ -26,19 +27,49 @@
|
||||
// wgengine/router/router_linux.go.
|
||||
const tailscaleBypassMark = 0x80000
|
||||
|
||||
// ipRuleOnce is the sync.Once & cached value for ipRuleAvailable.
|
||||
var ipRuleOnce struct {
|
||||
// socketMarkWorksOnce is the sync.Once & cached value for useSocketMark.
|
||||
var socketMarkWorksOnce struct {
|
||||
sync.Once
|
||||
v bool
|
||||
}
|
||||
|
||||
// ipRuleAvailable reports whether the 'ip rule' command works.
|
||||
// If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
|
||||
func ipRuleAvailable() bool {
|
||||
ipRuleOnce.Do(func() {
|
||||
ipRuleOnce.v = exec.Command("ip", "rule").Run() == nil
|
||||
// socketMarkWorks returns whether SO_MARK works.
|
||||
func socketMarkWorks() bool {
|
||||
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:1")
|
||||
if err != nil {
|
||||
return true // unsure, returning true does the least harm.
|
||||
}
|
||||
|
||||
sConn, err := net.DialUDP("udp", nil, addr)
|
||||
if err != nil {
|
||||
return true // unsure, return true
|
||||
}
|
||||
defer sConn.Close()
|
||||
|
||||
rConn, err := sConn.SyscallConn()
|
||||
if err != nil {
|
||||
return true // unsure, return true
|
||||
}
|
||||
|
||||
var sockErr error
|
||||
err = rConn.Control(func(fd uintptr) {
|
||||
sockErr = setBypassMark(fd)
|
||||
})
|
||||
return ipRuleOnce.v
|
||||
if err != nil || sockErr != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// useSocketMark reports whether SO_MARK works.
|
||||
// If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
|
||||
func useSocketMark() bool {
|
||||
socketMarkWorksOnce.Do(func() {
|
||||
ipRuleWorks := exec.Command("ip", "rule").Run() == nil
|
||||
socketMarkWorksOnce.v = ipRuleWorks && socketMarkWorks()
|
||||
})
|
||||
return socketMarkWorksOnce.v
|
||||
}
|
||||
|
||||
// ignoreErrors returns true if we should ignore setsocketopt errors in
|
||||
@ -49,7 +80,7 @@ func ignoreErrors() bool {
|
||||
// checks if it's setting up a world that needs netns to work.
|
||||
// But by default, assume that tests don't need netns and it's
|
||||
// harmless to ignore the sockopts failing.
|
||||
if flag.CommandLine.Lookup("test.v") != nil {
|
||||
if hostinfo.GetEnvType() == hostinfo.TestCase {
|
||||
return true
|
||||
}
|
||||
if os.Getuid() != 0 {
|
||||
@ -67,14 +98,14 @@ func control(network, address string, c syscall.RawConn) error {
|
||||
if hostinfo.GetEnvType() == hostinfo.TestCase {
|
||||
return nil
|
||||
}
|
||||
if IsLocalhost(address) {
|
||||
if isLocalhost(address) {
|
||||
// Don't bind to an interface for localhost connections.
|
||||
return nil
|
||||
}
|
||||
|
||||
var sockErr error
|
||||
err := c.Control(func(fd uintptr) {
|
||||
if ipRuleAvailable() {
|
||||
if useSocketMark() {
|
||||
sockErr = setBypassMark(fd)
|
||||
} else {
|
||||
sockErr = bindToDevice(fd)
|
||||
|
@ -49,3 +49,9 @@ func TestBypassMarkInSync(t *testing.T) {
|
||||
}
|
||||
t.Errorf("tailscaleBypassMark not found in router_linux.go")
|
||||
}
|
||||
|
||||
func TestSocketMarkWorks(t *testing.T) {
|
||||
_ = socketMarkWorks()
|
||||
// we cannot actually assert whether the test runner has SO_MARK available
|
||||
// or not, as we don't know. We're just checking that it doesn't panic.
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user