Skip to content

Commit a17de43

Browse files
chrisogopherbot
authored andcommittedMay 25, 2023
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 <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> TryBot-Bypass: Dmitri Shuralyov <[email protected]> Reviewed-by: Johan Brandhorst-Satzkorn <[email protected]> Auto-Submit: Johan Brandhorst-Satzkorn <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent c5c2184 commit a17de43

16 files changed

+665
-176
lines changed
 

Diff for: ‎misc/wasm/go_wasip1_wasm_exec

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ case "$GOWASIRUNTIME" in
1111
exec wasmer run --dir=/ --env PWD="$PWD" ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
1212
;;
1313
"wasmtime")
14-
exec wasmtime run --dir=/ --env PWD="$PWD" --max-wasm-stack 1048576 "$1" -- "${@:2}"
14+
exec wasmtime run --dir=/ --env PWD="$PWD" --max-wasm-stack 1048576 ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
1515
;;
1616
"wazero" | "")
17-
exec wazero run -mount /:/ -env-inherit -cachedir "${TMPDIR:-/tmp}"/wazero "$1" "${@:2}"
17+
exec wazero run -mount /:/ -env-inherit -cachedir "${TMPDIR:-/tmp}"/wazero ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
1818
;;
1919
*)
2020
echo "Unknown Go WASI runtime specified: $GOWASIRUNTIME"

Diff for: ‎src/internal/poll/fd_unix.go

+3-6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ type FD struct {
5252
// or "file".
5353
// Set pollable to true if fd should be managed by runtime netpoll.
5454
func (fd *FD) Init(net string, pollable bool) error {
55+
fd.SysFile.init()
56+
5557
// We don't actually care about the various network types.
5658
if net == "file" {
5759
fd.isFile = true
@@ -76,12 +78,7 @@ func (fd *FD) destroy() error {
7678
// so this must be executed before CloseFunc.
7779
fd.pd.close()
7880

79-
// We don't use ignoringEINTR here because POSIX does not define
80-
// whether the descriptor is closed if close returns EINTR.
81-
// If the descriptor is indeed closed, using a loop would race
82-
// with some other goroutine opening a new descriptor.
83-
// (The Linux kernel guarantees that it is closed on an EINTR error.)
84-
err := CloseFunc(fd.Sysfd)
81+
err := fd.SysFile.destroy(fd.Sysfd)
8582

8683
fd.Sysfd = -1
8784
runtime_Semrelease(&fd.csema)

Diff for: ‎src/internal/poll/fd_unixjs.go

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ type SysFile struct {
1313
iovecs *[]syscall.Iovec
1414
}
1515

16+
func (s *SysFile) init() {}
17+
18+
func (s *SysFile) destroy(fd int) error {
19+
// We don't use ignoringEINTR here because POSIX does not define
20+
// whether the descriptor is closed if close returns EINTR.
21+
// If the descriptor is indeed closed, using a loop would race
22+
// with some other goroutine opening a new descriptor.
23+
// (The Linux kernel guarantees that it is closed on an EINTR error.)
24+
return CloseFunc(fd)
25+
}
26+
1627
// dupCloseOnExecOld is the traditional way to dup an fd and
1728
// set its O_CLOEXEC bit, using two system calls.
1829
func dupCloseOnExecOld(fd int) (int, string, error) {

Diff for: ‎src/internal/poll/fd_wasip1.go

+53
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ import (
1111
)
1212

1313
type SysFile struct {
14+
// RefCountPtr is a pointer to the reference count of Sysfd.
15+
//
16+
// WASI preview 1 lacks a dup(2) system call. When the os and net packages
17+
// need to share a file/socket, instead of duplicating the underlying file
18+
// descriptor, we instead provide a way to copy FD instances and manage the
19+
// underlying file descriptor with reference counting.
20+
RefCountPtr *int32
21+
22+
// RefCount is the reference count of Sysfd. When a copy of an FD is made,
23+
// it points to the reference count of the original FD instance.
24+
RefCount int32
25+
1426
// Cache for the file type, lazily initialized when Seek is called.
1527
Filetype uint32
1628

@@ -29,6 +41,47 @@ type SysFile struct {
2941
// always set instead of being lazily initialized.
3042
}
3143

44+
func (s *SysFile) init() {
45+
if s.RefCountPtr == nil {
46+
s.RefCount = 1
47+
s.RefCountPtr = &s.RefCount
48+
}
49+
}
50+
51+
func (s *SysFile) ref() SysFile {
52+
atomic.AddInt32(s.RefCountPtr, +1)
53+
return SysFile{RefCountPtr: s.RefCountPtr}
54+
}
55+
56+
func (s *SysFile) destroy(fd int) error {
57+
if s.RefCountPtr != nil && atomic.AddInt32(s.RefCountPtr, -1) > 0 {
58+
return nil
59+
}
60+
61+
// We don't use ignoringEINTR here because POSIX does not define
62+
// whether the descriptor is closed if close returns EINTR.
63+
// If the descriptor is indeed closed, using a loop would race
64+
// with some other goroutine opening a new descriptor.
65+
// (The Linux kernel guarantees that it is closed on an EINTR error.)
66+
return CloseFunc(fd)
67+
}
68+
69+
// Copy creates a copy of the FD.
70+
//
71+
// The FD instance points to the same underlying file descriptor. The file
72+
// descriptor isn't closed until all FD instances that refer to it have been
73+
// closed/destroyed.
74+
func (fd *FD) Copy() FD {
75+
return FD{
76+
Sysfd: fd.Sysfd,
77+
SysFile: fd.SysFile.ref(),
78+
IsStream: fd.IsStream,
79+
ZeroReadIsEOF: fd.ZeroReadIsEOF,
80+
isBlocking: fd.isBlocking,
81+
isFile: fd.isFile,
82+
}
83+
}
84+
3285
// dupCloseOnExecOld always errors on wasip1 because there is no mechanism to
3386
// duplicate file descriptors.
3487
func dupCloseOnExecOld(fd int) (int, string, error) {

Diff for: ‎src/net/fd_wasip1.go

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build wasip1
6+
7+
package net
8+
9+
import (
10+
"internal/poll"
11+
"runtime"
12+
"syscall"
13+
"time"
14+
)
15+
16+
const (
17+
readSyscallName = "fd_read"
18+
writeSyscallName = "fd_write"
19+
)
20+
21+
// Network file descriptor.
22+
type netFD struct {
23+
pfd poll.FD
24+
25+
// immutable until Close
26+
family int
27+
sotype int
28+
isConnected bool // handshake completed or use of association with peer
29+
net string
30+
laddr Addr
31+
raddr Addr
32+
33+
// The only networking available in WASI preview 1 is the ability to
34+
// sock_accept on an pre-opened socket, and then fd_read, fd_write,
35+
// fd_close, and sock_shutdown on the resulting connection. We
36+
// intercept applicable netFD calls on this instance, and then pass
37+
// the remainder of the netFD calls to fakeNetFD.
38+
*fakeNetFD
39+
}
40+
41+
func newFD(sysfd int) (*netFD, error) {
42+
return newPollFD(poll.FD{
43+
Sysfd: sysfd,
44+
IsStream: true,
45+
ZeroReadIsEOF: true,
46+
})
47+
}
48+
49+
func newPollFD(pfd poll.FD) (*netFD, error) {
50+
ret := &netFD{
51+
pfd: pfd,
52+
net: "tcp",
53+
laddr: unknownAddr{},
54+
raddr: unknownAddr{},
55+
}
56+
return ret, nil
57+
}
58+
59+
func (fd *netFD) init() error {
60+
return fd.pfd.Init(fd.net, true)
61+
}
62+
63+
func (fd *netFD) name() string {
64+
return "unknown"
65+
}
66+
67+
func (fd *netFD) accept() (netfd *netFD, err error) {
68+
if fd.fakeNetFD != nil {
69+
return fd.fakeNetFD.accept()
70+
}
71+
d, _, errcall, err := fd.pfd.Accept()
72+
if err != nil {
73+
if errcall != "" {
74+
err = wrapSyscallError(errcall, err)
75+
}
76+
return nil, err
77+
}
78+
if netfd, err = newFD(d); err != nil {
79+
poll.CloseFunc(d)
80+
return nil, err
81+
}
82+
if err = netfd.init(); err != nil {
83+
netfd.Close()
84+
return nil, err
85+
}
86+
return netfd, nil
87+
}
88+
89+
func (fd *netFD) setAddr(laddr, raddr Addr) {
90+
fd.laddr = laddr
91+
fd.raddr = raddr
92+
runtime.SetFinalizer(fd, (*netFD).Close)
93+
}
94+
95+
func (fd *netFD) Close() error {
96+
if fd.fakeNetFD != nil {
97+
return fd.fakeNetFD.Close()
98+
}
99+
runtime.SetFinalizer(fd, nil)
100+
return fd.pfd.Close()
101+
}
102+
103+
func (fd *netFD) shutdown(how int) error {
104+
if fd.fakeNetFD != nil {
105+
return nil
106+
}
107+
err := fd.pfd.Shutdown(how)
108+
runtime.KeepAlive(fd)
109+
return wrapSyscallError("shutdown", err)
110+
}
111+
112+
func (fd *netFD) closeRead() error {
113+
if fd.fakeNetFD != nil {
114+
return fd.fakeNetFD.closeRead()
115+
}
116+
return fd.shutdown(syscall.SHUT_RD)
117+
}
118+
119+
func (fd *netFD) closeWrite() error {
120+
if fd.fakeNetFD != nil {
121+
return fd.fakeNetFD.closeWrite()
122+
}
123+
return fd.shutdown(syscall.SHUT_WR)
124+
}
125+
126+
func (fd *netFD) Read(p []byte) (n int, err error) {
127+
if fd.fakeNetFD != nil {
128+
return fd.fakeNetFD.Read(p)
129+
}
130+
n, err = fd.pfd.Read(p)
131+
runtime.KeepAlive(fd)
132+
return n, wrapSyscallError(readSyscallName, err)
133+
}
134+
135+
func (fd *netFD) Write(p []byte) (nn int, err error) {
136+
if fd.fakeNetFD != nil {
137+
return fd.fakeNetFD.Write(p)
138+
}
139+
nn, err = fd.pfd.Write(p)
140+
runtime.KeepAlive(fd)
141+
return nn, wrapSyscallError(writeSyscallName, err)
142+
}
143+
144+
func (fd *netFD) SetDeadline(t time.Time) error {
145+
if fd.fakeNetFD != nil {
146+
return fd.fakeNetFD.SetDeadline(t)
147+
}
148+
return fd.pfd.SetDeadline(t)
149+
}
150+
151+
func (fd *netFD) SetReadDeadline(t time.Time) error {
152+
if fd.fakeNetFD != nil {
153+
return fd.fakeNetFD.SetReadDeadline(t)
154+
}
155+
return fd.pfd.SetReadDeadline(t)
156+
}
157+
158+
func (fd *netFD) SetWriteDeadline(t time.Time) error {
159+
if fd.fakeNetFD != nil {
160+
return fd.fakeNetFD.SetWriteDeadline(t)
161+
}
162+
return fd.pfd.SetWriteDeadline(t)
163+
}
164+
165+
type unknownAddr struct{}
166+
167+
func (unknownAddr) Network() string { return "unknown" }
168+
func (unknownAddr) String() string { return "unknown" }

Diff for: ‎src/net/file_stub.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
//go:build (js && wasm) || wasip1
5+
//go:build js && wasm
66

77
package net
88

Diff for: ‎src/net/file_wasip1.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build wasip1
6+
7+
package net
8+
9+
import (
10+
"os"
11+
"syscall"
12+
_ "unsafe" // for go:linkname
13+
)
14+
15+
func fileListener(f *os.File) (Listener, error) {
16+
fd, err := newFileFD(f)
17+
if err != nil {
18+
return nil, err
19+
}
20+
return &TCPListener{fd: fd}, nil
21+
}
22+
23+
func fileConn(f *os.File) (Conn, error) {
24+
fd, err := newFileFD(f)
25+
if err != nil {
26+
return nil, err
27+
}
28+
return &TCPConn{conn{fd: fd}}, nil
29+
}
30+
31+
func filePacketConn(f *os.File) (PacketConn, error) { return nil, syscall.ENOPROTOOPT }
32+
33+
func newFileFD(f *os.File) (fd *netFD, err error) {
34+
pfd := f.PollFD().Copy()
35+
defer func() {
36+
if err != nil {
37+
pfd.Close()
38+
}
39+
}()
40+
filetype, err := fd_fdstat_get_type(pfd.Sysfd)
41+
if err != nil {
42+
return nil, err
43+
}
44+
if filetype != syscall.FILETYPE_SOCKET_STREAM {
45+
return nil, syscall.ENOTSOCK
46+
}
47+
fd, err = newPollFD(pfd)
48+
if err != nil {
49+
return nil, err
50+
}
51+
if err := fd.init(); err != nil {
52+
return nil, err
53+
}
54+
return fd, nil
55+
}
56+
57+
// This helper is implemented in the syscall package. It means we don't have
58+
// to redefine the fd_fdstat_get host import or the fdstat struct it
59+
// populates.
60+
//
61+
//go:linkname fd_fdstat_get_type syscall.fd_fdstat_get_type
62+
func fd_fdstat_get_type(fd int) (uint8, error)

Diff for: ‎src/net/net_fake.go

+61-55
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ package net
1010

1111
import (
1212
"context"
13-
"internal/poll"
1413
"io"
1514
"os"
1615
"sync"
@@ -33,61 +32,64 @@ func nextPort() int {
3332
return portCounter
3433
}
3534

36-
// Network file descriptor.
37-
type netFD struct {
35+
type fakeNetFD struct {
36+
listener bool
37+
laddr Addr
3838
r *bufferedPipe
3939
w *bufferedPipe
4040
incoming chan *netFD
4141

4242
closedMu sync.Mutex
4343
closed bool
44-
45-
// immutable until Close
46-
listener bool
47-
family int
48-
sotype int
49-
net string
50-
laddr Addr
51-
raddr Addr
52-
53-
// unused
54-
pfd poll.FD
55-
isConnected bool // handshake completed or use of association with peer
5644
}
5745

5846
// socket returns a network file descriptor that is ready for
5947
// asynchronous I/O using the network poller.
6048
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) {
6149
fd := &netFD{family: family, sotype: sotype, net: net}
50+
if laddr != nil && raddr == nil {
51+
return fakelistener(fd, laddr)
52+
}
53+
fd2 := &netFD{family: family, sotype: sotype, net: net}
54+
return fakeconn(fd, fd2, raddr)
55+
}
6256

63-
if laddr != nil && raddr == nil { // listener
64-
l := laddr.(*TCPAddr)
65-
fd.laddr = &TCPAddr{
66-
IP: l.IP,
67-
Port: nextPort(),
68-
Zone: l.Zone,
69-
}
70-
fd.listener = true
71-
fd.incoming = make(chan *netFD, 1024)
72-
listenersMu.Lock()
73-
listeners[fd.laddr.(*TCPAddr).String()] = fd
74-
listenersMu.Unlock()
75-
return fd, nil
57+
func fakelistener(fd *netFD, laddr sockaddr) (*netFD, error) {
58+
l := laddr.(*TCPAddr)
59+
fd.laddr = &TCPAddr{
60+
IP: l.IP,
61+
Port: nextPort(),
62+
Zone: l.Zone,
63+
}
64+
fd.fakeNetFD = &fakeNetFD{
65+
listener: true,
66+
laddr: fd.laddr,
67+
incoming: make(chan *netFD, 1024),
7668
}
69+
listenersMu.Lock()
70+
listeners[fd.laddr.(*TCPAddr).String()] = fd
71+
listenersMu.Unlock()
72+
return fd, nil
73+
}
7774

75+
func fakeconn(fd *netFD, fd2 *netFD, raddr sockaddr) (*netFD, error) {
7876
fd.laddr = &TCPAddr{
7977
IP: IPv4(127, 0, 0, 1),
8078
Port: nextPort(),
8179
}
8280
fd.raddr = raddr
83-
fd.r = newBufferedPipe(65536)
84-
fd.w = newBufferedPipe(65536)
8581

86-
fd2 := &netFD{family: fd.family, sotype: sotype, net: net}
82+
fd.fakeNetFD = &fakeNetFD{
83+
r: newBufferedPipe(65536),
84+
w: newBufferedPipe(65536),
85+
}
86+
fd2.fakeNetFD = &fakeNetFD{
87+
r: fd.fakeNetFD.w,
88+
w: fd.fakeNetFD.r,
89+
}
90+
8791
fd2.laddr = fd.raddr
8892
fd2.raddr = fd.laddr
89-
fd2.r = fd.w
90-
fd2.w = fd.r
9193
listenersMu.Lock()
9294
l, ok := listeners[fd.raddr.(*TCPAddr).String()]
9395
if !ok {
@@ -100,15 +102,15 @@ func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only
100102
return fd, nil
101103
}
102104

103-
func (fd *netFD) Read(p []byte) (n int, err error) {
105+
func (fd *fakeNetFD) Read(p []byte) (n int, err error) {
104106
return fd.r.Read(p)
105107
}
106108

107-
func (fd *netFD) Write(p []byte) (nn int, err error) {
109+
func (fd *fakeNetFD) Write(p []byte) (nn int, err error) {
108110
return fd.w.Write(p)
109111
}
110112

111-
func (fd *netFD) Close() error {
113+
func (fd *fakeNetFD) Close() error {
112114
fd.closedMu.Lock()
113115
if fd.closed {
114116
fd.closedMu.Unlock()
@@ -131,36 +133,36 @@ func (fd *netFD) Close() error {
131133
return nil
132134
}
133135

134-
func (fd *netFD) closeRead() error {
136+
func (fd *fakeNetFD) closeRead() error {
135137
fd.r.Close()
136138
return nil
137139
}
138140

139-
func (fd *netFD) closeWrite() error {
141+
func (fd *fakeNetFD) closeWrite() error {
140142
fd.w.Close()
141143
return nil
142144
}
143145

144-
func (fd *netFD) accept() (*netFD, error) {
146+
func (fd *fakeNetFD) accept() (*netFD, error) {
145147
c, ok := <-fd.incoming
146148
if !ok {
147149
return nil, syscall.EINVAL
148150
}
149151
return c, nil
150152
}
151153

152-
func (fd *netFD) SetDeadline(t time.Time) error {
154+
func (fd *fakeNetFD) SetDeadline(t time.Time) error {
153155
fd.r.SetReadDeadline(t)
154156
fd.w.SetWriteDeadline(t)
155157
return nil
156158
}
157159

158-
func (fd *netFD) SetReadDeadline(t time.Time) error {
160+
func (fd *fakeNetFD) SetReadDeadline(t time.Time) error {
159161
fd.r.SetReadDeadline(t)
160162
return nil
161163
}
162164

163-
func (fd *netFD) SetWriteDeadline(t time.Time) error {
165+
func (fd *fakeNetFD) SetWriteDeadline(t time.Time) error {
164166
fd.w.SetWriteDeadline(t)
165167
return nil
166168
}
@@ -265,55 +267,59 @@ func sysSocket(family, sotype, proto int) (int, error) {
265267
return 0, syscall.ENOSYS
266268
}
267269

268-
func (fd *netFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) {
270+
func (fd *fakeNetFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (syscall.Sockaddr, error) {
271+
return nil, syscall.ENOSYS
272+
}
273+
274+
func (fd *fakeNetFD) readFrom(p []byte) (n int, sa syscall.Sockaddr, err error) {
269275
return 0, nil, syscall.ENOSYS
270276

271277
}
272-
func (fd *netFD) readFromInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) {
278+
func (fd *fakeNetFD) readFromInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) {
273279
return 0, syscall.ENOSYS
274280
}
275281

276-
func (fd *netFD) readFromInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) {
282+
func (fd *fakeNetFD) readFromInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) {
277283
return 0, syscall.ENOSYS
278284
}
279285

280-
func (fd *netFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int, sa syscall.Sockaddr, err error) {
286+
func (fd *fakeNetFD) readMsg(p []byte, oob []byte, flags int) (n, oobn, retflags int, sa syscall.Sockaddr, err error) {
281287
return 0, 0, 0, nil, syscall.ENOSYS
282288
}
283289

284-
func (fd *netFD) readMsgInet4(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet4) (n, oobn, retflags int, err error) {
290+
func (fd *fakeNetFD) readMsgInet4(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet4) (n, oobn, retflags int, err error) {
285291
return 0, 0, 0, syscall.ENOSYS
286292
}
287293

288-
func (fd *netFD) readMsgInet6(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet6) (n, oobn, retflags int, err error) {
294+
func (fd *fakeNetFD) readMsgInet6(p []byte, oob []byte, flags int, sa *syscall.SockaddrInet6) (n, oobn, retflags int, err error) {
289295
return 0, 0, 0, syscall.ENOSYS
290296
}
291297

292-
func (fd *netFD) writeMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (n int, oobn int, err error) {
298+
func (fd *fakeNetFD) writeMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (n int, oobn int, err error) {
293299
return 0, 0, syscall.ENOSYS
294300
}
295301

296-
func (fd *netFD) writeMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (n int, oobn int, err error) {
302+
func (fd *fakeNetFD) writeMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (n int, oobn int, err error) {
297303
return 0, 0, syscall.ENOSYS
298304
}
299305

300-
func (fd *netFD) writeTo(p []byte, sa syscall.Sockaddr) (n int, err error) {
306+
func (fd *fakeNetFD) writeTo(p []byte, sa syscall.Sockaddr) (n int, err error) {
301307
return 0, syscall.ENOSYS
302308
}
303309

304-
func (fd *netFD) writeToInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) {
310+
func (fd *fakeNetFD) writeToInet4(p []byte, sa *syscall.SockaddrInet4) (n int, err error) {
305311
return 0, syscall.ENOSYS
306312
}
307313

308-
func (fd *netFD) writeToInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) {
314+
func (fd *fakeNetFD) writeToInet6(p []byte, sa *syscall.SockaddrInet6) (n int, err error) {
309315
return 0, syscall.ENOSYS
310316
}
311317

312-
func (fd *netFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) {
318+
func (fd *fakeNetFD) writeMsg(p []byte, oob []byte, sa syscall.Sockaddr) (n int, oobn int, err error) {
313319
return 0, 0, syscall.ENOSYS
314320
}
315321

316-
func (fd *netFD) dup() (f *os.File, err error) {
322+
func (fd *fakeNetFD) dup() (f *os.File, err error) {
317323
return nil, syscall.ENOSYS
318324
}
319325

Diff for: ‎src/net/net_fake_js.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Fake networking for js/wasm. It is intended to allow tests of other package to pass.
6+
7+
//go:build js && wasm
8+
9+
package net
10+
11+
import "internal/poll"
12+
13+
// Network file descriptor.
14+
type netFD struct {
15+
*fakeNetFD
16+
17+
// immutable until Close
18+
family int
19+
sotype int
20+
net string
21+
laddr Addr
22+
raddr Addr
23+
24+
// unused
25+
pfd poll.FD
26+
isConnected bool // handshake completed or use of association with peer
27+
}

Diff for: ‎src/os/file_wasip1.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build wasip1
6+
7+
package os
8+
9+
import "internal/poll"
10+
11+
// PollFD returns the poll.FD of the file.
12+
//
13+
// Other packages in std that also import internal/poll (such as net)
14+
// can use a type assertion to access this extension method so that
15+
// they can pass the *poll.FD to functions like poll.Splice.
16+
//
17+
// There is an equivalent function in net.rawConn.
18+
//
19+
// PollFD is not intended for use outside the standard library.
20+
func (f *file) PollFD() *poll.FD {
21+
return &f.pfd
22+
}

Diff for: ‎src/runtime/internal/wasitest/tcpecho_test.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package wasi_test
6+
7+
import (
8+
"bytes"
9+
"fmt"
10+
"math/rand"
11+
"net"
12+
"os"
13+
"os/exec"
14+
"testing"
15+
"time"
16+
)
17+
18+
func TestTCPEcho(t *testing.T) {
19+
if target != "wasip1/wasm" {
20+
t.Skip()
21+
}
22+
23+
// We're unable to pass port 0 here (let the OS choose a spare port).
24+
// Although wasmtime accepts port 0, and testdata/main.go successfully
25+
// listens, there's no way for this test case to query the chosen port
26+
// so that it can connect to the WASM module. The WASM module itself
27+
// cannot access any information about the socket due to limitations
28+
// with WASI preview 1 networking, and wasmtime does not log the address
29+
// when you preopen a socket. Instead, we probe for a free port here.
30+
var host string
31+
port := rand.Intn(10000) + 40000
32+
for attempts := 0; attempts < 10; attempts++ {
33+
host = fmt.Sprintf("127.0.0.1:%d", port)
34+
l, err := net.Listen("tcp", host)
35+
if err == nil {
36+
l.Close()
37+
break
38+
}
39+
port++
40+
}
41+
42+
subProcess := exec.Command("go", "run", "./testdata/tcpecho.go")
43+
44+
subProcess.Env = append(os.Environ(), "GOOS=wasip1", "GOARCH=wasm")
45+
46+
switch os.Getenv("GOWASIRUNTIME") {
47+
case "wasmtime":
48+
subProcess.Env = append(subProcess.Env, "GOWASIRUNTIMEARGS=--tcplisten="+host)
49+
default:
50+
t.Skip("WASI runtime does not support sockets")
51+
}
52+
53+
var b bytes.Buffer
54+
subProcess.Stdout = &b
55+
subProcess.Stderr = &b
56+
57+
if err := subProcess.Start(); err != nil {
58+
t.Log(b.String())
59+
t.Fatal(err)
60+
}
61+
defer subProcess.Process.Kill()
62+
63+
var conn net.Conn
64+
var err error
65+
for attempts := 0; attempts < 5; attempts++ {
66+
conn, err = net.Dial("tcp", host)
67+
if err == nil {
68+
break
69+
}
70+
time.Sleep(500 * time.Millisecond)
71+
}
72+
if err != nil {
73+
t.Log(b.String())
74+
t.Fatal(err)
75+
}
76+
defer conn.Close()
77+
78+
payload := []byte("foobar")
79+
if _, err := conn.Write(payload); err != nil {
80+
t.Fatal(err)
81+
}
82+
var buf [256]byte
83+
n, err := conn.Read(buf[:])
84+
if err != nil {
85+
t.Fatal(err)
86+
}
87+
if string(buf[:n]) != string(payload) {
88+
t.Error("unexpected payload")
89+
t.Logf("expect: %d bytes (%v)", len(payload), payload)
90+
t.Logf("actual: %d bytes (%v)", n, buf[:n])
91+
}
92+
}

Diff for: ‎src/runtime/internal/wasitest/testdata/tcpecho.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"errors"
9+
"net"
10+
"os"
11+
"syscall"
12+
)
13+
14+
func main() {
15+
if err := run(); err != nil {
16+
println(err)
17+
os.Exit(1)
18+
}
19+
}
20+
21+
func run() error {
22+
l, err := findListener()
23+
if err != nil {
24+
return err
25+
}
26+
if l == nil {
27+
return errors.New("no pre-opened sockets available")
28+
}
29+
defer l.Close()
30+
31+
c, err := l.Accept()
32+
if err != nil {
33+
return err
34+
}
35+
return handleConn(c)
36+
}
37+
38+
func handleConn(c net.Conn) error {
39+
defer c.Close()
40+
41+
var buf [128]byte
42+
n, err := c.Read(buf[:])
43+
if err != nil {
44+
return err
45+
}
46+
if _, err := c.Write(buf[:n]); err != nil {
47+
return err
48+
}
49+
if err := c.(*net.TCPConn).CloseWrite(); err != nil {
50+
return err
51+
}
52+
return c.Close()
53+
}
54+
55+
func findListener() (net.Listener, error) {
56+
// We start looking for pre-opened sockets at fd=3 because 0, 1, and 2
57+
// are reserved for stdio. Pre-opened directors also start at fd=3, so
58+
// we skip fds that aren't sockets. Once we reach EBADF we know there
59+
// are no more pre-opens.
60+
for preopenFd := uintptr(3); ; preopenFd++ {
61+
f := os.NewFile(preopenFd, "")
62+
l, err := net.FileListener(f)
63+
f.Close()
64+
65+
var se syscall.Errno
66+
switch errors.As(err, &se); se {
67+
case syscall.ENOTSOCK:
68+
continue
69+
case syscall.EBADF:
70+
err = nil
71+
}
72+
return l, err
73+
}
74+
}

Diff for: ‎src/syscall/fs_wasip1.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,12 @@ func fd_fdstat_get_flags(fd int) (uint32, error) {
279279
return uint32(stat.fdflags), errnoErr(errno)
280280
}
281281

282+
func fd_fdstat_get_type(fd int) (uint8, error) {
283+
var stat fdstat
284+
errno := fd_fdstat_get(int32(fd), unsafe.Pointer(&stat))
285+
return stat.filetype, errnoErr(errno)
286+
}
287+
282288
type preopentype = uint8
283289

284290
const (
@@ -331,12 +337,12 @@ func init() {
331337
if errno == EBADF {
332338
break
333339
}
340+
if errno == ENOTDIR || prestat.typ != preopentypeDir {
341+
continue
342+
}
334343
if errno != 0 {
335344
panic("fd_prestat: " + errno.Error())
336345
}
337-
if prestat.typ != preopentypeDir {
338-
continue
339-
}
340346
if int(prestat.dir.prNameLen) > len(dirNameBuf) {
341347
dirNameBuf = make([]byte, prestat.dir.prNameLen)
342348
}

Diff for: ‎src/syscall/net_fake.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Fake networking for js/wasm and wasip1/wasm.
6+
// This file only exists to make the compiler happy.
7+
8+
//go:build (js && wasm) || wasip1
9+
10+
package syscall
11+
12+
const (
13+
AF_UNSPEC = iota
14+
AF_UNIX
15+
AF_INET
16+
AF_INET6
17+
)
18+
19+
const (
20+
SOCK_STREAM = 1 + iota
21+
SOCK_DGRAM
22+
SOCK_RAW
23+
SOCK_SEQPACKET
24+
)
25+
26+
const (
27+
IPPROTO_IP = 0
28+
IPPROTO_IPV4 = 4
29+
IPPROTO_IPV6 = 0x29
30+
IPPROTO_TCP = 6
31+
IPPROTO_UDP = 0x11
32+
)
33+
34+
const (
35+
_ = iota
36+
IPV6_V6ONLY
37+
SOMAXCONN
38+
SO_ERROR
39+
)
40+
41+
// Misc constants expected by package net but not supported.
42+
const (
43+
_ = iota
44+
F_DUPFD_CLOEXEC
45+
SYS_FCNTL = 500 // unsupported
46+
)
47+
48+
type Sockaddr any
49+
50+
type SockaddrInet4 struct {
51+
Port int
52+
Addr [4]byte
53+
}
54+
55+
type SockaddrInet6 struct {
56+
Port int
57+
ZoneId uint32
58+
Addr [16]byte
59+
}
60+
61+
type SockaddrUnix struct {
62+
Name string
63+
}

Diff for: ‎src/syscall/net_js.go

-56
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,10 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// js/wasm uses fake networking directly implemented in the net package.
6-
// This file only exists to make the compiler happy.
7-
85
//go:build js && wasm
96

107
package syscall
118

12-
const (
13-
AF_UNSPEC = iota
14-
AF_UNIX
15-
AF_INET
16-
AF_INET6
17-
)
18-
19-
const (
20-
SOCK_STREAM = 1 + iota
21-
SOCK_DGRAM
22-
SOCK_RAW
23-
SOCK_SEQPACKET
24-
)
25-
26-
const (
27-
IPPROTO_IP = 0
28-
IPPROTO_IPV4 = 4
29-
IPPROTO_IPV6 = 0x29
30-
IPPROTO_TCP = 6
31-
IPPROTO_UDP = 0x11
32-
)
33-
34-
const (
35-
_ = iota
36-
IPV6_V6ONLY
37-
SOMAXCONN
38-
SO_ERROR
39-
)
40-
41-
// Misc constants expected by package net but not supported.
42-
const (
43-
_ = iota
44-
F_DUPFD_CLOEXEC
45-
SYS_FCNTL = 500 // unsupported
46-
)
47-
48-
type Sockaddr any
49-
50-
type SockaddrInet4 struct {
51-
Port int
52-
Addr [4]byte
53-
}
54-
55-
type SockaddrInet6 struct {
56-
Port int
57-
ZoneId uint32
58-
Addr [16]byte
59-
}
60-
61-
type SockaddrUnix struct {
62-
Name string
63-
}
64-
659
func Socket(proto, sotype, unused int) (fd int, err error) {
6610
return 0, ENOSYS
6711
}

Diff for: ‎src/syscall/net_wasip1.go

+17-53
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,27 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// wasip1/wasm uses fake networking directly implemented in the net package.
6-
// This file only exists to make the compiler happy.
7-
85
//go:build wasip1
96

107
package syscall
118

12-
const (
13-
AF_UNSPEC = iota
14-
AF_UNIX
15-
AF_INET
16-
AF_INET6
17-
)
9+
import "unsafe"
1810

1911
const (
20-
SOCK_STREAM = 1 + iota
21-
SOCK_DGRAM
22-
SOCK_RAW
23-
SOCK_SEQPACKET
12+
SHUT_RD = 0x1
13+
SHUT_WR = 0x2
14+
SHUT_RDWR = SHUT_RD | SHUT_WR
2415
)
2516

26-
const (
27-
IPPROTO_IP = 0
28-
IPPROTO_IPV4 = 4
29-
IPPROTO_IPV6 = 0x29
30-
IPPROTO_TCP = 6
31-
IPPROTO_UDP = 0x11
32-
)
17+
type sdflags = uint32
3318

34-
const (
35-
_ = iota
36-
IPV6_V6ONLY
37-
SOMAXCONN
38-
SO_ERROR
39-
)
19+
//go:wasmimport wasi_snapshot_preview1 sock_accept
20+
//go:noescape
21+
func sock_accept(fd int32, flags fdflags, newfd unsafe.Pointer) Errno
4022

41-
// Misc constants expected by package net but not supported.
42-
const (
43-
_ = iota
44-
F_DUPFD_CLOEXEC
45-
SYS_FCNTL = 500 // unsupported; same value as net_nacl.go
46-
)
47-
48-
type Sockaddr interface {
49-
}
50-
51-
type SockaddrInet4 struct {
52-
Port int
53-
Addr [4]byte
54-
}
55-
56-
type SockaddrInet6 struct {
57-
Port int
58-
ZoneId uint32
59-
Addr [16]byte
60-
}
61-
62-
type SockaddrUnix struct {
63-
Name string
64-
}
23+
//go:wasmimport wasi_snapshot_preview1 sock_shutdown
24+
//go:noescape
25+
func sock_shutdown(fd int32, flags sdflags) Errno
6526

6627
func Socket(proto, sotype, unused int) (fd int, err error) {
6728
return 0, ENOSYS
@@ -79,8 +40,10 @@ func Listen(fd int, backlog int) error {
7940
return ENOSYS
8041
}
8142

82-
func Accept(fd int) (newfd int, sa Sockaddr, err error) {
83-
return 0, nil, ENOSYS
43+
func Accept(fd int) (int, Sockaddr, error) {
44+
var newfd int32
45+
errno := sock_accept(int32(fd), 0, unsafe.Pointer(&newfd))
46+
return int(newfd), nil, errnoErr(errno)
8447
}
8548

8649
func Connect(fd int, sa Sockaddr) error {
@@ -120,5 +83,6 @@ func SetWriteDeadline(fd int, t int64) error {
12083
}
12184

12285
func Shutdown(fd int, how int) error {
123-
return ENOSYS
86+
errno := sock_shutdown(int32(fd), sdflags(how))
87+
return errnoErr(errno)
12488
}

0 commit comments

Comments
 (0)
Please sign in to comment.