mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-03 02:21:58 +00:00
wgengine: wrap tun.Device to support filtering and packet injection (#358)
Right now, filtering and packet injection in wgengine depend on a patch to wireguard-go that probably isn't suitable for upstreaming. This need not be the case: wireguard-go/tun.Device is an interface. For example, faketun.go implements it to mock a TUN device for testing. This patch implements the same interface to provide filtering and packet injection at the tunnel device level, at which point the wireguard-go patch should no longer be necessary. This patch has the following performance impact on i7-7500U @ 2.70GHz, tested in the following namespace configuration: ┌────────────────┐ ┌─────────────────────────────────┐ ┌────────────────┐ │ $ns1 │ │ $ns0 │ │ $ns2 │ │ client0 │ │ tailcontrol, logcatcher │ │ client1 │ │ ┌─────┐ │ │ ┌──────┐ ┌──────┐ │ │ ┌─────┐ │ │ │vethc│───────┼────┼──│vethrc│ │vethrs│──────┼─────┼──│veths│ │ │ ├─────┴─────┐ │ │ ├──────┴────┐ ├──────┴────┐ │ │ ├─────┴─────┐ │ │ │10.0.0.2/24│ │ │ │10.0.0.1/24│ │10.0.1.1/24│ │ │ │10.0.1.2/24│ │ │ └───────────┘ │ │ └───────────┘ └───────────┘ │ │ └───────────┘ │ └────────────────┘ └─────────────────────────────────┘ └────────────────┘ Before: --------------------------------------------------- | TCP send | UDP send | |------------------------|------------------------| | 557.0 (±8.5) Mbits/sec | 3.03 (±0.02) Gbits/sec | --------------------------------------------------- After: --------------------------------------------------- | TCP send | UDP send | |------------------------|------------------------| | 544.8 (±1.6) Mbits/sec | 3.13 (±0.02) Gbits/sec | --------------------------------------------------- The impact on receive performance is similar. Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
This commit is contained in:
committed by
GitHub
parent
9ccbcda612
commit
33b2f30cea
@@ -33,6 +33,7 @@ import (
|
||||
"tailscale.com/wgengine/monitor"
|
||||
"tailscale.com/wgengine/packet"
|
||||
"tailscale.com/wgengine/router"
|
||||
"tailscale.com/wgengine/tstun"
|
||||
)
|
||||
|
||||
// minimalMTU is the MTU we set on tailscale's tuntap
|
||||
@@ -49,7 +50,7 @@ type userspaceEngine struct {
|
||||
logf logger.Logf
|
||||
reqCh chan struct{}
|
||||
waitCh chan struct{}
|
||||
tundev tun.Device
|
||||
tundev *tstun.TUN
|
||||
wgdev *device.Device
|
||||
router router.Router
|
||||
magicConn *magicsock.Conn
|
||||
@@ -60,7 +61,6 @@ type userspaceEngine struct {
|
||||
lastCfg wgcfg.Config
|
||||
|
||||
mu sync.Mutex // guards following; see lock order comment below
|
||||
filt *filter.Filter
|
||||
statusCallback StatusCallback
|
||||
peerSequence []wgcfg.Key
|
||||
endpoints []string
|
||||
@@ -81,8 +81,8 @@ func (l *Loggify) Write(b []byte) (int, error) {
|
||||
|
||||
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
|
||||
logf("Starting userspace wireguard engine (FAKE tuntap device).")
|
||||
tun := NewFakeTun()
|
||||
return NewUserspaceEngineAdvanced(logf, tun, router.NewFake, listenPort)
|
||||
tundev := tstun.WrapTUN(logf, tstun.NewFakeTUN())
|
||||
return NewUserspaceEngineAdvanced(logf, tundev, router.NewFake, listenPort)
|
||||
}
|
||||
|
||||
// NewUserspaceEngine creates the named tun device and returns a
|
||||
@@ -94,13 +94,14 @@ func NewUserspaceEngine(logf logger.Logf, tunname string, listenPort uint16) (En
|
||||
|
||||
logf("Starting userspace wireguard engine with tun device %q", tunname)
|
||||
|
||||
tundev, err := tun.CreateTUN(tunname, minimalMTU)
|
||||
tun, err := tun.CreateTUN(tunname, minimalMTU)
|
||||
if err != nil {
|
||||
diagnoseTUNFailure(logf)
|
||||
logf("CreateTUN: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
logf("CreateTUN ok.")
|
||||
tundev := tstun.WrapTUN(logf, tun)
|
||||
|
||||
e, err := NewUserspaceEngineAdvanced(logf, tundev, router.New, listenPort)
|
||||
if err != nil {
|
||||
@@ -115,11 +116,11 @@ type RouterGen func(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (
|
||||
|
||||
// NewUserspaceEngineAdvanced is like NewUserspaceEngine but takes a pre-created TUN device and allows specifing
|
||||
// a custom router constructor and listening port.
|
||||
func NewUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen RouterGen, listenPort uint16) (Engine, error) {
|
||||
func NewUserspaceEngineAdvanced(logf logger.Logf, tundev *tstun.TUN, routerGen RouterGen, listenPort uint16) (Engine, error) {
|
||||
return newUserspaceEngineAdvanced(logf, tundev, routerGen, listenPort)
|
||||
}
|
||||
|
||||
func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen RouterGen, listenPort uint16) (_ Engine, reterr error) {
|
||||
func newUserspaceEngineAdvanced(logf logger.Logf, tundev *tstun.TUN, routerGen RouterGen, listenPort uint16) (_ Engine, reterr error) {
|
||||
e := &userspaceEngine{
|
||||
logf: logf,
|
||||
reqCh: make(chan struct{}, 1),
|
||||
@@ -161,16 +162,9 @@ func newUserspaceEngineAdvanced(logf logger.Logf, tundev tun.Device, routerGen R
|
||||
Info: dlog,
|
||||
Error: dlog,
|
||||
}
|
||||
nofilter := func(b []byte) device.FilterResult {
|
||||
// for safety, default to dropping all packets
|
||||
logf("Warning: you forgot to use wgengine.SetFilterInOut()! Packet dropped.")
|
||||
return device.FilterDrop
|
||||
}
|
||||
|
||||
opts := &device.DeviceOptions{
|
||||
Logger: &logger,
|
||||
FilterIn: nofilter,
|
||||
FilterOut: nofilter,
|
||||
Logger: &logger,
|
||||
HandshakeDone: func(peerKey wgcfg.Key, allowedIPs []net.IPNet) {
|
||||
// Send an unsolicited status event every time a
|
||||
// handshake completes. This makes sure our UI can
|
||||
@@ -320,7 +314,7 @@ func (e *userspaceEngine) pinger(peerKey wgcfg.Key, ips []wgcfg.IP) {
|
||||
}
|
||||
for _, dstIP := range dstIPs {
|
||||
b := packet.GenICMP(srcIP, dstIP, ipid, packet.EchoRequest, 0, payload)
|
||||
e.wgdev.SendPacket(b)
|
||||
e.tundev.InjectOutbound(b)
|
||||
}
|
||||
ipid++
|
||||
}
|
||||
@@ -385,57 +379,11 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config)
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) GetFilter() *filter.Filter {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
return e.filt
|
||||
return e.tundev.GetFilter()
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) SetFilter(filt *filter.Filter) {
|
||||
var filtin, filtout func(b []byte) device.FilterResult
|
||||
if filt == nil {
|
||||
e.logf("wgengine: nil filter provided; no access restrictions.")
|
||||
} else {
|
||||
ft, ft_ok := e.tundev.(*fakeTun)
|
||||
filtin = func(b []byte) device.FilterResult {
|
||||
runf := filter.LogDrops
|
||||
//runf |= filter.HexdumpDrops
|
||||
runf |= filter.LogAccepts
|
||||
//runf |= filter.HexdumpAccepts
|
||||
q := &packet.QDecode{}
|
||||
if filt.RunIn(b, q, runf) == filter.Accept {
|
||||
// Only in fake mode, answer any incoming pings
|
||||
if ft_ok && q.IsEchoRequest() {
|
||||
pb := q.EchoRespond()
|
||||
ft.InsertRead(pb)
|
||||
// We already handled it, stop.
|
||||
return device.FilterDrop
|
||||
}
|
||||
return device.FilterAccept
|
||||
}
|
||||
return device.FilterDrop
|
||||
}
|
||||
|
||||
filtout = func(b []byte) device.FilterResult {
|
||||
runf := filter.LogDrops
|
||||
//runf |= filter.HexdumpDrops
|
||||
runf |= filter.LogAccepts
|
||||
//runf |= filter.HexdumpAccepts
|
||||
q := &packet.QDecode{}
|
||||
if filt.RunOut(b, q, runf) == filter.Accept {
|
||||
return device.FilterAccept
|
||||
}
|
||||
return device.FilterDrop
|
||||
}
|
||||
}
|
||||
|
||||
e.wgLock.Lock()
|
||||
defer e.wgLock.Unlock()
|
||||
|
||||
e.wgdev.SetFilterInOut(filtin, filtout)
|
||||
|
||||
e.mu.Lock()
|
||||
e.filt = filt
|
||||
e.mu.Unlock()
|
||||
e.tundev.SetFilter(filt)
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) SetStatusCallback(cb StatusCallback) {
|
||||
|
||||
Reference in New Issue
Block a user