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:
julianknodt
2021-07-01 19:51:04 -07:00
committed by kadmin
parent 61e3d919ef
commit 6d10acc6dd
8 changed files with 191 additions and 8 deletions

View File

@@ -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
View File

@@ -0,0 +1,2 @@
liburing/
*.so

View File

@@ -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
View 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")

View File

@@ -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

View 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 }

View 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
View File

@@ -0,0 +1,3 @@
get_liburing:
git clone git@github.com:axboe/liburing.git
cd liburing && make