mirror of
https://github.com/tailscale/tailscale.git
synced 2026-04-04 06:36:01 -04:00
net/uring: add split for linux vs not for io_uring
This also adds a flag for checking whether it is active or not. Signed-off-by: julianknodt <julianknodt@gmail.com>
This commit is contained in:
@@ -531,7 +531,54 @@ func (t *Wrapper) Write(buf []byte, offset int) (int, error) {
|
||||
}
|
||||
|
||||
t.noteActivity()
|
||||
return t.tdev.Write(buf, offset)
|
||||
return t.write(buf, offset)
|
||||
}
|
||||
|
||||
func (t *Wrapper) write(buf []byte, offset int) (int, error) {
|
||||
if t.ring == nil {
|
||||
return t.tdev.Write(buf, offset)
|
||||
}
|
||||
|
||||
// below copied from wireguard-go NativeTUN.Write
|
||||
|
||||
// reserve space for header
|
||||
buf = buf[offset-4:]
|
||||
|
||||
// add packet information header
|
||||
buf[0] = 0x00
|
||||
buf[1] = 0x00
|
||||
if buf[4]>>4 == ipv6.Version {
|
||||
buf[2] = 0x86
|
||||
buf[3] = 0xdd
|
||||
} else {
|
||||
buf[2] = 0x08
|
||||
buf[3] = 0x00
|
||||
}
|
||||
|
||||
n, err := t.ring.Write(buf)
|
||||
if errors.Is(err, syscall.EBADFD) {
|
||||
err = os.ErrClosed
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (t *Wrapper) read(buf []byte, offset int) (n int, err error) {
|
||||
// TODO: upstream has graceful shutdown error handling here.
|
||||
buff := buf[offset-4:]
|
||||
if uring.URingAvailable() {
|
||||
n, err = t.ring.Read(buff[:])
|
||||
} else {
|
||||
n, err = t.tdev.(*wgtun.NativeTun).File().Read(buff[:])
|
||||
}
|
||||
if errors.Is(err, syscall.EBADFD) {
|
||||
err = os.ErrClosed
|
||||
}
|
||||
if n < 4 {
|
||||
n = 0
|
||||
} else {
|
||||
n -= 4
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (t *Wrapper) GetFilter() *filter.Filter {
|
||||
|
||||
2
net/uring/.gitignore
vendored
Normal file
2
net/uring/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
liburing/
|
||||
*.so
|
||||
@@ -1,3 +1,7 @@
|
||||
// +build linux
|
||||
|
||||
#if __has_include(<liburing.h>)
|
||||
|
||||
#include <arpa/inet.h> // debugging
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
@@ -7,6 +11,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <liburing.h>
|
||||
#include <linux/io_uring.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
@@ -160,3 +165,13 @@ static go_completion_result completion(struct io_uring *ring, int block) {
|
||||
io_uring_cqe_seen(ring, cqe);
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int has_io_uring(void) {
|
||||
#if __has_include(<liburing.h>)
|
||||
return 1;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
14
net/uring/io_uring.go
Normal file
14
net/uring/io_uring.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package uring
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
)
|
||||
|
||||
var useIOURing = flag.Bool("use-io-uring", true, "attempt to use io_uring if available")
|
||||
|
||||
// NotSupportedError indicates an operation was attempted when io_uring is not supported.
|
||||
var NotSupportedError = errors.New("io_uring not supported")
|
||||
|
||||
// DisabledError indicates that io_uring was explicitly disabled.
|
||||
var DisabledError = errors.New("io_uring disabled")
|
||||
@@ -1,6 +1,7 @@
|
||||
package uring
|
||||
|
||||
// #cgo LDFLAGS: -luring
|
||||
// #cgo CFLAGS: -I${SRCDIR}/liburing/src/include
|
||||
// #cgo LDFLAGS: -L${SRCDIR}/liburing/src/ -luring
|
||||
// #include "io_uring.c"
|
||||
import "C"
|
||||
|
||||
@@ -26,6 +27,8 @@
|
||||
|
||||
const bufferSize = device.MaxSegmentSize
|
||||
|
||||
func URingAvailable() bool { return *useIOURing && C.has_io_uring() > 0 }
|
||||
|
||||
// A UDPConn is a recv-only UDP fd manager.
|
||||
// We'd like to enqueue a bunch of recv calls and deqeueue them later,
|
||||
// but we have a problem with buffer management: We get our buffers just-in-time
|
||||
@@ -68,18 +71,31 @@ type UDPConn struct {
|
||||
}
|
||||
|
||||
func NewUDPConn(pconn net.PacketConn) (*UDPConn, error) {
|
||||
if !*useIOURing {
|
||||
return nil, DisabledError
|
||||
}
|
||||
conn, ok := pconn.(*net.UDPConn)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot use io_uring with conn of type %T", pconn)
|
||||
}
|
||||
}
|
||||
// this is dumb
|
||||
local := conn.LocalAddr().String()
|
||||
ip, err := netaddr.ParseIPPort(local)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse UDPConn local addr %s as IP: %w", local, err)
|
||||
local := conn.LocalAddr()
|
||||
var ipp netaddr.IPPort
|
||||
switch l := local.(type) {
|
||||
case *net.UDPAddr:
|
||||
ip, ok := netaddr.FromStdIP(l.IP)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to parse IP: %v", ip)
|
||||
}
|
||||
ipp = netaddr.IPPortFrom(ip, uint16(l.Port))
|
||||
default:
|
||||
var err error
|
||||
if ipp, err = netaddr.ParseIPPort(l.String()); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse UDPConn local addr %s as IP: %w", local, err)
|
||||
}
|
||||
}
|
||||
ipVersion := 6
|
||||
if ip.IP().Is4() {
|
||||
if ipp.IP().Is4() {
|
||||
ipVersion = 4
|
||||
}
|
||||
// TODO: probe for system capabilities: https://unixism.net/loti/tutorial/probe_liburing.html
|
||||
|
||||
56
net/uring/io_uring_notlinux.go
Normal file
56
net/uring/io_uring_notlinux.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// +build !linux
|
||||
|
||||
package uring
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
func URingAvailable() bool { return false }
|
||||
|
||||
// A UDPConn is a recv-only UDP fd manager.
|
||||
// TODO: Support writes.
|
||||
// TODO: support multiplexing multiple fds?
|
||||
// May be more expensive than having multiple urings, and certainly more complicated.
|
||||
// TODO: API review for performance.
|
||||
// We'd like to enqueue a bunch of recv calls and deqeueue them later,
|
||||
// but we have a problem with buffer management: We get our buffers just-in-time
|
||||
// from wireguard-go, which means we have to make copies.
|
||||
// That's OK for now, but later it could be a performance issue.
|
||||
// For now, keep it simple and enqueue/dequeue in a single step.
|
||||
// TODO: IPv6
|
||||
// TODO: Maybe combine the urings into a single uring with dispatch.
|
||||
type UDPConn struct{}
|
||||
|
||||
func NewUDPConn(conn *net.UDPConn) (*UDPConn, error) { return nil, NotSupportedError }
|
||||
func (c *UDPConn) LocalAddr() net.Addr { panic("Not supported") }
|
||||
|
||||
func (u *UDPConn) Close() error { return NotSupportedError }
|
||||
func (c *UDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
err = NotSupportedError
|
||||
return
|
||||
}
|
||||
func (u *UDPConn) ReadFromNetaddr(buf []byte) (n int, addr netaddr.IPPort, err error) {
|
||||
err = NotSupportedError
|
||||
return
|
||||
}
|
||||
func (u *UDPConn) WriteTo(p []byte, addr net.Addr) (int, error) { return 0, NotSupportedError }
|
||||
func (c *UDPConn) SetDeadline(t time.Time) error { return NotSupportedError }
|
||||
func (c *UDPConn) SetReadDeadline(t time.Time) error { return NotSupportedError }
|
||||
func (c *UDPConn) SetWriteDeadline(t time.Time) error { return NotSupportedError }
|
||||
|
||||
// A File is a write-only file fd manager.
|
||||
// TODO: Support reads
|
||||
// TODO: all the todos from UDPConn
|
||||
type File struct{}
|
||||
|
||||
func NewFile(file *os.File) (*File, error) { return nil, NotSupportedError }
|
||||
func (u *File) Write(buf []byte) (int, error) { return 0, NotSupportedError }
|
||||
|
||||
// Read data into buf[offset:].
|
||||
// We are allowed to write junk into buf[offset-4:offset].
|
||||
func (u *File) Read(buf []byte) (n int, err error) { return 0, NotSupportedError }
|
||||
30
net/uring/io_uring_test.go
Normal file
30
net/uring/io_uring_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// +build linux
|
||||
|
||||
package uring
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFile(t *testing.T) {
|
||||
tmpFile, err := ioutil.TempFile(".", "uring-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
f, err := NewFile(tmpFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create io_uring file: %v", err)
|
||||
}
|
||||
content := []byte("a test string to check writing works 😀 with non-unicode input")
|
||||
n, err := f.Write(content)
|
||||
if n != len(content) {
|
||||
t.Errorf("mismatch between reported written len and content len: want %d, got %d", len(content), n)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("file write failed: %v", err)
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
t.Errorf("file close failed: %v", err)
|
||||
}
|
||||
}
|
||||
3
net/uring/makefile
Normal file
3
net/uring/makefile
Normal file
@@ -0,0 +1,3 @@
|
||||
get_liburing:
|
||||
git clone git@github.com:axboe/liburing.git
|
||||
cd liburing && make
|
||||
Reference in New Issue
Block a user