TUN vectorised reads/writes (#1145)

This PR updates the Wireguard dependency and updates to use new
vectorised reads/writes, which should reduce the number of syscalls and
improve performance.

This will only make a difference on Linux as this is the only platform
for which the Wireguard TUN library supports vectorised reads/writes.
For other platforms, single reads and writes will be performed as usual.

---------

Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
This commit is contained in:
Neil 2024-07-20 15:24:30 +01:00 committed by GitHub
parent 04c0acf71b
commit 02d92ff81c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 92 additions and 24 deletions

View file

@ -1,42 +1,60 @@
package tun
const TUN_OFFSET_BYTES = 4
const TUN_OFFSET_BYTES = 80 // sizeof(virtio_net_hdr)
func (tun *TunAdapter) read() {
var buf [TUN_OFFSET_BYTES + 65535]byte
vs := tun.iface.BatchSize()
bufs := make([][]byte, vs)
sizes := make([]int, vs)
for i := range bufs {
bufs[i] = make([]byte, TUN_OFFSET_BYTES+65535)
}
for {
n, err := tun.iface.Read(buf[:], TUN_OFFSET_BYTES)
if n <= TUN_OFFSET_BYTES || err != nil {
n, err := tun.iface.Read(bufs, sizes, TUN_OFFSET_BYTES)
if err != nil {
tun.log.Errorln("Error reading TUN:", err)
ferr := tun.iface.Flush()
if ferr != nil {
tun.log.Errorln("Unable to flush packets:", ferr)
}
return
}
begin := TUN_OFFSET_BYTES
end := begin + n
bs := buf[begin:end]
if _, err := tun.rwc.Write(bs); err != nil {
tun.log.Debugln("Unable to send packet:", err)
for i, b := range bufs[:n] {
if _, err := tun.rwc.Write(b[TUN_OFFSET_BYTES : TUN_OFFSET_BYTES+sizes[i]]); err != nil {
tun.log.Debugln("Unable to send packet:", err)
}
}
}
}
func (tun *TunAdapter) write() {
var buf [TUN_OFFSET_BYTES + 65535]byte
func (tun *TunAdapter) queue() {
for {
bs := buf[TUN_OFFSET_BYTES:]
n, err := tun.rwc.Read(bs)
p := bufPool.Get().([]byte)[:bufPoolSize]
n, err := tun.rwc.Read(p)
if err != nil {
tun.log.Errorln("Exiting TUN writer due to core read error:", err)
return
}
tun.ch <- p[:n]
}
}
func (tun *TunAdapter) write() {
vs := cap(tun.ch)
bufs := make([][]byte, vs)
for i := range bufs {
bufs[i] = make([]byte, TUN_OFFSET_BYTES+65535)
}
for {
n := len(tun.ch)
if n == 0 {
n = 1 // Nothing queued up yet, wait for it instead
}
for i := 0; i < n; i++ {
msg := <-tun.ch
bufs[i] = append(bufs[i][:TUN_OFFSET_BYTES], msg...)
bufPool.Put(msg) // nolint:staticcheck
}
if !tun.isEnabled {
continue // Nothing to do, the tun isn't enabled
}
bs = buf[:TUN_OFFSET_BYTES+n]
if _, err = tun.iface.Write(bs, TUN_OFFSET_BYTES); err != nil {
if _, err := tun.iface.Write(bufs[:n], TUN_OFFSET_BYTES); err != nil {
tun.Act(nil, func() {
if !tun.isOpen {
tun.log.Errorln("TUN iface write error:", err)

View file

@ -10,9 +10,11 @@ import (
"fmt"
"io"
"net"
"sync"
"time"
"github.com/Arceliar/phony"
"golang.zx2c4.com/wireguard/tun"
wgtun "golang.zx2c4.com/wireguard/tun"
"github.com/yggdrasil-network/yggdrasil-go/src/address"
"github.com/yggdrasil-network/yggdrasil-go/src/config"
@ -39,7 +41,7 @@ type TunAdapter struct {
addr address.Address
subnet address.Subnet
mtu uint64
iface tun.Device
iface wgtun.Device
phony.Inbox // Currently only used for _handlePacket from the reader, TODO: all the stuff that currently needs a mutex below
isOpen bool
isEnabled bool // Used by the writer to drop sessionTraffic if not enabled
@ -48,6 +50,7 @@ type TunAdapter struct {
name InterfaceName
mtu InterfaceMTU
}
ch chan []byte
}
// Gets the maximum supported MTU for the platform based on the defaults in
@ -62,6 +65,20 @@ func getSupportedMTU(mtu uint64) uint64 {
return mtu
}
func waitForTUNUp(ch <-chan wgtun.Event) bool {
t := time.After(time.Second * 5)
for {
select {
case ev := <-ch:
if ev == wgtun.EventUp {
return true
}
case <-t:
return false
}
}
}
// Name returns the name of the adapter, e.g. "tun0". On Windows, this may
// return a canonical adapter name instead.
func (tun *TunAdapter) Name() string {
@ -145,6 +162,8 @@ func (tun *TunAdapter) _start() error {
tun.rwc.SetMTU(tun.MTU())
tun.isOpen = true
tun.isEnabled = true
tun.ch = make(chan []byte, tun.iface.BatchSize())
go tun.queue()
go tun.read()
go tun.write()
return nil
@ -178,3 +197,12 @@ func (tun *TunAdapter) _stop() error {
}
return nil
}
const bufPoolSize = TUN_OFFSET_BYTES + 65535
var bufPool = sync.Pool{
New: func() any {
b := [bufPoolSize]byte{}
return b[:]
},
}

View file

@ -80,6 +80,9 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if err != nil {
return fmt.Errorf("failed to create TUN: %w", err)
}
if !waitForTUNUp(iface.Events()) {
return fmt.Errorf("TUN did not come up in time")
}
tun.iface = iface
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(uint64(mtu))

View file

@ -27,6 +27,9 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if err != nil {
return fmt.Errorf("failed to create TUN: %w", err)
}
if !waitForTUNUp(iface.Events()) {
return fmt.Errorf("TUN did not come up in time")
}
tun.iface = iface
if m, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(uint64(m))
@ -55,6 +58,9 @@ func (tun *TunAdapter) setupFD(fd int32, addr string, mtu uint64) error {
unix.Close(dfd)
return fmt.Errorf("failed to create TUN from FD: %w", err)
}
if !waitForTUNUp(iface.Events()) {
return fmt.Errorf("TUN did not come up in time")
}
tun.iface = iface
if m, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(uint64(m))

View file

@ -21,6 +21,9 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if err != nil {
return fmt.Errorf("failed to create TUN: %w", err)
}
if !waitForTUNUp(iface.Events()) {
return fmt.Errorf("TUN did not come up in time")
}
tun.iface = iface
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(uint64(mtu))

View file

@ -18,6 +18,9 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if err != nil {
return fmt.Errorf("failed to create TUN: %w", err)
}
if !waitForTUNUp(iface.Events()) {
return fmt.Errorf("TUN did not come up in time")
}
tun.iface = iface
if mtu, err := iface.MTU(); err == nil {
tun.mtu = getSupportedMTU(uint64(mtu))

View file

@ -34,6 +34,9 @@ func (tun *TunAdapter) setup(ifname string, addr string, mtu uint64) error {
if iface, err = wgtun.CreateTUNWithRequestedGUID(ifname, &guid, int(mtu)); err != nil {
return err
}
if !waitForTUNUp(iface.Events()) {
return fmt.Errorf("TUN did not come up in time")
}
tun.iface = iface
if addr != "" {
if err = tun.setupAddress(addr); err != nil {