diff options
| author | Chris O'Hara <cohara87@gmail.com> | 2023-05-08 17:08:20 +1000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2023-05-25 00:12:41 +0000 |
| commit | a17de43ef12250cd9a0ffdd8ff2d05fb18fcf322 (patch) | |
| tree | 1a092d125865530314bfa393625262302029330e /src/runtime | |
| parent | c5c2184538411c8cf7abc4e536fbe7af8b0307f5 (diff) | |
| download | go-a17de43ef12250cd9a0ffdd8ff2d05fb18fcf322.tar.xz | |
net: implement wasip1 FileListener and FileConn
Implements net.FileListener and net.FileConn for wasip1.
net.FileListener can be used with a pre-opened socket. If the WASM
module knows the file descriptor, a listener can be constructed with:
l, err := net.FileListener(os.NewFile(fd, ""))
If the WASM module does not know the file descriptor, but knows that at
least one of the preopens is a socket, it can find the file descriptor
and construct a listener like so:
func findListener() (net.Listener, error) {
// We start looking for pre-opened sockets at fd=3 because 0, 1,
// and 2 are reserved for stdio. Pre-opened directories also
// start at fd=3, so we skip fds that aren't sockets. Once we
// reach EBADF we know there are no more pre-opens.
for preopenFd := uintptr(3); ; preopenFd++ {
l, err := net.FileListener(os.NewFile(preopenFd, ""))
var se syscall.Errno
switch errors.As(err, &se); se {
case syscall.ENOTSOCK:
continue
case syscall.EBADF:
err = nil
}
return l, err
}
}
A similar strategy can be used with net.FileConn and pre-opened
connection sockets.
The wasmtime runtime supports pre-opening listener sockets:
$ wasmtime --tcplisten 127.0.0.1:8080 module.wasm
Change-Id: Iec6ae4ffa84b3753cce4f56a2817e150445db643
Reviewed-on: https://go-review.googlesource.com/c/go/+/493358
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Bypass: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Auto-Submit: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/internal/wasitest/tcpecho_test.go | 92 | ||||
| -rw-r--r-- | src/runtime/internal/wasitest/testdata/tcpecho.go | 74 |
2 files changed, 166 insertions, 0 deletions
diff --git a/src/runtime/internal/wasitest/tcpecho_test.go b/src/runtime/internal/wasitest/tcpecho_test.go new file mode 100644 index 0000000000..506e6fe40a --- /dev/null +++ b/src/runtime/internal/wasitest/tcpecho_test.go @@ -0,0 +1,92 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package wasi_test + +import ( + "bytes" + "fmt" + "math/rand" + "net" + "os" + "os/exec" + "testing" + "time" +) + +func TestTCPEcho(t *testing.T) { + if target != "wasip1/wasm" { + t.Skip() + } + + // We're unable to pass port 0 here (let the OS choose a spare port). + // Although wasmtime accepts port 0, and testdata/main.go successfully + // listens, there's no way for this test case to query the chosen port + // so that it can connect to the WASM module. The WASM module itself + // cannot access any information about the socket due to limitations + // with WASI preview 1 networking, and wasmtime does not log the address + // when you preopen a socket. Instead, we probe for a free port here. + var host string + port := rand.Intn(10000) + 40000 + for attempts := 0; attempts < 10; attempts++ { + host = fmt.Sprintf("127.0.0.1:%d", port) + l, err := net.Listen("tcp", host) + if err == nil { + l.Close() + break + } + port++ + } + + subProcess := exec.Command("go", "run", "./testdata/tcpecho.go") + + subProcess.Env = append(os.Environ(), "GOOS=wasip1", "GOARCH=wasm") + + switch os.Getenv("GOWASIRUNTIME") { + case "wasmtime": + subProcess.Env = append(subProcess.Env, "GOWASIRUNTIMEARGS=--tcplisten="+host) + default: + t.Skip("WASI runtime does not support sockets") + } + + var b bytes.Buffer + subProcess.Stdout = &b + subProcess.Stderr = &b + + if err := subProcess.Start(); err != nil { + t.Log(b.String()) + t.Fatal(err) + } + defer subProcess.Process.Kill() + + var conn net.Conn + var err error + for attempts := 0; attempts < 5; attempts++ { + conn, err = net.Dial("tcp", host) + if err == nil { + break + } + time.Sleep(500 * time.Millisecond) + } + if err != nil { + t.Log(b.String()) + t.Fatal(err) + } + defer conn.Close() + + payload := []byte("foobar") + if _, err := conn.Write(payload); err != nil { + t.Fatal(err) + } + var buf [256]byte + n, err := conn.Read(buf[:]) + if err != nil { + t.Fatal(err) + } + if string(buf[:n]) != string(payload) { + t.Error("unexpected payload") + t.Logf("expect: %d bytes (%v)", len(payload), payload) + t.Logf("actual: %d bytes (%v)", n, buf[:n]) + } +} diff --git a/src/runtime/internal/wasitest/testdata/tcpecho.go b/src/runtime/internal/wasitest/testdata/tcpecho.go new file mode 100644 index 0000000000..819e352688 --- /dev/null +++ b/src/runtime/internal/wasitest/testdata/tcpecho.go @@ -0,0 +1,74 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "errors" + "net" + "os" + "syscall" +) + +func main() { + if err := run(); err != nil { + println(err) + os.Exit(1) + } +} + +func run() error { + l, err := findListener() + if err != nil { + return err + } + if l == nil { + return errors.New("no pre-opened sockets available") + } + defer l.Close() + + c, err := l.Accept() + if err != nil { + return err + } + return handleConn(c) +} + +func handleConn(c net.Conn) error { + defer c.Close() + + var buf [128]byte + n, err := c.Read(buf[:]) + if err != nil { + return err + } + if _, err := c.Write(buf[:n]); err != nil { + return err + } + if err := c.(*net.TCPConn).CloseWrite(); err != nil { + return err + } + return c.Close() +} + +func findListener() (net.Listener, error) { + // We start looking for pre-opened sockets at fd=3 because 0, 1, and 2 + // are reserved for stdio. Pre-opened directors also start at fd=3, so + // we skip fds that aren't sockets. Once we reach EBADF we know there + // are no more pre-opens. + for preopenFd := uintptr(3); ; preopenFd++ { + f := os.NewFile(preopenFd, "") + l, err := net.FileListener(f) + f.Close() + + var se syscall.Errno + switch errors.As(err, &se); se { + case syscall.ENOTSOCK: + continue + case syscall.EBADF: + err = nil + } + return l, err + } +} |
