Skip to content
1 change: 1 addition & 0 deletions netdev.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

const (
_AF_INET = 0x2
_AF_INET6 = 0xA
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't end up really used, nor did I test IPv6. Should we drop it for now?

_SOCK_STREAM = 0x1
_SOCK_DGRAM = 0x2
_SOL_SOCKET = 0x1
Expand Down
230 changes: 230 additions & 0 deletions netdev_wasip2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
//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"
ipnamelookup "internal/wasi/sockets/v0.2.0/ip-name-lookup"
"internal/wasi/sockets/v0.2.0/network"
)

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(),
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.IPSocketAddressIPv4(network.IPv4SocketAddress{
Port: ip.Port(),
})
}

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
Connect(globalNetwork instancenetwork.Network, host string, 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(),
})
}

func (n *wasip2Netdev) GetHostByName(name string) (netip.Addr, error) {
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) {
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
}
case _SOCK_DGRAM:
sock, err = createUDPSocket(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 {
return errInvalidSocketFD
}

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 {
return errInvalidSocketFD
}

return sock.Connect(n.net, host, tinygoToWasiAddr(ip))
}

func (n *wasip2Netdev) Listen(sockfd int, backlog int) error {
sock, ok := n.fds[sockfd]
if !ok {
return errInvalidSocketFD
}

return sock.Listen(backlog)
}

func (n *wasip2Netdev) Accept(sockfd int) (int, netip.AddrPort, error) {
sock, ok := n.fds[sockfd]
if !ok {
return -1, netip.AddrPort{}, errInvalidSocketFD
}

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 {
return -1, errInvalidSocketFD
}

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 {
return -1, errInvalidSocketFD
}

return sock.Recv(buf, flags, deadline)
}

func (n *wasip2Netdev) Close(sockfd int) error {
sock, ok := n.fds[sockfd]
if !ok {
return errInvalidSocketFD
}

delete(n.fds, sockfd)

return sock.Close()
}

func (n *wasip2Netdev) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
return errors.New("wasip2 TODO set socket option")
}
Loading