wgengine/netstack: in netstack/hybrid mode, fake ICMP using ping command

Change-Id: I42cb4b9b326337f4090d9cea532230e36944b6cb
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2021-12-08 16:44:11 -08:00 committed by Brad Fitzpatrick
parent 21741e111b
commit b59e7669c1

View File

@ -12,6 +12,8 @@
"io"
"log"
"net"
"os/exec"
"runtime"
"strconv"
"strings"
"sync"
@ -35,6 +37,7 @@
"tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial"
"tailscale.com/net/tstun"
"tailscale.com/syncs"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/wgengine"
@ -377,11 +380,69 @@ func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool {
return false
}
var userPingSem = syncs.NewSemaphore(20) // 20 child ping processes at once
// userPing tried to ping dstIP and if it succeeds, injects pingResPkt
// into the tundev.
//
// It's used in userspace/netstack mode when we don't have kernel
// support or raw socket access. As such, this does the dumbest thing
// that can work: runs the ping command. It's not super efficient, so
// 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.
//
// TODO(bradfitz): when we're running on Windows as the system user, use
// raw socket APIs instead of ping child processes.
func (ns *Impl) userPing(dstIP netaddr.IP, pingResPkt []byte) {
if !userPingSem.TryAcquire() {
return
}
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()
default:
err = exec.Command("ping", "-c", "1", "-W", "3", dstIP.String()).Run()
}
d := time.Since(t0)
if err != nil {
ns.logf("exec ping of %v failed in %v", dstIP, d)
return
}
if debugNetstack {
ns.logf("exec pinged %v in %v", dstIP, time.Since(t0))
}
if err := ns.tundev.InjectOutbound(pingResPkt); err != nil {
ns.logf("InjectOutbound ping response: %v", err)
}
}
func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
if !ns.shouldProcessInbound(p, t) {
// Let the host network stack (if any) deal with it.
return filter.Accept
}
destIP := p.Dst.IP()
if p.IsEchoRequest() && ns.ProcessSubnets && !tsaddr.IsTailscaleIP(destIP) {
var pong []byte // the reply to the ping, if our relayed ping works
if destIP.Is4() {
h := p.ICMP4Header()
h.ToResponse()
pong = packet.Generate(&h, p.Payload())
} else if destIP.Is6() {
h := p.ICMP6Header()
h.ToResponse()
pong = packet.Generate(&h, p.Payload())
}
go ns.userPing(destIP, pong)
return filter.DropSilently
}
var pn tcpip.NetworkProtocolNumber
switch p.IPVersion {
case 4: