diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index dfaca5242..ec8d3df30 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -12,6 +12,8 @@ import ( "io" "log" "net" + "os/exec" + "runtime" "strconv" "strings" "sync" @@ -35,6 +37,7 @@ import ( "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: