mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-08-29 23:39:14 +00:00
793b57c2a1
* feat(ffi): add async data plane API * feat(ffi): add async data plane examples * test(ffi): make async Go dataplane tests self-contained * docs(ffi): document Go async dataplane API * docs(android): document dataplane JNI API
1019 lines
30 KiB
Go
1019 lines
30 KiB
Go
// Package easytierffi contains a small Go wrapper around the EasyTier FFI
|
|
// examples. This file documents the async data-plane surface; the synchronous
|
|
// wrapper lives in easytier.go.
|
|
//
|
|
// Public async entry points:
|
|
//
|
|
// - OpenAsync(path) loads the EasyTier FFI dynamic library and binds the
|
|
// async dataplane symbols. Close releases only the dynamic library handle;
|
|
// network instances started through RunNetworkInstance are process-global
|
|
// EasyTier state.
|
|
//
|
|
// - (*AsyncNative).RunNetworkInstance(config) starts one EasyTier instance
|
|
// from TOML. The instance name in the config is used by all dataplane calls.
|
|
//
|
|
// - (*AsyncNative).DialContext(ctx, instance, "tcp", "ip:port") starts an
|
|
// async TCP connect and returns an AsyncConn implementing net.Conn.
|
|
//
|
|
// - (*AsyncNative).ListenContext(ctx, instance, "tcp", "0.0.0.0:port") starts
|
|
// an async TCP bind and returns an AsyncListener implementing net.Listener.
|
|
//
|
|
// - AsyncConn implements net.Conn. Read and Write each start one native async
|
|
// read/write op and wait for completion. Deadlines are mapped to operation
|
|
// timeouts. Close closes the underlying dataplane stream handle.
|
|
//
|
|
// - AsyncListener implements net.Listener. Accept starts one native async
|
|
// accept op and waits for a stream. Close closes the listener handle.
|
|
//
|
|
// - (*AsyncNative).UDPBindContext(ctx, instance, port) returns an
|
|
// AsyncUDPSocket. AsyncUDPSocket.SendTo and RecvFrom start one native async
|
|
// UDP send/receive op and wait for completion. Close closes the socket
|
|
// handle.
|
|
//
|
|
// - TCPConnectContext/TCPBindContext/TCPAcceptContext/TCPReadContext/
|
|
// TCPWriteContext and UDPSendToContext/UDPRecvFromContext are lower-level
|
|
// handle helpers used by the examples and tests. External callers should
|
|
// prefer DialContext, ListenContext, AsyncConn, AsyncListener, and
|
|
// AsyncUDPSocket because raw handle close helpers are intentionally internal
|
|
// to this example package.
|
|
//
|
|
// Async operation semantics:
|
|
//
|
|
// - Each Context method starts a native async op, polls data_plane_async_op_wait
|
|
// in short intervals, then calls the matching finish function. Finish is
|
|
// single-consume on the native side.
|
|
//
|
|
// - If the context is canceled or its deadline expires before completion, the
|
|
// wrapper cancels and frees the native op and returns the context error.
|
|
//
|
|
// - Read and RecvFrom copy Rust-owned output buffers into Go slices and free
|
|
// the native allocation before returning.
|
|
//
|
|
// - Write and SendTo keep the Go input buffer alive for the start call. The
|
|
// native async API copies the input buffer during start, so callers do not
|
|
// need to keep it alive after the Go method returns.
|
|
//
|
|
// - FFI calls that read the Rust thread-local error string pin the goroutine
|
|
// to one OS thread from the failing call through get_error_msg.
|
|
package easytierffi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"runtime"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/go-webgpu/goffi/ffi"
|
|
"github.com/go-webgpu/goffi/types"
|
|
)
|
|
|
|
const (
|
|
dataPlaneOpPending = int32(0)
|
|
dataPlaneOpReady = int32(1)
|
|
dataPlaneOpFailed = int32(-1)
|
|
dataPlaneOpInvalid = int32(-2)
|
|
|
|
asyncPollInterval = 50 * time.Millisecond
|
|
)
|
|
|
|
type AsyncNative struct {
|
|
lib unsafe.Pointer
|
|
|
|
runNetworkInstance symCall
|
|
deleteNetworkInst symCall
|
|
getErrorMsg symCall
|
|
freeString symCall
|
|
freeBytes symCall
|
|
|
|
asyncOpStatus symCall
|
|
asyncOpWait symCall
|
|
asyncOpCancel symCall
|
|
asyncOpFree symCall
|
|
|
|
tcpConnectStart symCall
|
|
tcpConnectFinish symCall
|
|
tcpBindStart symCall
|
|
tcpBindFinish symCall
|
|
tcpAcceptStart symCall
|
|
tcpAcceptFinish symCall
|
|
tcpReadStart symCall
|
|
tcpReadFinish symCall
|
|
tcpWriteStart symCall
|
|
tcpWriteFinish symCall
|
|
tcpClose symCall
|
|
tcpListenerClose symCall
|
|
|
|
udpBindStart symCall
|
|
udpBindFinish symCall
|
|
udpSendToStart symCall
|
|
udpSendToFinish symCall
|
|
udpRecvFromStart symCall
|
|
udpRecvFromFinish symCall
|
|
udpClose symCall
|
|
}
|
|
|
|
type AsyncConn struct {
|
|
native *AsyncNative
|
|
handle uint64
|
|
local net.Addr
|
|
remote net.Addr
|
|
closed atomicBool
|
|
rd atomicDeadline
|
|
wd atomicDeadline
|
|
}
|
|
|
|
type AsyncListener struct {
|
|
native *AsyncNative
|
|
handle uint64
|
|
addr net.Addr
|
|
closed atomicBool
|
|
}
|
|
|
|
type AsyncUDPSocket struct {
|
|
native *AsyncNative
|
|
handle uint64
|
|
addr *net.UDPAddr
|
|
closed atomicBool
|
|
}
|
|
|
|
type atomicBool struct{ v atomic.Bool }
|
|
|
|
func OpenAsync(path string) (*AsyncNative, error) {
|
|
lib, err := ffi.LoadLibrary(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
n := &AsyncNative{lib: lib}
|
|
if err := n.bind(); err != nil {
|
|
ffi.FreeLibrary(lib)
|
|
return nil, err
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (n *AsyncNative) Close() error {
|
|
if n.lib == nil {
|
|
return nil
|
|
}
|
|
ffi.FreeLibrary(n.lib)
|
|
n.lib = nil
|
|
return nil
|
|
}
|
|
|
|
func (n *AsyncNative) RunNetworkInstance(config string) error {
|
|
defer pinErrorThread()()
|
|
cfg := cString(config)
|
|
cfgPtr := unsafe.Pointer(&cfg[0])
|
|
var ret int32
|
|
err := n.runNetworkInstance.call(unsafe.Pointer(&ret), unsafe.Pointer(&cfgPtr))
|
|
runtime.KeepAlive(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret != 0 {
|
|
return n.lastError()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *AsyncNative) deleteNetworkInstances(names []string) error {
|
|
defer pinErrorThread()()
|
|
|
|
cNames := make([][]byte, len(names))
|
|
namePtrs := make([]unsafe.Pointer, len(names))
|
|
for i, name := range names {
|
|
cNames[i] = cString(name)
|
|
namePtrs[i] = unsafe.Pointer(&cNames[i][0])
|
|
}
|
|
|
|
var namesPtr unsafe.Pointer
|
|
if len(namePtrs) > 0 {
|
|
namesPtr = unsafe.Pointer(&namePtrs[0])
|
|
}
|
|
length := uint64(len(names))
|
|
var ret int32
|
|
err := n.deleteNetworkInst.call(unsafe.Pointer(&ret), unsafe.Pointer(&namesPtr), unsafe.Pointer(&length))
|
|
runtime.KeepAlive(cNames)
|
|
runtime.KeepAlive(namePtrs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ret != 0 {
|
|
return n.lastError()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *AsyncNative) DialContext(ctx context.Context, instance, network, address string) (net.Conn, error) {
|
|
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
|
return nil, net.UnknownNetworkError(network)
|
|
}
|
|
ip, port, err := parseIPPort(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
handle, local, err := n.TCPConnectContext(ctx, instance, ip.String(), uint16(port))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &AsyncConn{
|
|
native: n,
|
|
handle: handle,
|
|
local: local,
|
|
remote: &net.TCPAddr{IP: ip, Port: port},
|
|
}, nil
|
|
}
|
|
|
|
func (n *AsyncNative) ListenContext(ctx context.Context, instance, network, address string) (net.Listener, error) {
|
|
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
|
return nil, net.UnknownNetworkError(network)
|
|
}
|
|
port, err := parseListenPort(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
handle, local, err := n.TCPBindContext(ctx, instance, uint16(port))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &AsyncListener{native: n, handle: handle, addr: local}, nil
|
|
}
|
|
|
|
func (n *AsyncNative) TCPConnectContext(ctx context.Context, instance, ip string, port uint16) (uint64, *net.TCPAddr, error) {
|
|
timeout, err := contextTimeout(ctx)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
op, err := n.tcpConnectStartCall(instance, ip, port, timeout)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if err := n.waitOp(ctx, op); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
return n.tcpConnectFinishCall(op)
|
|
}
|
|
|
|
func (n *AsyncNative) TCPBindContext(ctx context.Context, instance string, port uint16) (uint64, *net.TCPAddr, error) {
|
|
timeout, err := contextTimeout(ctx)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
op, err := n.tcpBindStartCall(instance, port, timeout)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if err := n.waitOp(ctx, op); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
return n.tcpBindFinishCall(op)
|
|
}
|
|
|
|
func (n *AsyncNative) TCPAcceptContext(ctx context.Context, listener uint64) (uint64, *net.TCPAddr, *net.TCPAddr, error) {
|
|
timeout, err := contextTimeout(ctx)
|
|
if err != nil {
|
|
return 0, nil, nil, err
|
|
}
|
|
op, err := n.tcpAcceptStartCall(listener, timeout)
|
|
if err != nil {
|
|
return 0, nil, nil, err
|
|
}
|
|
if err := n.waitOp(ctx, op); err != nil {
|
|
return 0, nil, nil, err
|
|
}
|
|
return n.tcpAcceptFinishCall(op)
|
|
}
|
|
|
|
func (n *AsyncNative) TCPReadContext(ctx context.Context, stream uint64, maxLen uint32) ([]byte, error) {
|
|
timeout, err := contextTimeout(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
op, err := n.tcpReadStartCall(stream, maxLen, timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := n.waitOp(ctx, op); err != nil {
|
|
return nil, err
|
|
}
|
|
return n.tcpReadFinishCall(op)
|
|
}
|
|
|
|
func (n *AsyncNative) TCPWriteContext(ctx context.Context, stream uint64, data []byte) (int, error) {
|
|
timeout, err := contextTimeout(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
op, err := n.tcpWriteStartCall(stream, data, timeout)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if err := n.waitOp(ctx, op); err != nil {
|
|
return 0, err
|
|
}
|
|
return n.tcpWriteFinishCall(op)
|
|
}
|
|
|
|
func (n *AsyncNative) UDPBindContext(ctx context.Context, instance string, port uint16) (*AsyncUDPSocket, error) {
|
|
timeout, err := contextTimeout(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
op, err := n.udpBindStartCall(instance, port, timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := n.waitOp(ctx, op); err != nil {
|
|
return nil, err
|
|
}
|
|
handle, local, err := n.udpBindFinishCall(op)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &AsyncUDPSocket{native: n, handle: handle, addr: local}, nil
|
|
}
|
|
|
|
func (n *AsyncNative) UDPSendToContext(ctx context.Context, socket uint64, addr *net.UDPAddr, data []byte) (int, error) {
|
|
timeout, err := contextTimeout(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
op, err := n.udpSendToStartCall(socket, addr, data, timeout)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if err := n.waitOp(ctx, op); err != nil {
|
|
return 0, err
|
|
}
|
|
return n.udpSendToFinishCall(op)
|
|
}
|
|
|
|
func (n *AsyncNative) UDPRecvFromContext(ctx context.Context, socket uint64, maxLen uint32) ([]byte, *net.UDPAddr, error) {
|
|
timeout, err := contextTimeout(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
op, err := n.udpRecvFromStartCall(socket, maxLen, timeout)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if err := n.waitOp(ctx, op); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return n.udpRecvFromFinishCall(op)
|
|
}
|
|
|
|
func (c *AsyncConn) Read(b []byte) (int, error) {
|
|
if c.closed.Load() {
|
|
return 0, net.ErrClosed
|
|
}
|
|
if len(b) == 0 {
|
|
return 0, nil
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), c.rd.timeout(defaultTimeout))
|
|
defer cancel()
|
|
data, err := c.native.TCPReadContext(ctx, c.handle, uint32(len(b)))
|
|
if err != nil {
|
|
return 0, opError("read", c.remote, err)
|
|
}
|
|
if len(data) == 0 {
|
|
return 0, io.EOF
|
|
}
|
|
return copy(b, data), nil
|
|
}
|
|
|
|
func (c *AsyncConn) Write(b []byte) (int, error) {
|
|
if c.closed.Load() {
|
|
return 0, net.ErrClosed
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), c.wd.timeout(defaultTimeout))
|
|
defer cancel()
|
|
n, err := c.native.TCPWriteContext(ctx, c.handle, b)
|
|
if err != nil {
|
|
return 0, opError("write", c.remote, err)
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (c *AsyncConn) Close() error {
|
|
if !c.closed.CompareAndSwap(false, true) {
|
|
return net.ErrClosed
|
|
}
|
|
return c.native.tcpCloseHandle(c.handle)
|
|
}
|
|
|
|
func (c *AsyncConn) LocalAddr() net.Addr { return c.local }
|
|
func (c *AsyncConn) RemoteAddr() net.Addr { return c.remote }
|
|
func (c *AsyncConn) SetDeadline(t time.Time) error { c.rd.set(t); c.wd.set(t); return nil }
|
|
func (c *AsyncConn) SetReadDeadline(t time.Time) error { c.rd.set(t); return nil }
|
|
func (c *AsyncConn) SetWriteDeadline(t time.Time) error { c.wd.set(t); return nil }
|
|
|
|
func (l *AsyncListener) Accept() (net.Conn, error) {
|
|
if l.closed.Load() {
|
|
return nil, net.ErrClosed
|
|
}
|
|
for {
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
|
|
handle, local, peer, err := l.native.TCPAcceptContext(ctx, l.handle)
|
|
cancel()
|
|
if err == nil {
|
|
return &AsyncConn{native: l.native, handle: handle, local: local, remote: peer}, nil
|
|
}
|
|
if l.closed.Load() {
|
|
return nil, net.ErrClosed
|
|
}
|
|
var netErr net.Error
|
|
if errors.As(err, &netErr) && netErr.Timeout() {
|
|
continue
|
|
}
|
|
return nil, opError("accept", l.addr, err)
|
|
}
|
|
}
|
|
|
|
func (l *AsyncListener) Close() error {
|
|
if !l.closed.CompareAndSwap(false, true) {
|
|
return net.ErrClosed
|
|
}
|
|
return l.native.tcpListenerCloseHandle(l.handle)
|
|
}
|
|
|
|
func (l *AsyncListener) Addr() net.Addr { return l.addr }
|
|
|
|
func (s *AsyncUDPSocket) SendTo(ctx context.Context, data []byte, addr *net.UDPAddr) (int, error) {
|
|
if s.closed.Load() {
|
|
return 0, net.ErrClosed
|
|
}
|
|
return s.native.UDPSendToContext(ctx, s.handle, addr, data)
|
|
}
|
|
|
|
func (s *AsyncUDPSocket) RecvFrom(ctx context.Context, maxLen uint32) ([]byte, *net.UDPAddr, error) {
|
|
if s.closed.Load() {
|
|
return nil, nil, net.ErrClosed
|
|
}
|
|
return s.native.UDPRecvFromContext(ctx, s.handle, maxLen)
|
|
}
|
|
|
|
func (s *AsyncUDPSocket) Close() error {
|
|
if !s.closed.CompareAndSwap(false, true) {
|
|
return net.ErrClosed
|
|
}
|
|
return s.native.udpCloseHandle(s.handle)
|
|
}
|
|
|
|
func (s *AsyncUDPSocket) LocalAddr() *net.UDPAddr { return s.addr }
|
|
|
|
func (n *AsyncNative) bind() error {
|
|
return errors.Join(
|
|
n.bindSym(&n.runNetworkInstance, "run_network_instance", types.SInt32TypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.deleteNetworkInst, "delete_network_instance", types.SInt32TypeDescriptor, types.PointerTypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.getErrorMsg, "get_error_msg", types.VoidTypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.freeString, "free_string", types.VoidTypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.freeBytes, "data_plane_free_bytes", types.VoidTypeDescriptor, types.PointerTypeDescriptor, types.UInt32TypeDescriptor),
|
|
n.bindSym(&n.asyncOpStatus, "data_plane_async_op_status", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.asyncOpWait, "data_plane_async_op_wait", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.asyncOpCancel, "data_plane_async_op_cancel", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.asyncOpFree, "data_plane_async_op_free", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.tcpConnectStart, "data_plane_tcp_connect_start", types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor, types.UInt16TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.tcpConnectFinish, "data_plane_tcp_connect_finish", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.tcpBindStart, "data_plane_tcp_bind_start", types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.UInt16TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.tcpBindFinish, "data_plane_tcp_bind_finish", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.tcpAcceptStart, "data_plane_tcp_accept_start", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.tcpAcceptFinish, "data_plane_tcp_accept_finish", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.tcpReadStart, "data_plane_tcp_read_start", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.UInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.tcpReadFinish, "data_plane_tcp_read_finish", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.tcpWriteStart, "data_plane_tcp_write_start", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.UInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.tcpWriteFinish, "data_plane_tcp_write_finish", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.tcpClose, "data_plane_tcp_close", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.tcpListenerClose, "data_plane_tcp_listener_close", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.udpBindStart, "data_plane_udp_bind_start", types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.UInt16TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.udpBindFinish, "data_plane_udp_bind_finish", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.udpSendToStart, "data_plane_udp_send_to_start", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.UInt16TypeDescriptor, types.PointerTypeDescriptor, types.UInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.udpSendToFinish, "data_plane_udp_send_to_finish", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.udpRecvFromStart, "data_plane_udp_recv_from_start", types.UInt64TypeDescriptor, types.UInt64TypeDescriptor, types.UInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
n.bindSym(&n.udpRecvFromFinish, "data_plane_udp_recv_from_finish", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor, types.PointerTypeDescriptor),
|
|
n.bindSym(&n.udpClose, "data_plane_udp_close", types.SInt32TypeDescriptor, types.UInt64TypeDescriptor),
|
|
)
|
|
}
|
|
|
|
func (n *AsyncNative) bindSym(dst *symCall, name string, ret *types.TypeDescriptor, args ...*types.TypeDescriptor) error {
|
|
sym, err := ffi.GetSymbol(n.lib, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := ffi.PrepareCallInterface(&dst.cif, types.DefaultCall, ret, args); err != nil {
|
|
return err
|
|
}
|
|
dst.fn = sym
|
|
return nil
|
|
}
|
|
|
|
func (n *AsyncNative) tcpConnectStartCall(instance, ip string, port uint16, timeout time.Duration) (uint64, error) {
|
|
defer pinErrorThread()()
|
|
inst := cString(instance)
|
|
dst := cString(ip)
|
|
instPtr := unsafe.Pointer(&inst[0])
|
|
dstPtr := unsafe.Pointer(&dst[0])
|
|
timeoutMS := durationMillis(timeout)
|
|
var op uint64
|
|
err := n.tcpConnectStart.call(
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&instPtr),
|
|
unsafe.Pointer(&dstPtr),
|
|
unsafe.Pointer(&port),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
runtime.KeepAlive(inst)
|
|
runtime.KeepAlive(dst)
|
|
return n.startResult(op, err)
|
|
}
|
|
|
|
func (n *AsyncNative) tcpConnectFinishCall(op uint64) (uint64, *net.TCPAddr, error) {
|
|
defer pinErrorThread()()
|
|
var handle uint64
|
|
var outIP unsafe.Pointer
|
|
outIPArg := unsafe.Pointer(&outIP)
|
|
var outPort uint16
|
|
outPortArg := unsafe.Pointer(&outPort)
|
|
err := n.tcpConnectFinish.call(
|
|
unsafe.Pointer(&handle),
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&outIPArg),
|
|
unsafe.Pointer(&outPortArg),
|
|
)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if handle == 0 {
|
|
return 0, nil, n.lastError()
|
|
}
|
|
return handle, n.takeTCPAddr(outIP, outPort), nil
|
|
}
|
|
|
|
func (n *AsyncNative) tcpBindStartCall(instance string, port uint16, timeout time.Duration) (uint64, error) {
|
|
defer pinErrorThread()()
|
|
inst := cString(instance)
|
|
instPtr := unsafe.Pointer(&inst[0])
|
|
timeoutMS := durationMillis(timeout)
|
|
var op uint64
|
|
err := n.tcpBindStart.call(
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&instPtr),
|
|
unsafe.Pointer(&port),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
runtime.KeepAlive(inst)
|
|
return n.startResult(op, err)
|
|
}
|
|
|
|
func (n *AsyncNative) tcpBindFinishCall(op uint64) (uint64, *net.TCPAddr, error) {
|
|
defer pinErrorThread()()
|
|
var handle uint64
|
|
var outIP unsafe.Pointer
|
|
outIPArg := unsafe.Pointer(&outIP)
|
|
var outPort uint16
|
|
outPortArg := unsafe.Pointer(&outPort)
|
|
err := n.tcpBindFinish.call(
|
|
unsafe.Pointer(&handle),
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&outIPArg),
|
|
unsafe.Pointer(&outPortArg),
|
|
)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if handle == 0 {
|
|
return 0, nil, n.lastError()
|
|
}
|
|
return handle, n.takeTCPAddr(outIP, outPort), nil
|
|
}
|
|
|
|
func (n *AsyncNative) tcpAcceptStartCall(listener uint64, timeout time.Duration) (uint64, error) {
|
|
defer pinErrorThread()()
|
|
timeoutMS := durationMillis(timeout)
|
|
var op uint64
|
|
err := n.tcpAcceptStart.call(
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&listener),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
return n.startResult(op, err)
|
|
}
|
|
|
|
func (n *AsyncNative) tcpAcceptFinishCall(op uint64) (uint64, *net.TCPAddr, *net.TCPAddr, error) {
|
|
defer pinErrorThread()()
|
|
var handle uint64
|
|
var localIP unsafe.Pointer
|
|
localIPArg := unsafe.Pointer(&localIP)
|
|
var localPort uint16
|
|
localPortArg := unsafe.Pointer(&localPort)
|
|
var peerIP unsafe.Pointer
|
|
peerIPArg := unsafe.Pointer(&peerIP)
|
|
var peerPort uint16
|
|
peerPortArg := unsafe.Pointer(&peerPort)
|
|
err := n.tcpAcceptFinish.call(
|
|
unsafe.Pointer(&handle),
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&localIPArg),
|
|
unsafe.Pointer(&localPortArg),
|
|
unsafe.Pointer(&peerIPArg),
|
|
unsafe.Pointer(&peerPortArg),
|
|
)
|
|
if err != nil {
|
|
return 0, nil, nil, err
|
|
}
|
|
if handle == 0 {
|
|
return 0, nil, nil, n.lastError()
|
|
}
|
|
return handle, n.takeTCPAddr(localIP, localPort), n.takeTCPAddr(peerIP, peerPort), nil
|
|
}
|
|
|
|
func (n *AsyncNative) tcpReadStartCall(stream uint64, maxLen uint32, timeout time.Duration) (uint64, error) {
|
|
defer pinErrorThread()()
|
|
timeoutMS := durationMillis(timeout)
|
|
var op uint64
|
|
err := n.tcpReadStart.call(
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&stream),
|
|
unsafe.Pointer(&maxLen),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
return n.startResult(op, err)
|
|
}
|
|
|
|
func (n *AsyncNative) tcpReadFinishCall(op uint64) ([]byte, error) {
|
|
defer pinErrorThread()()
|
|
var ret int32
|
|
var ptr unsafe.Pointer
|
|
ptrArg := unsafe.Pointer(&ptr)
|
|
var len uint32
|
|
lenArg := unsafe.Pointer(&len)
|
|
err := n.tcpReadFinish.call(
|
|
unsafe.Pointer(&ret),
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&ptrArg),
|
|
unsafe.Pointer(&lenArg),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ret < 0 {
|
|
return nil, n.lastError()
|
|
}
|
|
return n.takeBytes(ptr, len), nil
|
|
}
|
|
|
|
func (n *AsyncNative) tcpWriteStartCall(stream uint64, data []byte, timeout time.Duration) (uint64, error) {
|
|
defer pinErrorThread()()
|
|
ptr := unsafe.Pointer(nil)
|
|
if len(data) > 0 {
|
|
ptr = unsafe.Pointer(&data[0])
|
|
}
|
|
timeoutMS := durationMillis(timeout)
|
|
length := uint32(len(data))
|
|
var op uint64
|
|
err := n.tcpWriteStart.call(
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&stream),
|
|
unsafe.Pointer(&ptr),
|
|
unsafe.Pointer(&length),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
runtime.KeepAlive(data)
|
|
return n.startResult(op, err)
|
|
}
|
|
|
|
func (n *AsyncNative) tcpWriteFinishCall(op uint64) (int, error) {
|
|
defer pinErrorThread()()
|
|
var ret int32
|
|
err := n.tcpWriteFinish.call(unsafe.Pointer(&ret), unsafe.Pointer(&op))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if ret < 0 {
|
|
return 0, n.lastError()
|
|
}
|
|
return int(ret), nil
|
|
}
|
|
|
|
func (n *AsyncNative) udpBindStartCall(instance string, port uint16, timeout time.Duration) (uint64, error) {
|
|
defer pinErrorThread()()
|
|
inst := cString(instance)
|
|
instPtr := unsafe.Pointer(&inst[0])
|
|
timeoutMS := durationMillis(timeout)
|
|
var op uint64
|
|
err := n.udpBindStart.call(
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&instPtr),
|
|
unsafe.Pointer(&port),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
runtime.KeepAlive(inst)
|
|
return n.startResult(op, err)
|
|
}
|
|
|
|
func (n *AsyncNative) udpBindFinishCall(op uint64) (uint64, *net.UDPAddr, error) {
|
|
defer pinErrorThread()()
|
|
var handle uint64
|
|
var outIP unsafe.Pointer
|
|
outIPArg := unsafe.Pointer(&outIP)
|
|
var outPort uint16
|
|
outPortArg := unsafe.Pointer(&outPort)
|
|
err := n.udpBindFinish.call(
|
|
unsafe.Pointer(&handle),
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&outIPArg),
|
|
unsafe.Pointer(&outPortArg),
|
|
)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if handle == 0 {
|
|
return 0, nil, n.lastError()
|
|
}
|
|
return handle, n.takeUDPAddr(outIP, outPort), nil
|
|
}
|
|
|
|
func (n *AsyncNative) udpSendToStartCall(socket uint64, addr *net.UDPAddr, data []byte, timeout time.Duration) (uint64, error) {
|
|
defer pinErrorThread()()
|
|
if addr == nil || addr.IP == nil {
|
|
return 0, errors.New("udp destination address is nil")
|
|
}
|
|
dst := cString(addr.IP.String())
|
|
dstPtr := unsafe.Pointer(&dst[0])
|
|
ptr := unsafe.Pointer(nil)
|
|
if len(data) > 0 {
|
|
ptr = unsafe.Pointer(&data[0])
|
|
}
|
|
port := uint16(addr.Port)
|
|
length := uint32(len(data))
|
|
timeoutMS := durationMillis(timeout)
|
|
var op uint64
|
|
err := n.udpSendToStart.call(
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&socket),
|
|
unsafe.Pointer(&dstPtr),
|
|
unsafe.Pointer(&port),
|
|
unsafe.Pointer(&ptr),
|
|
unsafe.Pointer(&length),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
runtime.KeepAlive(dst)
|
|
runtime.KeepAlive(data)
|
|
return n.startResult(op, err)
|
|
}
|
|
|
|
func (n *AsyncNative) udpSendToFinishCall(op uint64) (int, error) {
|
|
defer pinErrorThread()()
|
|
var ret int32
|
|
err := n.udpSendToFinish.call(unsafe.Pointer(&ret), unsafe.Pointer(&op))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if ret < 0 {
|
|
return 0, n.lastError()
|
|
}
|
|
return int(ret), nil
|
|
}
|
|
|
|
func (n *AsyncNative) udpRecvFromStartCall(socket uint64, maxLen uint32, timeout time.Duration) (uint64, error) {
|
|
defer pinErrorThread()()
|
|
timeoutMS := durationMillis(timeout)
|
|
var op uint64
|
|
err := n.udpRecvFromStart.call(
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&socket),
|
|
unsafe.Pointer(&maxLen),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
return n.startResult(op, err)
|
|
}
|
|
|
|
func (n *AsyncNative) udpRecvFromFinishCall(op uint64) ([]byte, *net.UDPAddr, error) {
|
|
defer pinErrorThread()()
|
|
var ret int32
|
|
var ptr unsafe.Pointer
|
|
ptrArg := unsafe.Pointer(&ptr)
|
|
var len uint32
|
|
lenArg := unsafe.Pointer(&len)
|
|
var peerIP unsafe.Pointer
|
|
peerIPArg := unsafe.Pointer(&peerIP)
|
|
var peerPort uint16
|
|
peerPortArg := unsafe.Pointer(&peerPort)
|
|
err := n.udpRecvFromFinish.call(
|
|
unsafe.Pointer(&ret),
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&ptrArg),
|
|
unsafe.Pointer(&lenArg),
|
|
unsafe.Pointer(&peerIPArg),
|
|
unsafe.Pointer(&peerPortArg),
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if ret < 0 {
|
|
return nil, nil, n.lastError()
|
|
}
|
|
return n.takeBytes(ptr, len), n.takeUDPAddr(peerIP, peerPort), nil
|
|
}
|
|
|
|
func (n *AsyncNative) waitOp(ctx context.Context, op uint64) error {
|
|
for {
|
|
if err := ctx.Err(); err != nil {
|
|
n.cancelAndFreeOp(op)
|
|
return err
|
|
}
|
|
|
|
wait := asyncPollInterval
|
|
if deadline, ok := ctx.Deadline(); ok {
|
|
remaining := time.Until(deadline)
|
|
if remaining <= 0 {
|
|
n.cancelAndFreeOp(op)
|
|
return context.DeadlineExceeded
|
|
}
|
|
if remaining < wait {
|
|
wait = remaining
|
|
}
|
|
}
|
|
|
|
status, err := n.opWaitStatus(op, wait)
|
|
if err != nil {
|
|
n.cancelAndFreeOp(op)
|
|
return err
|
|
}
|
|
switch status {
|
|
case dataPlaneOpPending:
|
|
continue
|
|
case dataPlaneOpReady, dataPlaneOpFailed:
|
|
return nil
|
|
case dataPlaneOpInvalid:
|
|
return errors.New("data plane async op is invalid")
|
|
default:
|
|
return fmt.Errorf("unexpected data plane async op status %d", status)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n *AsyncNative) opWaitStatus(op uint64, timeout time.Duration) (int32, error) {
|
|
timeoutMS := durationMillis(timeout)
|
|
var status int32
|
|
err := n.asyncOpWait.call(
|
|
unsafe.Pointer(&status),
|
|
unsafe.Pointer(&op),
|
|
unsafe.Pointer(&timeoutMS),
|
|
)
|
|
return status, err
|
|
}
|
|
|
|
func (n *AsyncNative) cancelAndFreeOp(op uint64) {
|
|
var ret int32
|
|
_ = n.asyncOpCancel.call(unsafe.Pointer(&ret), unsafe.Pointer(&op))
|
|
_ = n.asyncOpFree.call(unsafe.Pointer(&ret), unsafe.Pointer(&op))
|
|
}
|
|
|
|
func (n *AsyncNative) startResult(op uint64, err error) (uint64, error) {
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if op == 0 {
|
|
return 0, n.lastError()
|
|
}
|
|
return op, nil
|
|
}
|
|
|
|
func (n *AsyncNative) lastError() error {
|
|
var out unsafe.Pointer
|
|
outArg := unsafe.Pointer(&out)
|
|
if err := n.getErrorMsg.call(nil, unsafe.Pointer(&outArg)); err != nil {
|
|
return err
|
|
}
|
|
if out == nil {
|
|
return errors.New("easytier ffi call failed")
|
|
}
|
|
msg := readCString(out)
|
|
_ = n.freeCString(out)
|
|
if msg == "" {
|
|
return errors.New("easytier ffi call failed")
|
|
}
|
|
if containsTimeout(msg) {
|
|
return timeoutError(msg)
|
|
}
|
|
return errors.New(msg)
|
|
}
|
|
|
|
func (n *AsyncNative) freeCString(ptr unsafe.Pointer) error {
|
|
if ptr == nil {
|
|
return nil
|
|
}
|
|
return n.freeString.call(nil, unsafe.Pointer(&ptr))
|
|
}
|
|
|
|
func (n *AsyncNative) takeTCPAddr(ipPtr unsafe.Pointer, port uint16) *net.TCPAddr {
|
|
if ipPtr == nil {
|
|
return nil
|
|
}
|
|
ip := net.ParseIP(readCString(ipPtr))
|
|
_ = n.freeCString(ipPtr)
|
|
return &net.TCPAddr{IP: ip, Port: int(port)}
|
|
}
|
|
|
|
func (n *AsyncNative) takeUDPAddr(ipPtr unsafe.Pointer, port uint16) *net.UDPAddr {
|
|
if ipPtr == nil {
|
|
return nil
|
|
}
|
|
ip := net.ParseIP(readCString(ipPtr))
|
|
_ = n.freeCString(ipPtr)
|
|
return &net.UDPAddr{IP: ip, Port: int(port)}
|
|
}
|
|
|
|
func (n *AsyncNative) takeBytes(ptr unsafe.Pointer, len uint32) []byte {
|
|
if ptr == nil || len == 0 {
|
|
return nil
|
|
}
|
|
bytes := make([]byte, int(len))
|
|
copy(bytes, unsafe.Slice((*byte)(ptr), int(len)))
|
|
_ = n.freeBytes.call(nil, unsafe.Pointer(&ptr), unsafe.Pointer(&len))
|
|
return bytes
|
|
}
|
|
|
|
func (n *AsyncNative) tcpCloseHandle(handle uint64) error {
|
|
defer pinErrorThread()()
|
|
var ret int32
|
|
if err := n.tcpClose.call(unsafe.Pointer(&ret), unsafe.Pointer(&handle)); err != nil {
|
|
return err
|
|
}
|
|
if ret != 0 {
|
|
return n.lastError()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *AsyncNative) tcpListenerCloseHandle(handle uint64) error {
|
|
defer pinErrorThread()()
|
|
var ret int32
|
|
if err := n.tcpListenerClose.call(unsafe.Pointer(&ret), unsafe.Pointer(&handle)); err != nil {
|
|
return err
|
|
}
|
|
if ret != 0 {
|
|
return n.lastError()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *AsyncNative) udpCloseHandle(handle uint64) error {
|
|
defer pinErrorThread()()
|
|
var ret int32
|
|
if err := n.udpClose.call(unsafe.Pointer(&ret), unsafe.Pointer(&handle)); err != nil {
|
|
return err
|
|
}
|
|
if ret != 0 {
|
|
return n.lastError()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func contextTimeout(ctx context.Context) (time.Duration, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return 0, err
|
|
}
|
|
timeout := defaultTimeout
|
|
if deadline, ok := ctx.Deadline(); ok {
|
|
timeout = time.Until(deadline)
|
|
}
|
|
if timeout <= 0 {
|
|
return 0, context.DeadlineExceeded
|
|
}
|
|
return timeout, nil
|
|
}
|
|
|
|
func durationMillis(d time.Duration) uint64 {
|
|
if d <= 0 {
|
|
return 0
|
|
}
|
|
ms := d / time.Millisecond
|
|
if ms <= 0 {
|
|
return 1
|
|
}
|
|
return uint64(ms)
|
|
}
|
|
|
|
func containsTimeout(msg string) bool {
|
|
return strings.Contains(msg, "timed out") || strings.Contains(msg, "timeout")
|
|
}
|
|
|
|
func (b *atomicBool) Load() bool {
|
|
return b.v.Load()
|
|
}
|
|
|
|
func (b *atomicBool) CompareAndSwap(old, new bool) bool {
|
|
return b.v.CompareAndSwap(old, new)
|
|
}
|
|
|
|
var _ net.Conn = (*AsyncConn)(nil)
|
|
var _ net.Listener = (*AsyncListener)(nil)
|