wgengine: loop back tuntap packets destined for local IPs.

macOS incorrectly sends packets for the local Tailscale IP
into our tunnel interface. We have to turn the packets around
and send them back to the kernel.

Fixes tailscale/corp#189.

Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
David Anderson 2020-06-09 18:00:48 +00:00 committed by Dave Anderson
parent d1a30be275
commit d05b0500ac
1 changed files with 70 additions and 18 deletions

View File

@ -18,6 +18,7 @@ import (
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/tailscale/wireguard-go/device"
@ -49,15 +50,21 @@ import (
const minimalMTU = 1280
type userspaceEngine struct {
logf logger.Logf
reqCh chan struct{}
waitCh chan struct{} // chan is closed when first Close call completes; contrast with closing bool
tundev *tstun.TUN
wgdev *device.Device
router router.Router
resolver *tsdns.Resolver
magicConn *magicsock.Conn
linkMon *monitor.Mon
logf logger.Logf
reqCh chan struct{}
waitCh chan struct{} // chan is closed when first Close call completes; contrast with closing bool
tundev *tstun.TUN
wgdev *device.Device
router router.Router
resolver *tsdns.Resolver
useTailscaleDNS bool
magicConn *magicsock.Conn
linkMon *monitor.Mon
// localAddrs is the set of IP addresses assigned to the local
// tunnel interface. It's used to reflect local packets
// incorrectly sent to us.
localAddrs atomic.Value // of map[packet.IP]bool
wgLock sync.Mutex // serializes all wgdev operations; see lock order comment below
lastEngineSig string
@ -161,22 +168,22 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) {
logf := conf.Logf
e := &userspaceEngine{
logf: logf,
reqCh: make(chan struct{}, 1),
waitCh: make(chan struct{}),
tundev: tstun.WrapTUN(logf, conf.TUN),
resolver: tsdns.NewResolver(logf),
pingers: make(map[wgcfg.Key]*pinger),
logf: logf,
reqCh: make(chan struct{}, 1),
waitCh: make(chan struct{}),
tundev: tstun.WrapTUN(logf, conf.TUN),
resolver: tsdns.NewResolver(logf),
useTailscaleDNS: conf.UseTailscaleDNS,
pingers: make(map[wgcfg.Key]*pinger),
}
e.localAddrs.Store(map[packet.IP]bool{})
e.linkState, _ = getLinkState()
// Respond to all pings only in fake mode.
if conf.EchoRespondToAll {
e.tundev.PostFilterIn = echoRespondToAll
}
if conf.UseTailscaleDNS {
e.tundev.PreFilterOut = e.handleDNS
}
e.tundev.PreFilterOut = e.handleLocalPackets
mon, err := monitor.New(logf, func() { e.LinkChange(false) })
if err != nil {
@ -314,6 +321,40 @@ func echoRespondToAll(p *packet.ParsedPacket, t *tstun.TUN) filter.Response {
return filter.Accept
}
// handleLocalPackets inspects packets coming from the local network
// stack, and intercepts any packets that should be handled by
// tailscaled directly. Other packets are allowed to proceed into the
// main ACL filter.
func (e *userspaceEngine) handleLocalPackets(p *packet.ParsedPacket, t *tstun.TUN) filter.Response {
if e.useTailscaleDNS {
if verdict := e.handleDNS(p, t); verdict == filter.Drop {
// local DNS handled the packet.
return filter.Drop
}
}
if runtime.GOOS == "darwin" && e.isLocalAddr(p.DstIP) {
// macOS NetworkExtension directs packets destined to the
// tunnel's local IP address into the tunnel, instead of
// looping back within the kernel network stack. We have to
// notice that an outbound packet is actually destined for
// ourselves, and loop it back into macOS.
t.InjectInboundCopy(p.Buffer())
return filter.Drop
}
return filter.Accept
}
func (e *userspaceEngine) isLocalAddr(ip packet.IP) bool {
localAddrs, ok := e.localAddrs.Load().(map[packet.IP]bool)
if !ok {
e.logf("[unexpected] e.localAddrs was nil, can't check for loopback packet")
return false
}
return localAddrs[ip]
}
// handleDNS is an outbound pre-filter resolving Tailscale domains.
func (e *userspaceEngine) handleDNS(p *packet.ParsedPacket, t *tstun.TUN) filter.Response {
if e.resolver.AcceptsPacket(p) {
@ -460,6 +501,17 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
panic("routerCfg must not be nil")
}
localAddrs := map[packet.IP]bool{}
for _, addr := range routerCfg.LocalAddrs {
// TODO: ipv6
if !addr.IP.Is4() {
continue
}
bs := addr.IP.As16()
localAddrs[packet.NewIP(net.IP(bs[12:16]))] = true
}
e.localAddrs.Store(localAddrs)
e.wgLock.Lock()
defer e.wgLock.Unlock()