mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-20 11:58:39 +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 from tailscale.com/derp/derphttp
|
||||||
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
|
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
|
||||||
tailscale.com/disco from tailscale.com/derp
|
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 from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/metrics from tailscale.com/derp
|
tailscale.com/metrics from tailscale.com/derp
|
||||||
|
@ -7,14 +7,15 @@
|
|||||||
package netns
|
package netns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,19 +27,49 @@ import (
|
|||||||
// wgengine/router/router_linux.go.
|
// wgengine/router/router_linux.go.
|
||||||
const tailscaleBypassMark = 0x80000
|
const tailscaleBypassMark = 0x80000
|
||||||
|
|
||||||
// ipRuleOnce is the sync.Once & cached value for ipRuleAvailable.
|
// socketMarkWorksOnce is the sync.Once & cached value for useSocketMark.
|
||||||
var ipRuleOnce struct {
|
var socketMarkWorksOnce struct {
|
||||||
sync.Once
|
sync.Once
|
||||||
v bool
|
v bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ipRuleAvailable reports whether the 'ip rule' command works.
|
// socketMarkWorks returns whether SO_MARK works.
|
||||||
// If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
|
func socketMarkWorks() bool {
|
||||||
func ipRuleAvailable() bool {
|
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:1")
|
||||||
ipRuleOnce.Do(func() {
|
if err != nil {
|
||||||
ipRuleOnce.v = exec.Command("ip", "rule").Run() == 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
|
// 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.
|
// 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
|
// But by default, assume that tests don't need netns and it's
|
||||||
// harmless to ignore the sockopts failing.
|
// harmless to ignore the sockopts failing.
|
||||||
if flag.CommandLine.Lookup("test.v") != nil {
|
if hostinfo.GetEnvType() == hostinfo.TestCase {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
@ -67,14 +98,14 @@ func control(network, address string, c syscall.RawConn) error {
|
|||||||
if hostinfo.GetEnvType() == hostinfo.TestCase {
|
if hostinfo.GetEnvType() == hostinfo.TestCase {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if IsLocalhost(address) {
|
if isLocalhost(address) {
|
||||||
// Don't bind to an interface for localhost connections.
|
// Don't bind to an interface for localhost connections.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var sockErr error
|
var sockErr error
|
||||||
err := c.Control(func(fd uintptr) {
|
err := c.Control(func(fd uintptr) {
|
||||||
if ipRuleAvailable() {
|
if useSocketMark() {
|
||||||
sockErr = setBypassMark(fd)
|
sockErr = setBypassMark(fd)
|
||||||
} else {
|
} else {
|
||||||
sockErr = bindToDevice(fd)
|
sockErr = bindToDevice(fd)
|
||||||
|
@ -49,3 +49,9 @@ func TestBypassMarkInSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Errorf("tailscaleBypassMark not found in router_linux.go")
|
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…
x
Reference in New Issue
Block a user