mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
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
a6ad3c46e2
commit
ec101e4d54
2
go.mod
2
go.mod
@ -17,7 +17,7 @@ require (
|
|||||||
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
|
github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3
|
||||||
github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3
|
github.com/tailscale/hujson v0.0.0-20190930033718-5098e564d9b3
|
||||||
github.com/tailscale/winipcfg-go v0.0.0-20200213045944-185b07f8233f
|
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/crypto v0.0.0-20200210222208-86ce3cb69678
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867
|
||||||
|
@ -6,8 +6,10 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -38,11 +40,13 @@ type userspaceEngine struct {
|
|||||||
|
|
||||||
wgLock sync.Mutex // serializes all wgdev operations
|
wgLock sync.Mutex // serializes all wgdev operations
|
||||||
lastReconfig string
|
lastReconfig string
|
||||||
|
lastCfg wgcfg.Config
|
||||||
lastRoutes string
|
lastRoutes string
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
peerSequence []wgcfg.Key
|
peerSequence []wgcfg.Key
|
||||||
endpoints []string
|
endpoints []string
|
||||||
|
pingers map[wgcfg.Key]context.CancelFunc // mu must be held to call CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
type Loggify struct {
|
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) {
|
func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen RouterGen, listenPort uint16) (_ Engine, reterr error) {
|
||||||
e := &userspaceEngine{
|
e := &userspaceEngine{
|
||||||
logf: logf,
|
logf: logf,
|
||||||
reqCh: make(chan struct{}, 1),
|
reqCh: make(chan struct{}, 1),
|
||||||
waitCh: make(chan struct{}),
|
waitCh: make(chan struct{}),
|
||||||
tundev: tundev,
|
tundev: tundev,
|
||||||
|
pingers: make(map[wgcfg.Key]context.CancelFunc),
|
||||||
}
|
}
|
||||||
|
|
||||||
mon, err := monitor.New(logf, func() { e.LinkChange(false) })
|
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,
|
Logger: &logger,
|
||||||
FilterIn: nofilter,
|
FilterIn: nofilter,
|
||||||
FilterOut: nofilter,
|
FilterOut: nofilter,
|
||||||
HandshakeDone: func() {
|
HandshakeDone: func(peerKey wgcfg.Key, allowedIPs []net.IPNet) {
|
||||||
// Send an unsolicited status event every time a
|
// Send an unsolicited status event every time a
|
||||||
// handshake completes. This makes sure our UI can
|
// handshake completes. This makes sure our UI can
|
||||||
// update quickly as soon as it connects to a peer.
|
// 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
|
// into it, and wireguard is what called us to get
|
||||||
// here.
|
// here.
|
||||||
go e.RequestStatus()
|
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) {
|
CreateBind: func(uint16) (conn.Bind, uint16, error) {
|
||||||
return e.magicConn, e.magicConn.LocalPort(), nil
|
return e.magicConn, e.magicConn.LocalPort(), nil
|
||||||
@ -205,6 +226,77 @@ func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
|
|||||||
return e, nil
|
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.
|
// 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
|
// 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
|
// 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()
|
e.wgLock.Lock()
|
||||||
defer e.wgLock.Unlock()
|
defer e.wgLock.Unlock()
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
e.peerSequence = make([]wgcfg.Key, len(cfg.Peers))
|
e.peerSequence = make([]wgcfg.Key, len(cfg.Peers))
|
||||||
for i, p := range cfg.Peers {
|
for i, p := range cfg.Peers {
|
||||||
e.peerSequence[i] = p.PublicKey
|
e.peerSequence[i] = p.PublicKey
|
||||||
}
|
}
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
// TODO(apenwarr): get rid of uapi stuff for in-process comms
|
// TODO(apenwarr): get rid of uapi stuff for in-process comms
|
||||||
uapi, err := cfg.ToUAPI()
|
uapi, err := cfg.ToUAPI()
|
||||||
@ -231,6 +325,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, dnsDomains []string) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
e.lastReconfig = rc
|
e.lastReconfig = rc
|
||||||
|
e.lastCfg = cfg.Copy()
|
||||||
|
|
||||||
if err := e.wgdev.Reconfig(cfg); err != nil {
|
if err := e.wgdev.Reconfig(cfg); err != nil {
|
||||||
e.logf("wgdev.Reconfig: %v\n", err)
|
e.logf("wgdev.Reconfig: %v\n", err)
|
||||||
@ -458,6 +553,13 @@ func (e *userspaceEngine) RequestStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *userspaceEngine) Close() {
|
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(""))
|
r := bufio.NewReader(strings.NewReader(""))
|
||||||
e.wgdev.IpcSetOperation(r)
|
e.wgdev.IpcSetOperation(r)
|
||||||
e.linkMon.Close()
|
e.linkMon.Close()
|
||||||
|
Loading…
Reference in New Issue
Block a user