From 37358cf54c5298b724abc2203210f8333c1d23b8 Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 2 Aug 2025 14:04:06 +0200 Subject: [PATCH 1/9] Add _AF_INET6 for WASI sockets --- netdev.go | 1 + 1 file changed, 1 insertion(+) diff --git a/netdev.go b/netdev.go index c228f20..f824806 100644 --- a/netdev.go +++ b/netdev.go @@ -10,6 +10,7 @@ import ( const ( _AF_INET = 0x2 + _AF_INET6 = 0xA _SOCK_STREAM = 0x1 _SOCK_DGRAM = 0x2 _SOL_SOCKET = 0x1 From 5b385faccb45aaafb816d980c1522cbe0e852dbd Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 2 Aug 2025 14:05:14 +0200 Subject: [PATCH 2/9] Initial WASIp2 network driver --- netdev_wasip2.go | 200 +++++++++++++++++++++++++++++++++++++++++++++++ tcp_wasip2.go | 180 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 netdev_wasip2.go create mode 100644 tcp_wasip2.go diff --git a/netdev_wasip2.go b/netdev_wasip2.go new file mode 100644 index 0000000..5b1292e --- /dev/null +++ b/netdev_wasip2.go @@ -0,0 +1,200 @@ +//go:build wasip2 + +// L3/L4 network/transport layer implementation using WASI preview 2 sockets + +package net + +import ( + "errors" + "fmt" + "net/netip" + "time" + + instancenetwork "internal/wasi/sockets/v0.2.0/instance-network" + "internal/wasi/sockets/v0.2.0/network" +) + +func TinygoToWasiAddr(ip netip.AddrPort) network.IPSocketAddress { + if ip.Addr().Is4() { + return network.IPSocketAddressIPv4(network.IPv4SocketAddress{ + Port: ip.Port(), + Address: ip.Addr().As4(), + }) + } + + if ip.Addr().Is6() { + as16 := ip.Addr().As16() + var as8uint16 [8]uint16 + for i := 0; i < 8; i++ { + as8uint16[i] = uint16(as16[i*2])<<8 | uint16(as16[i*2+1]) + } + return network.IPSocketAddressIPv6(network.IPv6SocketAddress{ + Port: ip.Port(), + Address: as8uint16, + }) + } + + return network.IPSocketAddress{} +} + +func WasiAddrToTinygo(addr network.IPSocketAddress) netip.AddrPort { + if addr4 := addr.IPv4(); addr4 != nil { + return netip.AddrPortFrom(netip.AddrFrom4(addr4.Address), addr4.Port) + } + + if addr6 := addr.IPv6(); addr6 != nil { + var as16 [16]byte + for i := 0; i < 8; i++ { + as16[i*2] = byte(addr6.Address[i] >> 8) + as16[i*2+1] = byte(addr6.Address[i] & 0xFF) + } + return netip.AddrPortFrom(netip.AddrFrom16(as16), addr6.Port) + } + + return netip.AddrPort{} +} + +type wasip2Socket interface { + Recv(buf []byte, flags int, deadline time.Time) (int, error) + Send(buf []byte, flags int, deadline time.Time) (int, error) + Close() error + Listen(backlog int) error + Bind(globalNetwork instancenetwork.Network, ip network.IPSocketAddress) error + Accept() (wasip2Socket, *network.IPSocketAddress, error) +} + +// wasip2Netdev is a netdev that uses WASI preview 2 sockets +type wasip2Netdev struct { + fds map[int]wasip2Socket + nextFd int + net instancenetwork.Network +} + +func init() { + useNetdev(&wasip2Netdev{ + fds: make(map[int]wasip2Socket), + nextFd: 0, + net: instancenetwork.InstanceNetwork(), + }) +} + +// TODO: use ip-name-lookup +func (n *wasip2Netdev) GetHostByName(name string) (netip.Addr, error) { + fmt.Println("wasip2 TODO GetHostByName") /// + return netip.Addr{}, errors.New("wasip2 TODO GetHostByName") +} + +func (n *wasip2Netdev) Addr() (netip.Addr, error) { + fmt.Println("wasip2 TODO Addr") /// + return netip.Addr{}, errors.New("wasip2 TODO Addr") +} + +func (n *wasip2Netdev) getNextFD() int { + n.nextFd++ + return n.nextFd - 1 +} + +func (n *wasip2Netdev) Socket(domain int, stype int, protocol int) (sockfd int, _ error) { + af := network.IPAddressFamilyIPv4 + if domain == _AF_INET6 { + af = network.IPAddressFamilyIPv6 + } + + var sock wasip2Socket + var err error + + switch stype { + case _SOCK_STREAM: + sock, err = createTCPSocket(af) + if err != nil { + return -1, err + } + default: + return -1, fmt.Errorf("wasip2: unsupported socket type %d", stype) + } + + fd := n.getNextFD() + n.fds[fd] = sock + + return fd, nil +} + +func (n *wasip2Netdev) Bind(sockfd int, ip netip.AddrPort) error { + sock, ok := n.fds[sockfd] + if !ok { + fmt.Println("wasip2: invalid socket fd") /// + return errors.New("wasip2: invalid socket fd") + } + + return sock.Bind(n.net, TinygoToWasiAddr(ip)) +} + +func (n *wasip2Netdev) Connect(sockfd int, host string, ip netip.AddrPort) error { + fmt.Println("wasip2 TODO Connect", sockfd, host, ip) /// + return errors.New("wasip2 TODO Connect") +} + +func (n *wasip2Netdev) Listen(sockfd int, backlog int) error { + sock, ok := n.fds[sockfd] + if !ok { + fmt.Println("wasip2: invalid socket fd") /// + return errors.New("wasip2: invalid socket fd") + } + + return sock.Listen(backlog) +} + +func (n *wasip2Netdev) Accept(sockfd int) (int, netip.AddrPort, error) { + sock, ok := n.fds[sockfd] + if !ok { + fmt.Println("wasip2: invalid socket fd") /// + return -1, netip.AddrPort{}, errors.New("wasip2: invalid socket fd") + } + + newSock, raddr, err := sock.Accept() + if err != nil { + return -1, netip.AddrPort{}, fmt.Errorf("failed to accept connection: %s", err.Error()) + } + + fd := n.getNextFD() + n.fds[fd] = newSock + + return fd, WasiAddrToTinygo(*raddr), nil +} + +func (n *wasip2Netdev) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { + sock, ok := n.fds[sockfd] + if !ok { + fmt.Println("wasip2: invalid socket fd") /// + return -1, errors.New("wasip2: invalid socket fd") + } + + return sock.Send(buf, flags, deadline) +} + +func (n *wasip2Netdev) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { + sock, ok := n.fds[sockfd] + if !ok { + fmt.Println("wasip2: invalid socket fd") /// + return -1, errors.New("wasip2: invalid socket fd") + } + + return sock.Recv(buf, flags, deadline) +} + +func (n *wasip2Netdev) Close(sockfd int) error { + sock, ok := n.fds[sockfd] + if !ok { + fmt.Println("wasip2: invalid socket fd") /// + return errors.New("wasip2: invalid socket fd") + } + + delete(n.fds, sockfd) + + return sock.Close() +} + +func (n *wasip2Netdev) SetSockOpt(sockfd int, level int, opt int, value interface{}) error { + fmt.Println("wasip2 setsockopt (TODO)", sockfd, level, opt, value) /// + return errors.New("wasip2 TODO set socket option") +} diff --git a/tcp_wasip2.go b/tcp_wasip2.go new file mode 100644 index 0000000..59aa490 --- /dev/null +++ b/tcp_wasip2.go @@ -0,0 +1,180 @@ +//go:build wasip2 + +// WASI preview 2 TCP + +package net + +import ( + "fmt" + "time" + + "internal/cm" + "internal/wasi/io/v0.2.0/streams" + instancenetwork "internal/wasi/sockets/v0.2.0/instance-network" + "internal/wasi/sockets/v0.2.0/network" + "internal/wasi/sockets/v0.2.0/tcp" + tcpcreatesocket "internal/wasi/sockets/v0.2.0/tcp-create-socket" +) + +func createTCPSocket(af network.IPAddressFamily) (wasip2Socket, error) { + res := tcpcreatesocket.CreateTCPSocket(af) + if res.IsErr() { + return nil, fmt.Errorf("failed to create TCP socket: %s", res.Err().String()) + } + + sock := res.OK() + return tcpServerSocket{ + TCPSocket: sock, + Pollable: sock.Subscribe(), + }, nil +} + +type tcpServerSocket struct { + *tcpcreatesocket.TCPSocket + tcp.Pollable +} + +func (s tcpServerSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { + fmt.Println("TODO server Recv") /// + return 0, nil +} + +func (s tcpServerSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { + fmt.Println("TODO server Send") /// + return 0, nil +} + +func (s tcpServerSocket) Close() error { + fmt.Println("TODO server Close") /// + return nil +} + +func (s tcpServerSocket) Bind(globalNetwork instancenetwork.Network, addr network.IPSocketAddress) error { + res := s.StartBind(globalNetwork, addr) + if res.IsErr() { + return fmt.Errorf("failed to start binding socket: %s", res.Err().String()) + } + + res = s.FinishBind() + if res.IsErr() { + return fmt.Errorf("failed to finish binding socket: %s", res.Err().String()) + } + + return nil +} + +func (s tcpServerSocket) Listen(backlog int) error { + res := s.StartListen() + if res.IsErr() { + return fmt.Errorf("failed to start listening on socket: %s", res.Err().String()) + } + + res = s.FinishListen() + if res.IsErr() { + return fmt.Errorf("failed to finish listening on socket: %s", res.Err().String()) + } + + return nil +} + +func (s tcpServerSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error) { + var clientSocket *tcpcreatesocket.TCPSocket + var inStream *streams.InputStream + var outStream *streams.OutputStream + + for { + res := s.TCPSocket.Accept() + if res.IsOK() { + clientSocket, inStream, outStream = &res.OK().F0, &res.OK().F1, &res.OK().F2 + break + } + + if *res.Err() == network.ErrorCodeWouldBlock { + // FIXME: a proper way is to use Pollable.Block() + // But this seems to cause the single threaded runtime to block indefinitely + for { + if s.Pollable.Ready() { + break + } + + // HACK: Make sure to yield the execution to other goroutines + time.Sleep(100 * time.Millisecond) + } + continue + } + + return nil, nil, fmt.Errorf("failed to accept connection: %s", res.Err().String()) + + } + + raddrRes := clientSocket.RemoteAddress() + if raddrRes.IsErr() { + return nil, nil, fmt.Errorf("failed to get remote address: %s", raddrRes.Err().String()) + } + + sock := tcpSocket{ + TCPSocket: clientSocket, + InputStream: inStream, + OutputStream: outStream, + } + + return sock, raddrRes.OK(), nil +} + +type tcpSocket struct { + *tcpcreatesocket.TCPSocket + *streams.InputStream + *streams.OutputStream +} + +func (c tcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { + if flags != 0 { + fmt.Println("wasip2 TCP send flags TODO:", flags) /// + } + + res := c.BlockingWriteAndFlush(cm.ToList([]uint8(buf))) + if res.IsErr() { + return -1, fmt.Errorf("failed to write to output stream: %s", res.Err().String()) + } + + return len(buf), nil +} + +func (c tcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { + if flags != 0 { + fmt.Println("wasip2 TCP recv flags TODO:", flags) /// + } + + res := c.BlockingRead(uint64(len(buf))) + if res.IsErr() { + return -1, fmt.Errorf("failed to read from input stream: %s", res.Err().String()) + } + + return copy(buf, res.OK().Slice()), nil +} + +func (c tcpSocket) Close() error { + res := c.TCPSocket.Shutdown(tcp.ShutdownTypeBoth) + if res.IsErr() { + return fmt.Errorf("failed to shutdown client socket: %s", res.Err().String()) + } + + c.InputStream.ResourceDrop() + c.OutputStream.ResourceDrop() + c.TCPSocket.ResourceDrop() + + return nil +} + +func (s tcpSocket) Bind(globalNetwork instancenetwork.Network, addr network.IPSocketAddress) error { + fmt.Println("TODO client Bind") /// + return nil +} +func (s tcpSocket) Listen(backlog int) error { + fmt.Println("TODO client Listen") /// + return nil +} +func (s tcpSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error) { + fmt.Println("TODO client Accept") /// + return nil, nil, nil +} From 293efabea9801ad565a045d5edee911e861138a1 Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 2 Aug 2025 14:42:16 +0200 Subject: [PATCH 3/9] net: wasip2: implement GetHostByName --- netdev_wasip2.go | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/netdev_wasip2.go b/netdev_wasip2.go index 5b1292e..d821cbc 100644 --- a/netdev_wasip2.go +++ b/netdev_wasip2.go @@ -11,6 +11,7 @@ import ( "time" instancenetwork "internal/wasi/sockets/v0.2.0/instance-network" + ipnamelookup "internal/wasi/sockets/v0.2.0/ip-name-lookup" "internal/wasi/sockets/v0.2.0/network" ) @@ -78,10 +79,33 @@ func init() { }) } -// TODO: use ip-name-lookup func (n *wasip2Netdev) GetHostByName(name string) (netip.Addr, error) { - fmt.Println("wasip2 TODO GetHostByName") /// - return netip.Addr{}, errors.New("wasip2 TODO GetHostByName") + res := ipnamelookup.ResolveAddresses(n.net, name) + + if res.IsErr() { + return netip.Addr{}, fmt.Errorf("failed to resolve address: %s", res.Err().String()) + } + + stream := res.OK() + pollable := stream.Subscribe() + + for { + pollable.Block() + res := stream.ResolveNextAddress() + + if res.IsErr() { + return netip.Addr{}, fmt.Errorf("failed to get resolved address: %s", res.Err().String()) + } + + if res.OK().None() { + return netip.Addr{}, errors.New("no addresses found") + } + + // TODO: handle IPv6 + if addr4 := res.OK().Some().IPv4(); addr4 != nil { + return netip.AddrFrom4(*addr4), nil + } + } } func (n *wasip2Netdev) Addr() (netip.Addr, error) { From 3b08fdf0f8b0a5129d29fe80e89ca21889313478 Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 2 Aug 2025 15:37:03 +0200 Subject: [PATCH 4/9] net: wasip2: refactor, support TCP connect --- netdev_wasip2.go | 10 ++++- tcp_wasip2.go | 104 ++++++++++++++++++++++++----------------------- 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/netdev_wasip2.go b/netdev_wasip2.go index d821cbc..da1e77c 100644 --- a/netdev_wasip2.go +++ b/netdev_wasip2.go @@ -61,6 +61,7 @@ type wasip2Socket interface { Close() error Listen(backlog int) error Bind(globalNetwork instancenetwork.Network, ip network.IPSocketAddress) error + Connect(globalNetwork instancenetwork.Network, host string, ip network.IPSocketAddress) error Accept() (wasip2Socket, *network.IPSocketAddress, error) } @@ -154,8 +155,13 @@ func (n *wasip2Netdev) Bind(sockfd int, ip netip.AddrPort) error { } func (n *wasip2Netdev) Connect(sockfd int, host string, ip netip.AddrPort) error { - fmt.Println("wasip2 TODO Connect", sockfd, host, ip) /// - return errors.New("wasip2 TODO Connect") + sock, ok := n.fds[sockfd] + if !ok { + fmt.Println("wasip2: invalid socket fd") /// + return errors.New("wasip2: invalid socket fd") + } + + return sock.Connect(n.net, host, TinygoToWasiAddr(ip)) } func (n *wasip2Netdev) Listen(sockfd int, backlog int) error { diff --git a/tcp_wasip2.go b/tcp_wasip2.go index 59aa490..99fc90d 100644 --- a/tcp_wasip2.go +++ b/tcp_wasip2.go @@ -16,6 +16,13 @@ import ( tcpcreatesocket "internal/wasi/sockets/v0.2.0/tcp-create-socket" ) +type wasip2TcpSocket struct { + tcpcreatesocket.TCPSocket + tcp.Pollable + *streams.InputStream + *streams.OutputStream +} + func createTCPSocket(af network.IPAddressFamily) (wasip2Socket, error) { res := tcpcreatesocket.CreateTCPSocket(af) if res.IsErr() { @@ -23,33 +30,13 @@ func createTCPSocket(af network.IPAddressFamily) (wasip2Socket, error) { } sock := res.OK() - return tcpServerSocket{ - TCPSocket: sock, + return &wasip2TcpSocket{ + TCPSocket: *sock, Pollable: sock.Subscribe(), }, nil } -type tcpServerSocket struct { - *tcpcreatesocket.TCPSocket - tcp.Pollable -} - -func (s tcpServerSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { - fmt.Println("TODO server Recv") /// - return 0, nil -} - -func (s tcpServerSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { - fmt.Println("TODO server Send") /// - return 0, nil -} - -func (s tcpServerSocket) Close() error { - fmt.Println("TODO server Close") /// - return nil -} - -func (s tcpServerSocket) Bind(globalNetwork instancenetwork.Network, addr network.IPSocketAddress) error { +func (s *wasip2TcpSocket) Bind(globalNetwork instancenetwork.Network, addr network.IPSocketAddress) error { res := s.StartBind(globalNetwork, addr) if res.IsErr() { return fmt.Errorf("failed to start binding socket: %s", res.Err().String()) @@ -63,7 +50,7 @@ func (s tcpServerSocket) Bind(globalNetwork instancenetwork.Network, addr networ return nil } -func (s tcpServerSocket) Listen(backlog int) error { +func (s *wasip2TcpSocket) Listen(backlog int) error { res := s.StartListen() if res.IsErr() { return fmt.Errorf("failed to start listening on socket: %s", res.Err().String()) @@ -77,7 +64,7 @@ func (s tcpServerSocket) Listen(backlog int) error { return nil } -func (s tcpServerSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error) { +func (s *wasip2TcpSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error) { var clientSocket *tcpcreatesocket.TCPSocket var inStream *streams.InputStream var outStream *streams.OutputStream @@ -112,26 +99,45 @@ func (s tcpServerSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error return nil, nil, fmt.Errorf("failed to get remote address: %s", raddrRes.Err().String()) } - sock := tcpSocket{ - TCPSocket: clientSocket, + return &wasip2TcpSocket{ + TCPSocket: *clientSocket, + Pollable: clientSocket.Subscribe(), InputStream: inStream, OutputStream: outStream, + }, raddrRes.OK(), nil +} + +func (s *wasip2TcpSocket) Connect(globalNetwork instancenetwork.Network, host string, ip network.IPSocketAddress) error { + res := s.StartConnect(globalNetwork, ip) + if res.IsErr() { + return fmt.Errorf("failed to start connecting socket: %s", res.Err().String()) } - return sock, raddrRes.OK(), nil -} + for { + connRes := s.FinishConnect() + if connRes.IsOK() { + s.InputStream, s.OutputStream = &connRes.OK().F0, &connRes.OK().F1 + return nil + } -type tcpSocket struct { - *tcpcreatesocket.TCPSocket - *streams.InputStream - *streams.OutputStream + if *connRes.Err() == network.ErrorCodeWouldBlock { + s.Block() + continue + } + + return fmt.Errorf("failed to finish connecting socket: %s", connRes.Err().String()) + } } -func (c tcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { +func (c wasip2TcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { if flags != 0 { fmt.Println("wasip2 TCP send flags TODO:", flags) /// } + if c.OutputStream == nil { + return -1, fmt.Errorf("send called on a socket without open streams") + } + res := c.BlockingWriteAndFlush(cm.ToList([]uint8(buf))) if res.IsErr() { return -1, fmt.Errorf("failed to write to output stream: %s", res.Err().String()) @@ -140,11 +146,15 @@ func (c tcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) return len(buf), nil } -func (c tcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { +func (c wasip2TcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { if flags != 0 { fmt.Println("wasip2 TCP recv flags TODO:", flags) /// } + if c.InputStream == nil { + return -1, fmt.Errorf("recv called on a socket without open streams") + } + res := c.BlockingRead(uint64(len(buf))) if res.IsErr() { return -1, fmt.Errorf("failed to read from input stream: %s", res.Err().String()) @@ -153,28 +163,20 @@ func (c tcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) return copy(buf, res.OK().Slice()), nil } -func (c tcpSocket) Close() error { +func (c wasip2TcpSocket) Close() error { res := c.TCPSocket.Shutdown(tcp.ShutdownTypeBoth) if res.IsErr() { return fmt.Errorf("failed to shutdown client socket: %s", res.Err().String()) } - c.InputStream.ResourceDrop() - c.OutputStream.ResourceDrop() + if c.InputStream != nil { + c.InputStream.ResourceDrop() + } + if c.OutputStream != nil { + c.OutputStream.ResourceDrop() + } + c.Pollable.ResourceDrop() c.TCPSocket.ResourceDrop() return nil } - -func (s tcpSocket) Bind(globalNetwork instancenetwork.Network, addr network.IPSocketAddress) error { - fmt.Println("TODO client Bind") /// - return nil -} -func (s tcpSocket) Listen(backlog int) error { - fmt.Println("TODO client Listen") /// - return nil -} -func (s tcpSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error) { - fmt.Println("TODO client Accept") /// - return nil, nil, nil -} From 4c562f103955a1eb112d0ad1286e0db20629cf79 Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 2 Aug 2025 16:08:53 +0200 Subject: [PATCH 5/9] net: wasip2: UDP client --- netdev_wasip2.go | 5 ++ udp_wasip2.go | 135 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 udp_wasip2.go diff --git a/netdev_wasip2.go b/netdev_wasip2.go index da1e77c..8c5253c 100644 --- a/netdev_wasip2.go +++ b/netdev_wasip2.go @@ -134,6 +134,11 @@ func (n *wasip2Netdev) Socket(domain int, stype int, protocol int) (sockfd int, if err != nil { return -1, err } + case _SOCK_DGRAM: + sock, err = createUDPSocket(af) + if err != nil { + return -1, err + } default: return -1, fmt.Errorf("wasip2: unsupported socket type %d", stype) } diff --git a/udp_wasip2.go b/udp_wasip2.go new file mode 100644 index 0000000..af78f51 --- /dev/null +++ b/udp_wasip2.go @@ -0,0 +1,135 @@ +//go:build wasip2 + +// WASI preview 2 UDP + +package net + +import ( + "fmt" + "time" + + "internal/cm" + instancenetwork "internal/wasi/sockets/v0.2.0/instance-network" + "internal/wasi/sockets/v0.2.0/network" + "internal/wasi/sockets/v0.2.0/udp" + udpcreatesocket "internal/wasi/sockets/v0.2.0/udp-create-socket" +) + +type wasip2UdpSocket struct { + udpcreatesocket.UDPSocket + udp.Pollable + *udp.IncomingDatagramStream + *udp.OutgoingDatagramStream +} + +func createUDPSocket(af network.IPAddressFamily) (wasip2Socket, error) { + res := udpcreatesocket.CreateUDPSocket(af) + if res.IsErr() { + return nil, fmt.Errorf("failed to create UDP socket: %s", res.Err().String()) + } + + sock := res.OK() + return &wasip2UdpSocket{ + UDPSocket: *sock, + Pollable: sock.Subscribe(), + }, nil +} + +func (s *wasip2UdpSocket) Bind(globalNetwork instancenetwork.Network, addr network.IPSocketAddress) error { + res := s.StartBind(globalNetwork, addr) + if res.IsErr() { + return fmt.Errorf("failed to start binding socket: %s", res.Err().String()) + } + + res = s.FinishBind() + if res.IsErr() { + return fmt.Errorf("failed to finish binding socket: %s", res.Err().String()) + } + + return nil +} + +func (s *wasip2UdpSocket) Listen(backlog int) error { + fmt.Println("wasip2 UDP listen TODO:", backlog) /// + return nil +} + +func (s *wasip2UdpSocket) Accept() (wasip2Socket, *network.IPSocketAddress, error) { + return nil, nil, fmt.Errorf("wasip2 UDP sockets do not support Accept") +} + +func (s *wasip2UdpSocket) Connect(globalNetwork instancenetwork.Network, host string, ip network.IPSocketAddress) error { + res := s.UDPSocket.Stream(cm.Some(ip)) + + if res.IsErr() { + return fmt.Errorf("failed to connect UDP socket: %s", res.Err().String()) + } + + s.IncomingDatagramStream, s.OutgoingDatagramStream = &res.OK().F0, &res.OK().F1 + + return nil +} + +func (c wasip2UdpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { + if flags != 0 { + fmt.Println("wasip2 UDP send flags TODO:", flags) /// + } + + if c.OutgoingDatagramStream == nil { + return -1, fmt.Errorf("send called on a socket without open streams") + } + + res := c.OutgoingDatagramStream.CheckSend() + if res.IsErr() { + return -1, fmt.Errorf("failed to write to output stream: %s", res.Err().String()) + } + + if *res.OK() == 0 { + c.Block() + } + + a := []udp.OutgoingDatagram{ + { + Data: cm.NewList(&buf[0], len(buf)), + // RemoteAddress: cm.Some(c.raddr), + }, + } + + c.OutgoingDatagramStream.Send(cm.NewList(&a[0], 1)) + + return len(buf), nil +} + +func (c wasip2UdpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { + if flags != 0 { + fmt.Println("wasip2 UDP recv flags TODO:", flags) /// + } + + if c.IncomingDatagramStream == nil { + return -1, fmt.Errorf("recv called on a socket without open streams") + } + + res := c.Receive(1) + if res.IsErr() { + return -1, fmt.Errorf("failed to read from input stream: %s", res.Err().String()) + } + + if res.OK().Len() != 1 { + return -1, fmt.Errorf("expected 1 datagram, got %d", res.OK().Len()) + } + + return copy(buf, res.OK().Data().Data.Slice()), nil +} + +func (c wasip2UdpSocket) Close() error { + if c.IncomingDatagramStream != nil { + c.IncomingDatagramStream.ResourceDrop() + } + if c.OutgoingDatagramStream != nil { + c.OutgoingDatagramStream.ResourceDrop() + } + c.Pollable.ResourceDrop() + c.UDPSocket.ResourceDrop() + + return nil +} From 69662253b003f0b77f5b59ff5a612d96d8a51bcf Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 27 Sep 2025 19:09:16 +0200 Subject: [PATCH 6/9] logging fixup --- tcp_wasip2.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tcp_wasip2.go b/tcp_wasip2.go index 99fc90d..ae45778 100644 --- a/tcp_wasip2.go +++ b/tcp_wasip2.go @@ -131,7 +131,7 @@ func (s *wasip2TcpSocket) Connect(globalNetwork instancenetwork.Network, host st func (c wasip2TcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { if flags != 0 { - fmt.Println("wasip2 TCP send flags TODO:", flags) /// + return -1, fmt.Errorf("wasip2 TCP send flags TODO:", flags) } if c.OutputStream == nil { @@ -148,7 +148,7 @@ func (c wasip2TcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, e func (c wasip2TcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { if flags != 0 { - fmt.Println("wasip2 TCP recv flags TODO:", flags) /// + return -1, fmt.Errorf("wasip2 TCP recv flags TODO:", flags) } if c.InputStream == nil { From 61b4272110aed393dfa28cea9050e34eb6f37abf Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 27 Sep 2025 19:11:19 +0200 Subject: [PATCH 7/9] fix large TCP sends --- tcp_wasip2.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tcp_wasip2.go b/tcp_wasip2.go index ae45778..427381f 100644 --- a/tcp_wasip2.go +++ b/tcp_wasip2.go @@ -138,7 +138,15 @@ func (c wasip2TcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, e return -1, fmt.Errorf("send called on a socket without open streams") } - res := c.BlockingWriteAndFlush(cm.ToList([]uint8(buf))) + cw, err, iserr := c.CheckWrite().Result() + if iserr { + return -1, fmt.Errorf("failed to do check-write on the output stream: %s", err.String()) + } + if cw < uint64(len(buf)) { + return -1, fmt.Errorf("failed to send: writeable %d < length %d", cw, len(buf)) + } + + res := c.Write(cm.ToList([]uint8(buf))) if res.IsErr() { return -1, fmt.Errorf("failed to write to output stream: %s", res.Err().String()) } From c175c4982a84d88974613bea6d3f32c8ce33543f Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 27 Sep 2025 19:21:17 +0200 Subject: [PATCH 8/9] review comments --- netdev_wasip2.go | 35 ++++++++++++++--------------------- tcp_wasip2.go | 6 +++--- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/netdev_wasip2.go b/netdev_wasip2.go index 8c5253c..a76fe36 100644 --- a/netdev_wasip2.go +++ b/netdev_wasip2.go @@ -15,7 +15,9 @@ import ( "internal/wasi/sockets/v0.2.0/network" ) -func TinygoToWasiAddr(ip netip.AddrPort) network.IPSocketAddress { +var errInvalidSocketFD = errors.New("wasip2: invalid socket fd") + +func tinygoToWasiAddr(ip netip.AddrPort) network.IPSocketAddress { if ip.Addr().Is4() { return network.IPSocketAddressIPv4(network.IPv4SocketAddress{ Port: ip.Port(), @@ -38,7 +40,7 @@ func TinygoToWasiAddr(ip netip.AddrPort) network.IPSocketAddress { return network.IPSocketAddress{} } -func WasiAddrToTinygo(addr network.IPSocketAddress) netip.AddrPort { +func wasiAddrToTinygo(addr network.IPSocketAddress) netip.AddrPort { if addr4 := addr.IPv4(); addr4 != nil { return netip.AddrPortFrom(netip.AddrFrom4(addr4.Address), addr4.Port) } @@ -110,7 +112,6 @@ func (n *wasip2Netdev) GetHostByName(name string) (netip.Addr, error) { } func (n *wasip2Netdev) Addr() (netip.Addr, error) { - fmt.Println("wasip2 TODO Addr") /// return netip.Addr{}, errors.New("wasip2 TODO Addr") } @@ -152,28 +153,25 @@ func (n *wasip2Netdev) Socket(domain int, stype int, protocol int) (sockfd int, func (n *wasip2Netdev) Bind(sockfd int, ip netip.AddrPort) error { sock, ok := n.fds[sockfd] if !ok { - fmt.Println("wasip2: invalid socket fd") /// - return errors.New("wasip2: invalid socket fd") + return errInvalidSocketFD } - return sock.Bind(n.net, TinygoToWasiAddr(ip)) + return sock.Bind(n.net, tinygoToWasiAddr(ip)) } func (n *wasip2Netdev) Connect(sockfd int, host string, ip netip.AddrPort) error { sock, ok := n.fds[sockfd] if !ok { - fmt.Println("wasip2: invalid socket fd") /// - return errors.New("wasip2: invalid socket fd") + return errInvalidSocketFD } - return sock.Connect(n.net, host, TinygoToWasiAddr(ip)) + return sock.Connect(n.net, host, tinygoToWasiAddr(ip)) } func (n *wasip2Netdev) Listen(sockfd int, backlog int) error { sock, ok := n.fds[sockfd] if !ok { - fmt.Println("wasip2: invalid socket fd") /// - return errors.New("wasip2: invalid socket fd") + return errInvalidSocketFD } return sock.Listen(backlog) @@ -182,8 +180,7 @@ func (n *wasip2Netdev) Listen(sockfd int, backlog int) error { func (n *wasip2Netdev) Accept(sockfd int) (int, netip.AddrPort, error) { sock, ok := n.fds[sockfd] if !ok { - fmt.Println("wasip2: invalid socket fd") /// - return -1, netip.AddrPort{}, errors.New("wasip2: invalid socket fd") + return -1, netip.AddrPort{}, errInvalidSocketFD } newSock, raddr, err := sock.Accept() @@ -194,14 +191,13 @@ func (n *wasip2Netdev) Accept(sockfd int) (int, netip.AddrPort, error) { fd := n.getNextFD() n.fds[fd] = newSock - return fd, WasiAddrToTinygo(*raddr), nil + return fd, wasiAddrToTinygo(*raddr), nil } func (n *wasip2Netdev) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { sock, ok := n.fds[sockfd] if !ok { - fmt.Println("wasip2: invalid socket fd") /// - return -1, errors.New("wasip2: invalid socket fd") + return -1, errInvalidSocketFD } return sock.Send(buf, flags, deadline) @@ -210,8 +206,7 @@ func (n *wasip2Netdev) Send(sockfd int, buf []byte, flags int, deadline time.Tim func (n *wasip2Netdev) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { sock, ok := n.fds[sockfd] if !ok { - fmt.Println("wasip2: invalid socket fd") /// - return -1, errors.New("wasip2: invalid socket fd") + return -1, errInvalidSocketFD } return sock.Recv(buf, flags, deadline) @@ -220,8 +215,7 @@ func (n *wasip2Netdev) Recv(sockfd int, buf []byte, flags int, deadline time.Tim func (n *wasip2Netdev) Close(sockfd int) error { sock, ok := n.fds[sockfd] if !ok { - fmt.Println("wasip2: invalid socket fd") /// - return errors.New("wasip2: invalid socket fd") + return errInvalidSocketFD } delete(n.fds, sockfd) @@ -230,6 +224,5 @@ func (n *wasip2Netdev) Close(sockfd int) error { } func (n *wasip2Netdev) SetSockOpt(sockfd int, level int, opt int, value interface{}) error { - fmt.Println("wasip2 setsockopt (TODO)", sockfd, level, opt, value) /// return errors.New("wasip2 TODO set socket option") } diff --git a/tcp_wasip2.go b/tcp_wasip2.go index 427381f..e85ed22 100644 --- a/tcp_wasip2.go +++ b/tcp_wasip2.go @@ -129,7 +129,7 @@ func (s *wasip2TcpSocket) Connect(globalNetwork instancenetwork.Network, host st } } -func (c wasip2TcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { +func (c *wasip2TcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, error) { if flags != 0 { return -1, fmt.Errorf("wasip2 TCP send flags TODO:", flags) } @@ -154,7 +154,7 @@ func (c wasip2TcpSocket) Send(buf []byte, flags int, deadline time.Time) (int, e return len(buf), nil } -func (c wasip2TcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { +func (c *wasip2TcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, error) { if flags != 0 { return -1, fmt.Errorf("wasip2 TCP recv flags TODO:", flags) } @@ -171,7 +171,7 @@ func (c wasip2TcpSocket) Recv(buf []byte, flags int, deadline time.Time) (int, e return copy(buf, res.OK().Slice()), nil } -func (c wasip2TcpSocket) Close() error { +func (c *wasip2TcpSocket) Close() error { res := c.TCPSocket.Shutdown(tcp.ShutdownTypeBoth) if res.IsErr() { return fmt.Errorf("failed to shutdown client socket: %s", res.Err().String()) From 532e0bcfb4d900b41a39aadc9ee93fb9e63f56dc Mon Sep 17 00:00:00 2001 From: Dmitrii Sharshakov Date: Sat, 27 Sep 2025 20:02:31 +0200 Subject: [PATCH 9/9] Bind to 0.0.0.0 if no address specified (:80) --- netdev_wasip2.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/netdev_wasip2.go b/netdev_wasip2.go index a76fe36..a83530a 100644 --- a/netdev_wasip2.go +++ b/netdev_wasip2.go @@ -37,7 +37,9 @@ func tinygoToWasiAddr(ip netip.AddrPort) network.IPSocketAddress { }) } - return network.IPSocketAddress{} + return network.IPSocketAddressIPv4(network.IPv4SocketAddress{ + Port: ip.Port(), + }) } func wasiAddrToTinygo(addr network.IPSocketAddress) netip.AddrPort {