wgengine: add pinger to generate initial spray packets
For 3 seconds after a successful handshake, wgengine will send a ping packet every 300ms to its peer. This ensures the spray logic in magicsock has something to spray. Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
This commit is contained in:
parent
3988ddc85d
commit
7a3be96199
2
go.mod
2
go.mod
|
@ -17,7 +17,7 @@ require (
|
|||
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
|
||||
github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3
|
||||
github.com/tailscale/winipcfg-go v0.0.0-20200213045944-185b07f8233f
|
||||
github.com/tailscale/wireguard-go v0.0.0-20200224122332-ad79bbddc844
|
||||
github.com/tailscale/wireguard-go v0.0.0-20200225153850-22c50ed0a086
|
||||
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867
|
||||
|
|
|
@ -6,8 +6,10 @@ package wgengine
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -38,11 +40,13 @@ type userspaceEngine struct {
|
|||
|
||||
wgLock sync.Mutex // serializes all wgdev operations
|
||||
lastReconfig string
|
||||
lastCfg wgcfg.Config
|
||||
lastRoutes string
|
||||
|
||||
mu sync.Mutex
|
||||
peerSequence []wgcfg.Key
|
||||
endpoints []string
|
||||
pingers map[wgcfg.Key]context.CancelFunc // mu must be held to call CancelFunc
|
||||
}
|
||||
|
||||
type Loggify struct {
|
||||
|
@ -94,10 +98,11 @@ func NewUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
|
|||
|
||||
func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen RouterGen, listenPort uint16) (_ Engine, reterr error) {
|
||||
e := &userspaceEngine{
|
||||
logf: logf,
|
||||
reqCh: make(chan struct{}, 1),
|
||||
waitCh: make(chan struct{}),
|
||||
tundev: tundev,
|
||||
logf: logf,
|
||||
reqCh: make(chan struct{}, 1),
|
||||
waitCh: make(chan struct{}),
|
||||
tundev: tundev,
|
||||
pingers: make(map[wgcfg.Key]context.CancelFunc),
|
||||
}
|
||||
|
||||
mon, err := monitor.New(logf, func() { e.LinkChange(false) })
|
||||
|
@ -141,7 +146,7 @@ func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
|
|||
Logger: &logger,
|
||||
FilterIn: nofilter,
|
||||
FilterOut: nofilter,
|
||||
HandshakeDone: func() {
|
||||
HandshakeDone: func(peerKey wgcfg.Key, allowedIPs []net.IPNet) {
|
||||
// Send an unsolicited status event every time a
|
||||
// handshake completes. This makes sure our UI can
|
||||
// update quickly as soon as it connects to a peer.
|
||||
|
@ -151,6 +156,22 @@ func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
|
|||
// into it, and wireguard is what called us to get
|
||||
// here.
|
||||
go e.RequestStatus()
|
||||
|
||||
// All nodes have one primary IP address, and it
|
||||
// is the first entry on the AllowedIPs list.
|
||||
//
|
||||
// This code is written defensively in case we ever
|
||||
// end up with an empty AllowedIPs list or somehow
|
||||
// have a subnet as the first entry.
|
||||
if len(allowedIPs) > 0 {
|
||||
if ones, bits := allowedIPs[0].Mask.Size(); ones == bits && ones != 0 {
|
||||
var ip wgcfg.IP
|
||||
copy(ip.Addr[:], allowedIPs[0].IP.To16())
|
||||
e.startPinger(peerKey, ip)
|
||||
return
|
||||
}
|
||||
}
|
||||
logf("ERROR: peer %s has unexpected AllowedIPs: %v", peerKey.ShortString(), allowedIPs)
|
||||
},
|
||||
CreateBind: func(uint16) (conn.Bind, uint16, error) {
|
||||
return e.magicConn, e.magicConn.LocalPort(), nil
|
||||
|
@ -205,6 +226,77 @@ func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
|
|||
return e, nil
|
||||
}
|
||||
|
||||
// startPinger starts a goroutine that sends ping packets for a few seconds.
|
||||
//
|
||||
// These generated packets are used to ensure we trigger the spray logic in
|
||||
// the magicsock package for NAT traversal.
|
||||
func (e *userspaceEngine) startPinger(peerKey wgcfg.Key, ip wgcfg.IP) {
|
||||
e.logf("generating initial ping traffic to %s (%v)", peerKey.ShortString(), ip)
|
||||
var srcIP packet.IP
|
||||
|
||||
e.wgLock.Lock()
|
||||
if len(e.lastCfg.Addresses) > 0 {
|
||||
srcIP = packet.NewIP(e.lastCfg.Addresses[0].IP.IP())
|
||||
}
|
||||
e.wgLock.Unlock()
|
||||
|
||||
if srcIP == 0 {
|
||||
e.logf("generating initial ping traffic: no source IP")
|
||||
return
|
||||
}
|
||||
|
||||
e.mu.Lock()
|
||||
if cancel := e.pingers[peerKey]; cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
e.pingers[peerKey] = cancel
|
||||
e.mu.Unlock()
|
||||
|
||||
// sendFreq is slightly longer than sprayFreq in magicsock to ensure
|
||||
// that if these ping packets are the only source of early packets
|
||||
// sent to the peer, that each one will be sprayed.
|
||||
const sendFreq = 300 * time.Millisecond
|
||||
const stopAfter = 3 * time.Second
|
||||
|
||||
start := time.Now()
|
||||
dstIP := packet.NewIP(ip.IP())
|
||||
|
||||
payload := []byte("magicsock_spray") // no meaning
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
// If the pinger context is not done, then the
|
||||
// CancelFunc is still in the pingers map.
|
||||
delete(e.pingers, peerKey)
|
||||
}()
|
||||
|
||||
ipid := uint16(1)
|
||||
t := time.NewTicker(sendFreq)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
if time.Since(start) > stopAfter {
|
||||
return
|
||||
}
|
||||
b := packet.GenICMP(srcIP, dstIP, ipid, packet.EchoRequest, 0, payload)
|
||||
ipid++
|
||||
e.wgdev.SendPacket(b)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// TODO(apenwarr): dnsDomains really ought to be in wgcfg.Config.
|
||||
// However, we don't actually ever provide it to wireguard and it's not in
|
||||
// the traditional wireguard config format. On the other hand, wireguard
|
||||
|
@ -214,10 +306,12 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
|
|||
e.wgLock.Lock()
|
||||
defer e.wgLock.Unlock()
|
||||
|
||||
e.mu.Lock()
|
||||
e.peerSequence = make([]wgcfg.Key, len(cfg.Peers))
|
||||
for i, p := range cfg.Peers {
|
||||
e.peerSequence[i] = p.PublicKey
|
||||
}
|
||||
e.mu.Unlock()
|
||||
|
||||
// TODO(apenwarr): get rid of uapi stuff for in-process comms
|
||||
uapi, err := cfg.ToUAPI()
|
||||
|
@ -231,6 +325,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
|
|||
return nil
|
||||
}
|
||||
e.lastReconfig = rc
|
||||
e.lastCfg = cfg.Copy()
|
||||
|
||||
if err := e.wgdev.Reconfig(cfg); err != nil {
|
||||
e.logf("wgdev.Reconfig: %v\n", err)
|
||||
|
@ -458,6 +553,13 @@ func (e *userspaceEngine) RequestStatus() {
|
|||
}
|
||||
|
||||
func (e *userspaceEngine) Close() {
|
||||
e.mu.Lock()
|
||||
for key, cancel := range e.pingers {
|
||||
delete(e.pingers, key)
|
||||
cancel()
|
||||
}
|
||||
e.mu.Unlock()
|
||||
|
||||
r := bufio.NewReader(strings.NewReader(""))
|
||||
e.wgdev.IpcSetOperation(r)
|
||||
e.linkMon.Close()
|
||||
|
|
Loading…
Reference in New Issue