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>
exec wasmer run --dir=/ --env PWD="$PWD" ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
;;
"wasmtime")
- exec wasmtime run --dir=/ --env PWD="$PWD" --max-wasm-stack 1048576 "$1" -- "${@:2}"
+ exec wasmtime run --dir=/ --env PWD="$PWD" --max-wasm-stack 1048576 ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
;;
"wazero" | "")
- exec wazero run -mount /:/ -env-inherit -cachedir "${TMPDIR:-/tmp}"/wazero "$1" "${@:2}"
+ exec wazero run -mount /:/ -env-inherit -cachedir "${TMPDIR:-/tmp}"/wazero ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
;;
*)
echo "Unknown Go WASI runtime specified: $GOWASIRUNTIME"
// or "file".
// Set pollable to true if fd should be managed by runtime netpoll.
func (fd *FD) Init(net string, pollable bool) error {
+ fd.SysFile.init()
+
// We don't actually care about the various network types.
if net == "file" {
fd.isFile = true
// so this must be executed before CloseFunc.
fd.pd.close()
- // We don't use ignoringEINTR here because POSIX does not define
- // whether the descriptor is closed if close returns EINTR.
- // If the descriptor is indeed closed, using a loop would race
- // with some other goroutine opening a new descriptor.
- // (The Linux kernel guarantees that it is closed on an EINTR error.)
- err := CloseFunc(fd.Sysfd)
+ err := fd.SysFile.destroy(fd.Sysfd)
fd.Sysfd = -1
runtime_Semrelease(&fd.csema)
iovecs *[]syscall.Iovec
}
+func (s *SysFile) init() {}
+
+func (s *SysFile) destroy(fd int) error {
+ // We don't use ignoringEINTR here because POSIX does not define
+ // whether the descriptor is closed if close returns EINTR.
+ // If the descriptor is indeed closed, using a loop would race
+ // with some other goroutine opening a new descriptor.
+ // (The Linux kernel guarantees that it is closed on an EINTR error.)
+ return CloseFunc(fd)
+}
+
// dupCloseOnExecOld is the traditional way to dup an fd and
// set its O_CLOEXEC bit, using two system calls.
func dupCloseOnExecOld(fd int) (int, string, error) {
)
type SysFile struct {
+ // RefCountPtr is a pointer to the reference count of Sysfd.
+ //
+ // WASI preview 1 lacks a dup(2) system call. When the os and net packages
+ // need to share a file/socket, instead of duplicating the underlying file
+ // descriptor, we instead provide a way to copy FD instances and manage the
+ // underlying file descriptor with reference counting.
+ RefCountPtr *int32
+
+ // RefCount is the reference count of Sysfd. When a copy of an FD is made,
+ // it points to the reference count of the original FD instance.
+ RefCount int32
+
// Cache for the file type, lazily initialized when Seek is called.
Filetype uint32
// always set instead of being lazily initialized.
}
+func (s *SysFile) init() {
+ if s.RefCountPtr == nil {
+ s.RefCount = 1
+ s.RefCountPtr = &s.RefCount
+ }
+}
+
+func (s *SysFile) ref() SysFile {
+ atomic.AddInt32(s.RefCountPtr, +1)
+ return SysFile{RefCountPtr: s.RefCountPtr}
+}
+
+func (s *SysFile) destroy(fd int) error {
+ if s.RefCountPtr != nil && atomic.AddInt32(s.RefCountPtr, -1) > 0 {
+ return nil
+ }
+
+ // We don't use ignoringEINTR here because POSIX does not define
+ // whether the descriptor is closed if close returns EINTR.
+ // If the descriptor is indeed closed, using a loop would race
+ // with some other goroutine opening a new descriptor.
+ // (The Linux kernel guarantees that it is closed on an EINTR error.)
+ return CloseFunc(fd)
+}
+
+// Copy creates a copy of the FD.
+//
+// The FD instance points to the same underlying file descriptor. The file
+// descriptor isn't closed until all FD instances that refer to it have been
+// closed/destroyed.
+func (fd *FD) Copy() FD {
+ return FD{
+ Sysfd: fd.Sysfd,
+ SysFile: fd.SysFile.ref(),
+ IsStream: fd.IsStream,
+ ZeroReadIsEOF: fd.ZeroReadIsEOF,
+ isBlocking: fd.isBlocking,
+ isFile: fd.isFile,
+ }
+}
+
// dupCloseOnExecOld always errors on wasip1 because there is no mechanism to
// duplicate file descriptors.
func dupCloseOnExecOld(fd int) (int, string, error) {
--- /dev/null
+// 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.
+
+//go:build wasip1
+
+package net
+
+import (
+ "internal/poll"
+ "runtime"
+ "syscall"
+ "time"
+)
+
+const (
+ readSyscallName = "fd_read"
+ writeSyscallName = "fd_write"
+)
+
+// Network file descriptor.
+type netFD struct {
+ pfd poll.FD
+
+ // immutable until Close
+ family int
+ sotype int
+ isConnected bool // handshake completed or use of association with peer
+ net string
+ laddr Addr
+ raddr Addr
+
+ // The only networking available in WASI preview 1 is the ability to
+ // sock_accept on an pre-opened socket, and then fd_read, fd_write,
+ // fd_close, and sock_shutdown on the resulting connection. We
+ // intercept applicable netFD calls on this instance, and then pass
+ // the remainder of the netFD calls to fakeNetFD.
+ *fakeNetFD
+}
+
+func newFD(sysfd int) (*netFD, error) {
+ return newPollFD(poll.FD{
+ Sysfd: sysfd,
+ IsStream: true,
+ ZeroReadIsEOF: true,
+ })
+}
+
+func newPollFD(pfd poll.FD) (*netFD, error) {
+ ret := &netFD{
+ pfd: pfd,
+ net: "tcp",
+ laddr: unknownAddr{},
+ raddr: unknownAddr{},
+ }
+ return ret, nil
+}
+
+func (fd *netFD) init() error {
+ return fd.pfd.Init(fd.net, true)
+}
+
+func (fd *netFD) name() string {
+ return "unknown"
+}
+
+func (fd *netFD) accept() (netfd *netFD, err error) {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.accept()
+ }
+ d, _, errcall, err := fd.pfd.Accept()
+ if err != nil {
+ if errcall != "" {
+ err = wrapSyscallError(errcall, err)
+ }
+ return nil, err
+ }
+ if netfd, err = newFD(d); err != nil {
+ poll.CloseFunc(d)
+ return nil, err
+ }
+ if err = netfd.init(); err != nil {
+ netfd.Close()
+ return nil, err
+ }
+ return netfd, nil
+}
+
+func (fd *netFD) setAddr(laddr, raddr Addr) {
+ fd.laddr = laddr
+ fd.raddr = raddr
+ runtime.SetFinalizer(fd, (*netFD).Close)
+}
+
+func (fd *netFD) Close() error {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.Close()
+ }
+ runtime.SetFinalizer(fd, nil)
+ return fd.pfd.Close()
+}
+
+func (fd *netFD) shutdown(how int) error {
+ if fd.fakeNetFD != nil {
+ return nil
+ }
+ err := fd.pfd.Shutdown(how)
+ runtime.KeepAlive(fd)
+ return wrapSyscallError("shutdown", err)
+}
+
+func (fd *netFD) closeRead() error {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.closeRead()
+ }
+ return fd.shutdown(syscall.SHUT_RD)
+}
+
+func (fd *netFD) closeWrite() error {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.closeWrite()
+ }
+ return fd.shutdown(syscall.SHUT_WR)
+}
+
+func (fd *netFD) Read(p []byte) (n int, err error) {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.Read(p)
+ }
+ n, err = fd.pfd.Read(p)
+ runtime.KeepAlive(fd)
+ return n, wrapSyscallError(readSyscallName, err)
+}
+
+func (fd *netFD) Write(p []byte) (nn int, err error) {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.Write(p)
+ }
+ nn, err = fd.pfd.Write(p)
+ runtime.KeepAlive(fd)
+ return nn, wrapSyscallError(writeSyscallName, err)
+}
+
+func (fd *netFD) SetDeadline(t time.Time) error {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.SetDeadline(t)
+ }
+ return fd.pfd.SetDeadline(t)
+}
+
+func (fd *netFD) SetReadDeadline(t time.Time) error {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.SetReadDeadline(t)
+ }
+ return fd.pfd.SetReadDeadline(t)
+}
+
+func (fd *netFD) SetWriteDeadline(t time.Time) error {
+ if fd.fakeNetFD != nil {
+ return fd.fakeNetFD.SetWriteDeadline(t)
+ }
+ return fd.pfd.SetWriteDeadline(t)
+}
+
+type unknownAddr struct{}
+
+func (unknownAddr) Network() string { return "unknown" }
+func (unknownAddr) String() string { return "unknown" }
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build (js && wasm) || wasip1
+//go:build js && wasm
package net
--- /dev/null
+// 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.
+
+//go:build wasip1
+
+package net
+
+import (
+ "os"
+ "syscall"
+ _ "unsafe" // for go:linkname
+)
+
+func fileListener(f *os.File) (Listener, error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ return &TCPListener{fd: fd}, nil
+}
+
+func fileConn(f *os.File) (Conn, error) {
+ fd, err := newFileFD(f)
+ if err != nil {
+ return nil, err
+ }
+ return &TCPConn{conn{fd: fd}}, nil
+}
+
+func filePacketConn(f *os.File) (PacketConn, error) { return nil, syscall.ENOPROTOOPT }
+
+func newFileFD(f *os.File) (fd *netFD, err error) {
+ pfd := f.PollFD().Copy()
+ defer func() {
+ if err != nil {
+ pfd.Close()
+ }
+ }()
+ filetype, err := fd_fdstat_get_type(pfd.Sysfd)
+ if err != nil {
+ return nil, err
+ }
+ if filetype != syscall.FILETYPE_SOCKET_STREAM {
+ return nil, syscall.ENOTSOCK
+ }
+ fd, err = newPollFD(pfd)
+ if err != nil {
+ return nil, err
+ }
+ if err := fd.init(); err != nil {
+ return nil, err
+ }
+ return fd, nil
+}
+
+// This helper is implemented in the syscall package. It means we don't have
+// to redefine the fd_fdstat_get host import or the fdstat struct it
+// populates.
+//
+//go:linkname fd_fdstat_get_type syscall.fd_fdstat_get_type
+func fd_fdstat_get_type(fd int) (uint8, error)
import (
"context"
- "internal/poll"
"io"
"os"
"sync"
return portCounter
}
-// Network file descriptor.
-type netFD struct {
+type fakeNetFD struct {
+ listener bool
+ laddr Addr
r *bufferedPipe
w *bufferedPipe
incoming chan *netFD
closedMu sync.Mutex
closed bool
-
- // immutable until Close
- listener bool
- family int
- sotype int
- net string
- laddr Addr
- raddr Addr
-
- // unused
- pfd poll.FD
- isConnected bool // handshake completed or use of association with peer
}
// socket returns a network file descriptor that is ready for
// asynchronous I/O using the network poller.
func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, ctrlCtxFn func(context.Context, string, string, syscall.RawConn) error) (*netFD, error) {
fd := &netFD{family: family, sotype: sotype, net: net}
+ if laddr != nil && raddr == nil {
+ return fakelistener(fd, laddr)
+ }
+ fd2 := &netFD{family: family, sotype: sotype, net: net}
+ return fakeconn(fd, fd2, raddr)
+}
- if laddr != nil && raddr == nil { // listener
- l := laddr.(*TCPAddr)
- fd.laddr = &TCPAddr{
- IP: l.IP,
- Port: nextPort(),
- Zone: l.Zone,
- }
- fd.listener = true
- fd.incoming = make(chan *netFD, 1024)
- listenersMu.Lock()
- listeners[fd.laddr.(*TCPAddr).String()] = fd
- listenersMu.Unlock()
- return fd, nil
+func fakelistener(fd *netFD, laddr sockaddr) (*netFD, error) {
+ l := laddr.(*TCPAddr)
+ fd.laddr = &TCPAddr{
+ IP: l.IP,
+ Port: nextPort(),
+ Zone: l.Zone,
+ }
+ fd.fakeNetFD = &fakeNetFD{
+ listener: true,
+ laddr: fd.laddr,
+ incoming: make(chan *netFD, 1024),
}
+ listenersMu.Lock()
+ listeners[fd.laddr.(*TCPAddr).String()] = fd
+ listenersMu.Unlock()
+ return fd, nil
+}
+func fakeconn(fd *netFD, fd2 *netFD, raddr sockaddr) (*netFD, error) {
fd.laddr = &TCPAddr{
IP: IPv4(127, 0, 0, 1),
Port: nextPort(),
}
fd.raddr = raddr
- fd.r = newBufferedPipe(65536)
- fd.w = newBufferedPipe(65536)
- fd2 := &netFD{family: fd.family, sotype: sotype, net: net}
+ fd.fakeNetFD = &fakeNetFD{
+ r: newBufferedPipe(65536),
+ w: newBufferedPipe(65536),
+ }
+ fd2.fakeNetFD = &fakeNetFD{
+ r: fd.fakeNetFD.w,
+ w: fd.fakeNetFD.r,
+ }
+
fd2.laddr = fd.raddr
fd2.raddr = fd.laddr
- fd2.r = fd.w
- fd2.w = fd.r
listenersMu.Lock()
l, ok := listeners[fd.raddr.(*TCPAddr).String()]
if !ok {
return fd, nil
}
-func (fd *netFD) Read(p []byte) (n int, err error) {
+func (fd *fakeNetFD) Read(p []byte) (n int, err error) {
return fd.r.Read(p)
}
-func (fd *netFD) Write(p []byte) (nn int, err error) {
+func (fd *fakeNetFD) Write(p []byte) (nn int, err error) {
return fd.w.Write(p)
}
-func (fd *netFD) Close() error {
+func (fd *fakeNetFD) Close() error {
fd.closedMu.Lock()
if fd.closed {
fd.closedMu.Unlock()
return nil
}
-func (fd *netFD) closeRead() error {
+func (fd *fakeNetFD) closeRead() error {
fd.r.Close()
return nil
}
-func (fd *netFD) closeWrite() error {
+func (fd *fakeNetFD) closeWrite() error {
fd.w.Close()
return nil
}
-func (fd *netFD) accept() (*netFD, error) {
+func (fd *fakeNetFD) accept() (*netFD, error) {
c, ok := <-fd.incoming
if !ok {
return nil, syscall.EINVAL
return c, nil
}
-func (fd *netFD) SetDeadline(t time.Time) error {
+func (fd *fakeNetFD) SetDeadline(t time.Time) error {
fd.r.SetReadDeadline(t)
fd.w.SetWriteDeadline(t)
return nil
}
-func (fd *netFD) SetReadDeadline(t time.Time) error {
+func (fd *fakeNetFD) SetReadDeadline(t time.Time) error {
fd.r.SetReadDeadline(t)
return nil
}
-func (fd *netFD) SetWriteDeadline(t time.Time) error {
+func (fd *fakeNetFD) SetWriteDeadline(t time.Time) error {
fd.w.SetWriteDeadline(t)
return nil
}
return 0, syscall.ENOSYS
}
-func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) {
+func (fd *fakeNetFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (syscall.Sockaddr, error) {
+ return nil, syscall.ENOSYS
+}
+
+func (fd *fakeNetFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) {
return 0, nil, syscall.ENOSYS
}
-func (fd *netFD) readFromInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) {
+func (fd *fakeNetFD) readFromInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) {
return 0, syscall.ENOSYS
}
-func (fd *netFD) readFromInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) {
+func (fd *fakeNetFD) readFromInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) {
return 0, syscall.ENOSYS
}
-func (fd *netFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int, sa syscall.Sockaddr, err error) {
+func (fd *fakeNetFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int, sa syscall.Sockaddr, err error) {
return 0, 0, 0, nil, syscall.ENOSYS
}
-func (fd *netFD) readMsgInet4(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet4) (n, oobn, retflags int, err error) {
+func (fd *fakeNetFD) readMsgInet4(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet4) (n, oobn, retflags int, err error) {
return 0, 0, 0, syscall.ENOSYS
}
-func (fd *netFD) readMsgInet6(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet6) (n, oobn, retflags int, err error) {
+func (fd *fakeNetFD) readMsgInet6(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet6) (n, oobn, retflags int, err error) {
return 0, 0, 0, syscall.ENOSYS
}
-func (fd *netFD) writeMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (n int, oobn int, err error) {
+func (fd *fakeNetFD) writeMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (n int, oobn int, err error) {
return 0, 0, syscall.ENOSYS
}
-func (fd *netFD) writeMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (n int, oobn int, err error) {
+func (fd *fakeNetFD) writeMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (n int, oobn int, err error) {
return 0, 0, syscall.ENOSYS
}
-func (fd *netFD) writeTo(p []byte, sa syscall.Sockaddr) (n int, err error) {
+func (fd *fakeNetFD) writeTo(p []byte, sa syscall.Sockaddr) (n int, err error) {
return 0, syscall.ENOSYS
}
-func (fd *netFD) writeToInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) {
+func (fd *fakeNetFD) writeToInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) {
return 0, syscall.ENOSYS
}
-func (fd *netFD) writeToInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) {
+func (fd *fakeNetFD) writeToInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) {
return 0, syscall.ENOSYS
}
-func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) {
+func (fd *fakeNetFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) {
return 0, 0, syscall.ENOSYS
}
-func (fd *netFD) dup() (f *os.File, err error) {
+func (fd *fakeNetFD) dup() (f *os.File, err error) {
return nil, syscall.ENOSYS
}
--- /dev/null
+// 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.
+
+// Fake networking for js/wasm. It is intended to allow tests of other package to pass.
+
+//go:build js && wasm
+
+package net
+
+import "internal/poll"
+
+// Network file descriptor.
+type netFD struct {
+ *fakeNetFD
+
+ // immutable until Close
+ family int
+ sotype int
+ net string
+ laddr Addr
+ raddr Addr
+
+ // unused
+ pfd poll.FD
+ isConnected bool // handshake completed or use of association with peer
+}
--- /dev/null
+// 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.
+
+//go:build wasip1
+
+package os
+
+import "internal/poll"
+
+// PollFD returns the poll.FD of the file.
+//
+// Other packages in std that also import internal/poll (such as net)
+// can use a type assertion to access this extension method so that
+// they can pass the *poll.FD to functions like poll.Splice.
+//
+// There is an equivalent function in net.rawConn.
+//
+// PollFD is not intended for use outside the standard library.
+func (f *file) PollFD() *poll.FD {
+ return &f.pfd
+}
--- /dev/null
+// 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])
+ }
+}
--- /dev/null
+// 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
+ }
+}
return uint32(stat.fdflags), errnoErr(errno)
}
+func fd_fdstat_get_type(fd int) (uint8, error) {
+ var stat fdstat
+ errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat))
+ return stat.filetype, errnoErr(errno)
+}
+
type preopentype = uint8
const (
if errno == EBADF {
break
}
+ if errno == ENOTDIR || prestat.typ != preopentypeDir {
+ continue
+ }
if errno != 0 {
panic("fd_prestat: " + errno.Error())
}
- if prestat.typ != preopentypeDir {
- continue
- }
if int(prestat.dir.prNameLen) > len(dirNameBuf) {
dirNameBuf = make([]byte, prestat.dir.prNameLen)
}
--- /dev/null
+// 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.
+
+// Fake networking for js/wasm and wasip1/wasm.
+// This file only exists to make the compiler happy.
+
+//go:build (js && wasm) || wasip1
+
+package syscall
+
+const (
+ AF_UNSPEC = iota
+ AF_UNIX
+ AF_INET
+ AF_INET6
+)
+
+const (
+ SOCK_STREAM = 1 + iota
+ SOCK_DGRAM
+ SOCK_RAW
+ SOCK_SEQPACKET
+)
+
+const (
+ IPPROTO_IP = 0
+ IPPROTO_IPV4 = 4
+ IPPROTO_IPV6 = 0x29
+ IPPROTO_TCP = 6
+ IPPROTO_UDP = 0x11
+)
+
+const (
+ _ = iota
+ IPV6_V6ONLY
+ SOMAXCONN
+ SO_ERROR
+)
+
+// Misc constants expected by package net but not supported.
+const (
+ _ = iota
+ F_DUPFD_CLOEXEC
+ SYS_FCNTL = 500 // unsupported
+)
+
+type Sockaddr any
+
+type SockaddrInet4 struct {
+ Port int
+ Addr [4]byte
+}
+
+type SockaddrInet6 struct {
+ Port int
+ ZoneId uint32
+ Addr [16]byte
+}
+
+type SockaddrUnix struct {
+ Name string
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// js/wasm uses fake networking directly implemented in the net package.
-// This file only exists to make the compiler happy.
-
//go:build js && wasm
package syscall
-const (
- AF_UNSPEC = iota
- AF_UNIX
- AF_INET
- AF_INET6
-)
-
-const (
- SOCK_STREAM = 1 + iota
- SOCK_DGRAM
- SOCK_RAW
- SOCK_SEQPACKET
-)
-
-const (
- IPPROTO_IP = 0
- IPPROTO_IPV4 = 4
- IPPROTO_IPV6 = 0x29
- IPPROTO_TCP = 6
- IPPROTO_UDP = 0x11
-)
-
-const (
- _ = iota
- IPV6_V6ONLY
- SOMAXCONN
- SO_ERROR
-)
-
-// Misc constants expected by package net but not supported.
-const (
- _ = iota
- F_DUPFD_CLOEXEC
- SYS_FCNTL = 500 // unsupported
-)
-
-type Sockaddr any
-
-type SockaddrInet4 struct {
- Port int
- Addr [4]byte
-}
-
-type SockaddrInet6 struct {
- Port int
- ZoneId uint32
- Addr [16]byte
-}
-
-type SockaddrUnix struct {
- Name string
-}
-
func Socket(proto, sotype, unused int) (fd int, err error) {
return 0, ENOSYS
}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// wasip1/wasm uses fake networking directly implemented in the net package.
-// This file only exists to make the compiler happy.
-
//go:build wasip1
package syscall
-const (
- AF_UNSPEC = iota
- AF_UNIX
- AF_INET
- AF_INET6
-)
+import "unsafe"
const (
- SOCK_STREAM = 1 + iota
- SOCK_DGRAM
- SOCK_RAW
- SOCK_SEQPACKET
+ SHUT_RD = 0x1
+ SHUT_WR = 0x2
+ SHUT_RDWR = SHUT_RD | SHUT_WR
)
-const (
- IPPROTO_IP = 0
- IPPROTO_IPV4 = 4
- IPPROTO_IPV6 = 0x29
- IPPROTO_TCP = 6
- IPPROTO_UDP = 0x11
-)
+type sdflags = uint32
-const (
- _ = iota
- IPV6_V6ONLY
- SOMAXCONN
- SO_ERROR
-)
+//go:wasmimport wasi_snapshot_preview1 sock_accept
+//go:noescape
+func sock_accept(fd int32, flags fdflags, newfd unsafe.Pointer) Errno
-// Misc constants expected by package net but not supported.
-const (
- _ = iota
- F_DUPFD_CLOEXEC
- SYS_FCNTL = 500 // unsupported; same value as net_nacl.go
-)
-
-type Sockaddr interface {
-}
-
-type SockaddrInet4 struct {
- Port int
- Addr [4]byte
-}
-
-type SockaddrInet6 struct {
- Port int
- ZoneId uint32
- Addr [16]byte
-}
-
-type SockaddrUnix struct {
- Name string
-}
+//go:wasmimport wasi_snapshot_preview1 sock_shutdown
+//go:noescape
+func sock_shutdown(fd int32, flags sdflags) Errno
func Socket(proto, sotype, unused int) (fd int, err error) {
return 0, ENOSYS
return ENOSYS
}
-func Accept(fd int) (newfd int, sa Sockaddr, err error) {
- return 0, nil, ENOSYS
+func Accept(fd int) (int, Sockaddr, error) {
+ var newfd int32
+ errno := sock_accept(int32(fd), 0, unsafe.Pointer(&newfd))
+ return int(newfd), nil, errnoErr(errno)
}
func Connect(fd int, sa Sockaddr) error {
}
func Shutdown(fd int, how int) error {
- return ENOSYS
+ errno := sock_shutdown(int32(fd), sdflags(how))
+ return errnoErr(errno)
}