]> Cypherpunks.ru repositories - gostls13.git/commitdiff
net: implement wasip1 FileListener and FileConn
authorChris O'Hara <cohara87@gmail.com>
Mon, 8 May 2023 07:08:20 +0000 (17:08 +1000)
committerGopher Robot <gobot@golang.org>
Thu, 25 May 2023 00:12:41 +0000 (00:12 +0000)
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>
16 files changed:
misc/wasm/go_wasip1_wasm_exec
src/internal/poll/fd_unix.go
src/internal/poll/fd_unixjs.go
src/internal/poll/fd_wasip1.go
src/net/fd_wasip1.go [new file with mode: 0644]
src/net/file_stub.go
src/net/file_wasip1.go [new file with mode: 0644]
src/net/net_fake.go
src/net/net_fake_js.go [new file with mode: 0644]
src/os/file_wasip1.go [new file with mode: 0644]
src/runtime/internal/wasitest/tcpecho_test.go [new file with mode: 0644]
src/runtime/internal/wasitest/testdata/tcpecho.go [new file with mode: 0644]
src/syscall/fs_wasip1.go
src/syscall/net_fake.go [new file with mode: 0644]
src/syscall/net_js.go
src/syscall/net_wasip1.go

index abcac8df36cb748cdec9300eef758d9878e960fa..dcec1c6392e16273218f695764282f923e87927f 100755 (executable)
@@ -11,10 +11,10 @@ case "$GOWASIRUNTIME" in
                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"
index 0175b91ecfcbc39c8277d77f969be8b3fca098cb..61c2338305ada4b2399acf3775268c0c3f3969f0 100644 (file)
@@ -52,6 +52,8 @@ type FD struct {
 // 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
@@ -76,12 +78,7 @@ func (fd *FD) destroy() error {
        // 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)
index 07bf13f55cc0b2bba8eb4aab91cc68257ab163e0..090974d2b0d666d8145f15132ff743473897cec0 100644 (file)
@@ -13,6 +13,17 @@ type SysFile struct {
        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) {
index 749fa50220b6903abe15ca91bf8ed693eb0b5d0e..aecd89669b482c450de6bcb438059e29f27a5694 100644 (file)
@@ -11,6 +11,18 @@ import (
 )
 
 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
 
@@ -29,6 +41,47 @@ type SysFile struct {
        // 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) {
diff --git a/src/net/fd_wasip1.go b/src/net/fd_wasip1.go
new file mode 100644 (file)
index 0000000..3f64ff4
--- /dev/null
@@ -0,0 +1,168 @@
+// 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" }
index 1299f0e67f943497b321274571790e9de57f278b..91df926a5789144ef0ad8e197d854b8c5c01d1b0 100644 (file)
@@ -2,7 +2,7 @@
 // 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
 
diff --git a/src/net/file_wasip1.go b/src/net/file_wasip1.go
new file mode 100644 (file)
index 0000000..95fd540
--- /dev/null
@@ -0,0 +1,62 @@
+// 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)
index 8e801d15d1bba56892555cb9a0608688aeda0cef..a816213f8de0d86fe2d4891bb67935ead1687d2d 100644 (file)
@@ -10,7 +10,6 @@ package net
 
 import (
        "context"
-       "internal/poll"
        "io"
        "os"
        "sync"
@@ -33,61 +32,64 @@ func nextPort() int {
        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 {
@@ -100,15 +102,15 @@ func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only
        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()
@@ -131,17 +133,17 @@ func (fd *netFD) Close() error {
        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
@@ -149,18 +151,18 @@ func (fd *netFD) accept() (*netFD, error) {
        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
 }
@@ -265,55 +267,59 @@ func sysSocket(family, sotype, proto int) (int, error) {
        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
 }
 
diff --git a/src/net/net_fake_js.go b/src/net/net_fake_js.go
new file mode 100644 (file)
index 0000000..1fc0b50
--- /dev/null
@@ -0,0 +1,27 @@
+// 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
+}
diff --git a/src/os/file_wasip1.go b/src/os/file_wasip1.go
new file mode 100644 (file)
index 0000000..c9b05b3
--- /dev/null
@@ -0,0 +1,22 @@
+// 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
+}
diff --git a/src/runtime/internal/wasitest/tcpecho_test.go b/src/runtime/internal/wasitest/tcpecho_test.go
new file mode 100644 (file)
index 0000000..506e6fe
--- /dev/null
@@ -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 (file)
index 0000000..819e352
--- /dev/null
@@ -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
+       }
+}
index 25cabf82343660532cec038e15348a951ac77750..d60ab0b53eebfb473f7f95b7d394ddba8cc73208 100644 (file)
@@ -279,6 +279,12 @@ func fd_fdstat_get_flags(fd int) (uint32, error) {
        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 (
@@ -331,12 +337,12 @@ func init() {
                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)
                }
diff --git a/src/syscall/net_fake.go b/src/syscall/net_fake.go
new file mode 100644 (file)
index 0000000..689f6f8
--- /dev/null
@@ -0,0 +1,63 @@
+// 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
+}
index 2ed4e191bd3493a203c511254e48563c9e1202d2..cba33dfd2e3257043c27ed56b450f10a414559fd 100644 (file)
@@ -2,66 +2,10 @@
 // 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
 }
index 896dd3e7708d09675d502a6bdf49ee1343bd42b1..3918840a7e05e1a00624d642e816f76761b85a40 100644 (file)
@@ -2,66 +2,27 @@
 // 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
@@ -79,8 +40,10 @@ func Listen(fd int, backlog int) error {
        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 {
@@ -120,5 +83,6 @@ func SetWriteDeadline(fd int, t int64) error {
 }
 
 func Shutdown(fd int, how int) error {
-       return ENOSYS
+       errno := sock_shutdown(int32(fd), sdflags(how))
+       return errnoErr(errno)
 }