dispatchrun / wasi-go

A Go implementation of the WebAssembly System Interface (WASI)
Apache License 2.0
124 stars 7 forks source link

unix: implement sock_send and sock_recv #19

Closed chriso closed 1 year ago

chriso commented 1 year ago

This PR provides implementations for the last two stubs: sock_send and sock_recv.

These are like the sendmsg(2) and recvmsg(2) syscalls. The unix.SendmsgBuffers and unix.RecvmsgBuffers wrappers are available from x/sys/unix.

tcp-server.go ```go package main import ( "fmt" "log" "net" "os" "time" "unsafe" ) func main() { if err := run(); err != nil { log.Fatal(err) } } func run() error { l, err := net.FileListener(os.NewFile(3, "")) if err != nil { return err } defer l.Close() c, err := l.Accept() if err != nil { return err } // Assume the connection has fd=4. There's currently no way to // query the file descriptor given a c of type net.Conn or // *net.TCPConn. c.File() is not implemented because dup(2) is // not available. var fd int32 = 4 var n size var buf [1024]byte iovecs := []iovec{ { buf: uintptr32(uintptr(unsafe.Pointer(&buf))), bufLen: size(len(buf)), }, } read_loop: for { var iflags riflags var oflags roflags errno := sock_recv(fd, unsafe.Pointer(unsafe.SliceData(iovecs)), 1, iflags, unsafe.Pointer(&n), unsafe.Pointer(&oflags)) switch errno { case 0: // ESUCCESS break read_loop case 6: // EAGAIN time.Sleep(1 * time.Second) continue default: panic(fmt.Sprintf("errno=%d", errno)) } } iovecs[0].bufLen = n write_loop: for { var iflags siflags errno := sock_send(fd, unsafe.Pointer(unsafe.SliceData(iovecs)), 1, iflags, unsafe.Pointer(&n)) switch errno { case 0: // ESUCCESS break write_loop case 6: // EAGAIN time.Sleep(1 * time.Second) continue default: panic(fmt.Sprintf("errno=%d", errno)) } } return c.Close() } //go:wasmimport wasi_snapshot_preview1 sock_send //go:noescape func sock_send(fd int32, iovs unsafe.Pointer, iovsLen size, siflags riflags, nread unsafe.Pointer) errno //go:wasmimport wasi_snapshot_preview1 sock_recv //go:noescape func sock_recv(fd int32, iovs unsafe.Pointer, iovsLen size, riflags riflags, nread unsafe.Pointer, roflags unsafe.Pointer) errno type errno = int32 type uintptr32 = uint32 type size = uint32 type siflags = uint32 type riflags = uint32 type roflags = uint32 type iovec struct { buf uintptr32 bufLen size } ```

wasmtime:

$ wasmtime --tcplisten 127.0.0.1:8080 tcp-server.wasm &
[1] 7525
$ echo foo | nc localhost 8080                         
foo

wasirun:

$ wasirun --tcplisten 127.0.0.1:8080 tcp-server.wasm &                                                  
[1] 7522
$ echo foo | nc localhost 8080                        
foo

wasirun --trace:

$ wasirun --tcplisten 127.0.0.1:8080 --trace tcp-server.wasm
Preopen(0, "/dev/stdin", {FileType:CharacterDeviceType,Flags:FDFlags(0),RightsBase:AllRights,RightsInheriting:AllRights})) => ok
Preopen(1, "/dev/stdout", {FileType:CharacterDeviceType,Flags:FDFlags(0),RightsBase:AllRights,RightsInheriting:AllRights})) => ok
Preopen(2, "/dev/stderr", {FileType:CharacterDeviceType,Flags:FDFlags(0),RightsBase:AllRights,RightsInheriting:AllRights})) => ok
Preopen(3, "127.0.0.1:8080", {FileType:SocketStreamType,Flags:NonBlock,RightsBase:FDStatSetFlagsRight|FDFileStatGetRight|PollFDReadWriteRight|SockAcceptRight,RightsInheriting:FDReadRight|FDStatSetFlagsRight|FDWriteRight|FDFileStatGetRight|PollFDReadWriteRight|SockShutdownRight})) => ok
RandomGet([32]byte) => [32]byte("\xd0\x9c\x18\x94\x1d\xd5F}\x8b\xd5Q(T\xeb\xe2\xbb\x9bg\x0eQ\99\x8a\xfa\xcf\xe0\x80\xcf\xbdZ\xe2\8")
RandomGet([8]byte) => [8]byte("\46\xdd\xac{5@7")
ClockTimeGet(1, 0) => 154204667
ArgsGet() => ["tcp-server.wasm"]
ArgsGet() => ["tcp-server.wasm"]
EnvironGet() => []
ClockTimeGet(1, 0) => 158970917
ClockTimeGet(1, 0) => 158980375
ClockTimeGet(1, 0) => 159190125
ClockTimeGet(1, 0) => 159394375
ClockTimeGet(1, 0) => 159399167
FDPreStatGet(3) => ENOTDIR (Not a directory)
FDPreStatGet(4) => EBADF (Bad file number)
ClockTimeGet(1, 0) => 159792875
FDStatGet(0) => {FileType:CharacterDeviceType,Flags:FDFlags(0),RightsBase:AllRights,RightsInheriting:AllRights})
FDStatGet(1) => {FileType:CharacterDeviceType,Flags:FDFlags(0),RightsBase:AllRights,RightsInheriting:AllRights})
FDStatGet(2) => {FileType:CharacterDeviceType,Flags:FDFlags(0),RightsBase:AllRights,RightsInheriting:AllRights})
FDStatGet(3) => {FileType:SocketStreamType,Flags:NonBlock,RightsBase:FDStatSetFlagsRight|FDFileStatGetRight|PollFDReadWriteRight|SockAcceptRight,RightsInheriting:FDReadRight|FDStatSetFlagsRight|FDWriteRight|FDFileStatGetRight|PollFDReadWriteRight|SockShutdownRight})
FDStatGet(3) => {FileType:SocketStreamType,Flags:NonBlock,RightsBase:FDStatSetFlagsRight|FDFileStatGetRight|PollFDReadWriteRight|SockAcceptRight,RightsInheriting:FDReadRight|FDStatSetFlagsRight|FDWriteRight|FDFileStatGetRight|PollFDReadWriteRight|SockShutdownRight})
SockAccept(3, FDFlags(0)) => EAGAIN (Try again)
PollOneoff({EventType:ClockEvent,UserData:0x0,ID:Monotonic,Timeout:0,Precision:1000},{EventType:FDReadEvent,UserData:0x1344d88,FD:3}) => {}
ClockTimeGet(1, 0) => 160263333
ClockTimeGet(1, 0) => 160281208
PollOneoff({EventType:FDReadEvent,UserData:0x1344d88,FD:3}) => {EventType:FDReadEvent,UserData:0x1344d88,NBytes:0}
SockAccept(3, FDFlags(0)) => 4
FDStatGet(4) => {FileType:SocketStreamType,Flags:FDFlags(0),RightsBase:FDReadRight|FDStatSetFlagsRight|FDWriteRight|FDFileStatGetRight|PollFDReadWriteRight|SockShutdownRight,RightsInheriting:FDReadRight|FDStatSetFlagsRight|FDWriteRight|FDFileStatGetRight|PollFDReadWriteRight|SockShutdownRight})
FDStatSetFlags(4, NonBlock) => ok
SockRecv(4, [1]IOVec{[1024]Byte}, RIFlags(0)) => [4]byte: [1]IOVec{[4]byte("foo\n")}, ROFlags(0)
SockSend(4, [1]IOVec{[4]byte("foo\n")}, SIFlags(0)) => 4
FDClose(4) => ok
ProcExit(0) => % 
chriso commented 1 year ago

Once syscall.Recvmsg and syscall.SendmsgN are implemented (e.g. https://github.com/stealthrocket/go/commit/b49d8b655fe7578252b7753151da9b3653cb7587), there's no need to call the host functions directly.

package main

import (
    "log"
    "net"
    "os"
    "syscall"
    "time"
)

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    l, err := net.FileListener(os.NewFile(3, ""))
    if err != nil {
        return err
    }
    defer l.Close()

    c, err := l.Accept()
    if err != nil {
        return err
    }

    // Assume the connection has fd=4. There's currently no way to
    // query the file descriptor given a c of type net.Conn or
    // *net.TCPConn. c.File() is not implemented because dup(2) is
    // not available.
    fd := 4

    var n int
    var buf [1024]byte

    for {
        if n, _, _, _, err = syscall.Recvmsg(fd, buf[:], nil, 0); err != nil {
            if err == syscall.EAGAIN {
                time.Sleep(1 * time.Second)
                continue
            }
            panic(err)
        }
        break
    }

    for {
        _, err := syscall.SendmsgN(fd, buf[:n], nil, nil, 0)
        if err != nil {
            if err == syscall.EAGAIN {
                time.Sleep(1 * time.Second)
                continue
            }
            panic(err)
        }
        break
    }

    return c.Close()
}

And then if you're using the higher level net package, those EAGAIN errors are automatically handled by the net poller.