xcode: allow ICMP ping relay on macOS + iOS platforms (#12048)

Fixes tailscale/tailscale#10393
Fixes tailscale/corp#15412
Fixes tailscale/corp#19808

On Apple platforms, exit nodes and subnet routers have been unable to relay pings from Tailscale devices to non-Tailscale devices due to sandbox restrictions imposed on our network extensions by Apple. The sandbox prevented the code in netstack.go from spawning the `ping` process which we were using.

Replace that exec call with logic to send an ICMP echo request directly, which appears to work in userspace, and not trigger a sandbox violation in the syslog.

Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
This commit is contained in:
Andrea Gottardo
2024-05-16 11:57:57 -07:00
committed by GitHub
parent 59848fe14b
commit e5f67f90a2
6 changed files with 114 additions and 51 deletions

View File

@@ -15,8 +15,6 @@ import (
"math"
"net"
"net/netip"
"os"
"os/exec"
"runtime"
"strconv"
"sync"
@@ -55,7 +53,6 @@ import (
"tailscale.com/types/nettype"
"tailscale.com/util/clientmetric"
"tailscale.com/version"
"tailscale.com/version/distro"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
@@ -916,14 +913,8 @@ func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool {
return false
}
// setAmbientCapsRaw is non-nil on Linux for Synology, to run ping with
// CAP_NET_RAW from tailscaled's binary.
var setAmbientCapsRaw func(*exec.Cmd)
var userPingSem = syncs.NewSemaphore(20) // 20 child ping processes at once
var isSynology = runtime.GOOS == "linux" && distro.Get() == distro.Synology
type userPingDirection int
const (
@@ -944,6 +935,8 @@ const (
// it bounds the number of pings going on at once. The idea is that
// people only use ping occasionally to see if their internet's working
// so this doesn't need to be great.
// On Apple platforms, this function doesn't run the ping command. Instead,
// it sends a non-privileged ping.
//
// The 'direction' parameter is used to determine where the response "pong"
// packet should be written, if the ping succeeds. See the documentation on the
@@ -958,44 +951,7 @@ func (ns *Impl) userPing(dstIP netip.Addr, pingResPkt []byte, direction userPing
defer userPingSem.Release()
t0 := time.Now()
var err error
switch runtime.GOOS {
case "windows":
err = exec.Command("ping", "-n", "1", "-w", "3000", dstIP.String()).Run()
case "darwin", "freebsd":
// Note: 2000 ms is actually 1 second + 2,000
// milliseconds extra for 3 seconds total.
// See https://github.com/tailscale/tailscale/pull/3753 for details.
ping := "ping"
if dstIP.Is6() {
ping = "ping6"
}
err = exec.Command(ping, "-c", "1", "-W", "2000", dstIP.String()).Run()
case "openbsd":
ping := "ping"
if dstIP.Is6() {
ping = "ping6"
}
err = exec.Command(ping, "-c", "1", "-w", "3", dstIP.String()).Run()
case "android":
ping := "/system/bin/ping"
if dstIP.Is6() {
ping = "/system/bin/ping6"
}
err = exec.Command(ping, "-c", "1", "-w", "3", dstIP.String()).Run()
default:
ping := "ping"
if isSynology {
ping = "/bin/ping"
}
cmd := exec.Command(ping, "-c", "1", "-W", "3", dstIP.String())
if isSynology && os.Getuid() != 0 {
// On DSM7 we run as non-root and need to pass
// CAP_NET_RAW if our binary has it.
setAmbientCapsRaw(cmd)
}
err = cmd.Run()
}
err := ns.sendOutboundUserPing(dstIP, 3*time.Second)
d := time.Since(t0)
if err != nil {
if d < time.Second/2 {