Skip to content

Commit a11f0ba

Browse files
committed
Initial WASIp2 network driver
1 parent 089d295 commit a11f0ba

File tree

2 files changed

+380
-0
lines changed

2 files changed

+380
-0
lines changed

netdev_wasip2.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//go:build wasip2
2+
3+
// L3/L4 network/transport layer implementation using WASI preview 2 sockets
4+
5+
package net
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"net/netip"
11+
"time"
12+
13+
instancenetwork "internal/wasi/sockets/v0.2.0/instance-network"
14+
"internal/wasi/sockets/v0.2.0/network"
15+
)
16+
17+
func TinygoToWasiAddr(ip netip.AddrPort) network.IPSocketAddress {
18+
if ip.Addr().Is4() {
19+
return network.IPSocketAddressIPv4(network.IPv4SocketAddress{
20+
Port: ip.Port(),
21+
Address: ip.Addr().As4(),
22+
})
23+
}
24+
25+
if ip.Addr().Is6() {
26+
as16 := ip.Addr().As16()
27+
var as8uint16 [8]uint16
28+
for i := 0; i < 8; i++ {
29+
as8uint16[i] = uint16(as16[i*2])<<8 | uint16(as16[i*2+1])
30+
}
31+
return network.IPSocketAddressIPv6(network.IPv6SocketAddress{
32+
Port: ip.Port(),
33+
Address: as8uint16,
34+
})
35+
}
36+
37+
return network.IPSocketAddress{}
38+
}
39+
40+
func WasiAddrToTinygo(addr network.IPSocketAddress) netip.AddrPort {
41+
if addr4 := addr.IPv4(); addr4 != nil {
42+
return netip.AddrPortFrom(netip.AddrFrom4(addr4.Address), addr4.Port)
43+
}
44+
45+
if addr6 := addr.IPv6(); addr6 != nil {
46+
var as16 [16]byte
47+
for i := 0; i < 8; i++ {
48+
as16[i*2] = byte(addr6.Address[i] >> 8)
49+
as16[i*2+1] = byte(addr6.Address[i] & 0xFF)
50+
}
51+
return netip.AddrPortFrom(netip.AddrFrom16(as16), addr6.Port)
52+
}
53+
54+
return netip.AddrPort{}
55+
}
56+
57+
type wasip2Socket interface {
58+
Recv(buf []byte, flags int, deadline time.Time) (int, error)
59+
Send(buf []byte, flags int, deadline time.Time) (int, error)
60+
Close() error
61+
Listen(backlog int) error
62+
Bind(globalNetwork instancenetwork.Network, ip network.IPSocketAddress) error
63+
Accept() (wasip2Socket, *network.IPSocketAddress, error)
64+
}
65+
66+
// wasip2Netdev is a netdev that uses WASI preview 2 sockets
67+
type wasip2Netdev struct {
68+
fds map[int]wasip2Socket
69+
nextFd int
70+
net instancenetwork.Network
71+
}
72+
73+
func init() {
74+
useNetdev(&wasip2Netdev{
75+
fds: make(map[int]wasip2Socket),
76+
nextFd: 0,
77+
net: instancenetwork.InstanceNetwork(),
78+
})
79+
}
80+
81+
// TODO: use ip-name-lookup
82+
func (n *wasip2Netdev) GetHostByName(name string) (netip.Addr, error) {
83+
fmt.Println("wasip2 TODO GetHostByName") ///
84+
return netip.Addr{}, errors.New("wasip2 TODO GetHostByName")
85+
}
86+
87+
func (n *wasip2Netdev) Addr() (netip.Addr, error) {
88+
fmt.Println("wasip2 TODO Addr") ///
89+
return netip.Addr{}, errors.New("wasip2 TODO Addr")
90+
}
91+
92+
func (n *wasip2Netdev) getNextFD() int {
93+
n.nextFd++
94+
return n.nextFd - 1
95+
}
96+
97+
func (n *wasip2Netdev) Socket(domain int, stype int, protocol int) (sockfd int, _ error) {
98+
af := network.IPAddressFamilyIPv4
99+
if domain == _AF_INET6 {
100+
af = network.IPAddressFamilyIPv6
101+
}
102+
103+
var sock wasip2Socket
104+
var err error
105+
106+
switch stype {
107+
case _SOCK_STREAM:
108+
sock, err = createTCPSocket(af)
109+
if err != nil {
110+
return -1, err
111+
}
112+
default:
113+
return -1, fmt.Errorf("wasip2: unsupported socket type %d", stype)
114+
}
115+
116+
fd := n.getNextFD()
117+
n.fds[fd] = sock
118+
119+
return fd, nil
120+
}
121+
122+
func (n *wasip2Netdev) Bind(sockfd int, ip netip.AddrPort) error {
123+
sock, ok := n.fds[sockfd]
124+
if !ok {
125+
fmt.Println("wasip2: invalid socket fd") ///
126+
return errors.New("wasip2: invalid socket fd")
127+
}
128+
129+
return sock.Bind(n.net, TinygoToWasiAddr(ip))
130+
}
131+
132+
func (n *wasip2Netdev) Connect(sockfd int, host string, ip netip.AddrPort) error {
133+
fmt.Println("wasip2 TODO Connect", sockfd, host, ip) ///
134+
return errors.New("wasip2 TODO Connect")
135+
}
136+
137+
func (n *wasip2Netdev) Listen(sockfd int, backlog int) error {
138+
sock, ok := n.fds[sockfd]
139+
if !ok {
140+
fmt.Println("wasip2: invalid socket fd") ///
141+
return errors.New("wasip2: invalid socket fd")
142+
}
143+
144+
return sock.Listen(backlog)
145+
}
146+
147+
func (n *wasip2Netdev) Accept(sockfd int) (int, netip.AddrPort, error) {
148+
sock, ok := n.fds[sockfd]
149+
if !ok {
150+
fmt.Println("wasip2: invalid socket fd") ///
151+
return -1, netip.AddrPort{}, errors.New("wasip2: invalid socket fd")
152+
}
153+
154+
newSock, raddr, err := sock.Accept()
155+
if err != nil {
156+
return -1, netip.AddrPort{}, fmt.Errorf("failed to accept connection: %s", err.Error())
157+
}
158+
159+
fd := n.getNextFD()
160+
n.fds[fd] = newSock
161+
162+
return fd, WasiAddrToTinygo(*raddr), nil
163+
}
164+
165+
func (n *wasip2Netdev) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
166+
sock, ok := n.fds[sockfd]
167+
if !ok {
168+
fmt.Println("wasip2: invalid socket fd") ///
169+
return -1, errors.New("wasip2: invalid socket fd")
170+
}
171+
172+
return sock.Send(buf, flags, deadline)
173+
}
174+
175+
func (n *wasip2Netdev) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
176+
sock, ok := n.fds[sockfd]
177+
if !ok {
178+
fmt.Println("wasip2: invalid socket fd") ///
179+
return -1, errors.New("wasip2: invalid socket fd")
180+
}
181+
182+
return sock.Recv(buf, flags, deadline)
183+
}
184+
185+
func (n *wasip2Netdev) Close(sockfd int) error {
186+
sock, ok := n.fds[sockfd]
187+
if !ok {
188+
fmt.Println("wasip2: invalid socket fd") ///
189+
return errors.New("wasip2: invalid socket fd")
190+
}
191+
192+
delete(n.fds, sockfd)
193+
194+
return sock.Close()
195+
}
196+
197+
func (n *wasip2Netdev) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
198+
fmt.Println("wasip2 setsockopt (TODO)", sockfd, level, opt, value) ///
199+
return errors.New("wasip2 TODO set socket option")
200+
}

tcp_wasip2.go

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
//go:build wasip2
2+
3+
// WASI preview 2 TCP
4+
5+
package net
6+
7+
import (
8+
"fmt"
9+
"time"
10+
11+
"internal/cm"
12+
"internal/wasi/io/v0.2.0/streams"
13+
instancenetwork "internal/wasi/sockets/v0.2.0/instance-network"
14+
"internal/wasi/sockets/v0.2.0/network"
15+
"internal/wasi/sockets/v0.2.0/tcp"
16+
tcpcreatesocket "internal/wasi/sockets/v0.2.0/tcp-create-socket"
17+
)
18+
19+
func createTCPSocket(af network.IPAddressFamily) (wasip2Socket, error) {
20+
res := tcpcreatesocket.CreateTCPSocket(af)
21+
if res.IsErr() {
22+
return nil, fmt.Errorf("failed to create TCP socket: %s", res.Err().String())
23+
}
24+
25+
sock := res.OK()
26+
return tcpServerSocket{
27+
TCPSocket: sock,
28+
Pollable: sock.Subscribe(),
29+
}, nil
30+
}
31+
32+
type tcpServerSocket struct {
33+
*tcpcreatesocket.TCPSocket
34+
tcp.Pollable
35+
}
36+
37+
func (s tcpServerSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) {
38+
fmt.Printf("TODO server")
39+
return 0, nil
40+
}
41+
42+
func (s tcpServerSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) {
43+
fmt.Printf("TODO server")
44+
return 0, nil
45+
}
46+
47+
func (s tcpServerSocket) Close() error {
48+
fmt.Printf("TODO server")
49+
return nil
50+
}
51+
52+
func (s tcpServerSocket) Bind(globalNetwork instancenetwork.Network, addr network.IPSocketAddress) error {
53+
res := s.StartBind(globalNetwork, addr)
54+
if res.IsErr() {
55+
return fmt.Errorf("failed to start binding socket: %s", res.Err().String())
56+
}
57+
58+
res = s.FinishBind()
59+
if res.IsErr() {
60+
return fmt.Errorf("failed to finish binding socket: %s", res.Err().String())
61+
}
62+
63+
return nil
64+
}
65+
66+
func (s tcpServerSocket) Listen(backlog int) error {
67+
res := s.StartListen()
68+
if res.IsErr() {
69+
return fmt.Errorf("failed to start listening on socket: %s", res.Err().String())
70+
}
71+
72+
res = s.FinishListen()
73+
if res.IsErr() {
74+
return fmt.Errorf("failed to finish listening on socket: %s", res.Err().String())
75+
}
76+
77+
return nil
78+
}
79+
80+
func (s tcpServerSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error) {
81+
var clientSocket *tcpcreatesocket.TCPSocket
82+
var inStream *streams.InputStream
83+
var outStream *streams.OutputStream
84+
85+
for {
86+
res := s.TCPSocket.Accept()
87+
if res.IsOK() {
88+
clientSocket, inStream, outStream = &res.OK().F0, &res.OK().F1, &res.OK().F2
89+
break
90+
}
91+
92+
if *res.Err() == network.ErrorCodeWouldBlock {
93+
// FIXME: a proper way is to use Pollable.Block()
94+
// But this seems to cause the single threaded runtime to block indefinitely
95+
for {
96+
if s.Pollable.Ready() {
97+
break
98+
}
99+
100+
// HACK: Make sure to yield the execution to other goroutines
101+
time.Sleep(100 * time.Millisecond)
102+
}
103+
continue
104+
}
105+
106+
return nil, nil, fmt.Errorf("failed to accept connection: %s", res.Err().String())
107+
108+
}
109+
110+
raddrRes := clientSocket.RemoteAddress()
111+
if raddrRes.IsErr() {
112+
return nil, nil, fmt.Errorf("failed to get remote address: %s", raddrRes.Err().String())
113+
}
114+
115+
sock := tcpSocket{
116+
TCPSocket: clientSocket,
117+
InputStream: inStream,
118+
OutputStream: outStream,
119+
}
120+
121+
return sock, raddrRes.OK(), nil
122+
}
123+
124+
type tcpSocket struct {
125+
*tcpcreatesocket.TCPSocket
126+
*streams.InputStream
127+
*streams.OutputStream
128+
}
129+
130+
func (c tcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) {
131+
if flags != 0 {
132+
fmt.Println("wasip2 TCP send does not support flags:", flags) ///
133+
}
134+
135+
res := c.BlockingWriteAndFlush(cm.ToList([]uint8(buf)))
136+
if res.IsErr() {
137+
return -1, fmt.Errorf("failed to write to output stream: %s", res.Err().String())
138+
}
139+
140+
return len(buf), nil
141+
}
142+
143+
func (c tcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) {
144+
if flags != 0 {
145+
fmt.Println("wasip2 TCP recv does not support flags:", flags) ///
146+
}
147+
148+
res := c.BlockingRead(uint64(len(buf)))
149+
if res.IsErr() {
150+
return -1, fmt.Errorf("failed to read from input stream: %s", res.Err().String())
151+
}
152+
153+
return copy(buf, res.OK().Slice()), nil
154+
}
155+
156+
func (c tcpSocket) Close() error {
157+
res := c.TCPSocket.Shutdown(tcp.ShutdownTypeBoth)
158+
if res.IsErr() {
159+
return fmt.Errorf("failed to shutdown client socket: %s", res.Err().String())
160+
}
161+
162+
c.InputStream.ResourceDrop()
163+
c.OutputStream.ResourceDrop()
164+
c.TCPSocket.ResourceDrop()
165+
166+
return nil
167+
}
168+
169+
func (s tcpSocket) Bind(globalNetwork instancenetwork.Network, addr network.IPSocketAddress) error {
170+
fmt.Printf("TODO client") ///
171+
return nil
172+
}
173+
func (s tcpSocket) Listen(backlog int) error {
174+
fmt.Printf("TODO client") ///
175+
return nil
176+
}
177+
func (s tcpSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error) {
178+
fmt.Printf("TODO client") ///
179+
return nil, nil, nil
180+
}

0 commit comments

Comments
 (0)