From 79588681b2d31d56ae14138a8d4cdaa3825a226d Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 19 May 2022 15:55:31 +0200 Subject: [PATCH 1/5] syscall: add WASI `SockAccept` Signed-off-by: Roman Volosatovs --- src/syscall/syscall_libc_wasi.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index 99a9176499..7d28b55c8f 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -309,6 +309,14 @@ func Getpagesize() int { return 65536 } +func SockAccept(fd int, flags int) (nfd int, err error) { + var retptr0 __wasi_fd_t + if n := sock_accept(__wasi_fd_t(fd), __wasi_fdflags_t(flags), &retptr0); n != 0 { + return -1, Errno(n) + } + return int(retptr0), nil +} + // int stat(const char *path, struct stat * buf); // //export stat @@ -323,3 +331,14 @@ func libc_fstat(fd int32, ptr unsafe.Pointer) int32 // //export lstat func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32 + +type ( + __wasi_errno_t = uint16 + + __wasi_fd_t = int32 + __wasi_fdflags_t = uint16 +) + +//go:wasm-module wasi_snapshot_preview1 +//export sock_accept +func sock_accept(fd __wasi_fd_t, flags __wasi_fdflags_t, retptr0 *__wasi_fd_t) __wasi_errno_t From 6c775cdcd674bbfccf2f2b9fdec955f2784da71a Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 19 May 2022 16:32:47 +0200 Subject: [PATCH 2/5] net: wrap `netFD` in `conn` Directly duplicate from Go 1.18 implementation, use fake netFD for all systems (which is only used for js&wasm in stdlib) Signed-off-by: Roman Volosatovs --- src/net/net.go | 98 +++++++++++++++++++- src/net/net_fake.go | 215 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 src/net/net_fake.go diff --git a/src/net/net.go b/src/net/net.go index db4d8f117f..11bbf04407 100644 --- a/src/net/net.go +++ b/src/net/net.go @@ -1,4 +1,4 @@ -// The following is copied from Go 1.18 official implementation. +// The following is copied and adapted from Go 1.18 official implementation. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -8,6 +8,7 @@ package net import ( "io" + "syscall" "time" ) @@ -82,7 +83,100 @@ type Conn interface { } type conn struct { - // + fd *netFD +} + +func (c *conn) ok() bool { return c != nil && c.fd != nil } + +// Implementation of the Conn interface. + +// Read implements the Conn Read method. +func (c *conn) Read(b []byte) (int, error) { + if !c.ok() { + return 0, syscall.EINVAL + } + n, err := c.fd.Read(b) + if err != nil && err != io.EOF { + err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return n, err +} + +// Write implements the Conn Write method. +func (c *conn) Write(b []byte) (int, error) { + if !c.ok() { + return 0, syscall.EINVAL + } + n, err := c.fd.Write(b) + if err != nil { + err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return n, err +} + +// Close closes the connection. +func (c *conn) Close() error { + if !c.ok() { + return syscall.EINVAL + } + err := c.fd.Close() + if err != nil { + err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err} + } + return err +} + +// LocalAddr returns the local network address. +// The Addr returned is shared by all invocations of LocalAddr, so +// do not modify it. +func (c *conn) LocalAddr() Addr { + if !c.ok() { + return nil + } + return c.fd.laddr +} + +// RemoteAddr returns the remote network address. +// The Addr returned is shared by all invocations of RemoteAddr, so +// do not modify it. +func (c *conn) RemoteAddr() Addr { + if !c.ok() { + return nil + } + return c.fd.raddr +} + +// SetDeadline implements the Conn SetDeadline method. +func (c *conn) SetDeadline(t time.Time) error { + if !c.ok() { + return syscall.EINVAL + } + if err := c.fd.SetDeadline(t); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err} + } + return nil +} + +// SetReadDeadline implements the Conn SetReadDeadline method. +func (c *conn) SetReadDeadline(t time.Time) error { + if !c.ok() { + return syscall.EINVAL + } + if err := c.fd.SetReadDeadline(t); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err} + } + return nil +} + +// SetWriteDeadline implements the Conn SetWriteDeadline method. +func (c *conn) SetWriteDeadline(t time.Time) error { + if !c.ok() { + return syscall.EINVAL + } + if err := c.fd.SetWriteDeadline(t); err != nil { + return &OpError{Op: "set", Net: c.fd.net, Source: nil, Addr: c.fd.laddr, Err: err} + } + return nil } // A Listener is a generic network listener for stream-oriented protocols. diff --git a/src/net/net_fake.go b/src/net/net_fake.go new file mode 100644 index 0000000000..525e08710a --- /dev/null +++ b/src/net/net_fake.go @@ -0,0 +1,215 @@ +// The following is copied from Go 1.18 official implementation. + +// Copyright 2018 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. It is intended to allow tests of other package to pass. + +package net + +import ( + "io" + "os" + "sync" + "syscall" + "time" +) + +var listenersMu sync.Mutex +var listeners = make(map[string]*netFD) + +var portCounterMu sync.Mutex +var portCounter = 0 + +func nextPort() int { + portCounterMu.Lock() + defer portCounterMu.Unlock() + portCounter++ + return portCounter +} + +// Network file descriptor. +type netFD struct { + 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 + + isConnected bool // handshake completed or use of association with peer +} + +func (fd *netFD) Read(p []byte) (n int, err error) { + return fd.r.Read(p) +} + +func (fd *netFD) Write(p []byte) (nn int, err error) { + return fd.w.Write(p) +} + +func (fd *netFD) Close() error { + fd.closedMu.Lock() + if fd.closed { + fd.closedMu.Unlock() + return nil + } + fd.closed = true + fd.closedMu.Unlock() + + if fd.listener { + listenersMu.Lock() + delete(listeners, fd.laddr.String()) + close(fd.incoming) + fd.listener = false + listenersMu.Unlock() + return nil + } + + fd.r.Close() + fd.w.Close() + return nil +} + +func (fd *netFD) closeRead() error { + fd.r.Close() + return nil +} + +func (fd *netFD) closeWrite() error { + fd.w.Close() + return nil +} + +func (fd *netFD) accept() (*netFD, error) { + c, ok := <-fd.incoming + if !ok { + return nil, syscall.EINVAL + } + return c, nil +} + +func (fd *netFD) SetDeadline(t time.Time) error { + fd.r.SetReadDeadline(t) + fd.w.SetWriteDeadline(t) + return nil +} + +func (fd *netFD) SetReadDeadline(t time.Time) error { + fd.r.SetReadDeadline(t) + return nil +} + +func (fd *netFD) SetWriteDeadline(t time.Time) error { + fd.w.SetWriteDeadline(t) + return nil +} + +func newBufferedPipe(softLimit int) *bufferedPipe { + p := &bufferedPipe{softLimit: softLimit} + p.rCond.L = &p.mu + p.wCond.L = &p.mu + return p +} + +type bufferedPipe struct { + softLimit int + mu sync.Mutex + buf []byte + closed bool + rCond sync.Cond + wCond sync.Cond + rDeadline time.Time + wDeadline time.Time +} + +func (p *bufferedPipe) Read(b []byte) (int, error) { + p.mu.Lock() + defer p.mu.Unlock() + + for { + if p.closed && len(p.buf) == 0 { + return 0, io.EOF + } + if !p.rDeadline.IsZero() { + d := time.Until(p.rDeadline) + if d <= 0 { + return 0, syscall.EAGAIN + } + time.AfterFunc(d, p.rCond.Broadcast) + } + if len(p.buf) > 0 { + break + } + p.rCond.Wait() + } + + n := copy(b, p.buf) + p.buf = p.buf[n:] + p.wCond.Broadcast() + return n, nil +} + +func (p *bufferedPipe) Write(b []byte) (int, error) { + p.mu.Lock() + defer p.mu.Unlock() + + for { + if p.closed { + return 0, syscall.ENOTCONN + } + if !p.wDeadline.IsZero() { + d := time.Until(p.wDeadline) + if d <= 0 { + return 0, syscall.EAGAIN + } + time.AfterFunc(d, p.wCond.Broadcast) + } + if len(p.buf) <= p.softLimit { + break + } + p.wCond.Wait() + } + + p.buf = append(p.buf, b...) + p.rCond.Broadcast() + return len(b), nil +} + +func (p *bufferedPipe) Close() { + p.mu.Lock() + defer p.mu.Unlock() + + p.closed = true + p.rCond.Broadcast() + p.wCond.Broadcast() +} + +func (p *bufferedPipe) SetReadDeadline(t time.Time) { + p.mu.Lock() + defer p.mu.Unlock() + + p.rDeadline = t + p.rCond.Broadcast() +} + +func (p *bufferedPipe) SetWriteDeadline(t time.Time) { + p.mu.Lock() + defer p.mu.Unlock() + + p.wDeadline = t + p.wCond.Broadcast() +} + +func (fd *netFD) dup() (f *os.File, err error) { + return nil, syscall.ENOSYS +} From f9495a05157f033406f7f01e78e7b79e8462d568 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 19 May 2022 16:33:55 +0200 Subject: [PATCH 3/5] net: add `wrapSyscallError` Signed-off-by: Roman Volosatovs --- src/net/error_posix.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/net/error_posix.go diff --git a/src/net/error_posix.go b/src/net/error_posix.go new file mode 100644 index 0000000000..ca844501d7 --- /dev/null +++ b/src/net/error_posix.go @@ -0,0 +1,24 @@ +// The following is copied and adapted from Go 1.18 official implementation. + +// Copyright 2017 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 aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || windows || wasi +// +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris windows wasi + +package net + +import ( + "os" + "syscall" +) + +// wrapSyscallError takes an error and a syscall name. If the error is +// a syscall.Errno, it wraps it in a os.SyscallError using the syscall name. +func wrapSyscallError(name string, err error) error { + if _, ok := err.(syscall.Errno); ok { + err = os.NewSyscallError(name, err) + } + return err +} From 519a89d6e4677afa685f09570ae5d26d95e7d48c Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 19 May 2022 16:35:00 +0200 Subject: [PATCH 4/5] net: implement `netFD` for WASI Signed-off-by: Roman Volosatovs --- src/net/fd_wasi.go | 57 +++++++++++++++++++++++++++++++++++++++++++++ src/net/net_fake.go | 3 +++ 2 files changed, 60 insertions(+) create mode 100644 src/net/fd_wasi.go diff --git a/src/net/fd_wasi.go b/src/net/fd_wasi.go new file mode 100644 index 0000000000..87f26d52eb --- /dev/null +++ b/src/net/fd_wasi.go @@ -0,0 +1,57 @@ +//go:build wasi +// +build wasi + +package net + +import ( + "io" + "os" + "syscall" + "time" +) + +// Network file descriptor. +type netFD struct { + *os.File + + net string + laddr Addr + raddr Addr +} + +// Read implements the Conn Read method. +func (c *netFD) Read(b []byte) (int, error) { + // TODO: Handle EAGAIN and perform poll + return c.File.Read(b) +} + +// Write implements the Conn Write method. +func (c *netFD) Write(b []byte) (int, error) { + // TODO: Handle EAGAIN and perform poll + return c.File.Write(b) +} + +// LocalAddr implements the Conn LocalAddr method. +func (*netFD) LocalAddr() Addr { + return nil +} + +// RemoteAddr implements the Conn RemoteAddr method. +func (*netFD) RemoteAddr() Addr { + return nil +} + +// SetDeadline implements the Conn SetDeadline method. +func (*netFD) SetDeadline(t time.Time) error { + return ErrNotImplemented +} + +// SetReadDeadline implements the Conn SetReadDeadline method. +func (*netFD) SetReadDeadline(t time.Time) error { + return ErrNotImplemented +} + +// SetWriteDeadline implements the Conn SetWriteDeadline method. +func (*netFD) SetWriteDeadline(t time.Time) error { + return ErrNotImplemented +} diff --git a/src/net/net_fake.go b/src/net/net_fake.go index 525e08710a..5a4baa353f 100644 --- a/src/net/net_fake.go +++ b/src/net/net_fake.go @@ -6,6 +6,9 @@ // Fake networking. It is intended to allow tests of other package to pass. +//go:build !wasi +// +build !wasi + package net import ( From 902c2a2b8e0e189fff4172016173bd8ee64e5307 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 19 May 2022 16:35:24 +0200 Subject: [PATCH 5/5] net: add `FileListener` for WASI Signed-off-by: Roman Volosatovs --- src/net/fd_wasi.go | 27 ++++++++++++++++++++++++--- src/net/file.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/net/file.go diff --git a/src/net/fd_wasi.go b/src/net/fd_wasi.go index 87f26d52eb..ce37933587 100644 --- a/src/net/fd_wasi.go +++ b/src/net/fd_wasi.go @@ -4,7 +4,6 @@ package net import ( - "io" "os" "syscall" "time" @@ -21,13 +20,13 @@ type netFD struct { // Read implements the Conn Read method. func (c *netFD) Read(b []byte) (int, error) { - // TODO: Handle EAGAIN and perform poll + // TODO: Handle EAGAIN and perform poll return c.File.Read(b) } // Write implements the Conn Write method. func (c *netFD) Write(b []byte) (int, error) { - // TODO: Handle EAGAIN and perform poll + // TODO: Handle EAGAIN and perform poll return c.File.Write(b) } @@ -55,3 +54,25 @@ func (*netFD) SetReadDeadline(t time.Time) error { func (*netFD) SetWriteDeadline(t time.Time) error { return ErrNotImplemented } + +type listener struct { + *os.File +} + +// Accept implements the Listener Accept method. +func (l *listener) Accept() (Conn, error) { + fd, err := syscall.SockAccept(int(l.File.Fd()), syscall.O_NONBLOCK) + if err != nil { + return nil, wrapSyscallError("sock_accept", err) + } + return &netFD{File: os.NewFile(uintptr(fd), "conn"), net: "file+net", laddr: nil, raddr: nil}, nil +} + +// Addr implements the Listener Addr method. +func (*listener) Addr() Addr { + return nil +} + +func fileListener(f *os.File) (Listener, error) { + return &listener{File: f}, nil +} diff --git a/src/net/file.go b/src/net/file.go new file mode 100644 index 0000000000..487b789ea2 --- /dev/null +++ b/src/net/file.go @@ -0,0 +1,31 @@ +//go:build wasi +// +build wasi + +// The following is copied from Go 1.18 official implementation. + +// Copyright 2015 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 net + +import ( + "os" +) + +type fileAddr string + +func (fileAddr) Network() string { return "file+net" } +func (f fileAddr) String() string { return string(f) } + +// FileListener returns a copy of the network listener corresponding +// to the open file f. +// It is the caller's responsibility to close ln when finished. +// Closing ln does not affect f, and closing f does not affect ln. +func FileListener(f *os.File) (ln Listener, err error) { + ln, err = fileListener(f) + if err != nil { + err = &OpError{Op: "file", Net: "file+net", Source: nil, Addr: fileAddr(f.Name()), Err: err} + } + return +}